1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * setuid.c --- management of runtime privileges.
4  *
5  * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation.  No representations are made about the suitability of this
12  * software for any purpose.  It is provided "as is" without express or
13  * implied warranty.
14  */
15 
16 #include "config.h"
17 
18 #ifdef USE_SETRES
19 #define _GNU_SOURCE
20 #endif /* USE_SETRES */
21 
22 #include <errno.h>
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28 #include <pwd.h>		/* for getpwnam() and struct passwd */
29 #include <grp.h>		/* for getgrgid() and struct group */
30 
31 #include "setuid.h"
32 
33 static char *
uid_gid_string(uid_t uid,gid_t gid)34 uid_gid_string (uid_t uid,
35                 gid_t gid)
36 {
37         static char   *buf;
38         struct passwd *p = NULL;
39         struct group  *g = NULL;
40 
41         p = getpwuid (uid);
42         g = getgrgid (gid);
43 
44         buf = g_strdup_printf ("%s/%s (%ld/%ld)",
45                                (p && p->pw_name ? p->pw_name : "???"),
46                                (g && g->gr_name ? g->gr_name : "???"),
47                                (long) uid, (long) gid);
48 
49         return buf;
50 }
51 
52 static gboolean
set_ids_by_number(uid_t uid,gid_t gid,char ** message_ret)53 set_ids_by_number (uid_t  uid,
54                    gid_t  gid,
55                    char **message_ret)
56 {
57         int uid_errno = 0;
58         int gid_errno = 0;
59         int sgs_errno = 0;
60         struct passwd *p = getpwuid (uid);
61         struct group  *g = getgrgid (gid);
62 
63         if (message_ret)
64                 *message_ret = NULL;
65 
66         /* Rumor has it that some implementations of of setuid() do nothing
67            when called with -1; therefore, if the "nobody" user has a uid of
68            -1, then that would be Really Bad.  Rumor further has it that such
69            systems really ought to be using -2 for "nobody", since that works.
70            So, if we get a uid (or gid, for good measure) of -1, switch to -2
71            instead.  Note that this must be done after we've looked up the
72            user/group names with getpwuid(-1) and/or getgrgid(-1).
73         */
74         if (gid == (gid_t) -1) gid = (gid_t) -2;
75         if (uid == (uid_t) -1) uid = (uid_t) -2;
76 
77 #ifndef USE_SETRES
78          errno = 0;
79          if (setgroups (1, &gid) < 0)
80                  sgs_errno = errno ? errno : -1;
81 
82         errno = 0;
83         if (setgid (gid) != 0)
84                 gid_errno = errno ? errno : -1;
85 
86         errno = 0;
87         if (setuid (uid) != 0)
88                 uid_errno = errno ? errno : -1;
89 #else /* !USE_SETRES */
90         errno = 0;
91         if (setresgid (gid, gid, gid) != 0)
92                 gid_errno = errno ? errno : -1;
93 
94         errno = 0;
95         if (setresuid (uid, uid, uid) != 0)
96                 uid_errno = errno ? errno : -1;
97 #endif /* USE_SETRES */
98 
99         if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0) {
100                 static char *reason;
101                 reason = g_strdup_printf ("changed uid/gid to %s/%s (%ld/%ld).",
102                                           (p && p->pw_name ? p->pw_name : "???"),
103                                           (g && g->gr_name ? g->gr_name : "???"),
104                                           (long) uid, (long) gid);
105                 if (message_ret)
106                         *message_ret = g_strdup (reason);
107 
108                 g_free (reason);
109 
110                 return TRUE;
111         } else {
112                 char *reason = NULL;
113 
114                 if (sgs_errno) {
115                         reason = g_strdup_printf ("couldn't setgroups to %s (%ld)",
116                                                   (g && g->gr_name ? g->gr_name : "???"),
117                                                   (long) gid);
118                         if (sgs_errno == -1)
119                                 fprintf (stderr, "%s: unknown error\n", reason);
120                         else {
121                                 errno = sgs_errno;
122                                 perror (reason);
123                         }
124                         g_free (reason);
125                         reason = NULL;
126                 }
127 
128                 if (gid_errno) {
129                         reason = g_strdup_printf ("couldn't set gid to %s (%ld)",
130                                                   (g && g->gr_name ? g->gr_name : "???"),
131                                                   (long) gid);
132                         if (gid_errno == -1)
133                                 fprintf (stderr, "%s: unknown error\n", reason);
134                         else {
135                                 errno = gid_errno;
136                                 perror (reason);
137                         }
138                         g_free (reason);
139                         reason = NULL;
140                 }
141 
142                 if (uid_errno) {
143                         reason = g_strdup_printf ("couldn't set uid to %s (%ld)",
144                                                   (p && p->pw_name ? p->pw_name : "???"),
145                                                   (long) uid);
146                         if (uid_errno == -1)
147                                 fprintf (stderr, "%s: unknown error\n", reason);
148                         else {
149                                 errno = uid_errno;
150                                 perror (reason);
151                         }
152                         g_free (reason);
153                         reason = NULL;
154                 }
155                 return FALSE;
156         }
157         return FALSE;
158 }
159 
160 
161 /* If we've been run as setuid or setgid to someone else (most likely root)
162    turn off the extra permissions so that random user-specified programs
163    don't get special privileges.  (On some systems it is necessary to install
164    this program as setuid root in order to read the passwd file to implement
165    lock-mode.)
166 
167    *** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE!
168    If you do so, you will open a security hole.  See the sections
169    of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
170    and "USING XDM".
171 */
172 
173 /* Returns TRUE if OK to lock, FALSE otherwise */
174 gboolean
hack_uid(char ** nolock_reason,char ** orig_uid,char ** uid_message)175 hack_uid (char **nolock_reason,
176           char **orig_uid,
177           char **uid_message)
178 {
179         char    *reason;
180         gboolean ret;
181 
182         ret = TRUE;
183         reason = NULL;
184 
185         if (nolock_reason != NULL) {
186                 *nolock_reason = NULL;
187         }
188         if (orig_uid != NULL) {
189                 *orig_uid = NULL;
190         }
191         if (uid_message != NULL) {
192                 *uid_message = NULL;
193         }
194 
195         /* Discard privileges, and set the effective user/group ids to the
196            real user/group ids.  That is, give up our "chmod +s" rights.
197         */
198         {
199                 uid_t euid = geteuid ();
200                 gid_t egid = getegid ();
201                 uid_t uid  = getuid ();
202                 gid_t gid  = getgid ();
203 
204                 if (orig_uid != NULL) {
205                         *orig_uid = uid_gid_string (euid, egid);
206                 }
207 
208                 if (uid != euid || gid != egid) {
209 #ifndef USE_SETRES
210                         if (! set_ids_by_number (uid, gid, uid_message)) {
211 #else /* !USE_SETRES */
212                         if (! set_ids_by_number (euid == 0 ? uid : euid, egid == 0 ? gid : egid, uid_message)) {
213 #endif /* USE_SETRES */
214                                 reason = g_strdup ("unable to discard privileges.");
215 
216                                 ret = FALSE;
217                                 goto out;
218                         }
219                 }
220         }
221 
222 
223         /* Locking can't work when running as root, because we have no way of
224            knowing what the user id of the logged in user is (so we don't know
225            whose password to prompt for.)
226 
227            *** WARNING: DO NOT DISABLE THIS CODE!
228            If you do so, you will open a security hole.  See the sections
229            of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
230            and "USING XDM".
231         */
232         if (getuid () == (uid_t) 0) {
233                 reason = g_strdup ("running as root");
234                 ret = FALSE;
235                 goto out;
236         }
237 
238  out:
239         if (nolock_reason != NULL) {
240                 *nolock_reason = g_strdup (reason);
241         }
242         g_free (reason);
243 
244         return ret;
245 }
246