def main(argv=None):
    """
    A simple command-line interface for the containerexecutor module of BenchExec.
    """
    if argv is None:
        argv = sys.argv

    # parse options
    parser = argparse.ArgumentParser(
        fromfile_prefix_chars='@',
        description=
        """Execute a command inside a simple container, i.e., partially isolated from the host.
           Command-line parameters can additionally be read from a file if file name prefixed with '@' is given as argument.
           Part of BenchExec: https://github.com/sosy-lab/benchexec/""")
    parser.add_argument("--dir", metavar="DIR",
                        help="working directory for executing the command (default is current directory)")
    parser.add_argument("--root", action="store_true",
                        help="use UID 0 and GID 0 (i.e., fake root account) within container")
    parser.add_argument("--uid", metavar="UID", type=int, default=None,
                        help="use given UID within container (default: current UID)")
    parser.add_argument("--gid", metavar="GID", type=int, default=None,
                        help="use given GID within container (default: current UID)")
    add_basic_container_args(parser)
    add_container_output_args(parser)
    baseexecutor.add_basic_executor_options(parser)

    options = parser.parse_args(argv[1:])
    baseexecutor.handle_basic_executor_options(options, parser)
    container_options = handle_basic_container_args(options, parser)
    container_output_options = handle_container_output_args(options, parser)

    if options.root:
        if options.uid is not None or options.gid is not None:
            parser.error("Cannot combine option --root with --uid/--gid")
        options.uid = 0
        options.gid = 0

    formatted_args = " ".join(map(util.escape_string_shell, options.args))
    logging.info('Starting command %s', formatted_args)

    executor = ContainerExecutor(uid=options.uid, gid=options.gid, **container_options)

    # ensure that process gets killed on interrupt/kill signal
    def signal_handler_kill(signum, frame):
        executor.stop()
    signal.signal(signal.SIGTERM, signal_handler_kill)
    signal.signal(signal.SIGINT,  signal_handler_kill)

    # actual run execution
    try:
        result = executor.execute_run(options.args, workingDir=options.dir,
                                      **container_output_options)
    except (BenchExecException, OSError) as e:
        if options.debug:
            logging.exception(e)
        sys.exit("Cannot execute {0}: {1}".format(util.escape_string_shell(options.args[0]), e))
    return result.signal or result.value
예제 #2
0
def main(argv=None):
    """
    A simple command-line interface for the containerexecutor module of BenchExec.
    """
    if argv is None:
        argv = sys.argv

    # parse options
    parser = argparse.ArgumentParser(
        fromfile_prefix_chars='@',
        description=
        """Execute a command inside a simple container, i.e., partially isolated from the host.
           Command-line parameters can additionally be read from a file if file name prefixed with '@' is given as argument.
           Part of BenchExec: https://github.com/sosy-lab/benchexec/""")
    parser.add_argument("--dir", metavar="DIR",
                        help="working directory for executing the command (default is current directory)")
    parser.add_argument("--root", action="store_true",
                        help="use UID 0 and GID 0 (i.e., fake root account) within container")
    parser.add_argument("--uid", metavar="UID", type=int, default=None,
                        help="use given UID within container (default: current UID)")
    parser.add_argument("--gid", metavar="GID", type=int, default=None,
                        help="use given GID within container (default: current UID)")
    add_basic_container_args(parser)
    add_container_output_args(parser)
    baseexecutor.add_basic_executor_options(parser)

    options = parser.parse_args(argv[1:])
    baseexecutor.handle_basic_executor_options(options, parser)
    container_options = handle_basic_container_args(options, parser)
    container_output_options = handle_container_output_args(options, parser)

    if options.root:
        if options.uid is not None or options.gid is not None:
            parser.error("Cannot combine option --root with --uid/--gid")
        options.uid = 0
        options.gid = 0

    formatted_args = " ".join(map(util.escape_string_shell, options.args))
    logging.info('Starting command %s', formatted_args)

    executor = ContainerExecutor(uid=options.uid, gid=options.gid, **container_options)

    # ensure that process gets killed on interrupt/kill signal
    def signal_handler_kill(signum, frame):
        executor.stop()
    signal.signal(signal.SIGTERM, signal_handler_kill)
    signal.signal(signal.SIGINT,  signal_handler_kill)

    # actual run execution
    try:
        result = executor.execute_run(options.args, workingDir=options.dir,
                                      **container_output_options)
    except (BenchExecException, OSError) as e:
        if options.debug:
            logging.exception(e)
        sys.exit("Cannot execute {0}: {1}.".format(util.escape_string_shell(options.args[0]), e))
    return result.signal or result.value
