setuid —
checklist for security of setuid programs
Please note: This manual page was written long ago, and is in
  need of updating to match today's systems. We think it is valuable enough to
  include, even though parts of it are outdated. A carefully-researched updated
  version would be very useful, if anyone is feeling enthusiastic...
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 generally three categories of
    environment manipulation: supplying 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 inadequate 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 programs like
      passwd(1) should always have
      private userids, to minimize possible damage from penetrations
    elsewhere.
- The result of getlogin(2)
      or ttyname(3) may be wrong
      if the descriptors have been meddled with. There is no
      foolproof way to determine the controlling terminal or the login name (as
      opposed to uid) on V7.
- On some systems, 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(3) for locking can foul
      up when run by root; use of
      link(2) is preferred when
      implementing locking. Using
      chmod(2) for locking is an
      obvious disaster.
- Breaking an existing lock is very dangerous; the breakdown 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(2) is
    recommended.
- 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(2) but before
      exec(3) is vital.
- Similarly, programs executed at user request must not receive other
      sensitive resources, notably file descriptors. Use of
      fcntl(2)
      F_CLOSEM,FILENO_STDERR +
      1(close all fd's greater than stderr) and/or
      fcntl(2)F_SETFD,FD_CLOEXEC(close-on-exec) arrangements on systems which have them is recommended.Other resources should also be examined for sanity and
        possibly set to desired settings, such as the current working directory,
        signal disposition, resource limits, environment, umask, group
        membership, chroot. 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 program executes
      another setuid program, since the permissions 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. Permissions should be arranged
      by using umask(2) in advance,
      rather than by creating the file wide-open and then using
      chmod(2). 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(2) 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
      PATHsetting.PATHshould
      always be standardized before invoking a shell (either
      directly or via popen(3) or
      execvp(3) or
      execlp(3)).
- Similarly, a bizarre IFSsetting may alter the
      interpretation of a shell command in really strange ways, possibly causing
      a user-supplied program to be invoked.IFStoo
      should always be standardized before invoking a shell.
- Environment variables in general cannot be trusted. Their contents should
      never be taken for granted.
- Setuid shell files (on systems which implement such) simply 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 terminally insecure, and must be avoided.
- Relying on the contents of files placed in publically-writable
      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) 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 writable even if the
      files in the directory are protected. Among other sinister manipulations
      that can be performed, note that on many Unixes, 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(2)
      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(3).
- 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 conditions.
- 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 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 there may be an
      alarm(3) 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 format, user-supplied data should never be inserted into the
      file without being checked first. Meticulous checking includes allowing
      for the possibility of non-ASCII characters.
- 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(2) or
      popen(3)) will fail.
- There might be many files open, exhausting the supply of descriptors.
      Running fcntl(2)
      F_CLOSEMon systems which have it, is
    recommended.
- There might be many arguments.
- The arguments and the environment together might occupy a great deal of
      space.
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 environment is only a partial solution.
passwd(1),
  pwd(1),
  access(2),
  chdir(2),
  chroot(2),
  execve(2),
  fcntl(2),
  fork(2),
  getlogin(2),
  link(2),
  setegid(2),
  seteuid(2),
  setgid(2),
  setgroups(2),
  setrlimit(2),
  setuid(2),
  sigaction(2),
  umask(2),
  alarm(3),
  creat(3),
  execvp(3),
  popen(3),
  printf(3),
  ttyname(3)
Written by Henry Spencer, and based on additional outside contributions.
The list really is rather long... and probably incomplete.