Class 18: System Calls

Posted: Thu 03 April 2014

Schedule your PS4 demo by 11:59pm today: Signup Form
PS4 is due Sunday, 6 April.

Course Goal Reminder: Minimizing Magic

By the time you finish PS4 (that is, this Sunday!), you really should feel like you understand everything important that is going on in a computing system, from the level of a web service like Google or Facebook down to the level of a transistor (but not necessarily the physics that makes the transistor work). There are, of course, lots of details and clever algorithms needed to make things work efficiently, but nothing should seem beyond your understanding about how these systems work.

The one important gap we have left to cover in this class is how to build concurrency abstractions like the MutexArc without starting with mutual exclusion mechanisms. I promised to talk about this back in Class 14 (when Leslie Lamport won the Turing Award), but will finally get around to it next week. The other major gap that you may still have is how a compiler turns a high-level program into something at the level of machine code. We won't cover that in this class, but you should have a reasonable idea how this works from cs2150, and I would encourage you to take a compilers course to learn more.

If there are other gaps in your understanding between the transistor and web service, you should comment about them below, or email me directly, and I'll try to include these topics in remaining classes.

Access Control

Why is chroot not a completely satisfactory way to limit the files that might be exposed by a web server?

Should file permissions be part of the directory structure or part of the inode?

struct inode {
        umode_t                 i_mode;
        unsigned short          i_opflags;
        uid_t                   i_uid;
        gid_t                   i_gid;
        unsigned int            i_flags;
        ...

From include/linux/fs.h

Why is chmod 666 filename usually a bad idea?

setuid

Why does setuid(0) do? When should it be allowed?

How does changing the effective uid to serve a web request follow the principle of least priviledge?

Hao Chen, David Wagner, and Drew Dean. Setuid Demystified (USENIX Security 2002).

From Apache httpd's support/suexec.c:

/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * ...
 */

/*
 * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
 ***********************************************************************
 * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
 *         editing this code might open up your system in unexpected
 *         ways to would-be crackers.  Every precaution has been taken
 *         to make this code as safe as possible; alter it at your own
 *         risk.
 ***********************************************************************
 */

...
    /*
     * Log the transaction here to be sure we have an open log
     * before we setuid().
     */
    log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
               target_uname, actual_uname,
               target_gname, actual_gname,
               cmd);

    /*
     * Error out if attempt is made to execute as root or as
     * a UID less than AP_UID_MIN.  Tsk tsk.
     */
    if ((uid == 0) || (uid < AP_UID_MIN)) {
        log_err("cannot run as forbidden uid (%lu/%s)\n", (unsigned long)uid, cmd);
        exit(107);
    }

    /*
     * Error out if attempt is made to execute as root group
     * or as a GID less than AP_GID_MIN.  Tsk tsk.
     */
    if ((gid == 0) || (gid < AP_GID_MIN)) {
        log_err("cannot run as forbidden gid (%lu/%s)\n", (unsigned long)gid, cmd);
        exit(108);
    }

    /*
     * Change UID/GID here so that the following tests work over NFS.
     *
     * Initialize the group access list for the target user,
     * and setgid() to the target group. If unsuccessful, error out.
     */
    if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
        log_err("failed to setgid (%lu: %s)\n", (unsigned long)gid, cmd);
        exit(109);
    }

    /*
     * setuid() to the target user.  Error out on fail.
     */
    if ((setuid(uid)) != 0) {
        log_err("failed to setuid (%lu: %s)\n", (unsigned long)uid, cmd);
        exit(110);
    }

    /*
     * Get the current working directory, as well as the proper
     * document root (dependant upon whether or not it is a
     * ~userdir request).  Error out if we cannot get either one,
     * or if the current working directory is not in the docroot.
     * Use chdir()s and getcwd()s to avoid problems with symlinked
     * directories.  Yuck.
     */
    if (getcwd(cwd, AP_MAXPATH) == NULL) {
        log_err("cannot get current working directory\n");
        exit(111);
    }

    if (userdir) {
        if (((chdir(target_homedir)) != 0) ||
            ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
            ((chdir(cwd)) != 0)) {
            log_err("cannot get docroot information (%s)\n", target_homedir);
            exit(112);
        }
    }
    else {
        if (((chdir(AP_DOC_ROOT)) != 0) ||
            ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
            ((chdir(cwd)) != 0)) {
            log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
            exit(113);
        }
    }

    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
        exit(114);
    }

    /*
     * Stat the cwd and verify it is a directory, or error out.
     */
    if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
        log_err("cannot stat directory: (%s)\n", cwd);
        exit(115);
    }

    /*
     * Error out if cwd is writable by others.
     */
    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
        log_err("directory is writable by others: (%s)\n", cwd);
        exit(116);
    }

    /*
     * Error out if we cannot stat the program.
     */
    if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
        log_err("cannot stat program: (%s)\n", cmd);
        exit(117);
    }

    /*
     * Error out if the program is writable by others.
     */
    if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
        log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
        exit(118);
    }

    /*
     * Error out if the file is setuid or setgid.
     */
    if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
        log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
        exit(119);
    }

    /*
     * Error out if the target name/group is different from
     * the name/group of the cwd or the program.
     */
    if ((uid != dir_info.st_uid) ||
        (gid != dir_info.st_gid) ||
        (uid != prg_info.st_uid) ||
        (gid != prg_info.st_gid)) {
        log_err("target uid/gid (%lu/%lu) mismatch "
                "with directory (%lu/%lu) or program (%lu/%lu)\n",
                (unsigned long)uid, (unsigned long)gid,
                (unsigned long)dir_info.st_uid, (unsigned long)dir_info.st_gid,
                (unsigned long)prg_info.st_uid, (unsigned long)prg_info.st_gid);
        exit(120);
    }
    /*
     * Error out if the program is not executable for the user.
     * Otherwise, she won't find any error in the logs except for
     * "[error] Premature end of script headers: ..."
     */
    if (!(prg_info.st_mode & S_IXUSR)) {
        log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
        exit(121);
    }

