Mercurial > hg > mercurial-crew-with-dirclash
comparison mercurial/util.py @ 5141:d316124ebbea
Make audit_path more stringent.
The following properties of a path are now checked for:
- under top-level .hg
- starts at the root of a windows drive
- contains ".."
- traverses a symlink (e.g. a/symlink_here/b)
- inside a nested repository
If any of these is true, the path is rejected.
The check for traversing a symlink is arguably stricter than necessary;
perhaps we should be checking for symlinks that point outside the
repository.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Fri, 10 Aug 2007 10:46:03 -0700 |
parents | a2c11f49e989 |
children | d84329a11fdd |
comparison
equal
deleted
inserted
replaced
5140:f6c520fd70cf | 5141:d316124ebbea |
---|---|
11 This contains helper routines that are independent of the SCM core and hide | 11 This contains helper routines that are independent of the SCM core and hide |
12 platform-specific details from the core. | 12 platform-specific details from the core. |
13 """ | 13 """ |
14 | 14 |
15 from i18n import _ | 15 from i18n import _ |
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile | 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil |
17 import os, threading, time, calendar, ConfigParser, locale, glob | 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob |
18 | 18 |
19 try: | 19 try: |
20 set = set | 20 set = set |
21 frozenset = frozenset | 21 frozenset = frozenset |
22 except NameError: | 22 except NameError: |
364 rootsep = root + os.sep | 364 rootsep = root + os.sep |
365 name = myname | 365 name = myname |
366 if not os.path.isabs(name): | 366 if not os.path.isabs(name): |
367 name = os.path.join(root, cwd, name) | 367 name = os.path.join(root, cwd, name) |
368 name = os.path.normpath(name) | 368 name = os.path.normpath(name) |
369 audit_path = path_auditor(root) | |
369 if name != rootsep and name.startswith(rootsep): | 370 if name != rootsep and name.startswith(rootsep): |
370 name = name[len(rootsep):] | 371 name = name[len(rootsep):] |
371 audit_path(name) | 372 audit_path(name) |
372 return pconvert(name) | 373 return pconvert(name) |
373 elif name == root: | 374 elif name == root: |
678 hardlink = False | 679 hardlink = False |
679 shutil.copy(src, dst) | 680 shutil.copy(src, dst) |
680 else: | 681 else: |
681 shutil.copy(src, dst) | 682 shutil.copy(src, dst) |
682 | 683 |
683 def audit_path(path): | 684 class path_auditor(object): |
684 """Abort if path contains dangerous components""" | 685 '''ensure that a filesystem path contains no banned components. |
685 parts = os.path.normcase(path).split(os.sep) | 686 the following properties of a path are checked: |
686 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') | 687 |
687 or os.pardir in parts): | 688 - under top-level .hg |
688 raise Abort(_("path contains illegal component: %s") % path) | 689 - starts at the root of a windows drive |
690 - contains ".." | |
691 - traverses a symlink (e.g. a/symlink_here/b) | |
692 - inside a nested repository''' | |
693 | |
694 def __init__(self, root): | |
695 self.audited = {} | |
696 self.root = root | |
697 | |
698 def __call__(self, path): | |
699 if path in self.audited: | |
700 return | |
701 parts = os.path.normcase(path).split(os.sep) | |
702 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') | |
703 or os.pardir in parts): | |
704 raise Abort(_("path contains illegal component: %s") % path) | |
705 def check(prefix): | |
706 curpath = os.path.join(self.root, prefix) | |
707 try: | |
708 st = os.lstat(curpath) | |
709 except OSError, err: | |
710 if err.errno != errno.ENOENT: | |
711 raise | |
712 else: | |
713 if stat.S_ISLNK(st.st_mode): | |
714 raise Abort(_('path %r traverses symbolic link %r') % | |
715 (path, prefix)) | |
716 if os.path.exists(os.path.join(curpath, '.hg')): | |
717 raise Abort(_('path %r is inside repo %r') % | |
718 (path, prefix)) | |
719 self.audited[prefix] = True | |
720 for c in strutil.rfindall(path, os.sep): | |
721 check(path[:c]) | |
722 self.audited[path] = True | |
689 | 723 |
690 def _makelock_file(info, pathname): | 724 def _makelock_file(info, pathname): |
691 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) | 725 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) |
692 os.write(ld, info) | 726 os.write(ld, info) |
693 os.close(ld) | 727 os.close(ld) |
1260 This class is used to hide the details of COW semantics and | 1294 This class is used to hide the details of COW semantics and |
1261 remote file access from higher level code. | 1295 remote file access from higher level code. |
1262 """ | 1296 """ |
1263 def __init__(self, base, audit=True): | 1297 def __init__(self, base, audit=True): |
1264 self.base = base | 1298 self.base = base |
1265 self.audit = audit | 1299 if audit: |
1300 self.audit_path = path_auditor(base) | |
1301 else: | |
1302 self.audit_path = always | |
1266 | 1303 |
1267 def __getattr__(self, name): | 1304 def __getattr__(self, name): |
1268 if name == '_can_symlink': | 1305 if name == '_can_symlink': |
1269 self._can_symlink = checklink(self.base) | 1306 self._can_symlink = checklink(self.base) |
1270 return self._can_symlink | 1307 return self._can_symlink |
1271 raise AttributeError(name) | 1308 raise AttributeError(name) |
1272 | 1309 |
1273 def __call__(self, path, mode="r", text=False, atomictemp=False): | 1310 def __call__(self, path, mode="r", text=False, atomictemp=False): |
1274 if self.audit: | 1311 self.audit_path(path) |
1275 audit_path(path) | |
1276 f = os.path.join(self.base, path) | 1312 f = os.path.join(self.base, path) |
1277 | 1313 |
1278 if not text and "b" not in mode: | 1314 if not text and "b" not in mode: |
1279 mode += "b" # for that other OS | 1315 mode += "b" # for that other OS |
1280 | 1316 |
1291 if nlink > 1: | 1327 if nlink > 1: |
1292 rename(mktempcopy(f), f) | 1328 rename(mktempcopy(f), f) |
1293 return posixfile(f, mode) | 1329 return posixfile(f, mode) |
1294 | 1330 |
1295 def symlink(self, src, dst): | 1331 def symlink(self, src, dst): |
1296 if self.audit: | 1332 self.audit_path(dst) |
1297 audit_path(dst) | |
1298 linkname = os.path.join(self.base, dst) | 1333 linkname = os.path.join(self.base, dst) |
1299 try: | 1334 try: |
1300 os.unlink(linkname) | 1335 os.unlink(linkname) |
1301 except OSError: | 1336 except OSError: |
1302 pass | 1337 pass |