On the topic of reducing privilege, one thing I've always wanted
to do (but never had time for!) is what I'd call "syscall wrappers"
for lack of a better term.
Most UNIXes have a jump table that defines all the system calls,
and almost all include the source for the jump table as part of
the basic binary distribution, so that you can add new ones if you
want. So, the idea:
1) write a call-through interface that replaces all the original
calls in syscalls.c. On a BSD4 system its /sys/kern/syscalls.c
and it looks like:
char *syscallnames[] = {
"#0", /* 0 = indir or out-of-range */
"exit", /* 1 = exit */
"fork", /* 2 = fork */
"read", /* 3 = read */
"write", /* 4 = write */
This is just the names. There's a mapping table that is
built which maps syscall #4 to the actual kernel function
that does write( ) -- so you'd replace that with
syscall_wrapper_write( ) which would do various things
and maybe call the original write( ) just the same way
that tcp_wrappers "replaces" the original program that
inetd would call with an intermediary.
2) maintain a separate data structure that is a call permissions
table, then keep a set of reference counted structures around,
which are inherited across exec( ) and fork( ) just like file
descriptors. if you want to be somewhat hands off, I think you
could almost do it so that you didn't even need to put a hook
in struct proc but obviously fixing struct proc would be much
easier
3) add a new ioctl that allows a process to give away its right
to use a syscall. this would be irrevocable. it should have nothing
to do with being uid == 0.
4) perhaps add a new syscall that allows you to adopt a complete
set of syscalls. to use this you must be uid == 0 and not have given
the right to use it away. this way uid == 0 could give away a
process branch's ability to ever regain syscalls, by simply giving
away the magic syscall
5) whenever a call is made you check and see if the process is
allowed to do it, or you return EPERM and a new errno value.
You could further extend the system by having stub syscalls
(let's not call them proxies, shall we?) that actually unrolled some
arguments and further checked them. For example, the read( )
stub syscall might check the file read/write/append mode. Then
you could have a process' parent gave away its child's ability to
do anything but write( ) to a file after it had open( )ed it in append
mode. I suppose you could do things like disable relative path
processing ("..") on a per-process basis. There are times when
I really would have liked that... Or the ability to only exec( ) files
in "." This would extend the Java sandbox type concept into the
operating system -- a very useful idea if anyone could take
advantage of it.
There are problems with this scheme (aren't there always!?) -- lots
and lots of UNIX libraries do mysterious things under the sheets
that would break. DNS, for example, expects to read /etc/resolv.conf.
On other systems it reads other files before even doing that. And
so on. Another problem is that while it's a useful idea it's a kludge.
Normally it'd be better to design a generalized mechanism for
granting O/S interface permissions as part of a complete system
for permissions (files, ACLs, O/S calls, network calls) The other
problem with syscall wrappers is that they assume that the person
using them has an awareness of all the "gotchas" and ways a
particular call could be abused by a given application. That's too
large a knowledge set to expect anyone to have, for any practical
purpose.
It'd be a good hack for building firewall systems, if I were still
doing that kinda stuff. :)
mjr.
--
Marcus J. Ranum, CEO, Network Flight Recorder, Inc.
work - http://www.nfr.net
home - http://www.clark.net/pub/mjr
Received on Nov 16 1997