/* setuid.c --- management of runtime privileges.
- * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-1998, 2005 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
struct group *g = 0;
p = getpwuid (uid);
g = getgrgid (gid);
- sprintf (buf, "%s/%s (%ld/%ld)",
+ sprintf (buf, "%.100s/%.100s (%ld/%ld)",
(p && p->pw_name ? p->pw_name : "???"),
(g && g->gr_name ? g->gr_name : "???"),
(long) uid, (long) gid);
}
+/* Returns true if we need to call setgroups().
+
+ Without calling setgroups(), the process will retain any supplementary
+ gids associated with the uid, e.g.:
+
+ % groups root
+ root : root bin daemon sys adm disk wheel
+
+ However, setgroups() can only be called by root, and returns EPERM
+ for other users even if the call would be a no-op (e.g., setting the
+ group list to the current list.) So, to avoid that spurious error,
+ before calling setgroups() we first check whether the current list
+ of groups contains only one element, our target group. If so, we
+ don't need to call setgroups().
+ */
static int
-set_ids_by_name (struct passwd *p, struct group *g, char **message_ret)
+setgroups_needed_p (uid_t target_group)
+{
+ gid_t groups[1024];
+ int n = getgroups (sizeof(groups)-1, groups);
+ if (n < 0)
+ {
+ char buf [1024];
+ sprintf (buf, "%s: getgroups(%d, ...)", blurb(), sizeof(groups)-1);
+ perror (buf);
+ return 1;
+ }
+ else if (n == 0) /* an empty list means only egid is in effect. */
+ return 0;
+ else if (n == 1 && groups[0] == target_group) /* one element, the target */
+ return 0;
+ else /* more than one, or the wrong one. */
+ return 1;
+}
+
+
+static int
+set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
{
int uid_errno = 0;
int gid_errno = 0;
- uid_t uid = p->pw_uid;
- gid_t gid = g->gr_gid;
+ int sgs_errno = 0;
+ struct passwd *p = getpwuid (uid);
+ struct group *g = getgrgid (gid);
if (message_ret)
*message_ret = 0;
-1, then that would be Really Bad. Rumor further has it that such
systems really ought to be using -2 for "nobody", since that works.
So, if we get a uid (or gid, for good measure) of -1, switch to -2
- instead.
+ instead. Note that this must be done after we've looked up the
+ user/group names with getpwuid(-1) and/or getgrgid(-1).
*/
if (gid == (gid_t) -1) gid = (gid_t) -2;
if (uid == (uid_t) -1) uid = (uid_t) -2;
+ errno = 0;
+ if (setgroups_needed_p (gid) &&
+ setgroups (1, &gid) < 0)
+ sgs_errno = errno ? errno : -1;
+
errno = 0;
if (setgid (gid) != 0)
gid_errno = errno ? errno : -1;
if (setuid (uid) != 0)
uid_errno = errno ? errno : -1;
- if (uid_errno == 0 && gid_errno == 0)
+ if (uid_errno == 0 && gid_errno == 0 && sgs_errno == 0)
{
static char buf [1024];
- sprintf (buf, "changed uid/gid to %s/%s (%ld/%ld).",
- p->pw_name, (g ? g->gr_name : "???"),
+ sprintf (buf, "changed uid/gid to %.100s/%.100s (%ld/%ld).",
+ (p && p->pw_name ? p->pw_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"),
(long) uid, (long) gid);
if (message_ret)
*message_ret = buf;
else
{
char buf [1024];
+ gid_t groups[1024];
+ int n;
+
+ if (sgs_errno)
+ {
+ sprintf (buf, "%s: couldn't setgroups to %.100s (%ld)",
+ blurb(),
+ (g && g->gr_name ? g->gr_name : "???"),
+ (long) gid);
+ if (sgs_errno == -1)
+ fprintf(stderr, "%s: unknown error\n", buf);
+ else
+ {
+ errno = sgs_errno;
+ perror(buf);
+ }
+
+ fprintf (stderr, "%s: effective group list: ", blurb());
+ n = getgroups (sizeof(groups)-1, groups);
+ if (n < 0)
+ fprintf (stderr, "unknown!\n");
+ else
+ {
+ int i;
+ fprintf (stderr, "[");
+ for (i = 0; i < n; i++)
+ {
+ g = getgrgid (groups[i]);
+ if (i > 0) fprintf (stderr, ", ");
+ if (g && g->gr_name) fprintf (stderr, "%s", g->gr_name);
+ else fprintf (stderr, "%ld", (long) groups[i]);
+ }
+ fprintf (stderr, "]\n");
+ }
+ }
+
if (gid_errno)
{
- sprintf (buf, "%s: couldn't set gid to %s (%ld)",
+ sprintf (buf, "%s: couldn't set gid to %.100s (%ld)",
blurb(),
- (g ? g->gr_name : "???"),
+ (g && g->gr_name ? g->gr_name : "???"),
(long) gid);
if (gid_errno == -1)
fprintf(stderr, "%s: unknown error\n", buf);
else
- perror(buf);
+ {
+ errno = gid_errno;
+ perror(buf);
+ }
}
if (uid_errno)
{
- sprintf (buf, "%s: couldn't set uid to %s (%ld)",
+ sprintf (buf, "%s: couldn't set uid to %.100s (%ld)",
blurb(),
- (p ? p->pw_name : "???"),
+ (p && p->pw_name ? p->pw_name : "???"),
(long) uid);
if (uid_errno == -1)
fprintf(stderr, "%s: unknown error\n", buf);
else
- perror(buf);
+ {
+ errno = uid_errno;
+ perror(buf);
+ }
}
return -1;
}
}
-static int
-set_ids_by_number (uid_t uid, gid_t gid, char **message_ret)
-{
- struct passwd *p;
- struct group *g;
-
- errno = 0;
- p = getpwuid (uid);
- if (!p)
- {
- char buf [1024];
- sprintf (buf, "%s: error looking up name of user %d", blurb(),
- (long) uid);
- if (errno)
- perror (buf);
- else
- fprintf (stderr, "%s: unknown error.\n", buf);
- return -1;
- }
-
- errno = 0;
- g = getgrgid (gid);
- if (!g)
- {
- char buf [1024];
- sprintf (buf, "%s: error looking up name of group %d", blurb(),
- (long) gid);
- if (errno)
- perror (buf);
- else
- fprintf (stderr, "%s: unknown error.\n", buf);
- return -1;
- }
-
- return set_ids_by_name (p, g, message_ret);
-}
-
/* If we've been run as setuid or setgid to someone else (most likely root)
turn off the extra permissions so that random user-specified programs
!strcmp (p->pw_name, "games"))
{
static char buf [1024];
- sprintf (buf, "running as %s",
+ sprintf (buf, "running as %.100s",
(p && p->pw_name && *p->pw_name
? p->pw_name : "<unknown>"));
si->nolock_reason = buf;