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)
97 size = sizeof(groups) / sizeof(gid_t);
98 n = getgroups (size - 1, groups);
102 sprintf (buf, "%s: getgroups(%ld, ...)", blurb(), (long int)(size - 1));
106 else if (n == 0) /* an empty list means only egid is in effect. */
108 else if (n == 1 && groups[0] == target_group) /* one element, the target */
110 else /* more than one, or the wrong one. */
116 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
121 struct passwd *p = getpwuid (uid);
122 struct group *g = getgrgid (gid);
127 /* Rumor has it that some implementations of of setuid() do nothing
128 when called with -1; therefore, if the "nobody" user has a uid of
129 -1, then that would be Really Bad. Rumor further has it that such
130 systems really ought to be using -2 for "nobody", since that works.
131 So, if we get a uid (or gid, for good measure) of -1, switch to -2
132 instead. Note that this must be done after we've looked up the
133 user/group names with getpwuid(-1) and/or getgrgid(-1).
135 if (gid == (gid_t) -1) gid = (gid_t) -2;
136 if (uid == (uid_t) -1) uid = (uid_t) -2;
139 if (setgroups_needed_p (gid) &&
140 setgroups (1, &gid) < 0)
141 sgs_errno = errno ? errno : -1;
144 if (setgid (gid) != 0)
145 gid_errno = errno ? errno : -1;
148 if (setuid (uid) != 0)
149 uid_errno = errno ? errno : -1;
151 if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
153 static char buf [1024];
154 sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
155 (p && p->pw_name ? p->pw_name : "???"),
156 (g && g->gr_name ? g->gr_name : "???"),
157 (long) uid, (long) gid);
170 sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
172 (g && g->gr_name ? g->gr_name : "???"),
175 fprintf(stderr, "%s: unknown error\n", buf);
182 fprintf (stderr, "%s: effective group list: ", blurb());
183 size = sizeof(groups) / sizeof(gid_t);
184 n = getgroups (size - 1, groups);
186 fprintf (stderr, "unknown!\n");
190 fprintf (stderr, "[");
191 for (i = 0; i < n; i++)
193 g = getgrgid (groups[i]);
194 if (i > 0) fprintf (stderr, ", ");
195 if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name);
196 else fprintf (stderr, "%ld", (long) groups[i]);
198 fprintf (stderr, "]\n");
204 sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
206 (g && g->gr_name ? g->gr_name : "???"),
209 fprintf(stderr, "%s: unknown error\n", buf);
219 sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
221 (p && p->pw_name ? p->pw_name : "???"),
224 fprintf(stderr, "%s: unknown error\n", buf);
237 /* If we've been run as setuid or setgid to someone else (most likely root)
238 turn off the extra permissions so that random user-specified programs
239 don't get special privileges. (On some systems it is necessary to install
240 this program as setuid root in order to read the passwd file to implement
243 *** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE!
244 If you do so, you will open a security hole. See the sections
245 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
249 hack_uid (saver_info *si)
252 /* Discard privileges, and set the effective user/group ids to the
253 real user/group ids. That is, give up our "chmod +s" rights.
256 uid_t euid = geteuid();
257 gid_t egid = getegid();
258 uid_t uid = getuid();
259 gid_t gid = getgid();
261 si->orig_uid = strdup (uid_gid_string (euid, egid));
263 if (uid != euid || gid != egid)
264 if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
265 saver_exit (si, 1, 0);
269 /* Locking can't work when running as root, because we have no way of
270 knowing what the user id of the logged in user is (so we don't know
271 whose password to prompt for.)
273 *** WARNING: DO NOT DISABLE THIS CODE!
274 If you do so, you will open a security hole. See the sections
275 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
278 if (getuid() == (uid_t) 0)
280 si->locking_disabled_p = True;
281 si->nolock_reason = "running as root";
285 /* If we're running as root, switch to a safer user. This is above and
286 beyond the fact that we've disabling locking, above -- the theory is
287 that running graphics demos as root is just always a stupid thing
288 to do, since they have probably never been security reviewed and are
289 more likely to be buggy than just about any other kind of program.
290 (And that assumes non-malicious code. There are also attacks here.)
292 *** WARNING: DO NOT DISABLE THIS CODE!
293 If you do so, you will open a security hole. See the sections
294 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
297 if (getuid() == (uid_t) 0)
301 p = getpwnam ("nobody");
302 if (! p) p = getpwnam ("noaccess");
303 if (! p) p = getpwnam ("daemon");
307 "%s: running as root, and couldn't find a safer uid.\n",
309 saver_exit(si, 1, 0);
312 if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
313 saver_exit (si, -1, 0);
317 /* If there's anything even remotely funny looking about the passwd struct,
318 or if we're running as some other user from the list below (a
319 non-comprehensive selection of users known to be privileged in some way,
320 and not normal end-users) then disable locking. If it was possible,
321 switching to "nobody" would be the thing to do, but only root itself has
322 the privs to do that.
324 *** WARNING: DO NOT DISABLE THIS CODE!
325 If you do so, you will open a security hole. See the sections
326 of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
330 uid_t uid = getuid (); /* get it again */
331 struct passwd *p = getpwuid (uid); /* get it again */
337 p->pw_uid == (uid_t) 0 ||
338 p->pw_uid == (uid_t) -1 ||
339 p->pw_uid == (uid_t) -2 ||
342 !strcmp (p->pw_name, "root") ||
343 !strcmp (p->pw_name, "nobody") ||
344 !strcmp (p->pw_name, "noaccess") ||
345 !strcmp (p->pw_name, "operator") ||
346 !strcmp (p->pw_name, "daemon") ||
347 !strcmp (p->pw_name, "bin") ||
348 !strcmp (p->pw_name, "adm") ||
349 !strcmp (p->pw_name, "sys") ||
350 !strcmp (p->pw_name, "games"))
352 static char buf [1024];
353 sprintf (buf, "running as %.100s",
354 (p && p->pw_name && *p->pw_name
355 ? p->pw_name : "<unknown>"));
356 si->nolock_reason = buf;
357 si->locking_disabled_p = True;
358 si->dangerous_uid_p = True;