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