72228a5244c7d4a2691f1bea99b7aca05777cebc
[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 static int
78 set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
79 {
80   int uid_errno = 0;
81   int gid_errno = 0;
82   int sgs_errno = 0;
83   struct passwd *p = getpwuid (uid);
84   struct group  *g = getgrgid (gid);
85
86   if (message_ret)
87     *message_ret = 0;
88
89   /* Rumor has it that some implementations of of setuid() do nothing
90      when called with -1; therefore, if the "nobody" user has a uid of
91      -1, then that would be Really Bad.  Rumor further has it that such
92      systems really ought to be using -2 for "nobody", since that works.
93      So, if we get a uid (or gid, for good measure) of -1, switch to -2
94      instead.  Note that this must be done after we've looked up the
95      user/group names with getpwuid(-1) and/or getgrgid(-1).
96    */
97   if (gid == (gid_t) -1) gid = (gid_t) -2;
98   if (uid == (uid_t) -1) uid = (uid_t) -2;
99
100   errno = 0;
101   if (setgroups (1, &gid) < 0)
102     sgs_errno = errno ? errno : -1;
103
104   errno = 0;
105   if (setgid (gid) != 0)
106     gid_errno = errno ? errno : -1;
107
108   errno = 0;
109   if (setuid (uid) != 0)
110     uid_errno = errno ? errno : -1;
111
112   if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
113     {
114       static char buf [1024];
115       sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
116                (p && p->pw_name ? p->pw_name : "???"),
117                (g && g->gr_name ? g->gr_name : "???"),
118                (long) uid, (long) gid);
119       if (message_ret)
120         *message_ret = buf;
121       return 0;
122     }
123   else
124     {
125       char buf [1024];
126       if (sgs_errno)
127         {
128           sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
129                    blurb(),
130                    (g && g->gr_name ? g->gr_name : "???"),
131                    (long) gid);
132           if (sgs_errno == -1)
133             fprintf(stderr, "%s: unknown error\n", buf);
134           else
135             {
136               errno = sgs_errno;
137               perror(buf);
138             }
139         }
140
141       if (gid_errno)
142         {
143           sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
144                    blurb(),
145                    (g && g->gr_name ? g->gr_name : "???"),
146                    (long) gid);
147           if (gid_errno == -1)
148             fprintf(stderr, "%s: unknown error\n", buf);
149           else
150             {
151               errno = gid_errno;
152               perror(buf);
153             }
154         }
155
156       if (uid_errno)
157         {
158           sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
159                    blurb(),
160                    (p && p->pw_name ? p->pw_name : "???"),
161                    (long) uid);
162           if (uid_errno == -1)
163             fprintf(stderr, "%s: unknown error\n", buf);
164           else
165             {
166               errno = uid_errno;
167               perror(buf);
168             }
169         }
170
171       return -1;
172     }
173 }
174
175
176 /* If we've been run as setuid or setgid to someone else (most likely root)
177    turn off the extra permissions so that random user-specified programs
178    don't get special privileges.  (On some systems it is necessary to install
179    this program as setuid root in order to read the passwd file to implement
180    lock-mode.)
181
182      *** WARNING: DO NOT DISABLE ANY OF THE FOLLOWING CODE!
183          If you do so, you will open a security hole.  See the sections
184          of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", 
185          and "USING XDM".
186  */
187 void
188 hack_uid (saver_info *si)
189 {
190
191   /* Discard privileges, and set the effective user/group ids to the
192      real user/group ids.  That is, give up our "chmod +s" rights.
193    */
194   {
195     uid_t euid = geteuid();
196     gid_t egid = getegid();
197     uid_t uid = getuid();
198     gid_t gid = getgid();
199
200     si->orig_uid = strdup (uid_gid_string (euid, egid));
201
202     if (uid != euid || gid != egid)
203       if (set_ids_by_number (uid, gid, &si->uid_message) != 0)
204         saver_exit (si, 1, 0);
205   }
206
207
208   /* Locking can't work when running as root, because we have no way of
209      knowing what the user id of the logged in user is (so we don't know
210      whose password to prompt for.)
211
212      *** WARNING: DO NOT DISABLE THIS CODE!
213          If you do so, you will open a security hole.  See the sections
214          of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
215          and "USING XDM".
216    */
217   if (getuid() == (uid_t) 0)
218     {
219       si->locking_disabled_p = True;
220       si->nolock_reason = "running as root";
221     }
222
223
224   /* If we're running as root, switch to a safer user.  This is above and
225      beyond the fact that we've disabling locking, above -- the theory is
226      that running graphics demos as root is just always a stupid thing
227      to do, since they have probably never been security reviewed and are
228      more likely to be buggy than just about any other kind of program.
229      (And that assumes non-malicious code.  There are also attacks here.)
230
231      *** WARNING: DO NOT DISABLE THIS CODE!
232          If you do so, you will open a security hole.  See the sections
233          of the xscreensaver manual titled "LOCKING AND ROOT LOGINS", 
234          and "USING XDM".
235    */
236   if (getuid() == (uid_t) 0)
237     {
238       struct passwd *p;
239
240       p = getpwnam ("nobody");
241       if (! p) p = getpwnam ("noaccess");
242       if (! p) p = getpwnam ("daemon");
243       if (! p)
244         {
245           fprintf (stderr,
246                    "%s: running as root, and couldn't find a safer uid.\n",
247                    blurb());
248           saver_exit(si, 1, 0);
249         }
250
251       if (set_ids_by_number (p->pw_uid, p->pw_gid, &si->uid_message) != 0)
252         saver_exit (si, -1, 0);
253     }
254
255
256   /* If there's anything even remotely funny looking about the passwd struct,
257      or if we're running as some other user from the list below (a
258      non-comprehensive selection of users known to be privileged in some way,
259      and not normal end-users) then disable locking.  If it was possible,
260      switching to "nobody" would be the thing to do, but only root itself has
261      the privs to do that.
262
263      *** WARNING: DO NOT DISABLE THIS CODE!
264          If you do so, you will open a security hole.  See the sections
265          of the xscreensaver manual titled "LOCKING AND ROOT LOGINS",
266          and "USING XDM".
267    */
268   {
269     uid_t uid = getuid ();              /* get it again */
270     struct passwd *p = getpwuid (uid);  /* get it again */
271
272     if (!p ||
273         uid == (uid_t)  0 ||
274         uid == (uid_t) -1 ||
275         uid == (uid_t) -2 ||
276         p->pw_uid == (uid_t)  0 ||
277         p->pw_uid == (uid_t) -1 ||
278         p->pw_uid == (uid_t) -2 ||
279         !p->pw_name ||
280         !*p->pw_name ||
281         !strcmp (p->pw_name, "root") ||
282         !strcmp (p->pw_name, "nobody") ||
283         !strcmp (p->pw_name, "noaccess") ||
284         !strcmp (p->pw_name, "operator") ||
285         !strcmp (p->pw_name, "daemon") ||
286         !strcmp (p->pw_name, "bin") ||
287         !strcmp (p->pw_name, "adm") ||
288         !strcmp (p->pw_name, "sys") ||
289         !strcmp (p->pw_name, "games"))
290       {
291         static char buf [1024];
292         sprintf (buf, "running as %.100s",
293                  (p && p->pw_name && *p->pw_name
294                   ? p->pw_name : "<unknown>"));
295         si->nolock_reason = buf;
296         si->locking_disabled_p = True;
297         si->dangerous_uid_p = True;
298       }
299   }
300 }