import os from stat import ST_MODE, ST_UID, ST_GID, S_ISREG from os import strerror, stat, mkdir, chown, walk, listdir, fstat from os.path import realpath, split, join, exists, isdir from errno import errorcode from ctypes import CDLL, POINTER, Structure, c_int, c_char_p, c_uint32, c_long from ctypes.util import find_library from cStringIO import StringIO as stringio from subprocess import Popen, PIPE ### errno manipulationA libc = CDLL(find_library('c')) libc.__errno_location.restype = POINTER(c_int) def get_errno(): return libc.__errno_location().contents.value class CError(Exception): """Base class for VServer syscall errors.""" def __init__(self, errno=None): Exception.__init__(self) if errno is None: errno = get_errno() self.errno = errno self.errsym = errorcode[errno] self.errstring = strerror(errno) def __repr__(self): return '<%s errno=%s>'%(self.__class__.__name__, self.errno) def __str__(self): return '%s: %s'%(self.errsym, self.errstring) def errno_errcheck(errval, exception): """Assign to the 'errcheck' attr of a ctypes._CFuncPtr. :errval: a return code indicating error, and :exception: an exception type like CError. >>> func.errcheck = errno_errcheck(errval, excpt)""" def _inner(result, func, args): if result == errval: errno = get_errno() raise exception(errno) return result return _inner libc_errcheck = errno_errcheck(-1, CError) ### Logging, profiling, and error reporting def make_reporter(verbosity, quiet, filelike): if not quiet: def report(level, msg, *args): if level <= verbosity: if len(args): filelike.write(msg % args + '\n') else: filelike.write(msg + '\n') else: def report(level, msg, *args): pass return report def profile(profiler): def wrapper(fn): def inner(*args, **kwargs): profiler.runcall(fn, *args, **kwargs) return inner return wrapper def trace(etype=None, value=None, tb=None): try: from IPython.ultraTB import AutoFormattedTB trace_any = AutoFormattedTB(mode='Verbose', color_scheme='NoColor', call_pdb=0) trace_exact = trace_any except: from traceback import print_exc, print_exception trace_any = print_exc trace_exact = print_exception if etype or value or tb: trace_exact(etype, value, tb) else: trace_any() ### File mode checking. class Checker(object): """Sanity-check file-system permissions. Note: This class should NOT to be used for security-critical tests. In particular, this class does not know the special rules that adhere to uid 0. """ def __init__(self, path, uid, gid, groups=None): self.path = path self.uid = uid self.gid = gid self.groups = set(groups or []).union(set([gid])) self.observation = stat(self.path) def __repr__(self): return "Checker(%r, %r, %r, groups=%r)" % (self.path, self.uid, self.gid, self.groups) def __str__(self): return ("Checker(%s, %s, %s, %s) -> mode: %o uid: %s, gid: %s" % (self.path, self.uid, self.gid, self.groups, self.observation[ST_MODE], self.observation[ST_UID], self.observation[ST_GID])) def positive(self, needed, mask): # I'm going to try to turn off bits in mode as I verify that they can # be satisfied. o = self.observation real_mode = o[ST_MODE] if self.uid == o[ST_UID]: needed &= ~((real_mode & 0700) >> 6) if o[ST_GID] in self.groups: needed &= ~((real_mode & 0070) >> 3) needed &= ~(real_mode & 0007) # Make sure needed is empty and real_mode satisfies stat. return (needed == 0) and ((mask & real_mode) == mask) def negative(self, avoid, mask): # I'm going to try to turn on bits for things we're permitted to do, # then check if any of them are disallowed. o = self.observation real_mode = o[ST_MODE] allowed = 0 if self.uid == o[ST_UID]: allowed |= (real_mode & 0700) >> 6 if o[ST_GID] in self.groups: allowed |= (real_mode & 0070) >> 3 allowed |= (real_mode & 0007) # Did we avoid everything? return ((avoid & allowed) == 0) and (mask & real_mode == 0) ### Paths def components(p): """Take a path and return a list of its components: e.g. /abc/de or /abc/de/ ---> ['abc', 'de']""" path = realpath(p) (head, tail) = split(path) if tail == '': (head, tail) = split(head) cpts = [] while tail != '': cpts = [tail] + cpts (head, tail) = split(head) return cpts def make_dirs(pth, uid, gid, mode): """A replacement for os.makedirs that sets ownership as it creates directories.""" if exists(pth): return root = '/' for cpt in components(pth): root = join(root, cpt) if not exists(root): #print "Made %s for (%d, %d)" % (root, uid, gid) # XXX: A picky person would be worried about races here. mkdir(root, mode) chown(root, uid, gid) def get_mounted_paths(): mounts = [] procfile = open('/proc/mounts') for line in procfile.readlines(): mounts.append(line.split(' ')[1]) procfile.close() return mounts ### Processes def tokenize(data): """Take a string and split it on whitespace-strings not contained in matched quotes. """ quotes = ''''"''' words = [] state = '\0' current_word = stringio() c = '' for c in data: if state == '\0': if c in quotes: state = c continue if c.isspace(): val = current_word.getvalue() if val != '': words.append(val) current_word.truncate(0) continue if state in quotes: if c == state: state = '\0' continue current_word.write(c) if state in quotes: current_word.write(c) words.append(current_word.getvalue()) return words def lout(cmd): if isinstance(cmd, basestring): cmd = tokenize(cmd) return Popen(cmd, stdout=PIPE).stdout.readlines() def get_fds(): for _, _, files in walk("/proc/self/fd"): for name in files: yield int(name) ### syscall(), clone(), daemon(), faccessat(), unshare() CSIGNAL = 0xff # /* Signal mask to be sent at exit. */ CLONE_VM = 0x100 # /* Set if VM shared between processes. */ CLONE_FS = 0x200 # /* Set if fs info shared between processes. */ CLONE_FILES = 0x400 # /* Set if open files shared between processes. */ CLONE_SIGHAND = 0x800 # /* Set if signal handlers shared. */ CLONE_PTRACE = 0x2000 # /* Set if tracing continues on the child. */ CLONE_VFORK = 0x4000 # /* Set if the parent wants the child to wake it up on mm_release. */ CLONE_PARENT = 0x8000 # /* Set if we want to have the same parent as the cloner. */ CLONE_THREAD = 0x10000 # /* Set to add to same thread group. */ CLONE_NEWNS = 0x20000 # /* Set to create new namespace. */ CLONE_SYSVSEM = 0x40000 # /* Set to shared SVID SEM_UNDO semantics. */ CLONE_SETTLS = 0x80000 # /* Set TLS info. */ CLONE_PARENT_SETTID = 0x100000 # /* Store TID in userlevel buffer before MM copy. */ CLONE_CHILD_CLEARTID = 0x200000 # /* Register exit futex and memory location to clear. */ CLONE_DETACHED = 0x400000 # /* Create clone detached. */ CLONE_UNTRACED = 0x800000 # /* Set if the tracing process can't force CLONE_PTRACE on this clone. */ CLONE_CHILD_SETTID = 0x1000000 # /* Store TID in userlevel buffer in the child. */ CLONE_STOPPED = 0x2000000 # /* Start in stopped state. */ CLONE_NEWUTS = 0x04000000# /* New utsname group? */ CLONE_NEWIPC = 0x08000000# /* New ipcs */ CLONE_NEWUSER = 0x10000000# /* New user namespace */ CLONE_NEWPID = 0x20000000# /* New pid namespace */ CLONE_NEWNET = 0x40000000# /* New network namespace */ CLONE_IO = 0x80000000# /* Clone io context */ syscall = libc.syscall # We want the simple one. # int clone(int number, int flags, void *child_stack) # int clone(int number, int flags, void *child_stack, int *parent_tidptr, int *child_tidptr) SYS_clone = 120 def clone(flags): """Perform sys_clone() system call, which creates a new process in a manner similar to fork(). If flags is SIGCHLD, then this is identical to fork(). XXX: i386 wrapper. """ return libc_errcheck(syscall(SYS_clone, flags, None), None, None) # int unshare(int flags); unshare = libc.unshare unshare.__doc__ = "Libc's unshare call; see unshare (2)" unshare.argtypes = [c_int] unshare.restype = c_int unshare.errcheck = libc_errcheck # int daemon(int nochdir, int noclose); daemon = libc.daemon daemon.__doc__ = "Libc's daemon call; see daemon (3)." daemon.argtypes = [c_int, c_int] daemon.restype = c_int daemon.errcheck = libc_errcheck AT_FDCWD = -100 # Special value used to indicate the at functions should use the current working directory. AT_SYMLINK_NOFOLLOW = 0x100 # Do not follow symbolic links. AT_REMOVEDIR = 0x200 # Remove directory instead of unlinking file. AT_SYMLINK_FOLLOW = 0x400 # Follow symbolic links. AT_EACCESS = 0x200 # Test access permitted for effective IDs, not real IDs. # int faccessat(int fd, const char* path, int mode, int flags); faccessat = libc.faccessat faccessat.__doc__ = "Libc's faccessat call; see faccessat (3)." faccessat.argtypes = [c_int, c_char_p, c_int, c_int] faccessat.restype = c_int faccessat.errcheck = libc_errcheck ### reboot(), shutdown(), and poweroff() _reboot = libc.reboot _reboot.__doc__ = "Libc's reboot call; see reboot (2)." _reboot.argtypes = [c_int] _reboot.restype = c_int # XXX: What is reboot()'s errval? _reboot.errcheck = libc_errcheck RB_AUTOBOOT = 0x01234567 RB_HALT_SYSTEM = 0xcdef0123 RB_ENABLE_CAD = 0x89abcdef RB_DISABLE_CAD = 0 RB_POWER_OFF = 0x4321fedc def reboot(): """Reboot the machine immediately. If no sync is done first, data will be lost.""" return _reboot(RB_AUTOBOOT) def halt(): """Halt the machine immediately. If no sync is done first, data will be lost.""" return _reboot(RB_HALT_SYSTEM) def poweroff(): """Poweroff the machine immediately. If no sync is done first, data will be lost.""" return _reboot(RB_POWER_OFF) ### statfs(), fstatfs() # XXX: The LSB has deprecated statfs() and recommends statvfs() instead. # Unfortunately, python's os.statvfs() doesn't give any information about file # system time. # XXX: This is a very 32-bit Linux-only python module. # struct statfs { # long f_type; /* type of filesystem (see below) */ # long f_bsize; /* optimal transfer block size */ # long f_blocks; /* total data blocks in file system */ # long f_bfree; /* free blocks in fs */ # long f_bavail; /* free blocks avail to non-superuser */ # long f_files; /* total file nodes in file system */ # long f_ffree; /* free file nodes in fs */ # fsid_t f_fsid; /* file system id */ # long f_namelen; /* maximum length of filenames */ # }; class c_fsid_t(Structure): _fields_ = [('val', c_int*3)] class c_statfs(Structure): _fields_ = [('f_type', c_long), ('f_bsize', c_long), ('f_blocks', c_long), ('f_bfree', c_long), ('f_bavail', c_long), ('f_files', c_long), ('f_ffree', c_long), ('f_fsid', c_fsid_t), ('f_namelen', c_long),] c_statfs_p = POINTER(c_statfs) # The function statfs() returns information about a mounted file system. # path is the pathname of any file within the mounted filesystem. buf is a # pointer to a statfs structure defined approximately as follows: # include /* or */ # int statfs(const char *path, struct statfs *buf); statfs = libc.statfs statfs.__doc__ = "Libc's statfs call; see statfs(2)" statfs.argtypes = [c_char_p, c_statfs_p] statfs.restype = c_int statfs.errcheck = libc_errcheck # int fstatfs(int fd, struct statfs *buf); fstatfs = libc.fstatfs fstatfs.__doc__ = "Libc's fstatfs call; see fstatfs(2)" fstatfs.argtypes = [c_int, c_statfs_p] fstatfs.restype = c_int fstatfs.errcheck = libc_errcheck # File system types: ADFS_SUPER_MAGIC = 0xadf5 AFFS_SUPER_MAGIC = 0xADFF BEFS_SUPER_MAGIC = 0x42465331 BFS_MAGIC = 0x1BADFACE CIFS_MAGIC_NUMBER = 0xFF534D42 CODA_SUPER_MAGIC = 0x73757245 COH_SUPER_MAGIC = 0x012FF7B7 CRAMFS_MAGIC = 0x28cd3d45 DEVFS_SUPER_MAGIC = 0x1373 EFS_SUPER_MAGIC = 0x00414A53 EXT_SUPER_MAGIC = 0x137D EXT2_OLD_SUPER_MAGIC = 0xEF51 EXT2_SUPER_MAGIC = 0xEF53 EXT3_SUPER_MAGIC = 0xEF53 HFS_SUPER_MAGIC = 0x4244 HPFS_SUPER_MAGIC = 0xF995E849 HUGETLBFS_MAGIC = 0x958458f6 ISOFS_SUPER_MAGIC = 0x9660 JFFS2_SUPER_MAGIC = 0x72b6 JFS_SUPER_MAGIC = 0x3153464a MINIX_SUPER_MAGIC = 0x137F # /* orig. minix */ MINIX_SUPER_MAGIC2 = 0x138F # /* 30 char minix */ MINIX2_SUPER_MAGIC = 0x2468 # /* minix V2 */ MINIX2_SUPER_MAGIC2 = 0x2478 # /* minix V2, 30 char names */ MSDOS_SUPER_MAGIC = 0x4d44 NCP_SUPER_MAGIC = 0x564c NFS_SUPER_MAGIC = 0x6969 NTFS_SB_MAGIC = 0x5346544e OPENPROM_SUPER_MAGIC = 0x9fa1 PROC_SUPER_MAGIC = 0x9fa0 QNX4_SUPER_MAGIC = 0x002f REISERFS_SUPER_MAGIC = 0x52654973 ROMFS_MAGIC = 0x7275 SMB_SUPER_MAGIC = 0x517B SYSV2_SUPER_MAGIC = 0x012FF7B6 SYSV4_SUPER_MAGIC = 0x012FF7B5 TMPFS_MAGIC = 0x01021994 UDF_SUPER_MAGIC = 0x15013346 UFS_MAGIC = 0x00011954 USBDEVICE_SUPER_MAGIC = 0x9fa2 VXFS_SUPER_MAGIC = 0xa501FCF5 XENIX_SUPER_MAGIC = 0x012FF7B4 XFS_SUPER_MAGIC = 0x58465342 _XIAFS_SUPER_MAGIC = 0x012FD16D ### mount(), umount(), and bindmount() class MountError(CError): pass MS_RDONLY = 1 # Mount read-only. MS_NOSUID = 2 # Ignore suid and sgid bits. MS_NODEV = 4 # Disallow access to device special files. MS_NOEXEC = 8 # Disallow program execution. MS_SYNCHRONOUS = 16 # Writes are synced at once. MS_REMOUNT = 32 # Alter flags of a mounted FS. MS_MANDLOCK = 64 # Allow mandatory locks on an FS. S_WRITE = 128 # Write on file/directory/symlink. S_APPEND = 256 # Append-only file. S_IMMUTABLE = 512 # Immutable file. MS_NOATIME = 1024 # Do not update access times. MS_NODIRATIME = 2048 # Do not update directory access times. MS_BIND = 4096 # Bind directory at different place. MS_REC = 16384 # Recursive mount (use with MS_BIN). MS_MGC_VAL = 0xc0ed0000 # Magic flag number to indicate "new" flags MS_MGC_MSK = 0xffff0000 # Magic flag number mask #extern int mount (__const char *__special_file, __const char *__dir, # __const char *__fstype, unsigned long int __rwflag, # __const void *__data) __THROW; mount = libc.mount mount.__doc__ = "Libc's mount call; see mount (2)." mount.argtypes = [c_char_p, c_char_p, c_char_p, c_uint32, c_char_p] mount.restype = c_int mount.errcheck = libc_errcheck #extern int umount (__const char *__special_file) __THROW; umount = libc.umount umount.__doc__ = "Libc's umount call; see umount (2)." umount.argtypes = [c_char_p] umount.restype = c_int umount.errcheck = libc_errcheck def bindmount(src, tgt, read_only, recursive): """Calls mount with appropriate arguments. """ flags = MS_BIND if read_only: flags |= MS_RDONLY if recursive: flags |= MS_REC mount(src, tgt, '', flags, '') def read_envdir(envdir, overrides=None): # We're going to read an envdir. # We'll add k->v pairs to our dictionary for every # regular file or symlink to a regular file in opts.envdir. # XXX: Several of these calls can block. Do we care? # XXX: How many bytes should we read? # XXX: Any issues with special characters? env = {} d = envdir if d and isdir(d): for p in listdir(d): f = None try: f = os.open(join(d, p), os.O_RDONLY) s = fstat(f) if S_ISREG(s[ST_MODE]): env[p] = os.read(f, 1024) finally: if f is not None: os.close(f) if overrides: for binding in overrides: k, v = binding.split('=',1) env[k] = v return env