userv - how to make cron (et al) not setuid

From: Aleph One (aleph1at_private)
Date: Tue Dec 23 1997 - 09:12:52 PST

  • Next message: Marcin Bohosiewicz: "SECURITY: util-linux-2.5-39 (RH4.2)"

    ---------- Forwarded message ----------
    Date: Wed, 17 Dec 97 11:19 GMT
    From: Ian Jackson <userv-maintat_private>
    To: linux-securityat_private
    Subject: [linux-security] userv - how to make cron (et al) not setuid
    
    0. Introduction
    
    Some time ago I posted on linux-security to say that I was working on
    a client/server pair which would allow you to invoke a privileged
    service in a more secure manner.  I've now completed this, and it's
    been service by way of alpha-test on my own system for some time,
    implementing user-provided CGI scripts (which run as the user).
    
    Perhaps I should explain some more.  There are a number of places on a
    Unix system where program invocations cross security boundaries.  At
    the moment this is handled using setuid programs, which has a number
    of disadvantages: you can't easily draw the security boundary where
    you want it; the setuid program really has to be written in C (with
    attendant buffer overrun problems et al); and sanitising the
    environment (not just the environment variables, but things like
    umasks, ulimits, and all the other guff that children inherit) to make
    it safe to execute is very difficult.
    
    My replacement works as follows: you design your program so that the
    security boundary is exactly at a program invocation, and replace the
    setuid call with a call via my `userv' facility.  userv is responsible
    for properly enforcing the security boundary, so that the called parts
    of your program can trust their PATH, ulimits, controlling tty, et
    al.  userv has to be secure, but you only have to write it once.
    Furthermore, the client/server model means you don't have to worry so
    much about resetting all the many inherited properties of processes.
    
    1. Applications
    
    The applications are many.  A lot of Unix facilities have mutually
    untrusting security domains (users) calling each others' programs.
    For example, mail and lpr (with deferred printing) want to be able to
    access the users' files without trusting those users, but the user
    doesn't want to trust them more than they have to.
    
    Below I'll describe an example of how to use userv to implement a more
    secure cron/at daemon.  I haven't actually gotten round to writing
    this yet, mainly because it seemed an uninteresting problem - a few
    fairly trivial Perl scripts.  Compare that to the onerous task of
    writing a secure cron subsystem in C using only conventional setuid
    calls ...
    
    3. Configuration
    
    userv has a configuration language which allows users (and the system
    administrator on behalf of users) to control which commands can be
    executed as them and exactly how, by which other users.  Details about
    the invoking user are also passed to the executed program in special
    USERV_... environment variables, so that it can implement its own
    access control or other features.
    
    The configuration scheme has the facility for system administrators to
    provide default configurations of services, which the user can
    override, or for the sysadmin to provide `mandatory' services.  For
    example, a sysadmin might wish to be able to securely remove old files
    in /var/tmp, and could enforce the provision of a `remove this file in
    /var/tmp' facility to some semi-trusted set of operator users.
    
    4. Limitations
    
    userv does not solve all security problems with privileged programs.
    For example, privileged programs written in C invoked by userv will
    still be vulnerable to any buffer overruns in their argument parsing
    (if userv is configured to allow arguments to be passed to them).
    However, with userv it is no longer necessary to use C for many
    privileged programs.  It is much easier to make a correct and secure
    privileged program if it can be written in a language like Perl,
    Python or whatever.
    
    userv is also not suitable for all applications.  Situations where the
    privileged program needs (for example) to make ioctls on the
    controlling tty cannot be resolved using userv, because userv isolates
    the called program from the caller's tty.  Programs like `really'
    which are intended to allow already-trusted accounts (which are not
    root to avoid mistakes rather than intrusion) to become root are not
    appropriate for userv, because userv services do not inherit the
    caller's environment.
    
    5. URL
    
    Anyway, userv has a WWW page:
     <URL:http://www.chiark.greenend.org.uk/~ian/userv/>
    
    userv is short for `user services', and is pronounced `you-serve'.
    
    6. cron example
    
    Here I will describe the overall design of a cron subsystem which has
    a much smaller probability of having security bugs than a conventional
    cron.  Because userv is used to deal with many of the security issues,
    the design can concentrate on scheduling (cron's ostensible task)
    rather than security.  The system is even extensible - new crontab
    formats with enhanced semantics can be introduced without needing to
    touch the system-provided core.
    
    The system is split into two security areas: the user-side utilities,
    and a system-provided daemon which calls back the user when the time
    for a job has been reached.  The daemon runs as `cron'; there is no
    root component except userv.
    
    The daemon maintains in core a list of times at which each user wanted
    to be called back.  It repeatedly sleeps until the next time has been
    reached, and then it invokes
      userv <user> cron/callback <execution-time>
    
    The system configuration files for userv are arranged so that the
    `cron' user (and no other) can run cron/callback as any user, and that
    this causes the program /usr/lib/cron/services/callback to be run.
    This program checks that the current time is `close enough' to the
    time specified on its command line, scans the user's crontab file,
    kept in ~/.userv/.servdata/cron/crontab (not in /var), and executes
    each command which needs to be run that minute.  It deals with mailing
    the output (if any) back to the user.
    
    How does the user update their crontab ?  They call the `crontab'
    command, in /usr/bin, as usual.  This program stores the new crontab
    in ~/.userv/.servdata/cron/crontab, and invokes
    /usr/lib/cron/services/tellcron.
    
    /usr/lib/cron/services/tellcron runs
      userv cron cron/update <horizon-time>
    feeding it on standard input a list of times before <horizon-time> (a
    unix time in decimal) when the user wants to be woken up (this data
    being derived from ~/.userv/.servdata/cron/crontab).  The
    <horizon-time> would be some fixed time (an hour, perhaps) from now.
    
    The userv configuration arranges for any user to be able to run
    /usr/lib/cron/services/update as cron by calling cron/update.  The
    update script checks that the calling user (in $ENV{'USERV_USER'}) is
    allowed to use cron, checks the syntax of the input, and writes the
    data (with the name of the user) to a named pipe in /var/run/cron
    (some locking is required here, and formatting of the data in a way
    that means that partial writes are not accepted and do not interfere
    with the next update's write).  It reads the pipe to wait for the cron
    daemon's reply.
    
    At system startup the cron daemon runs
      userv <user> cron/tellcron
    as each user who is allowed to run cron jobs.  This is configured to
    run /usr/lib/cron/services/tellcron.
    
    Note that the system can easily be extended to at jobs, without
    needing to change the daemon.
    
    >From a security point of view, the worst that the cron daemon could do
    would be to cause genuine cron jobs to be executed too often or not at
    all, or to repeatedly request `tellcron'.  The worst that a user can
    do is repeatedly chop and change their list of pending times.
    
    The data crossing the security boundary - just timestamps (with
    usernames supplied by userv) - is very simple and so easy to check and
    hard to misparse, and the programs can all be written in a
    straightforward interpreted language.  The user's crontab, whose
    parsing is complicated, and which contains data which is to be
    executed, stays entirely within the user's own security boundary -
    noone but the user themselves ever needs to touch that data.
    
    --
    Ian Jackson, at home.           Local/personal: ijacksonat_private
    ianat_private         http://www.chiark.greenend.org.uk/~ijackson/
    
    --
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 13:37:19 PDT