#ifdef AP_SUEXEC_UMASK
    /*
     * umask() uses inverse logic; bits are CLEAR for allowed access.
     */
    if ((~AP_SUEXEC_UMASK) & 0022) {
        log_err("notice: AP_SUEXEC_UMASK of %03o allows "
                "write permission to group and/or other\n", AP_SUEXEC_UMASK);
    }
    umask(AP_SUEXEC_UMASK);
#endif /* AP_SUEXEC_UMASK */

    /* Be sure to close the log file so the CGI can't mess with it. */
    if (log != NULL) {
#if APR_HAVE_FCNTL_H
        /*
         * ask fcntl(2) to set the FD_CLOEXEC flag on the log file,
         * so it'll be automagically closed if the exec() call succeeds.
         */
        fflush(log);
        setbuf(log, NULL);
        if ((fcntl(fileno(log), F_SETFD, FD_CLOEXEC) == -1)) {
            log_err("error: can't set close-on-exec flag");
            exit(122);
        }
#else
        /*
         * In this case, exec() errors won't be logged because we have already
         * dropped privileges and won't be able to reopen the log file.
         */
        fclose(log);
        log = NULL;
#endif
    }

    /*
     * Execute the command, replacing our image with its own.
     */
#ifdef NEED_HASHBANG_EMUL
    /* We need the #! emulation when we want to execute scripts */
    {
        extern char **environ;

        ap_execve(cmd, &argv[3], environ);
    }
#else /*NEED_HASHBANG_EMUL*/
    execv(cmd, &argv[3]);
#endif /*NEED_HASHBANG_EMUL*/

    /*
     * (I can't help myself...sorry.)
     *
     * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
     * EARTH-shattering kaboom!
     *
     * Oh well, log the failure and error out.
     */
    log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
    exit(255);
}

Identify at least 10 ways this code is better than Apple's SSL implementation:

Implementing System Calls

What is libc and why does it exist?

Why can't setuid in glibc call directly into the kernel?

Exploring the Kernel

Here's how to get the linux source code for the kernel you are running:

apt-get source linux-image-$(uname -r)

(this works in Ubuntu using the bash shell, you may need to do something slightly different on other platforms).

Here's an example of a useful command for searching the kernel code:

find . -name "*.h" -print | xargs grep "IA32_SYSCALL_VECTOR"

What does set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall) (in linux-3.2.0/arch/x86/kernel/traps.c) do?

What are all the steps in going from the setuid(uid) call in suexec.c to changing the user id of the running process?

comments powered by Disqus