Writing safe suid programs

Writing safe suid programs

Post by Jim Jagiels » Wed, 07 Aug 1991 22:41:22



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());
        .
        .
        .

Quote:}

--------------------

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 *ism in the British Navy. Absolutely none. And
   when I say none, I mean there is a certain amount..."

 
 
 

Writing safe suid programs

Post by Dave Eis » Thu, 08 Aug 1991 15:41:34



>--------------------

>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...

The rub is that this is UNIX-version dependent.

In BSD systems, setreuid works exactly as you want it to. If your
program is run setuid, you can switch the effective uid to be the
real uid or the original effective uid at will as often as you
want in your program.

In SYSV systems, unless your process's effective uid is root, you
are pretty much up the creek. And once you switch the euid to
be the user's real uid, you can't switch it back.

It sounds like your system is SYSVish (BTW --- it is usually helpful
if you tell us what kind of system you are running if you want us to
figure out s system call that works differently from what you have read),
so you can't swap uids around so easily. What you want to do is to
have a routine that spawns off your subshells and in this routine, take
care of the uid stuff. Something like this:

your_system (const char *cmd)
{
   int pid;

   switch (pid = fork ()) {
   case -1: /* fork failed. Bail */
      return -1;

   case 0: /* The child */
      setgid (getgid ());
      setuid (getuid ());
      execl ("/bin/sh", "sh", "-c", cmd, (char *) 0);
      _exit (1); /* Only called if exec fails */
   default: /* The parent */

   /* ... */
   }

Quote:}

and the child subprocesses are run safely while the parent process's
uids and gids aren't touched.

--

      There's something in my library to offend everybody.
        --- Washington Coalition Against Censorship

 
 
 

Writing safe suid programs

Post by Mike Stefan » Fri, 09 Aug 1991 07:40:57



|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.

In the case of sub-shells, you can change the UID and GID of the child
process after the fork, and before the exec.  For example:

        if ( (pid = fork()) > 0 )
                wait(&status);
        else if ( ! pid ) {
                setuid(foo);
                setgid(bar);
                execl("/bin/sh", "sh", (char *)NULL);
        }

As for changing the UID and GID to non-zero and then back, I don't think
this can be (or should be) done within the context of a single process.
--
Mike Stefanik, MGI Inc., Los Angeles -- Opinions stated are never realistic!

 
 
 

Writing safe suid programs

Post by Chris Lew » Fri, 09 Aug 1991 04:55:58




>>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...
>The rub is that this is UNIX-version dependent.

That's for sure.

Quote:>In BSD systems, setreuid works exactly as you want it to. If your
>program is run setuid, you can switch the effective uid to be the
>real uid or the original effective uid at will as often as you
>want in your program.
>In SYSV systems, unless your process's effective uid is root, you
>are pretty much up the creek. And once you switch the euid to
>be the user's real uid, you can't switch it back.

SYSV has the saved-uid mechanism that Jim was referring to, but he's
not running on pure System V because System V doesn't *have* setreuid().
(it only has setuid(), because it doesn't permit the individual settings
of real or effective uid).  Some purported System V compliant systems
didn't have the saved-uid implemented or it didn't work.  (Old Xenix
and 3b1 comes to mind)

Quote:>It sounds like your system is SYSVish (BTW --- it is usually helpful
>if you tell us what kind of system you are running if you want us to
>figure out s system call that works differently from what you have read),

Agreed - especially on things as obscure as this, you should *REALLY*
say what version of UNIX you are on.

On the other hand, if it *doesn't* work, and he *does* have setreuid(),
it implies to me that he's on a Sun or possibly RS/6000 with the saved-setuid
feature disabled or nonexistant - some of these routines are no-ops on
RS/6000 (causes configuration problems with Perl if you're trying to use
setuid Perl scripts).  On Sun this behaviour is configurable.

 
 
 

Writing safe suid programs

Post by Darryl P. Wagon » Fri, 09 Aug 1991 21:48:09



>Howdy!

What you want to do is only possible with forks.  The parent process
remains root.

All return code for setreuid must be check to issure security.

#include <stdio.h>
#include <sys/types.h>
main() {
        uid_t saved_uid;
        saved_uid = getuid();  /* note change to getuid (real) */
        if (setreuid(0,0))        /* note! This scheme will only for
           {
                perror("This program must be suid to root);
                exit(1);
           }
        /* we are now

/* normal dude */
        if (fork() == 0)
          {
          if (setreuid(saved_uid,saved_uid))
                {
                perror("Error setreuid");
                exit(1);
                }
          do the non-root stuff
          exit(0);
          }
        else
          wait((int *) 0);  /* status should be check, exercise for
                                reader */
        .
        .
        .
/* now back again to real uid */
        if (setreuid(saved_uid,saved_uid))
           {
           perror("Error setreuid");
           exit(2);
           }
        .
        .
        .

Quote:}

--

12 Oak Hill Road
Brookline, NH 03033
Office: 603.672.0736            Home: 603.673.0578
 
 
 

Writing safe suid programs

Post by Veteran of a Thousand Psychic Wa » Sun, 11 Aug 1991 09:47:08



 * Howdy!

[ Jim wants to start a program suid-root, immediately switch to joe-user,
switch to root to open a file and switch to joe user to continue running. ]

If you need to open a file as root, why not do that at the beginning?

If you need to switch lots between root and joe user, why not

        /* as root */

        fd = open(file, access, mode);

        /* become other user */

        if ((pid = fork()) < 0) {/* error */
                handle_error();
        }
        else if (pid) { /* parent */
                wait(0);        /* wait3(), wait4(), whatever... */
        }
        else {  /* child */
            setreuid(joe_userid, joe_userid);
            .
            .
            .
        }

