1 /* setuid.c --- management of runtime privileges.
2 * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
17 #include <X11/Xlib.h> /* not used for much... */
19 /* This file doesn't need the Xt headers, so stub these types out... */
21 #define XtAppContext void*
22 #define XrmDatabase void*
23 #define XtIntervalId void*
24 #define XtPointer void*
27 #include "xscreensaver.h"
33 #include <pwd.h> /* for getpwnam() and struct passwd */
34 #include <grp.h> /* for getgrgid() and struct group */
37 uid_gid_string (uid_t uid, gid_t gid)
44 sprintf (buf, "%.100s/%.100s (%ld/%ld)",
45 (p && p->pw_name ? p->pw_name : "???"),
46 (g && g->gr_name ? g->gr_name : "???"),
47 (long) uid, (long) gid);
53 describe_uids (saver_info *si, FILE *out)
57 uid_t euid = geteuid();
58 gid_t egid = getegid();
59 char *s1 = strdup (uid_gid_string (uid, gid));
60 char *s2 = strdup (uid_gid_string (euid, egid));
62 if (si->orig_uid && *si->orig_uid &&
63 (!!strcmp (si->orig_uid, s1) ||
64 !!strcmp (si->orig_uid, s2)))
65 fprintf (out, "%s: initial effective uid/gid was %s\n", blurb(),
68 fprintf (out, "%s: running as %s", blurb(), s1);
69 if (uid != euid || gid != egid)
70 fprintf (out, "; effectively %s", s2);
77 /* Returns true if we need to call setgroups().
79 Without calling setgroups(), the process will retain any supplementary
80 gids associated with the uid, e.g.:
83 root : root bin daemon sys adm disk wheel
85 However, setgroups() can only be called by root, and returns EPERM
86 for other users even if the call would be a no-op (e.g., setting the
87 group list to the current list.) So, to avoid that spurious error,
88 before calling setgroups() we first check whether the current list
89 of groups contains only one element, our target group. If so, we
90 don't need to call setgroups().
93 setgroups_needed_p (uid_t target_group)
96 int n = getgroups (sizeof(groups)-1, groups);
100 sprintf (buf, "%s: getgroups(%d, ...)", blurb(), sizeof(groups)-1);
104 else if (n == 0) /* an empty list means only egid is in effect. */
106 else if (n == 1 && groups[0] == target_group) /* one element, the target */
108 else /* more than one, or the wrong one. */
114 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
119 struct passwd *p = getpwuid (uid);
120 struct group *g = getgrgid (gid);
125 /* Rumor has it that some implementations of of setuid() do nothing
126 when called with -1; therefore, if the "nobody" user has a uid of
127 -1, then that would be Really Bad. Rumor further has it that such
128 systems really ought to be using -2 for "nobody", since that works.
129 So, if we get a uid (or gid, for good measure) of -1, switch to -2
130 instead. Note that this must be done after we've looked up the
131 user/group names with getpwuid(-1) and/or getgrgid(-1).
133 if (gid == (gid_t) -1) gid = (gid_t) -2;
134 if (uid == (uid_t) -1) uid = (uid_t) -2;
137 if (setgroups_needed_p (gid) &&
138 setgroups (1, &gid) < 0)
139 sgs_errno = errno ? errno : -1;
142 if (setgid (gid) != 0)
143 gid_errno = errno ? errno : -1;
146 if (setuid (uid) != 0)
147 uid_errno = errno ? errno : -1;
149 if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
151 static char buf [1024];
152 sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
153 (p && p->pw_name ? p->pw_name : "???"),
154 (g && g->gr_name ? g->gr_name : "???"),
155 (long) uid, (long) gid);
168 sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
170 (g && g->gr_name ? g->gr_name : "???"),
173 fprintf(stderr, "%s: unknown error\n", buf);
180 fprintf (stderr, "%s: effective group list: ", blurb());
181 n = getgroups (sizeof(groups)-1, groups);
183 fprintf (stderr, "unknown!\n");
187 fprintf (stderr, "[");
188 for (i = 0; i < n; i++)
190 g = getgrgid (groups[i]);
191 if (i > 0) fprintf (stderr, ", ");
192 if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name);
193 else fprintf (stderr, "%ld", (long) groups[i]);
195 fprintf (stderr, "]\n");
201 sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
203 (g && g->gr_name ? g->gr_name : "???"),
206 fprintf(stderr, "%s: unknown error\n", buf);
216 sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
218 (p && p->pw_name ? p->pw_name : "???"),
221 fprintf(stderr, "%s: unknown error\n", buf);
234 /* If we've been run as setuid or setgid to someone else (most likely root)
235 turn off the extra permissions so that random user-specified programs
236 don't get special privileges. (On some systems it is necessary to install
237 this program as setuid root in order to read the passwd file to implement
240 *** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE!
241 If you do so, you will open a security hole. See the sections
242 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
246 hack_uid (saver_info *si)
249 /* Discard privileges, and set the effective user/group ids to the
250 real user/group ids. That is, give up our "chmod +s" rights.
253 uid_t euid = geteuid();
254 gid_t egid = getegid();
255 uid_t uid = getuid();
256 gid_t gid = getgid();
258 si->orig_uid = strdup (uid_gid_string (euid, egid));
260 if (uid != euid || gid != egid)
261 if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
262 saver_exit (si, 1, 0);
266 /* Locking can't work when running as root, because we have no way of
267 knowing what the user id of the logged in user is (so we don't know
268 whose password to prompt for.)
270 *** WARNING: DO NOT DISABLE THIS CODE!
271 If you do so, you will open a security hole. See the sections
272 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
275 if (getuid() == (uid_t) 0)
277 si->locking_disabled_p = True;
278 si->nolock_reason = "running as root";
282 /* If we're running as root, switch to a safer user. This is above and
283 beyond the fact that we've disabling locking, above -- the theory is
284 that running graphics demos as root is just always a stupid thing
285 to do, since they have probably never been security reviewed and are
286 more likely to be buggy than just about any other kind of program.
287 (And that assumes non-malicious code. There are also attacks here.)
289 *** WARNING: DO NOT DISABLE THIS CODE!
290 If you do so, you will open a security hole. See the sections
291 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
294 if (getuid() == (uid_t) 0)
298 p = getpwnam ("nobody");
299 if (! p) p = getpwnam ("noaccess");
300 if (! p) p = getpwnam ("daemon");
304 "%s: running as root, and couldn't find a safer uid.\n",
306 saver_exit(si, 1, 0);
309 if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
310 saver_exit (si, -1, 0);
314 /* If there's anything even remotely funny looking about the passwd struct,
315 or if we're running as some other user from the list below (a
316 non-comprehensive selection of users known to be privileged in some way,
317 and not normal end-users) then disable locking. If it was possible,
318 switching to "nobody" would be the thing to do, but only root itself has
319 the privs to do that.
321 *** WARNING: DO NOT DISABLE THIS CODE!
322 If you do so, you will open a security hole. See the sections
323 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
327 uid_t uid = getuid (); /* get it again */
328 struct passwd *p = getpwuid (uid); /* get it again */
334 p->pw_uid == (uid_t) 0 ||
335 p->pw_uid == (uid_t) -1 ||
336 p->pw_uid == (uid_t) -2 ||
339 !strcmp (p->pw_name, "root") ||
340 !strcmp (p->pw_name, "nobody") ||
341 !strcmp (p->pw_name, "noaccess") ||
342 !strcmp (p->pw_name, "operator") ||
343 !strcmp (p->pw_name, "daemon") ||
344 !strcmp (p->pw_name, "bin") ||
345 !strcmp (p->pw_name, "adm") ||
346 !strcmp (p->pw_name, "sys") ||
347 !strcmp (p->pw_name, "games"))
349 static char buf [1024];
350 sprintf (buf, "running as %.100s",
351 (p && p->pw_name && *p->pw_name
352 ? p->pw_name : "<unknown>"));
353 si->nolock_reason = buf;
354 si->locking_disabled_p = True;
355 si->dangerous_uid_p = True;