예제 #3
0
def duplicate_mount_hierarchy(mount_base, temp_base, work_base, dir_modes):
    """
    Setup a copy of the system's mount hierarchy below a specified directory,
    and apply all specified directory modes (e.g., read-only access or hidden)
    in that new hierarchy.
    Afterwards, the new mount hierarchy can be chroot'ed into.
    @param mount_base: the base directory of the new mount hierarchy
    @param temp_base: the base directory for all temporary files
    @param work_base: the base directory for all overlayfs work files
    @param dir_modes: the directory modes to apply (without mount_base prefix)
    """
    # Create a copy of all mountpoints.
    # Setting MS_PRIVATE flag discouples the new mounts from the original mounts,
    # i.e., mounts we do are not seen outside the mount namespace,
    # and any (un)mounts that are made later in the main system are not seen by us.
    # The latter is desired such that new mounts (e.g., USB sticks being plugged in)
    # do not appear in the container.
    # Blocking host-side unmounts from being propagated has the disadvantage
    # that any unmounts done by the sysadmin won't really unmount the device
    # because it stays mounted in the container and thus keep the device busy
    # (cf. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=739593#85).
    # We could allow unmounts being propated with MS_SLAVE instead of MS_PRIVATE,
    # but we prefer to have the mount namespace of the container being
    # unchanged during run execution.
    make_bind_mount(b"/", mount_base, recursive=True, private=True)

    # Ensure each special dir is a mountpoint such that the next loop covers it.
    for special_dir in dir_modes.keys():
        mount_path = mount_base + special_dir
        temp_path = temp_base + special_dir
        try:
            make_bind_mount(mount_path, mount_path)
        except OSError as e:
            # on btrfs, non-recursive bind mounts fail
            if e.errno == errno.EINVAL:
                try:
                    make_bind_mount(mount_path, mount_path, recursive=True)
                except OSError as e2:
                    logging.debug(
                        "Failed to make %s a (recursive) bind mount: %s", mount_path, e2
                    )
            else:
                logging.debug("Failed to make %s a bind mount: %s", mount_path, e)
        if not os.path.exists(temp_path):
            os.makedirs(temp_path)

    for unused_source, full_mountpoint, fstype, options in list(get_mount_points()):
        if not util.path_is_below(full_mountpoint, mount_base):
            continue
        mountpoint = full_mountpoint[len(mount_base) :] or b"/"
        mode = determine_directory_mode(dir_modes, mountpoint, fstype)
        if not mode:
            continue

        if not os.access(os.path.dirname(mountpoint), os.X_OK):
            # If parent is not accessible we cannot mount something on mountpoint.
            # We mark the inaccessible directory as hidden
            # because otherwise the mountpoint could become accessible (directly!)
            # if the permissions on the parent are relaxed during container execution.
            original_mountpoint = mountpoint
            parent = os.path.dirname(mountpoint)
            while not os.access(parent, os.X_OK):
                mountpoint = parent
                parent = os.path.dirname(mountpoint)
            mode = DIR_HIDDEN
            logging.debug(
                "Marking inaccessible directory '%s' as hidden "
                "because it contains a mountpoint at '%s'",
                mountpoint.decode(),
                original_mountpoint.decode(),
            )
        else:
            logging.debug("Mounting '%s' as %s", mountpoint.decode(), mode)

        mount_path = mount_base + mountpoint
        temp_path = temp_base + mountpoint
        work_path = work_base + mountpoint

        if mode == DIR_OVERLAY:
            if not os.path.exists(temp_path):
                os.makedirs(temp_path)
            if not os.path.exists(work_path):
                os.makedirs(work_path)
            try:
                # Previous mount in this place not needed if replaced with overlay dir.
                libc.umount(mount_path)
            except OSError as e:
                logging.debug(e)
            try:
                make_overlay_mount(mount_path, mountpoint, temp_path, work_path)
            except OSError as e:
                mp = mountpoint.decode()
                raise OSError(
                    e.errno,
                    "Creating overlay mount for '{}' failed: {}. Please use "
                    "other directory modes, for example '--read-only-dir {}'.".format(
                        mp, os.strerror(e.errno), util.escape_string_shell(mp)
                    ),
                )

        elif mode == DIR_HIDDEN:
            if not os.path.exists(temp_path):
                os.makedirs(temp_path)
            try:
                # Previous mount in this place not needed if replaced with hidden dir.
                libc.umount(mount_path)
            except OSError as e:
                logging.debug(e)
            make_bind_mount(temp_path, mount_path)

        elif mode == DIR_READ_ONLY:
            try:
                remount_with_additional_flags(mount_path, options, libc.MS_RDONLY)
            except OSError as e:
                if e.errno == errno.EACCES:
                    logging.warning(
                        "Cannot mount '%s', directory may be missing from container.",
                        mountpoint.decode(),
                    )
                else:
                    # If this mountpoint is below an overlay/hidden dir,
                    # re-create mountpoint.
                    # Linux does not support making read-only bind mounts in one step:
                    # https://lwn.net/Articles/281157/
                    # http://man7.org/linux/man-pages/man8/mount.8.html
                    make_bind_mount(
                        mountpoint, mount_path, recursive=True, private=True
                    )
                    remount_with_additional_flags(mount_path, options, libc.MS_RDONLY)

        elif mode == DIR_FULL_ACCESS:
            try:
                # Ensure directory is still a mountpoint by attempting to remount.
                remount_with_additional_flags(mount_path, options, 0)
            except OSError as e:
                if e.errno == errno.EACCES:
                    logging.warning(
                        "Cannot mount '%s', directory may be missing from container.",
                        mountpoint.decode(),
                    )
                else:
                    # If this mountpoint is below an overlay/hidden dir,
                    # re-create mountpoint.
                    make_bind_mount(
                        mountpoint, mount_path, recursive=True, private=True
                    )

        else:
            assert False
예제 #4
0
 def get_group_name(gid):
     try:
         name = grp.getgrgid(gid).gr_name
     except KeyError:
         name = None
     return util.escape_string_shell(name or str(gid))