If you need some way of saving state between processes, there are quite
a few methods of IPC available -- some primitive (i.e. read/write to/from
a file), some not (shared memory, sockets, pipes).  But you can accomplish
what you want to do without relying on the not-always-consistent effects
of setreuid().
--

# "...to raise a signal means to turn the light on; ... Responding to a
#  signal means turning the light off (and, under System V, hoping the bulb
#  won't blow when it's next turned on)..." -- Dan Bernstein

 
 
 

Writing safe suid programs

Post by Guy Harr » Sun, 11 Aug 1991 15:11:40


 >On the other hand, if it *doesn't* work, and he *does* have setreuid(),
 >it implies to me that he's on a Sun or possibly RS/6000 with the saved-setuid
 >feature disabled or nonexistant - some of these routines are no-ops on
 >RS/6000 (causes configuration problems with Perl if you're trying to use
 >setuid Perl scripts).  On Sun this behaviour is configurable.

The only ways to configure the saved-setuid feature out of SunOS are:

1) install a version of SunOS prior to SunOS 3.2;

2) get the source, hack the kernel source to rip it out, rebuild the
   kernel, and reboot with the new kernel.

There is *NO* SunOS configuration option to disable it.  (And I'm the
guy who put it in, so I should know.)

In any case, his code does what he wants it to do when compiled in
either the BSD or System V environment in SunOS 4.x.

I suspect he may be running on some *other* system with "setreuid()" and
no saved-setuid feature, e.g. BSD before 4.3-reno.  If so, the strategy
to use for flipping back and forth between the initial real and
effective user IDs is:

        initial_real_uid = getuid();
        initial_effective_uid = geteuid();

        /* effective UID is "initial_effective_uid" */

        setreuid(initial_effective_uid, initial_real_uid);

        /* effective UID is now "initial_real_uid" */

        setreuid(initial_real_uid, initial_effective_uid);

        /* effective UID is now "initial_effective_uid" again */

which also happens to work in SunOS and probably still works in 4.3-reno
as well.

I think an upcoming update to POSIX will have "seteuid()", which sets
the effective UID and leaves the real and saved set-user IDs alone, even
if the effective UID is currently "root"; it will then have to show up
in a future SV release (although it may already be there, but
undocumented, in SVR4 as it exists now).  It already exists in 4.3BSD
(probably all 4.2BSD as well), SunOS, and probably lots of other UNIXes.

 
 
 

Writing safe suid programs

Post by Guy Harr » Sun, 11 Aug 1991 15:18:00


Quote:>As for changing the UID and GID to non-zero and then back, I don't think
>this can be (or should be) done within the context of a single process.

I don't think the POSIX folk agree with you here, fortunately; I think
they'll be putting "seteuid()" and "setegid()" into a future 1003.1
update, which sets the effective [UG]ID and leaves the saved
set-{user,group} ID alone, even if the effective UID is currently 0, so
that you can change the [UG]ID back and forth from the original
effective [UG]ID and the original real [UG]ID multiple times within the
context of a single process.

Some systems, e.g. SunOS 3.2 and later, and BSD4.3-reno and later,
already have both "sete[ug]id()" and saved set-{user,group} IDs.  If
POSIX adds it, others will follow.

Of course, if you're running a system with a BSD-style "setre[ug]id()",
you can probably switch back and forth multiple times by swapping the
real and effective {user,group} IDs, as those calls let you set the real
[UG]ID to match the effective [UG]ID and set the effective [UG]ID to
match the real [UG]ID.  That call appears to be deprecated in 4.3-reno,
though; saved set-{user,group} IDs are the way to go there.

 
 
 

Writing safe suid programs

Post by Chris Lew » Tue, 13 Aug 1991 13:03:12



>There is *NO* SunOS configuration option to disable it.  (And I'm the
>guy who put it in, so I should know.)

Oops.  Misread the manual page.  Sorry.

The RS/6000 has some of these routines, but they're *documented* to do
nothing other than return an error.
--
Chris Lewis; UUCP: ...!{cunews,uunet,latour}!ecicrl!clewis;



 
 
 

1. Writing a safe suid root program

What are the things one has to take care of when writing a suid root
program?

I've found a few points, and I'm wondering if other people could
add some more.

- Don't use buffers which could be overrun by the user; use dynamic
  memory or truncuate his input, if necessary.  Using gets() or
  scanf("%s") is a sure way to ruin.

- Don't use functions which rely on the user's environment.  Invoking
  another program via system(), popen() or exec?p without explicitly
  setting your own environment including PATH and IFS is a no-no,
  as is using the tmpnam() function, which rely on the user's
  TMPDIR.

- Don't write to files if the user has write permission in any
  of the directories leading up to it, if you need to open the
  file to write to it.  Writing to stdout and stderr should
  be ok (IS THAT RIGHT?), as should be reading from stdin.

- Don't trust filenames supplied by the user for either reading or
  writing.  Using access() is not enough, because of possible race
  conditions and symbolic links.  (The only solution I've found so far
  is to fork off a child, which does a setgid(getgid());
  setuid(getuid());, then opens the file and reads from/ writes to it,
  communicating with its parent with a pipe.  This is far from elegant,
  though).

- Check for every failed function call, and abort if necessary.

Anything more?
--

The joy of engineering is to find a straight line on a double
logarithmic diagram.

2. Required patches for Solaris 2.3

3. Safe pseudo-suid execution of user programs

4. FWD: SNMP and Linux question

5. Are there any guidelines for writing a secure SUID program?

6. Is There a Linux Driver for Acer M314x Graphics Card ?

7. Writing suid root program

8. Swap and I/O

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

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

11. How do you make a safe SUID script?

12. hints, guidelines for safe SUID C apps?

13. Is procmail safe to suid root?