1  /* Dropping uid/gid privileges of the current process permanently.
       2     Copyright (C) 2009-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #include <config.h>
      18  
      19  #include "idpriv.h"
      20  
      21  #include <stdlib.h>
      22  #include <sys/types.h>
      23  #include <unistd.h>
      24  
      25  int
      26  idpriv_drop (void)
      27  {
      28  #if HAVE_GETUID
      29    int uid = getuid ();
      30  #endif
      31  #if HAVE_GETGID
      32    int gid = getgid ();
      33  #endif
      34  
      35    /* Drop the gid privilege first, because in some cases the gid privilege
      36       cannot be dropped after the uid privilege has been dropped.  */
      37  
      38    /* This is for executables that have the setgid bit set.  */
      39  #if HAVE_SETRESGID /* glibc, FreeBSD, OpenBSD, HP-UX */
      40    /* This code is needed: In particular, on HP-UX 11.11, setregid (gid, gid)
      41       may leave the saved gid as 0.  See also the comment below regarding
      42       setresuid.  */
      43    if (setresgid (gid, gid, gid) < 0)
      44      return -1;
      45  #elif HAVE_SETREGID /* Mac OS X, NetBSD, AIX, IRIX, Solaris, OSF/1, Cygwin */
      46    if (setregid (gid, gid) < 0)
      47      return -1;
      48  #elif HAVE_SETEGID /* Solaris 2.4 */
      49    if (setegid (gid) < 0)
      50      return -1;
      51  #endif
      52  
      53    /* This is for executables that have the setuid bit set.  */
      54  #if HAVE_SETRESUID /* glibc, FreeBSD, OpenBSD, HP-UX */
      55    /* On systems which have setresuid(), we use it instead of setreuid(),
      56       because
      57         Hao Chen, David Wagner, Drew Dean: Setuid Demystified
      58         <https://www.usenix.org/legacy/publications/library/proceedings/sec02/full_papers/chen/chen.pdf>
      59       says about setreuid(): "The rule by which the saved uid id is modified
      60       is complicated." Similarly, <https://unixpapa.com/incnote/setuid.html>
      61       says about setreuid(): "What exactly happens to the saved UID when this
      62       is used seems to vary a lot."  */
      63    if (setresuid (uid, uid, uid) < 0)
      64      return -1;
      65  #elif HAVE_SETREUID /* Mac OS X, NetBSD, AIX, IRIX, Solaris, OSF/1, Cygwin */
      66    if (setreuid (uid, uid) < 0)
      67      return -1;
      68  #elif HAVE_SETEUID /* Solaris 2.4 */
      69    if (seteuid (uid) < 0)
      70      return -1;
      71  #endif
      72  
      73    /* Verify that the privileges have really been dropped.
      74       This verification is here for security reasons.  Doesn't matter if it
      75       takes a couple of system calls.
      76       On Solaris (which has saved uids and gids but no getresuid, getresgid
      77       functions), we could read /proc/<pid>/cred and verify the saved uid and
      78       gid found there. But it's not clear to me when to interpret the file as a
      79       'prcred_t' and when as a 'prcred32_t'.
      80       Hao Chen, David Wagner, Drew Dean: Setuid Demystified
      81       <https://www.usenix.org/legacy/publications/library/proceedings/sec02/full_papers/chen/chen.pdf>
      82       section 8.1.3 also recommends to use a setreuid call as a probe, but
      83       this call would unexpectedly succeed (and the verification thus fail)
      84       on Linux if the process has the CAP_SETUID capability.
      85       When the verification fails, it indicates that we need to use different
      86       API in the code above. Therefore 'abort ()', not 'return -1'.  */
      87  #if HAVE_GETRESUID /* glibc, FreeBSD, OpenBSD, HP-UX */
      88    {
      89      uid_t real;
      90      uid_t effective;
      91      uid_t saved;
      92      if (getresuid (&real, &effective, &saved) < 0
      93          || real != uid
      94          || effective != uid
      95          || saved != uid)
      96        abort ();
      97    }
      98  #else
      99  # if HAVE_GETEUID
     100    if (geteuid () != uid)
     101      abort ();
     102  # endif
     103  # if HAVE_GETUID
     104    if (getuid () != uid)
     105      abort ();
     106  # endif
     107  #endif
     108  #if HAVE_GETRESGID /* glibc, FreeBSD, OpenBSD, HP-UX */
     109    {
     110      gid_t real;
     111      gid_t effective;
     112      gid_t saved;
     113      if (getresgid (&real, &effective, &saved) < 0
     114          || real != gid
     115          || effective != gid
     116          || saved != gid)
     117        abort ();
     118    }
     119  #else
     120  # if HAVE_GETEGID
     121    if (getegid () != gid)
     122      abort ();
     123  # endif
     124  # if HAVE_GETGID
     125    if (getgid () != gid)
     126      abort ();
     127  # endif
     128  #endif
     129  
     130    return 0;
     131  }