http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.24.tar.gz
[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, size;
97   size = sizeof(groups) / sizeof(gid_t);
98   n = getgroups (size - 1, groups);
99   if (n < 0)
100     {
101       char buf [1024];
102       sprintf (buf, "%s: getgroups(%ld, ...)", blurb(), (long int)(size - 1));
103       perror (buf);
104       return 1;
105     }
106   else if (n == 0)            /* an empty list means only egid is in effect. */
107     return 0;
108   else if (n == 1 && groups[0] == target_group)   /* one element, the target */
109     return 0;
110   else                        /* more than one, or the wrong one. */
111     return 1;
112 }
113
114
115 static int
116 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
117 {
118   int uid_errno = 0;
119   int gid_errno = 0;
120   int sgs_errno = 0;
121   struct passwd *p = getpwuid (uid);
122   struct group  *g = getgrgid (gid);
123
124   if (message_ret)
125     *message_ret = 0;
126
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).
134    */
135   if (gid == (gid_t) -1) gid = (gid_t) -2;
136   if (uid == (uid_t) -1) uid = (uid_t) -2;
137
138   errno = 0;
139   if (setgroups_needed_p (gid) &&
140       setgroups (1, &gid) < 0)
141     sgs_errno = errno ? errno : -1;
142
143   errno = 0;
144   if (setgid (gid) != 0)
145     gid_errno = errno ? errno : -1;
146
147   errno = 0;
148   if (setuid (uid) != 0)
149     uid_errno = errno ? errno : -1;
150
151   if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
152     {
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);
158       if (message_ret)
159         *message_ret = buf;
160       return 0;
161     }
162   else
163     {
164       char buf [1024];
165       gid_t groups[1024];
166       int n, size;
167
168       if (sgs_errno)
169         {
170           sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
171                    blurb(),
172                    (g && g->gr_name ? g->gr_name : "???"),
173                    (long) gid);
174           if (sgs_errno == -1)
175             fprintf(stderr, "%s: unknown error\n", buf);
176           else
177             {
178               errno = sgs_errno;
179               perror(buf);
180             }
181
182           fprintf (stderr, "%s: effective group list: ", blurb());
183           size = sizeof(groups) / sizeof(gid_t);
184           n = getgroups (size - 1, groups);
185           if (n < 0)
186             fprintf (stderr, "unknown!\n");
187           else
188             {
189               int i;
190               fprintf (stderr, "[");
191               for (i = 0; i < n; i++)
192                 {
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]);
197                 }
198               fprintf (stderr, "]\n");
199             }
200         }
201
202       if (gid_errno)
203         {
204           sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
205                    blurb(),
206                    (g && g->gr_name ? g->gr_name : "???"),
207                    (long) gid);
208           if (gid_errno == -1)
209             fprintf(stderr, "%s: unknown error\n", buf);
210           else
211             {
212               errno = gid_errno;
213               perror(buf);
214             }
215         }
216
217       if (uid_errno)
218         {
219           sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
220                    blurb(),
221                    (p && p->pw_name ? p->pw_name : "???"),
222                    (long) uid);
223           if (uid_errno == -1)
224             fprintf(stderr, "%s: unknown error\n", buf);
225           else
226             {
227               errno = uid_errno;
228               perror(buf);
229             }
230         }
231
232       return -1;
233     }
234 }
235
236
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
241    lock-mode.)
242
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", 
246          and "USING XDM".
247  */
248 void
249 hack_uid (saver_info *si)
250 {
251
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.
254    */
255   {
256     uid_t euid = geteuid();
257     gid_t egid = getegid();
258     uid_t uid = getuid();
259     gid_t gid = getgid();
260
261     si->orig_uid = strdup (uid_gid_string (euid, egid));
262
263     if (uid != euid || gid != egid)
264       if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
265         saver_exit (si, 1, 0);
266   }
267
268
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.)
272
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",
276          and "USING XDM".
277    */
278   if (getuid() == (uid_t) 0)
279     {
280       si->locking_disabled_p = True;
281       si->nolock_reason = "running as root";
282     }
283
284
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.)
291
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", 
295          and "USING XDM".
296    */
297   if (getuid() == (uid_t) 0)
298     {
299       struct passwd *p;
300
301       p = getpwnam ("nobody");
302       if (! p) p = getpwnam ("noaccess");
303       if (! p) p = getpwnam ("daemon");
304       if (! p)
305         {
306           fprintf (stderr,
307                    "%s: running as root, and couldn't find a safer uid.\n",
308                    blurb());
309           saver_exit(si, 1, 0);
310         }
311
312       if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
313         saver_exit (si, -1, 0);
314     }
315
316
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.
323
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",
327          and "USING XDM".
328    */
329   {
330     uid_t uid = getuid ();              /* get it again */
331     struct passwd *p = getpwuid (uid);  /* get it again */
332
333     if (!p ||
334         uid == (uid_t)  0 ||
335         uid == (uid_t) -1 ||
336         uid == (uid_t) -2 ||
337         p->pw_uid == (uid_t)  0 ||
338         p->pw_uid == (uid_t) -1 ||
339         p->pw_uid == (uid_t) -2 ||
340         !p->pw_name ||
341         !*p->pw_name ||
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"))
351       {
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;
359       }
360   }
361 }