Browse Source

Patch from Jacob Appelbaum and me to make User option more robust, properly set supplementary groups, deprecated the Group option, and log more information on credential switching

svn:r17200
Steven Murdoch 16 years ago
parent
commit
9d68ed08e9
6 changed files with 194 additions and 36 deletions
  1. 7 0
      ChangeLog
  2. 3 0
      configure.in
  3. 1 5
      doc/tor.1.in
  4. 178 24
      src/common/compat.c
  5. 1 1
      src/common/compat.h
  6. 4 6
      src/or/config.c

+ 7 - 0
ChangeLog

@@ -5,6 +5,13 @@ Changes in version 0.2.1.7-alpha - 2008-11-xx
       exit policy doesn't allow it, we would remember what IP address
       exit policy doesn't allow it, we would remember what IP address
       the relay said the destination address resolves to, even if it's
       the relay said the destination address resolves to, even if it's
       an internal IP address. Bugfix on 0.2.0.7-alpha; patch by rovv.
       an internal IP address. Bugfix on 0.2.0.7-alpha; patch by rovv.
+    - The "User" and "Group" config options did not clear the
+      supplementary group entries for the process. The "User" option
+      has been made more robust, and also now also sets the groups to
+      the specified user's primary group. The "Group" option is now
+      ignored. For more detailed logging on credential switching, set
+      CREDENTIAL_LOG_LEVEL in common/compat.c to LOG_NOTICE or higher;
+      patch by Jacob Appelbaum and Steven Murdoch.
 
 
   o Minor features:
   o Minor features:
     - Now NodeFamily and MyFamily config options allow spaces in
     - Now NodeFamily and MyFamily config options allow spaces in

+ 3 - 0
configure.in

@@ -630,6 +630,9 @@ syslog_facility="$withval", syslog_facility="LOG_DAEMON")
 AC_DEFINE_UNQUOTED(LOGFACILITY,$syslog_facility,[name of the syslog facility])
 AC_DEFINE_UNQUOTED(LOGFACILITY,$syslog_facility,[name of the syslog facility])
 AC_SUBST(LOGFACILITY)
 AC_SUBST(LOGFACILITY)
 
 
+# Check if we have getresuid and getresgid
+AC_CHECK_FUNCS(getresuid getresgid)
+
 # Check for gethostbyname_r in all its glorious incompatible versions.
 # Check for gethostbyname_r in all its glorious incompatible versions.
 #   (This logic is based on that in Python's configure.in)
 #   (This logic is based on that in Python's configure.in)
 AH_TEMPLATE(HAVE_GETHOSTBYNAME_R,
 AH_TEMPLATE(HAVE_GETHOSTBYNAME_R,

+ 1 - 5
doc/tor.1.in

@@ -264,10 +264,6 @@ script to enumerate Tor nodes that exit to certain addresses.
 (Default: 0)
 (Default: 0)
 .LP
 .LP
 .TP
 .TP
-\fBGroup \fR\fIGID\fP
-On startup, setgid to this group.
-.LP
-.TP
 \fBHttpProxy\fR \fIhost\fR[:\fIport\fR]\fP
 \fBHttpProxy\fR \fIhost\fR[:\fIport\fR]\fP
 Tor will make all its directory requests through this host:port
 Tor will make all its directory requests through this host:port
 (or host:80 if port is not specified),
 (or host:80 if port is not specified),
@@ -350,7 +346,7 @@ about what sites a user might have visited. (Default: 1)
 .LP
 .LP
 .TP
 .TP
 \fBUser \fR\fIUID\fP
 \fBUser \fR\fIUID\fP
-On startup, setuid to this user.
+On startup, setuid to this user and setgid to their primary group.
 .LP
 .LP
 .TP
 .TP
 \fBHardwareAccel \fR\fB0\fR|\fB1\fP
 \fBHardwareAccel \fR\fB0\fR|\fB1\fP

+ 178 - 24
src/common/compat.c

@@ -920,61 +920,215 @@ set_max_file_descriptors(rlim_t limit, int *max_out)
   return 0;
   return 0;
 }
 }
 
 
-/** Call setuid and setgid to run as <b>user</b>:<b>group</b>.  Return 0 on
+/** Log details of current user and group credentials. Return 0 on
- * success.  On failure, log and return -1.
+ * success. Logs and return -1 on failure.
  */
  */
 int
 int
