Safe pseudo-suid execution of user programs

Safe pseudo-suid execution of user programs

Post by Dan Egn » Thu, 04 Apr 1996 04:00:00



Hi all,

It's often useful to allow for user hooks in system daemons.  Sendmail,
for example, allows this with the pipe feature of .forward files (users
can provide their own filters); many Web servers allow user execution of
programs via server-side includes or (with cgiwrap) per-user CGI directories.

I want to go one step further.  I'm setting up a finger server for our site.
I want to not only allow users to have ".finger" scripts which will be run
when they're fingered, but to have a world-writable (but sticky) directory
(following our site naming conventions, I'll refer to it as
"/ug/drop/finger"); if any user creates "/ug/drop/finger/foobar", and there
is no user "foobar", "finger foobar" (locally or remotely) would run
"/ug/drop/finger/foobar" as that user.  It would have to work under HP-UX
and IRIX.

The trick, of course, is figuring out what user to run it as (both OS'
allow give-away chown).  My current plan is to require such files to have
mode bits 4100 set and bits 0013 _not_ set.  Since the file can't be
executable by anyone by the owner, the setuid bit will introduce no security
holes; but its required existence will mean that only its actual owner could
have put the file there.

However, I don't want to assume that any file on the filesystem that fits
those criteria is intended to be run by anyone, so links are disallowed.

Moreover, I don't want to allow disabled or captive accounts (i.e. with a
shell that's not in /etc/shells) to run arbitrary commands this way.

In particular, the routine (which I may use for similar purposes in the
future) does the following:

  make sure the name fingered contains only alphanumeric characters and
  is no longer than (say) 32 characters.  prepend "/ug/drop/finger/" to
  form the file to run.

  | split the file into basename and dirname.
  | chdir() to the dirname.
  | getcwd() the canonical pathname.
  | chdir("..") until we're at the root directory.  at every point,
  |   stat(".") and make sure it's owned by root and that if it's
  |   world-writable it's also sticky.
  | chdir() back to the canonical pathname we saved.
  (The above is just sanity checking.)

  lstat() the basename.

  if the file doesn't exist (!), error and go away.
  if it's not a regular file (S_IFREG), error and go away.
  if the link count != 1, error and go away.
  if the mode bits don't match the above restrictions, error and go away.

  getpwuid() the owner of the file.
  if the return is NULL, error and go away.

  | if their shell isn't in /etc/shells, error and go away.
  | setgid(), initgroups(), setuid() to the user.
  | zorch the environment and rebuild it with a standard set.
  | close all file descriptors except stdin/stdout/stderr.
  | execve() the basename.
  (The above is handled by my custom "login" program.)

Have I thought of everything?  This is a pretty hairy thing that (AFAIK)
isn't done that much, are there any hidden gotchas?  The only thing I can
think of is a possible (but iffy) race condition.  If, for some reason, the
actual owner of the file deletes it between when the daemon performs the
lstat() and the execve() (perhaps the owner doesn't realize it's about to
be run), someone malicious could insert their own file and have it run as
the user.  This is a pretty limited window of opportunity, though; an
attacker would have to sit there like a hawk, generating request after
request (which would be logged) and waiting for someone to delete a file.

Another similar attack would be to find a file somewhere on the same
filesystem which had a mode which matched the required bits.  The "attacker"
could make a hard link into /ug/drop/finger.  If the original were then
deleted by its unsuspecting owner, the file could then be run (though the
"attacker" would have no control over its contents -- but it would be a file
they normally didn't have access to run, and it would be run as the file's
owner).  Mode 4744 files are kinda rare, though, and there isn't much else
on the same filesystem.

Dan

 
 
 

Safe pseudo-suid execution of user programs

Post by Doug Siebe » Fri, 05 Apr 1996 04:00:00



>"/ug/drop/finger/foobar" as that user.  It would have to work under HP-UX
>and IRIX.
>The trick, of course, is figuring out what user to run it as (both OS'
>allow give-away chown).  My current plan is to require such files to have

You can disable give-away chown in both HP-UX (see setprivgrp) and Irix
(see systune)

--
Doug Siebert              || "Usenet is essentially Letters to the Editor
University of Iowa        ||  without the editor.  Editors don't appreciate

(c) 1996 Doug Siebert.  Redistribution via the Microsoft Network is prohibited.

 
 
 

1. Writing safe suid programs

Howdy!

Ok, I'm investigating something I'm not quite sure about and it concerns
the use of either setreuid or seteuid to change between user id's.

Here's the thing: what I'd like to do is have a program which is suid to root.
Now as soon as you enter into this program, I want it to change the effective
ID back to the calling process, since it does stuff (like creating sub-shells)
that I DON'T want it to be root as.

Anyway, after I while, I want it to open a file which is only read/writeable
by root. At this point I'd like to reset the effective user ID back to root,
open the file, do my stuff, close it, and reset euid back to real-uid.

Kinda like this:
--------------------
#include <stdio.h>
#include <sys/types.h>
main() {
        uid_t saved_uid;
        saved_uid = geteuid();
        setreuid(-1,getuid());
/* normal dude */
        .
        .
        .
/* now go back to root */
        setreuid(-1,saved_uid);
        .
        .
        .
/* now back again to real uid */
        setreuid(-1,getuid());
        .
        .
        .
--------------------

Anyway, as it's "implemented" above, the 2nd setreuid ("back" to root)
fails, since, at this point, the process no longer has an effective root
uid. But I seem to recall a long time ago that the set-user uid was saved
somewhere to allow this to happen. In fact, I even have a few books that
mention this "technique" in writting correct suid scripts... so what's the
rub...

Or is this not even possible??
--
==============================================================================
#include <std/disclaimer.h>

           Jim Jagielski                    NASA/GSFC, Code 711.4

  "...there is no cannibalism in the British Navy. Absolutely none. And
   when I say none, I mean there is a certain amount..."

2. I'm more ignernt than I thought - what the hell is "autoconf"'s --help good for?

3. Writing a safe suid root program

4. MAIL, can I read, but not send?

5. IS there a way to trace suid program with suid permissions

6. Syscope - a graphical host monitoring tool

7. getting suid program to output to user's term

8. GNU/Linux in the Prepress World

9. QUESTION: suspicious suid program in user dir

10. New Linux user, program execution problems

11. Limiting Execution of SUID/SGID Binaries

12. SUID or SGID execution

13. Pointer to WWW site for "safe SUID code" guidelines/examples?