SUMMARY:SETUID Bit on C Executable

From: Lau, Victoria H (vlau@msmail2.hac.com)
Date: Tue Sep 02 1997 - 22:50:38 CDT


Thank you very much to all of you who have responded and helped me
with my problem. I was warned again and again not to do setuid scripts
or code, and that I should use sudo instead.

I'm definitely going to install sudo in the very near future. But
for those who are interested in a solution of running c code with
setuid bit, there must be a line like "setuid(0);" in the code, in
addition to chmod 4111 on the executable.

Since I've received many responses, and some of those are pretty lengthy
(thank you), I'll just pick out the major info that I found helpful and
place them randomly in the Solutions section.

My heartfelt THANKYOU to (sorry if I missed anybody's name):
"No Name"--per your wish
D. Stew McLeod
seanw@amgen.com
Jason marshall
Steve Franks
Francois Leclerc
Oscar Goldes
rsk@itw.com
Mariel Feder
Craig Robertson
Peter M Allan
Neal S. Pressman
Jens Fischer
Brian O'Mahoney
Steve Bigley
Jay a. Cohen
DonWilliams
Tim Carlson
Bobby Grover
Bryan Hodgson
Shriman Gurung

===========================================================================
Original question:

We want to allow any regular users to perform some daily tasks which
require root privilege, like mounting a file system from their removable
cartridge drives or copying some files to /tftpboot directory so that they
could download their code to another CPU (Motorola PPC). I wrote some
C code to perform one of the above tasks, compiled the code, then setuid
on the executable to 4111. However, when I (a regular user) executed the
code, it said my euid (effective uid) is 0--root, but the command failed
with "permission denied."

OS: Solaris 2.5.1
Platform: tested on Ultra Enterprise II and SPARCstation 20

I'm enclosing a piece of c code to copy a file to /tftpboot. After I
compiled the code, I did a "chmod 4111" (or chmod 4755") on the executable;
then ran the code as a regular user.

The "effective uid" (euid) confirmed that I was root with my_euid=0 just
before the "cp" command. However, it fails on the "cp" command because
the target directory /tftpboot is owned by root:other and the protection
is 755 (or any directory like /tmp/vicky with the same ownership and
protection).

The same code, converted to ksh script with the above file ownership
and protections (root:other and 4111) and above ownership and protections on
/tftpboot (root:other and 755) runs fine--the regular user can
copy a file to /tftpboot directory using the ksh script.

QUESTION: How to set setuid on c executable to allow a regular user to
run a single or multiple commands that require root privilege?

Thank you for your help.

Vicky Lau
vlau@msmail2.hac.com
(714) 446-3077

<<<<<< Attached TEXT file named "mvbootx.c" follows >>>>>>
/****************************************************************************/
/* */
/* File Name: mvbootx.c */
/* Date: August 25, 1997 */
/* */
/* */
/* Purpose: Allows any user to copy /user1/jvill/boot.x code to */
/* /tftpboot directory. This code is setuid to root. */
/* */
/* History: */
/* */
/****************************************************************************/

#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/* #define BOOTX_PATH "/user1/jvill/boot.x"
#define BOOTX_FILE "boot.x" */

#define BOOTX_PATH "/user1/vlau/vicky.x"
#define BOOTX_FILE "vicky.x"

#define OPENMODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP
                                        /* define open file mode with r/w permission
                                           for user and group */

int sighandler();

main()
{
  char cmd[80];
  int bootx_fd; /* bootx file descriptor for open and
                                           write functions */
  int status; /* return status */
  uid_t my_euid; /* tet euid (effective uid) */

  /* SET ERROR HANDLER TO EXIT CODE IF USER USES CONTROL_C */
  signal(SIGINT, sighandler);

sprintf(cmd,"/usr/bin/id\n");
system(cmd);

  /* Check if euid has been changed to root */
  my_euid = geteuid();
  printf("my euid = %d \n",my_euid );

  /* Check if file /user1/jvill/boot.x exists */
  bootx_fd=open(BOOTX_PATH, O_RDWR, OPENMODE);

  /* If can't open file--file does not exist */
  if ( bootx_fd == -1 )
  {
    printf ("File %s does not exist. Exiting program...\n\n",BOOTX_PATH);
  }
  else
  {

    my_euid = geteuid();
    printf("my 2 euid = %d \n",my_euid );

    sprintf(cmd,"/usr/bin/cp %s /tftpboot",BOOTX_PATH);
    status=system(cmd);

    if ( status != 0 ) /* if return status is no good */
    {
      /* EXIT WITH ERROR */
      printf("\nCannot copy file %s to /tftpboot--check with your System
Administrator.\n",BOOTX_PATH);
      exit(1);
    }
    else
    {
      /* CHANGE FILE PROTECTIONS TO "read for all" */
      sprintf(cmd,"/usr/bin/chmod 444 /tftpboot/%s",BOOTX_FILE);
      status=system(cmd);

      if ( status != 0 ) /* if return status is bad */
      {
        /* EXIT WITH ERROR */
        printf("\nCannot change file protection on /tftpboot/%s--check with
your System Administrator.\n",BOOTX_FILE);
        exit(1);
      }
    }

    exit(0);
  }
}
  
  
int sighandler()
{
  printf("\nDetected ^C...exiting program.\n");
  exit(1);
}

===========================================================================
Solutions:

sudo
http://www.courtesan.com/courtesan/products/sudo/
ftp.cs.colorado.edu:/pub/sysadmin/sudo
http://ww.cs.colorado.edu/~millert/sudo

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

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> /* for execXX */

main()
{
   setuid(geteuid());
   setuid(geteuid());
   execvp("/usr/local/bin/interleaf_license_killer","");
   printf("error\n\n");
}

try using execve() instead of system()

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

> sprintf(cmd,"/usr/bin/cp %s /tftpboot",BOOTX_PATH);
> status=system(cmd);

The problem is that the setuid status is not surviving the 'exec'
that is part of the 'system' call.

This is often controlled by a flag to the 'execve' system call that
you can't pass through the 'system' interface. Look at the public domain
utility 'sudo'.

Also you need to check if the real uid/gid can access the file -
else you create a huge security breach.

Having done an 'accesss' call to do that I would just copy the
data in your program. A lot more efficient than forking a new copy
of sh to parse and execute your cp command.

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

Setting the bit is not enough, you need to call setuid(0) too.

You may wish to grab the current uid of the
user so you can setuid(old_id) when you're done with the stuff that needs
root access. Or, if the program is really simple you can become root
(uid=0; euid=0) and stay root until the program ends and root privs are
relinquished. Be careful with this latter, though; it can get you in a
lot of trouble if you do non-priviileged stuff while you're still root!

you need to set your real UID in your program with setreuid,
e.g.:
   if(setreuid(geteuid(),geteuid()) != 0) {
      printf("Cannot change UID!\n");
      exit(1);
      }

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

to strengthen your code, you may want to read

"Practical Unix and Internet Security" from Spafford & Garfinkel
Chapter 23 "Writing Secure SUID and Network Programs"
available at http://www.ora.com

In particular, system and sprintf are nice entry holes for hackers
man seteuid
man setuid
man exec
man snprintf

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

>From the man pages of the system command:
> NOTES
> system() will fail to execute setuid() or setgid() if either
> the uid or gid of the application's owner/group is less than
> 100. (see useradd(1M) and setuid(2)).

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

You have the right euid in the program, but you then
call system() and get through to csh. csh then refuses
to honour the setuid-ness and so no copy takes place.
sh and ksh don't do that.

------------------
You could look at sudo, or mtools.
There's also the rlprm approach, which I like,
but then I would.

At the end of this mail I'm including a man page.

ftp.cert.org has a ton of stuff to read.

OLD SUMMARY:
Date: Tue, 3 May 1994 13:16:17 -0400
From: Chris Metcalf <metcalf@catfish.LCS.MIT.EDU>
To: sun-managers@ra.mcs.anl.gov
Subject: WARNINGS: How to mount /pcfs without root privileges ?

> How to mount /pcfs without root privileges ?

Nearly all of the answers provided here allow a user to get root access
in under a minute.

> [1] setuid scripts.

There are several ways to hijack shell scripts.

The most entertaining is to symlink "-i" to a shell script and then run
the resulting "-i" script to get an interactive shell; using "-b" in a
csh script header or "-" in a sh script avoids this particular approach.
(Note that Sun's csh won't let you run setuid without a -b.)

In general one can take advantage of the fact that there is a lag between
when the shell specified in the script starts, and when the shell itself
reads the script. For example, if you create a symlink to a setuid shell
script and execute from the symlink, you can replace the symlink with
a script of your own during the window before the shell reads the script
(this usually requires careful timing and many attempts, but is likely
to succeed in the end).

In any case, many systems (though not SunOS 4.x; I don't know about 5.x)
don't perform the setuid change on scripts, which keeps people from
making these mistakes. I believe setuid Perl scripts can be run safely,
via the taintperl binary, and even on systems that have disabled setuid
shell scripts.

> [2] setuid programs.
>
> This is similar to above, though more secure.
> Write a quick and dirty C program to do it. This way you
> get around the security risk of a setuid script.

In fact, the examples provided in this section are even LESS secure than
the shell scripts provided (which used csh -b scripts).

> main ()
> {
> system ("/etc/mount /pcfs") ;
> }

Since system() invokes /bin/sh, all you need to do is set IFS to
"/", create a script of your own named "etc", and run this binary.
Your "etc" script will run as root (with arguments "mount pcfs").
Similarly, one creates a "usr" script if system() is invoked with
"/usr/etc/mount_pcfs"..., of course.

> Here is one other provided by danny@ews7.dseg.ti.com
>
> ...
> system ("mount /pcfs");

Here, of course, the user just needs to set their PATH to start with ".",
place a script named "mount" in the current directory, and run the setuid
binary; the user's "mount" script will get run as root.

> [3] PD software

For this specific situation, mtools is almost always the right answer.
I rarely bother to mount floppies; I just mcopy and mtype to them.

In general, I recommend "sudo" or "op" for allowing users to do
well-controlled operations that require root privilege.

If you want to use a simple C program to do it, you want something
like this. Notice that the binary does *not* use system(), but rather
the essentially uncompromisable execve(), straight to the kernel.

    /*
     * Make this binary setuid to run mount as root.
     */
    
    char *envp[] = {
            "HOME=/",
            "LOGNAME=root",
            "PATH=/usr/etc:/usr/ucb:/usr/bin",
            "SHELL=/bin/sh",
            "TERM=dumb",
            "USER=root",
            0
    };
    
    char *argv[] = { "mount", "/pcfs", 0 };
    
    main()
    {
            return execve("/usr/etc/mount", argv, envp);
    }

                        Chris Metcalf, MIT Laboratory for Computer Science
                        metcalf@cag.lcs.mit.edu // +1 (617) 253-7766

SETUID(7) ENVIRONMENTS, TABLES, AND TROFF MACROS SETUID(7)

NAME
     setuid - checklist for security of setuid programs

DESCRIPTION
     Writing a secure setuid (or setgid) program is tricky.
     There are a number of possible ways of subverting such a
     program. The most conspicuous security holes occur when a
     setuid program is not sufficiently careful to avoid giving
     away access to resources it legitimately has the use of.
     Most of the other attacks are basically a matter of altering
     the program's environment in unexpected ways and hoping it
     will fail in some security-breaching manner. There are gen-
     erally three categories of environment manipulation: supply-
     ing a legal but unexpected environment that may cause the
     program to directly do something insecure, arranging for
     error conditions that the program may not handle correctly,
     and the specialized subcategory of giving the program inade-
     quate resources in hopes that it won't respond properly.

     The following are general considerations of security when
     writing a setuid program.

     [] The program should run with the weakest userid possible,
        preferably one used only by itself. A security hole in a
        setuid program running with a highly-privileged userid
        can compromise an entire system. Security-critical pro-
        grams like passwd(1) should always have private userids,
        to minimize possible damage from penetrations elsewhere.

     [] The result of getlogin or ttyname may be wrong if the
        descriptors have been meddled with. There is no fool-
        proof way to determine the controlling terminal or the
        login name (as opposed to uid) on V7.

     [] On some systems (not ours), the setuid bit may not be
        honored if the program is run by root, so the program may
        find itself running as root.

     [] Programs that attempt to use creat for locking can foul
        up when run by root; use of link is preferred when imple-
        menting locking. Using chmod for locking is an obvious
        disaster.

     [] Breaking an existing lock is very dangerous; the break-
        down of a locking protocol may be symptomatic of far
        worse problems. Doing so on the basis of the lock being
        `old' is sometimes necessary, but programs can run for
        surprising lengths of time on heavily-loaded systems.

     [] Care must be taken that user requests for i/o are checked
        for permissions using the user's permissions, not the
        program's. Use of access is recommended.

Sun Release 4.1 Last change: local 1

SETUID(7) ENVIRONMENTS, TABLES, AND TROFF MACROS SETUID(7)

     [] Programs executed at user request (e.g. shell escapes)
        must not receive the setuid program's permissions; use of
        daughter processes and setuid(getuid()) plus
        setgid(getgid()) after fork but before exec is vital.

     [] Similarly, programs executed at user request must not
        receive other sensitive resources, notably file descrip-
        tors. Use of closeall(3) or close-on-exec arrangements,
        on systems which have them, is recommended.

     [] Programs activated by one user but handling traffic on
        behalf of others (e.g. daemons) should avoid doing
        setuid(getuid()) or setgid(getgid()), since the original
        invoker's identity is almost certainly inappropriate. On
        systems which permit it, use of setuid(geteuid()) and
        setgid(getegid()) is recommended when performing work on
        behalf of the system as opposed to a specific user.

     [] There are inherent permission problems when a setuid pro-
        gram executes another setuid program, since the permis-
        sions are not additive. Care should be taken that
        created files are not owned by the wrong person. Use of
        setuid(geteuid()) and its gid counterpart can help, if
        the system allows them.

     [] Care should be taken that newly-created files do not have
        the wrong permission or ownership even momentarily. Per-
        missions should be arranged by using umask in advance,
        rather than by creating the file wide-open and then using
        chmod. Ownership can get sticky due to the limitations
        of the setuid concept, although using a daughter process
        connected by a pipe can help.

     [] Setuid programs should be especially careful about error
        checking, and the normal response to a strange situation
        should be termination, rather than an attempt to carry
        on.

     The following are ways in which the program may be induced
     to carelessly give away its special privileges.

     [] The directory the program is started in, or directories
        it may plausibly chdir to, may contain programs with the
        same names as system programs, placed there in hopes that
        the program will activate a shell with a permissive PATH
        setting. PATH should always be standardized before
        invoking a shell (either directly or via popen or
        execvp/execlp).

     [] Similarly, a bizarre IFS setting may alter the interpre-
        tation of a shell command in really strange ways, possi-
        bly causing a user-supplied program to be invoked. IFS

Sun Release 4.1 Last change: local 2

SETUID(7) ENVIRONMENTS, TABLES, AND TROFF MACROS SETUID(7)

        too should always be standardized before invoking a
        shell. (Our shell does this automatically.)

     [] Environment variables in general cannot be trusted.
        Their contents should never be taken for granted.

     [] Setuid shell files (on systems which implement such) sim-
        ply cannot cope adequately with some of these problems.
        They also have some nasty problems like trying to run a
        .profile when run under a suitable name. They are termi-
        nally insecure, and must be avoided.

     [] Relying on the contents of files placed in publically-
        writeable directories, such as /tmp, is a nearly-
        incurable security problem. Setuid programs should avoid
        using /tmp entirely, if humanly possible. The sticky-
        directories modification (sticky bit on for a directory
        means only owner of a file can remove it) (we have this
        feature) helps, but is not a complete solution.

     [] A related problem is that spool directories, holding
        information that the program will trust later, must never
        be publically writeable even if the files in the direc-
        tory are protected. Among other sinister manipulations
        that can be performed, note that on many Unixes (not
        ours), a core dump of a setuid program is owned by the
        program's owner and not by the user running it.

     The following are unusual but possible error conditions that
     the program should cope with properly (resource-exhaustion
     questions are considered separately, see below).

     [] The value of argc might be 0.

     [] The setting of the umask might not be sensible. In any
        case, it should be standardized when creating files not
        intended to be owned by the user.

     [] One or more of the standard descriptors might be closed,
        so that an opened file might get (say) descriptor 1,
        causing chaos if the program tries to do a printf.

     [] The current directory (or any of its parents) may be
        unreadable and unsearchable. On many systems pwd(1) does
        not run setuid-root, so it can fail under such condi-
        tions.

     [] Descriptors shared by other processes (i.e., any that are
        open on startup) may be manipulated in strange ways by
        said processes.

     [] The standard descriptors may refer to a terminal which

Sun Release 4.1 Last change: local 3

SETUID(7) ENVIRONMENTS, TABLES, AND TROFF MACROS SETUID(7)

        has a bizarre mode setting, or which cannot be opened
        again, or which gives end-of-file on any read attempt, or
        which cannot be read or written successfully.

     [] The process may be hit by interrupt, quit, hangup, or
        broken-pipe signals, singly or in fast succession. The
        user may deliberately exploit the race conditions
        inherent in catching signals; ignoring signals is safe,
        but catching them is not.

     [] Although non-keyboard signals cannot be sent by ordinary
        users in V7, they may perhaps be sent by the system
        authorities (e.g. to indicate that the system is about to
        shut down), so the possibility cannot be ignored.

     [] On some systems (not ours) there may be an alarm signal
        pending on startup.

     [] The program may have children it did not create. This is
        normal when the process is part of a pipeline.

     [] In some non-V7 systems, users can change the ownerships
        of their files. Setuid programs should avoid trusting
        the owner identification of a file.

     [] User-supplied arguments and input data must be checked
        meticulously. Overly-long input stored in an array
        without proper bound checking can easily breach security.
        When software depends on a file being in a specific for-
        mat, user-supplied data should never be inserted into the
        file without being checked first. Meticulous checking
        includes allowing for the possibility of non-ASCII char-
        acters.

     [] Temporary files left in public directories like /tmp
        might vanish at inconvenient times.

     The following are resource-exhaustion possibilities that the
     program should respond properly to.

     [] The user might have used up all of his allowed processes,
        so any attempt to create a new one (via fork or popen)
        will fail.

     [] There might be many files open, exhausting the supply of
        descriptors. Running closeall(3), on systems which have
        it, is recommended.

     [] There might be many arguments.

     [] The arguments and the environment together might occupy a
        great deal of space.

Sun Release 4.1 Last change: local 4

SETUID(7) ENVIRONMENTS, TABLES, AND TROFF MACROS SETUID(7)

     Systems which impose other resource limitations can open
     setuid programs to similar resource-exhaustion attacks.

     Setuid programs which execute ordinary programs without
     reducing authority pass all the above problems on to such
     unprepared children. Standardizing the execution environ-
     ment is only a partial solution.

SEE ALSO
     closeall(3), standard(3)

HISTORY
     Locally written, although based on outside contributions.
     This file came with COPS_1.04, and is distributed by Peter
     Allan with rlprm because it bears repetition.

BUGS
     The list really is rather long... and probably incomplete.

     Neither the author nor the University of Toronto (nor PA)
     accepts any responsibility whatever for the use or non-use
     of this information

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

Appreciate all your help. Vicky Lau



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:12:01 CDT