-switch_id(const char *user, const char *group)
+log_credential_status()
+{
+#define CREDENTIAL_LOG_LEVEL LOG_INFO
+#ifndef MS_WINDOWS
+  /* Real, effective and saved UIDs */
+  uid_t ruid, euid, suid;
+  /* Read, effective and saved GIDs */
+  gid_t rgid, egid, sgid;
+  /* Supplementary groups */
+  gid_t sup_gids[NGROUPS_MAX + 1];
+  /* Number of supplementary groups */
+  int ngids;
+
+  /* log UIDs */
+#ifdef HAVE_GETRESUID
+  if (getresuid(&ruid, &euid, &suid) != 0 ) {
+    log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
+    return -1;
+  } else {
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "UID is %u (real), %u (effective), %u (saved)", ruid, euid, suid);
+  }
+#else
+  /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
+  ruid = getuid();
+  euid = geteuid();
+  (void)suid;
+
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "UID is %u (real), %u (effective), unknown (saved)", ruid, euid);
+#endif
+
+  /* log GIDs */
+#ifdef HAVE_GETRESGID
+  if (getresgid(&rgid, &egid, &sgid) != 0 ) {
+    log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
+    return -1;
+  } else {
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "GID is %u (real), %u (effective), %u (saved)", rgid, egid, sgid);
+  }
+#else
+  /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
+  rgid = getgid();
+  egid = getegid();
+  (void)sgid;
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "GID is %u (real), %u (effective), unknown (saved)", rgid, egid);
+#endif
+
+  /* log supplementary groups */
+  if((ngids = getgroups(NGROUPS_MAX + 1, sup_gids)) < 0) {
+    log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s", strerror(errno));
+    return -1;
+  } else {
+    int i;
+    char *strgid;
+    char *s = NULL;
+    int formatting_error = 0;
+    smartlist_t *elts = smartlist_create();
+
+    for (i = 0; i<ngids; i++) {
+      strgid = tor_malloc(11);
+      if (tor_snprintf(strgid, 11, "%u", (unsigned)sup_gids[i]) == -1) {
+        log_warn(LD_GENERAL, "Error printing supplementary GIDs");
+        formatting_error = 1;
+        goto error;
+      }
+      smartlist_add(elts, strgid);
+    }
+
+    s = smartlist_join_strings(elts, " ", 0, NULL);
+
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s", s);
+
+   error:
+    tor_free(s);
+    SMARTLIST_FOREACH(elts, char *, cp,
+    {
+      tor_free(cp);
+    });
+    smartlist_free(elts);
+
+    if (formatting_error)
+      return -1;
+  }
+#endif
+
+  return 0;
+}
+
+/** Call setuid and setgid to run as <b>user</b> and only switch to their
+ * primary group.  Return 0 on success.  On failure, log and return -1.
+ */
+int
+switch_id(const char *user)
 {
 {
 #ifndef MS_WINDOWS
 #ifndef MS_WINDOWS
   struct passwd *pw = NULL;
   struct passwd *pw = NULL;
-  struct group *gr = NULL;
+  uid_t old_uid;
+  gid_t old_gid;
+  
+  tor_assert(user);
 
 
+  /* Log the initial credential state */ 
   if (user) {
   if (user) {
-    pw = getpwnam(user);
+    if (log_credential_status()) {
-    if (pw == NULL) {
-      log_warn(LD_CONFIG,"User '%s' not found.", user);
       return -1;
       return -1;
     }
     }
   }
   }
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
 
 
-  /* switch the group first, while we still have the privileges to do so */
+  /* Get old UID/GID to check if we changed correctly */
-  if (group) {
+  old_uid = getuid();
-    gr = getgrnam(group);
+  old_gid = getgid();
-    if (gr == NULL) {
+
-      log_warn(LD_CONFIG,"Group '%s' not found.", group);
+  /* Lookup the user and group information, if we have a problem, bail out. */
+  if (user) {
+    pw = getpwnam(user);
+    if (pw == NULL) {
+      log_warn(LD_CONFIG, "Error setting configured user: "
+      "'%s' not found.", user);
       return -1;
       return -1;
     }
     }
+  } else {
+    /* We have no user supplied and so we'll bail out. */
+    log_warn(LD_CONFIG, "Error setting configured user: No user supplied.");
+    return -1;
+  }
 
 
-    if (setgid(gr->gr_gid) != 0) {
+  /* Properly switch egid,gid,euid,uid here or bail out */
-      log_warn(LD_GENERAL,"Error setting to configured GID: %s",
+  if (setgroups(1, &pw->pw_gid)) {
-               strerror(errno));
+    log_warn(LD_GENERAL, "Error setting configured groups: %s",
+    strerror(errno));
+    return -1;
+  }
+
+  if (setegid(pw->pw_gid)) {
+    log_warn(LD_GENERAL, "Error setting configured egid: %s",
+    strerror(errno));
+    return -1;
+  }
+
+  if (setgid(pw->pw_gid)) {
+    log_warn(LD_GENERAL, "Error setting configured gid: %s",
+    strerror(errno));
+    return -1;
+  }
+
+  if (setuid(pw->pw_uid)) {
+    log_warn(LD_GENERAL, "Error setting configured uid: %s",
+    strerror(errno));
+    return -1;
+  }
+
+  if (seteuid(pw->pw_uid)){
+    log_warn(LD_GENERAL, "Error setting configured euid: %s",
+    strerror(errno));
+    return -1;
+  }
+
+  /* This is how OpenBSD rolls:
+  if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
+      setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
+      setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
+    log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
+    strerror(errno));
+    return -1;
+  }
+  */
+
+  /* We've properly switched egid, gid, euid, uid, and supplementary groups if
+   * we're here. */
+
+#if !defined(CYGWIN) && !defined(__CYGWIN__)
+  /* If we tried to drop privilege to a group/user other than root, attempt to
+   * restore root (E)(U|G)ID, and abort if the operation succeeds */
+
+  /* Only check for privilege dropping if we were asked to be non-root */
+  if (pw->pw_uid) {
+    /* Try changing GID/EGID */
+    if (pw->pw_gid != old_gid && (setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
+      log_warn(LD_GENERAL, "Was able to restore group credentials");
       return -1;
       return -1;
     }
     }
-  } else if (user) {
+  
-    if (setgid(pw->pw_gid) != 0) {
+    /* Try changing UID/EUID */
-      log_warn(LD_GENERAL,"Error setting to user GID: %s", strerror(errno));
+    if (pw->pw_uid != old_uid && (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
+      log_warn(LD_GENERAL, "Was able to restore user credentials");
       return -1;
       return -1;
     }
     }
   }
   }
+#endif
 
 
-  /* now that the group is switched, we can switch users and lose
+  /* Check what really happened */
-     privileges */
   if (user) {
   if (user) {
-    if (setuid(pw->pw_uid) != 0) {
+    if (log_credential_status()) {
-      log_warn(LD_GENERAL,"Error setting UID: %s", strerror(errno));
       return -1;
       return -1;
     }
     }
   }
   }
 
 
   return 0;
   return 0;
+
 #else
 #else
   (void)user;
   (void)user;
-  (void)group;
 #endif
 #endif
 
 
   log_warn(LD_CONFIG,
   log_warn(LD_CONFIG,
-           "User or group specified, but switching users is not supported.");
+           "User specified but switching users is unsupported on your OS.");
   return -1;
   return -1;
 }
 }
 
 

+ 1 - 1
src/common/compat.h

@@ -447,7 +447,7 @@ void set_uint32(char *cp, uint32_t v) ATTR_NONNULL((1));
 typedef unsigned long rlim_t;
 typedef unsigned long rlim_t;
 #endif
 #endif
 int set_max_file_descriptors(rlim_t limit, int *max);
 int set_max_file_descriptors(rlim_t limit, int *max);
-int switch_id(const char *user, const char *group);
+int switch_id(const char *user);
 #ifdef HAVE_PWD_H
 #ifdef HAVE_PWD_H
 char *get_user_homedir(const char *username);
 char *get_user_homedir(const char *username);
 #endif
 #endif

+ 4 - 6
src/or/config.c

@@ -219,7 +219,7 @@ static config_var_t _option_vars[] = {
   V(GeoIPFile,                   FILENAME,
   V(GeoIPFile,                   FILENAME,
     SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"),
     SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"),
 #endif
 #endif
-  V(Group,                       STRING,   NULL),
+  OBSOLETE("Group"),
   V(HardwareAccel,               BOOL,     "0"),
   V(HardwareAccel,               BOOL,     "0"),
   V(HashedControlPassword,       LINELIST, NULL),
   V(HashedControlPassword,       LINELIST, NULL),
   V(HidServDirectoryV2,          BOOL,     "1"),
   V(HidServDirectoryV2,          BOOL,     "1"),
@@ -434,7 +434,6 @@ static config_var_description_t options_description[] = {
   /* { "FastFirstHopPK", "" }, */
   /* { "FastFirstHopPK", "" }, */
   /* FetchServerDescriptors, FetchHidServDescriptors,
   /* FetchServerDescriptors, FetchHidServDescriptors,
    * FetchUselessDescriptors */
    * FetchUselessDescriptors */
-  { "Group", "On startup, setgid to this group." },
   { "HardwareAccel", "If set, Tor tries to use hardware crypto accelerators "
   { "HardwareAccel", "If set, Tor tries to use hardware crypto accelerators "
     "when it can." },
     "when it can." },
   /* HashedControlPassword */
   /* HashedControlPassword */
@@ -1084,13 +1083,12 @@ options_act_reversible(or_options_t *old_options, char **msg)
 #endif
 #endif
 
 
   /* Setuid/setgid as appropriate */
   /* Setuid/setgid as appropriate */
-  if (options->User || options->Group) {
+  if (options->User) {
     /* XXXX021 We should only do this the first time through, not on
     /* XXXX021 We should only do this the first time through, not on
      * every setconf. */
      * every setconf. */
-    if (switch_id(options->User, options->Group) != 0) {
+    if (switch_id(options->User) != 0) {
       /* No need to roll back, since you can't change the value. */
       /* No need to roll back, since you can't change the value. */
-      *msg = tor_strdup("Problem with User or Group value. "
+      *msg = tor_strdup("Problem with User value. See logs for details.");
-                        "See logs for details.");
       goto done;
       goto done;
     }
     }
   }
   }