Date: 12/23/97 12:54:53 PM From: Aleph One Subject: userv - how to make cron (et al) not setuid To: (""@LOCAL) ---------- Forwarded message ---------- Date: Wed, 17 Dec 97 11:19 GMT From: Ian Jackson To: linux-security@redhat.com 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: 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 cron/callback 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 feeding it on standard input a list of times before (a unix time in decimal) when the user wants to be woken up (this data being derived from ~/.userv/.servdata/cron/crontab). The 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 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: ijackson@chiark.greenend.org.uk ian@chiark.greenend.org.uk http://www.chiark.greenend.org.uk/~ijackson/ --