74d8318b8ea1860b7119949f5b2b861c77e69592
[xscreensaver] / driver / setuid.c
1 /* setuid.c --- management of runtime privileges.
2  * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski <jwz@jwz.org>
3  *
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 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include <X11/Xlib.h>           /* not used for much... */
18
19 /* This file doesn't need the Xt headers, so stub these types out... */
20 #undef XtPointer
21 #define XtAppContext void*
22 #define XrmDatabase  void*
23 #define XtIntervalId void*
24 #define XtPointer    void*
25 #define Widget       void*
26
27 #include "xscreensaver.h"
28
29 #ifndef EPERM
30 #include <errno.h>
31 #endif
32
33 #include <pwd.h>                /* for getpwnam() and struct passwd */
34 #include <grp.h>                /* for getgrgid() and struct group */
35
36 static const char *
37 uid_gid_string (uid_t uid, gid_t gid)
38 {
39   static char buf[255];
40   struct passwd *p = 0;
41   struct group *g = 0;
42   p = getpwuid (uid);
43   g = getgrgid (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);
48   return buf;
49 }
50
51
52 void
53 describe_uids (saver_info *si, FILE *out)
54 {
55   uid_t uid = getuid();
56   gid_t gid = getgid();
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));
61
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(),
66              si->orig_uid);
67
68   fprintf (out, "%s: running as %s", blurb(), s1);
69   if (uid != euid || gid != egid)
70     fprintf (out, "; effectively %s", s2);
71   fprintf(out, "\n");
72   free(s1);
73   free(s2);
74 }
75
76
77 /* Returns true if we need to call setgroups().
78
79    Without calling setgroups(), the process will retain any supplementary
80    gids associated with the uid, e.g.:
81
82        % groups root
83        root : root bin daemon sys adm disk wheel
84
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().
91  */
92 static int
93 setgroups_needed_p (uid_t target_group)
94 {
95   gid_t groups[1024];
96   int n = getgroups (sizeof(groups)-1, groups);
97   if (n < 0)
98     {
99       char buf [1024];
100       sprintf (buf, "%s: getgroups(%d, ...)", blurb(), sizeof(groups)-1);
101       perror (buf);
102       return 1;
103     }
104   else if (n == 0)            /* an empty list means only egid is in effect. */
105     return 0;
106   else if (n == 1 && groups[0] == target_group)   /* one element, the target */
107     return 0;
108   else                        /* more than one, or the wrong one. */
109     return 1;
110 }
111
112
113 static int
114 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
115 {
116   int uid_errno = 0;
117   int gid_errno = 0;
118   int sgs_errno = 0;
119   struct passwd *p = getpwuid (uid);
120   struct group  *g = getgrgid (gid);
121
122   if (message_ret)
123     *message_ret = 0;
124
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).
132    */
133   if (gid == (gid_t) -1) gid = (gid_t) -2;
134   if (uid == (uid_t) -1) uid = (uid_t) -2;
135
136   errno = 0;
137   if (setgroups_needed_p (gid) &&
138       setgroups (1, &gid) < 0)
139     sgs_errno = errno ? errno : -1;
140
141   errno = 0;
142   if (setgid (gid) != 0)
143     gid_errno = errno ? errno : -1;
144
145   errno = 0;
146   if (setuid (uid) != 0)
147     uid_errno = errno ? errno : -1;
148
149   if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
150     {
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);
156       if (message_ret)
157         *message_ret = buf;
158       return 0;
159     }
160   else
161     {
162       char buf [1024];
163       gid_t groups[1024];
164       int n;
165
166       if (sgs_errno)
167         {
168           sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
169                    blurb(),
170                    (g && g->gr_name ? g->gr_name : "???"),
171                    (long) gid);
172           if (sgs_errno == -1)
173             fprintf(stderr, "%s: unknown error\n", buf);
174           else
175             {
176               errno = sgs_errno;
177               perror(buf);
178             }
179
180           fprintf (stderr, "%s: effective group list: ", blurb());
181           n = getgroups (sizeof(groups)-1, groups);
182           if (n < 0)
183             fprintf (stderr, "unknown!\n");
184           else
185             {
186               int i;
187               fprintf (stderr, "[");
188               for (i = 0; i < n; i++)
189                 {
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]);
194                 }
195               fprintf (stderr, "]\n");
196             }
197         }
198
199       if (gid_errno)
200         {
201           sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
202                    blurb(),
203                    (g && g->gr_name ? g->gr_name : "???"),
204                    (long) gid);
205           if (gid_errno == -1)
206             fprintf(stderr, "%s: unknown error\n", buf);
207           else
208             {
209               errno = gid_errno;
210               perror(buf);
211             }
212         }
213
214       if (uid_errno)
215         {
216           sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
217                    blurb(),
218                    (p && p->pw_name ? p->pw_name : "???"),
219                    (long) uid);
220           if (uid_errno == -1)
221             fprintf(stderr, "%s: unknown error\n", buf);
222           else
223             {
224               errno = uid_errno;
225               perror(buf);
226             }
227         }
228
229       return -1;
230     }
231 }
232
233
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
238    lock-mode.)
239
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", 
243          and "USING XDM".
244  */
245 void
246 hack_uid (saver_info *si)
247 {
248
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.
251    */
252   {
253     uid_t euid = geteuid();
254     gid_t egid = getegid();
255     uid_t uid = getuid();
256     gid_t gid = getgid();
257
258     si->orig_uid = strdup (uid_gid_string (euid, egid));
259
260     if (uid != euid || gid != egid)
261       if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
262         saver_exit (si, 1, 0);
263   }
264
265
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.)
269
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",
273          and "USING XDM".
274    */
275   if (getuid() == (uid_t) 0)
276     {
277       si->locking_disabled_p = True;
278       si->nolock_reason = "running as root";
279     }
280
281
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.)
288
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", 
292          and "USING XDM".
293    */
294   if (getuid() == (uid_t) 0)
295     {
296       struct passwd *p;
297
298       p = getpwnam ("nobody");
299       if (! p) p = getpwnam ("noaccess");
300       if (! p) p = getpwnam ("daemon");
301       if (! p)
302         {
303           fprintf (stderr,
304                    "%s: running as root, and couldn't find a safer uid.\n",
305                    blurb());
306           saver_exit(si, 1, 0);
307         }
308
309       if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
310         saver_exit (si, -1, 0);
311     }
312
313
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.
320
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",
324          and "USING XDM".
325    */
326   {
327     uid_t uid = getuid ();              /* get it again */
328     struct passwd *p = getpwuid (uid);  /* get it again */
329
330     if (!p ||
331         uid == (uid_t)  0 ||
332         uid == (uid_t) -1 ||
333         uid == (uid_t) -2 ||
334         p->pw_uid == (uid_t)  0 ||
335         p->pw_uid == (uid_t) -1 ||
336         p->pw_uid == (uid_t) -2 ||
337         !p->pw_name ||
338         !*p->pw_name ||
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"))
348       {
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;
356       }
357   }
358 }