uwrap: First do garbage collection before exporting ids
[uid_wrapper.git] / src / uid_wrapper.c
index 44ef8c13bde9b5c9c8f8538fa1648a9b8019de31..a48cc3bda275597cec8126c14446ef5e747e0f0d 100644 (file)
@@ -133,54 +133,52 @@ enum uwrap_dbglvl_e {
        UWRAP_LOG_TRACE
 };
 
-#ifdef NDEBUG
-# define UWRAP_LOG(...)
-#else /* NDEBUG */
-static void uwrap_log(enum uwrap_dbglvl_e dbglvl, const char *format, ...) PRINTF_ATTRIBUTE(2, 3);
-# define UWRAP_LOG(dbglvl, ...) uwrap_log((dbglvl), __VA_ARGS__)
+static void uwrap_log(enum uwrap_dbglvl_e dbglvl, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(3, 4);
+# define UWRAP_LOG(dbglvl, ...) uwrap_log((dbglvl), __func__, __VA_ARGS__)
 
-static void uwrap_log(enum uwrap_dbglvl_e dbglvl, const char *format, ...)
+static void uwrap_log(enum uwrap_dbglvl_e dbglvl, const char *function, const char *format, ...)
 {
        char buffer[1024];
        va_list va;
        const char *d;
        unsigned int lvl = 0;
+       const char *prefix = "UWRAP";
 
        d = getenv("UID_WRAPPER_DEBUGLEVEL");
        if (d != NULL) {
                lvl = atoi(d);
        }
 
+       if (lvl < dbglvl) {
+               return;
+       }
+
        va_start(va, format);
        vsnprintf(buffer, sizeof(buffer), format, va);
        va_end(va);
 
-       if (lvl >= dbglvl) {
-               switch (dbglvl) {
-                       case UWRAP_LOG_ERROR:
-                               fprintf(stderr,
-                                       "UWRAP_ERROR(%d): %s\n",
-                                       (int)getpid(), buffer);
-                               break;
-                       case UWRAP_LOG_WARN:
-                               fprintf(stderr,
-                                       "UWRAP_WARN(%d): %s\n",
-                                       (int)getpid(), buffer);
-                               break;
-                       case UWRAP_LOG_DEBUG:
-                               fprintf(stderr,
-                                       "UWRAP_DEBUG(%d): %s\n",
-                                       (int)getpid(), buffer);
-                               break;
-                       case UWRAP_LOG_TRACE:
-                               fprintf(stderr,
-                                       "UWRAP_TRACE(%d): %s\n",
-                                       (int)getpid(), buffer);
-                               break;
-               }
+       switch (dbglvl) {
+               case UWRAP_LOG_ERROR:
+                       prefix = "UWRAP_ERROR";
+                       break;
+               case UWRAP_LOG_WARN:
+                       prefix = "UWRAP_WARN";
+                       break;
+               case UWRAP_LOG_DEBUG:
+                       prefix = "UWRAP_DEBUG";
+                       break;
+               case UWRAP_LOG_TRACE:
+                       prefix = "UWRAP_TRACE";
+                       break;
        }
+
+       fprintf(stderr,
+               "%s(%d) - %s: %s\n",
+               prefix,
+               (int)getpid(),
+               function,
+               buffer);
 }
-#endif /* NDEBUG */
 
 /*****************
  * LIBC
@@ -407,6 +405,13 @@ static void *uwrap_load_lib_handle(enum uwrap_lib lib)
                                if (handle != NULL) {
                                        break;
                                }
+
+                               /* glibc on Alpha and IA64 is libc.so.6.1 */
+                               snprintf(soname, sizeof(soname), "libc.so.%d.1", i);
+                               handle = dlopen(soname, flags);
+                               if (handle != NULL) {
+                                       break;
+                               }
                        }
 
                        uwrap.libc.handle = handle;
@@ -656,6 +661,7 @@ static void uwrap_pthread_exit(void *retval)
        if (id == NULL) {
                UWRAP_UNLOCK(uwrap_id);
                libpthread_pthread_exit(retval);
+               return;
        }
 
        UWRAP_DLIST_REMOVE(uwrap.ids, id);
@@ -807,12 +813,103 @@ int pthread_create(pthread_t *thread,
  * UWRAP ID HANDLING
  *********************************************************/
 
+#define GROUP_STRING_SIZE 16384
+#define GROUP_MAX_COUNT (GROUP_STRING_SIZE / (10 + 1))
+
+/**
+ * This function exports all the IDs of the current user so if
+ * we fork and then exec we can setup uid_wrapper in the new process
+ * with those IDs.
+ */
+static void uwrap_export_ids(struct uwrap_thread *id)
+{
+       char groups_str[GROUP_STRING_SIZE] = {0};
+       size_t groups_str_size = sizeof(groups_str);
+       char unsigned_str[16] = {0}; /* We need 10 + 1 (+ 1) */
+       int i;
+
+       /* UIDS */
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->ruid);
+       setenv("UID_WRAPPER_INITIAL_RUID", unsigned_str, 1);
+
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->euid);
+       setenv("UID_WRAPPER_INITIAL_EUID", unsigned_str, 1);
+
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->suid);
+       setenv("UID_WRAPPER_INITIAL_SUID", unsigned_str, 1);
+
+       /* GIDS */
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->rgid);
+       setenv("UID_WRAPPER_INITIAL_RGID", unsigned_str, 1);
+
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->egid);
+       setenv("UID_WRAPPER_INITIAL_EGID", unsigned_str, 1);
+
+       snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->sgid);
+       setenv("UID_WRAPPER_INITIAL_SGID", unsigned_str, 1);
+
+       if (id->ngroups > GROUP_MAX_COUNT) {
+               UWRAP_LOG(UWRAP_LOG_ERROR,
+                         "ERROR: Number of groups (%u) exceeds maximum value "
+                         "uid_wrapper can handle (%u).",
+                         id->ngroups,
+                         GROUP_MAX_COUNT);
+               exit(-1);
+       }
+
+       /* GROUPS */
+       for (i = 0; i < id->ngroups; i++) {
+               size_t groups_str_len = strlen(groups_str);
+               size_t groups_str_avail = groups_str_size - groups_str_len - 1;
+               int len;
+
+               len = snprintf(unsigned_str, sizeof(unsigned_str), ",%u", id->groups[i]);
+               if (len <= 1) {
+                       UWRAP_LOG(UWRAP_LOG_ERROR,
+                                 "snprintf failed for groups[%d]=%u",
+                                 i,
+                                 id->groups[i]);
+                       break;
+               }
+               if (((size_t)len) >= groups_str_avail) {
+                       UWRAP_LOG(UWRAP_LOG_ERROR,
+                                 "groups env string is to small for %d groups",
+                                 i);
+                       break;
+               }
+
+               len = snprintf(groups_str + groups_str_len,
+                              groups_str_size - groups_str_len,
+                              "%s",
+                              i == 0 ? unsigned_str + 1 : unsigned_str);
+               if (len < 1) {
+                       UWRAP_LOG(UWRAP_LOG_ERROR,
+                                 "snprintf failed to create groups string at groups[%d]=%u",
+                                 i,
+                                 id->groups[i]);
+                       break;
+               }
+       }
+
+       if (id->ngroups == i) {
+               setenv("UID_WRAPPER_INITIAL_GROUPS", groups_str, 1);
+
+               snprintf(unsigned_str, sizeof(unsigned_str), "%u", id->ngroups);
+               setenv("UID_WRAPPER_INITIAL_GROUPS_COUNT", unsigned_str, 1);
+       }
+}
+
 static void uwrap_thread_prepare(void)
 {
        struct uwrap_thread *id = uwrap_tls_id;
 
        UWRAP_LOCK_ALL;
 
+       /* uid_wrapper is loaded but not enabled */
+       if (id == NULL) {
+               return;
+       }
+
        /*
         * What happens if another atfork prepare functions calls a uwrap
         * function? So disable it in case another atfork prepare function
@@ -825,6 +922,13 @@ static void uwrap_thread_prepare(void)
 static void uwrap_thread_parent(void)
 {
        struct uwrap_thread *id = uwrap_tls_id;
+
+       /* uid_wrapper is loaded but not enabled */
+       if (id == NULL) {
+               UWRAP_UNLOCK_ALL;
+               return;
+       }
+
        id->enabled = true;
 
        UWRAP_UNLOCK_ALL;
@@ -835,6 +939,12 @@ static void uwrap_thread_child(void)
        struct uwrap_thread *id = uwrap_tls_id;
        struct uwrap_thread *u = uwrap.ids;
 
+       /* uid_wrapper is loaded but not enabled */
+       if (id == NULL) {
+               UWRAP_UNLOCK_ALL;
+               return;
+       }
+
        /*
         * "Garbage collector" - Inspired by DESTRUCTOR.
         * All threads (except one which called fork()) are dead now.. Dave
@@ -855,11 +965,119 @@ static void uwrap_thread_child(void)
                u = uwrap.ids;
        }
 
+       uwrap_export_ids(id);
+
        id->enabled = true;
 
        UWRAP_UNLOCK_ALL;
 }
 
+/*
+ * This initializes uid_wrapper with the IDs exported to the environment. Those
+ * are normally set after we forked and executed.
+ */
+static void uwrap_init_env(struct uwrap_thread *id)
+{
+       const char *env;
+       int ngroups = 0;
+
+       env = getenv("UID_WRAPPER_INITIAL_RUID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initialize ruid with %s", env);
+               id->ruid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_RUID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_EUID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initalize euid with %s", env);
+               id->euid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_EUID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_SUID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initalize suid with %s", env);
+               id->suid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_SUID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_RGID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initialize ruid with %s", env);
+               id->rgid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_RGID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_EGID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initalize egid with %s", env);
+               id->egid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_EGID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_SGID");
+       if (env != NULL && env[0] != '\0') {
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initalize sgid with %s", env);
+               id->sgid = strtoul(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_SGID");
+       }
+
+       env = getenv("UID_WRAPPER_INITIAL_GROUPS_COUNT");
+       if (env != NULL && env[0] != '\0') {
+               ngroups = strtol(env, (char **)NULL, 10);
+               unsetenv("UID_WRAPPER_INITIAL_GROUPS_COUNT");
+       }
+
+       if (ngroups > 0 && ngroups < GROUP_MAX_COUNT) {
+               int i = 0;
+
+               id->ngroups = 0;
+
+               free(id->groups);
+               id->groups = malloc(sizeof(gid_t) * ngroups);
+               if (id->groups == NULL) {
+                       UWRAP_LOG(UWRAP_LOG_ERROR,
+                                 "Unable to allocate memory");
+                       exit(-1);
+               }
+
+               env = getenv("UID_WRAPPER_INITIAL_GROUPS");
+               if (env != NULL && env[0] != '\0') {
+                       char *groups_str = NULL;
+                       char *saveptr = NULL;
+                       const char *p = NULL;
+
+                       groups_str = strdup(env);
+                       if (groups_str == NULL) {
+                               exit(-1);
+                       }
+
+                       p = strtok_r(groups_str, ",", &saveptr);
+                       while (p != NULL) {
+                               id->groups[i] = strtol(p, (char **)NULL, 10);
+                               i++;
+
+                               p = strtok_r(NULL, ",", &saveptr);
+                       }
+                       SAFE_FREE(groups_str);
+               }
+
+               if (i != ngroups) {
+                       UWRAP_LOG(UWRAP_LOG_ERROR,
+                                 "ERROR: The number of groups (%u) passed, "
+                                 "does not match the number of groups (%u) "
+                                 "we parsed.",
+                                 ngroups,
+                                 i);
+                       exit(-1);
+               }
+
+               UWRAP_LOG(UWRAP_LOG_DEBUG, "Initalize groups with %s", env);
+               id->ngroups = ngroups;
+       }
+}
+
 static void uwrap_init(void)
 {
        const char *env;
@@ -948,16 +1166,19 @@ static void uwrap_init(void)
                        }
                }
 
+               uwrap_init_env(id);
+
                id->enabled = true;
 
                UWRAP_LOG(UWRAP_LOG_DEBUG,
-                         "Enabled uid_wrapper as %s",
-                         uwrap.myuid == 0 ? "root" : "user");
+                         "Enabled uid_wrapper as %s (real uid=%u)",
+                         id->ruid == 0 ? "root" : "user",
+                         (unsigned int)uwrap.myuid);
        }
 
        UWRAP_UNLOCK(uwrap_id);
 
-       UWRAP_LOG(UWRAP_LOG_DEBUG, "Succeccfully initialized uid_wrapper");
+       UWRAP_LOG(UWRAP_LOG_DEBUG, "Successfully initialized uid_wrapper");
 }
 
 bool uid_wrapper_enabled(void)
@@ -976,33 +1197,61 @@ bool uid_wrapper_enabled(void)
        return enabled;
 }
 
-#ifdef HAVE_GETRESUID
-static int uwrap_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid)
+/*
+ * UWRAP_SETxUID FUNCTIONS
+ */
+
+static int uwrap_setresuid_args(uid_t ruid, uid_t euid, uid_t suid)
 {
        struct uwrap_thread *id = uwrap_tls_id;
 
-       UWRAP_LOCK(uwrap_id);
-
-       *ruid = id->ruid;
-       *euid = id->euid;
-       *suid = id->suid;
-
-       UWRAP_UNLOCK(uwrap_id);
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d, suid %d -> %d",
+                 id->ruid, ruid, id->euid, euid, id->suid, suid);
+
+       if (id->euid != 0) {
+               if (ruid != (uid_t)-1 &&
+                   ruid != id->ruid &&
+                   ruid != id->euid &&
+                   ruid != id->suid) {
+                       errno = EPERM;
+                       return -1;
+               }
+               if (euid != (uid_t)-1 &&
+                   euid != id->ruid &&
+                   euid != id->euid &&
+                   euid != id->suid) {
+                       errno = EPERM;
+                       return -1;
+               }
+               if (suid != (uid_t)-1 &&
+                   suid != id->ruid &&
+                   suid != id->euid &&
+                   suid != id->suid) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
 
        return 0;
 }
-#endif
 
 static int uwrap_setresuid_thread(uid_t ruid, uid_t euid, uid_t suid)
 {
        struct uwrap_thread *id = uwrap_tls_id;
+       int rc;
 
-       if (ruid == (uid_t)-1 && euid == (uid_t)-1 && suid == (uid_t)-1) {
-               errno = EINVAL;
-               return -1;
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d, suid %d -> %d",
+                 id->ruid, ruid, id->euid, euid, id->suid, suid);
+
+       rc = uwrap_setresuid_args(ruid, euid, suid);
+       if (rc != 0) {
+               return rc;
        }
 
        UWRAP_LOCK(uwrap_id);
+
        if (ruid != (uid_t)-1) {
                id->ruid = ruid;
        }
@@ -1020,6 +1269,200 @@ static int uwrap_setresuid_thread(uid_t ruid, uid_t euid, uid_t suid)
        return 0;
 }
 
+static int uwrap_setresuid(uid_t ruid, uid_t euid, uid_t suid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d, suid %d -> %d",
+                 id->ruid, ruid, id->euid, euid, id->suid, suid);
+
+       rc = uwrap_setresuid_args(ruid, euid, suid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       UWRAP_LOCK(uwrap_id);
+
+       for (id = uwrap.ids; id; id = id->next) {
+               if (ruid != (uid_t)-1) {
+                       id->ruid = ruid;
+               }
+
+               if (euid != (uid_t)-1) {
+                       id->euid = euid;
+               }
+
+               if (suid != (uid_t)-1) {
+                       id->suid = suid;
+               }
+       }
+
+       UWRAP_UNLOCK(uwrap_id);
+
+       return 0;
+}
+
+static int uwrap_setreuid_args(uid_t ruid, uid_t euid,
+                              uid_t *_new_ruid,
+                              uid_t *_new_euid,
+                              uid_t *_new_suid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       uid_t new_ruid = -1, new_euid = -1, new_suid = -1;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d",
+                 id->ruid, ruid, id->euid, euid);
+
+       if (ruid != (uid_t)-1) {
+               new_ruid = ruid;
+               if (ruid != id->ruid &&
+                   ruid != id->euid &&
+                   id->euid != 0) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
+
+       if (euid != (uid_t)-1) {
+               new_euid = euid;
+               if (euid != id->ruid &&
+                   euid != id->euid &&
+                   euid != id->suid &&
+                   id->euid != 0) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
+
+       if (ruid != (uid_t) -1 ||
+           (euid != (uid_t)-1 && id->ruid != euid)) {
+               new_suid = new_euid;
+       }
+
+       *_new_ruid = new_ruid;
+       *_new_euid = new_euid;
+       *_new_suid = new_suid;
+
+       return 0;
+}
+
+static int uwrap_setreuid_thread(uid_t ruid, uid_t euid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       uid_t new_ruid = -1, new_euid = -1, new_suid = -1;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d",
+                 id->ruid, ruid, id->euid, euid);
+
+       rc = uwrap_setreuid_args(ruid, euid, &new_ruid, &new_euid, &new_suid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresuid_thread(new_ruid, new_euid, new_suid);
+}
+
+#ifdef HAVE_SETREUID
+static int uwrap_setreuid(uid_t ruid, uid_t euid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       uid_t new_ruid = -1, new_euid = -1, new_suid = -1;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "ruid %d -> %d, euid %d -> %d",
+                 id->ruid, ruid, id->euid, euid);
+
+       rc = uwrap_setreuid_args(ruid, euid, &new_ruid, &new_euid, &new_suid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresuid(new_ruid, new_euid, new_suid);
+}
+#endif
+
+static int uwrap_setuid_args(uid_t uid,
+                            uid_t *new_ruid,
+                            uid_t *new_euid,
+                            uid_t *new_suid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "uid %d -> %d",
+                 id->ruid, uid);
+
+       if (uid == (uid_t)-1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (id->euid == 0) {
+               *new_suid = *new_ruid = uid;
+       } else if (uid != id->ruid &&
+                  uid != id->suid) {
+               errno = EPERM;
+               return -1;
+       }
+
+       *new_euid = uid;
+
+       return 0;
+}
+
+static int uwrap_setuid_thread(uid_t uid)
+{
+       uid_t new_ruid = -1, new_euid = -1, new_suid = -1;
+       int rc;
+
+       rc = uwrap_setuid_args(uid, &new_ruid, &new_euid, &new_suid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresuid_thread(new_ruid, new_euid, new_suid);
+}
+
+static int uwrap_setuid(uid_t uid)
+{
+       uid_t new_ruid = -1, new_euid = -1, new_suid = -1;
+       int rc;
+
+       rc = uwrap_setuid_args(uid, &new_ruid, &new_euid, &new_suid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresuid(new_ruid, new_euid, new_suid);
+}
+
+/*
+ * UWRAP_GETxUID FUNCTIONS
+ */
+
+#ifdef HAVE_GETRESUID
+static int uwrap_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+
+       UWRAP_LOCK(uwrap_id);
+
+       *ruid = id->ruid;
+       *euid = id->euid;
+       *suid = id->suid;
+
+       UWRAP_UNLOCK(uwrap_id);
+
+       return 0;
+}
+#endif
+
 #ifdef HAVE_GETRESGID
 static int uwrap_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid)
 {
@@ -1037,27 +1480,105 @@ static int uwrap_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid)
 }
 #endif
 
-static int uwrap_setresuid(uid_t ruid, uid_t euid, uid_t suid)
+/*
+ * UWRAP_SETxGID FUNCTIONS
+ */
+
+static int uwrap_setresgid_args(gid_t rgid, gid_t egid, gid_t sgid)
 {
-       struct uwrap_thread *id;
+       struct uwrap_thread *id = uwrap_tls_id;
 
-       if (ruid == (uid_t)-1 && euid == (uid_t)-1 && suid == (uid_t)-1) {
-               errno = EINVAL;
-               return -1;
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d, sgid %d -> %d",
+                 id->rgid, rgid, id->egid, egid, id->sgid, sgid);
+
+       if (id->euid != 0) {
+               if (rgid != (gid_t)-1 &&
+                   rgid != id->rgid &&
+                   rgid != id->egid &&
+                   rgid != id->sgid) {
+                       errno = EPERM;
+                       return -1;
+               }
+               if (egid != (gid_t)-1 &&
+                   egid != id->rgid &&
+                   egid != id->egid &&
+                   egid != id->sgid) {
+                       errno = EPERM;
+                       return -1;
+               }
+               if (sgid != (gid_t)-1 &&
+                   sgid != id->rgid &&
+                   sgid != id->egid &&
+                   sgid != id->sgid) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int uwrap_setresgid_thread(gid_t rgid, gid_t egid, gid_t sgid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d, sgid %d -> %d",
+                 id->rgid, rgid, id->egid, egid, id->sgid, sgid);
+
+       rc = uwrap_setresgid_args(rgid, egid, sgid);
+       if (rc != 0) {
+               return rc;
        }
 
        UWRAP_LOCK(uwrap_id);
+
+       if (rgid != (gid_t)-1) {
+               id->rgid = rgid;
+       }
+
+       if (egid != (gid_t)-1) {
+               id->egid = egid;
+       }
+
+       if (sgid != (gid_t)-1) {
+               id->sgid = sgid;
+       }
+
+       UWRAP_UNLOCK(uwrap_id);
+
+       return 0;
+}
+
+static int uwrap_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d, sgid %d -> %d",
+                 id->rgid, rgid, id->egid, egid, id->sgid, sgid);
+
+       rc = uwrap_setresgid_args(rgid, egid, sgid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       UWRAP_LOCK(uwrap_id);
+
        for (id = uwrap.ids; id; id = id->next) {
-               if (ruid != (uid_t)-1) {
-                       id->ruid = ruid;
+               if (rgid != (gid_t)-1) {
+                       id->rgid = rgid;
                }
 
-               if (euid != (uid_t)-1) {
-                       id->euid = euid;
+               if (egid != (gid_t)-1) {
+                       id->egid = egid;
                }
 
-               if (suid != (uid_t)-1) {
-                       id->suid = suid;
+               if (sgid != (gid_t)-1) {
+                       id->sgid = sgid;
                }
        }
 
@@ -1066,6 +1587,144 @@ static int uwrap_setresuid(uid_t ruid, uid_t euid, uid_t suid)
        return 0;
 }
 
+static int uwrap_setregid_args(gid_t rgid, gid_t egid,
+                              gid_t *_new_rgid,
+                              gid_t *_new_egid,
+                              gid_t *_new_sgid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       gid_t new_rgid = -1, new_egid = -1, new_sgid = -1;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d",
+                 id->rgid, rgid, id->egid, egid);
+
+       if (rgid != (gid_t)-1) {
+               new_rgid = rgid;
+               if (rgid != id->rgid &&
+                   rgid != id->egid &&
+                   id->euid != 0) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
+
+       if (egid != (gid_t)-1) {
+               new_egid = egid;
+               if (egid != id->rgid &&
+                   egid != id->egid &&
+                   egid != id->sgid &&
+                   id->euid != 0) {
+                       errno = EPERM;
+                       return -1;
+               }
+       }
+
+       if (rgid != (gid_t) -1 ||
+           (egid != (gid_t)-1 && id->rgid != egid)) {
+               new_sgid = new_egid;
+       }
+
+       *_new_rgid = new_rgid;
+       *_new_egid = new_egid;
+       *_new_sgid = new_sgid;
+
+       return 0;
+}
+
+static int uwrap_setregid_thread(gid_t rgid, gid_t egid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       gid_t new_rgid = -1, new_egid = -1, new_sgid = -1;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d",
+                 id->rgid, rgid, id->egid, egid);
+
+       rc = uwrap_setregid_args(rgid, egid, &new_rgid, &new_egid, &new_sgid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresgid_thread(new_rgid, new_egid, new_sgid);
+}
+
+#ifdef HAVE_SETREGID
+static int uwrap_setregid(gid_t rgid, gid_t egid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+       gid_t new_rgid = -1, new_egid = -1, new_sgid = -1;
+       int rc;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "rgid %d -> %d, egid %d -> %d",
+                 id->rgid, rgid, id->egid, egid);
+
+       rc = uwrap_setregid_args(rgid, egid, &new_rgid, &new_egid, &new_sgid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresgid(new_rgid, new_egid, new_sgid);
+}
+#endif
+
+static int uwrap_setgid_args(gid_t gid,
+                            gid_t *new_rgid,
+                            gid_t *new_egid,
+                            gid_t *new_sgid)
+{
+       struct uwrap_thread *id = uwrap_tls_id;
+
+       UWRAP_LOG(UWRAP_LOG_TRACE,
+                 "gid %d -> %d",
+                 id->rgid, gid);
+
+       if (gid == (gid_t)-1) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (id->euid == 0) {
+               *new_sgid = *new_rgid = gid;
+       } else if (gid != id->rgid &&
+                  gid != id->sgid) {
+               errno = EPERM;
+               return -1;
+       }
+
+       *new_egid = gid;
+
+       return 0;
+}
+
+static int uwrap_setgid_thread(gid_t gid)
+{
+       gid_t new_rgid = -1, new_egid = -1, new_sgid = -1;
+       int rc;
+
+       rc = uwrap_setgid_args(gid, &new_rgid, &new_egid, &new_sgid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresgid_thread(new_rgid, new_egid, new_sgid);
+}
+
+static int uwrap_setgid(gid_t gid)
+{
+       gid_t new_rgid = -1, new_egid = -1, new_sgid = -1;
+       int rc;
+
+       rc = uwrap_setgid_args(gid, &new_rgid, &new_egid, &new_sgid);
+       if (rc != 0) {
+               return rc;
+       }
+
+       return uwrap_setresgid(new_rgid, new_egid, new_sgid);
+}
+
 /*
  * SETUID
  */
@@ -1076,21 +1735,22 @@ int setuid(uid_t uid)
        }
 
        uwrap_init();
-       return uwrap_setresuid(uid, -1, -1);
+       return uwrap_setuid(uid);
 }
 
 #ifdef HAVE_SETEUID
 int seteuid(uid_t euid)
 {
+       if (!uid_wrapper_enabled()) {
+               return libc_seteuid(euid);
+       }
+
+       /* On FreeBSD the uid_t -1 is set and doesn't produce and error */
        if (euid == (uid_t)-1) {
                errno = EINVAL;
                return -1;
        }
 
-       if (!uid_wrapper_enabled()) {
-               return libc_seteuid(euid);
-       }
-
        uwrap_init();
        return uwrap_setresuid(-1, euid, -1);
 }
@@ -1099,17 +1759,12 @@ int seteuid(uid_t euid)
 #ifdef HAVE_SETREUID
 int setreuid(uid_t ruid, uid_t euid)
 {
-       if (ruid == (uid_t)-1 && euid == (uid_t)-1) {
-               errno = EINVAL;
-               return -1;
-       }
-
        if (!uid_wrapper_enabled()) {
                return libc_setreuid(ruid, euid);
        }
 
        uwrap_init();
-       return uwrap_setresuid(ruid, euid, -1);
+       return uwrap_setreuid(ruid, euid);
 }
 #endif
 
@@ -1193,61 +1848,6 @@ uid_t geteuid(void)
        return uwrap_geteuid();
 }
 
-static int uwrap_setresgid_thread(gid_t rgid, gid_t egid, gid_t sgid)
-{
-       struct uwrap_thread *id = uwrap_tls_id;
-
-       if (rgid == (gid_t)-1 && egid == (gid_t)-1 && sgid == (gid_t)-1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       UWRAP_LOCK(uwrap_id);
-       if (rgid != (gid_t)-1) {
-               id->rgid = rgid;
-       }
-
-       if (egid != (gid_t)-1) {
-               id->egid = egid;
-       }
-
-       if (sgid != (gid_t)-1) {
-               id->sgid = sgid;
-       }
-
-       UWRAP_UNLOCK(uwrap_id);
-
-       return 0;
-}
-
-static int uwrap_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
-{
-       struct uwrap_thread *id;
-
-       if (rgid == (gid_t)-1 && egid == (gid_t)-1 && sgid == (gid_t)-1) {
-               errno = EINVAL;
-               return -1;
-       }
-
-       UWRAP_LOCK(uwrap_id);
-       for (id = uwrap.ids; id; id = id->next) {
-               if (rgid != (gid_t)-1) {
-                       id->rgid = rgid;
-               }
-
-               if (egid != (gid_t)-1) {
-                       id->egid = egid;
-               }
-
-               if (sgid != (gid_t)-1) {
-                       id->sgid = sgid;
-               }
-       }
-       UWRAP_UNLOCK(uwrap_id);
-
-       return 0;
-}
-
 /*
  * SETGID
  */
@@ -1258,7 +1858,7 @@ int setgid(gid_t gid)
        }
 
        uwrap_init();
-       return uwrap_setresgid(gid, -1, -1);
+       return uwrap_setgid(gid);
 }
 
 #ifdef HAVE_SETEGID
@@ -1268,6 +1868,12 @@ int setegid(gid_t egid)
                return libc_setegid(egid);
        }
 
+       /* On FreeBSD the uid_t -1 is set and doesn't produce and error */
+       if (egid == (gid_t)-1) {
+               errno = EINVAL;
+               return -1;
+       }
+
        uwrap_init();
        return uwrap_setresgid(-1, egid, -1);
 }
@@ -1281,7 +1887,7 @@ int setregid(gid_t rgid, gid_t egid)
        }
 
        uwrap_init();
-       return uwrap_setresgid(rgid, egid, -1);
+       return uwrap_setregid(rgid, egid);
 }
 #endif
 
@@ -1483,7 +2089,11 @@ static long int uwrap_syscall (long int sysno, va_list vp)
 
        switch (sysno) {
                /* gid */
+#ifdef __alpha__
+               case SYS_getxgid:
+#else
                case SYS_getgid:
+#endif
 #ifdef HAVE_LINUX_32BIT_SYSCALLS
                case SYS_getgid32:
 #endif
@@ -1508,7 +2118,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                        {
                                gid_t gid = (gid_t) va_arg(vp, gid_t);
 
-                               rc = uwrap_setresgid_thread(gid, -1, -1);
+                               rc = uwrap_setgid_thread(gid);
                        }
                        break;
                case SYS_setregid:
@@ -1519,7 +2129,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                                gid_t rgid = (gid_t) va_arg(vp, gid_t);
                                gid_t egid = (gid_t) va_arg(vp, gid_t);
 
-                               rc = uwrap_setresgid_thread(rgid, egid, -1);
+                               rc = uwrap_setregid_thread(rgid, egid);
                        }
                        break;
 #ifdef SYS_setresgid
@@ -1536,7 +2146,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                        }
                        break;
 #endif /* SYS_setresgid */
-#ifdef SYS_getresgid
+#if defined(SYS_getresgid) && defined(HAVE_GETRESGID)
                case SYS_getresgid:
 #ifdef HAVE_LINUX_32BIT_SYSCALLS
                case SYS_getresgid32:
@@ -1549,10 +2159,14 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                                rc = uwrap_getresgid(rgid, egid, sgid);
                        }
                        break;
-#endif /* SYS_getresgid */
+#endif /* SYS_getresgid && HAVE_GETRESGID */
 
                /* uid */
+#ifdef __alpha__
+               case SYS_getxuid:
+#else
                case SYS_getuid:
+#endif
 #ifdef HAVE_LINUX_32BIT_SYSCALLS
                case SYS_getuid32:
 #endif
@@ -1577,7 +2191,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                        {
                                uid_t uid = (uid_t) va_arg(vp, uid_t);
 
-                               rc = uwrap_setresuid_thread(uid, -1, -1);
+                               rc = uwrap_setuid_thread(uid);
                        }
                        break;
                case SYS_setreuid:
@@ -1588,7 +2202,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                                uid_t ruid = (uid_t) va_arg(vp, uid_t);
                                uid_t euid = (uid_t) va_arg(vp, uid_t);
 
-                               rc = uwrap_setresuid_thread(ruid, euid, -1);
+                               rc = uwrap_setreuid_thread(ruid, euid);
                        }
                        break;
 #ifdef SYS_setresuid
@@ -1605,7 +2219,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                        }
                        break;
 #endif /* SYS_setresuid */
-#ifdef SYS_getresuid
+#if defined(SYS_getresuid) && defined(HAVE_GETRESUID)
                case SYS_getresuid:
 #ifdef HAVE_LINUX_32BIT_SYSCALLS
                case SYS_getresuid32:
@@ -1618,7 +2232,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                                rc = uwrap_getresuid(ruid, euid, suid);
                        }
                        break;
-#endif /* SYS_getresuid */
+#endif /* SYS_getresuid && HAVE_GETRESUID*/
                /* groups */
                case SYS_setgroups:
 #ifdef HAVE_LINUX_32BIT_SYSCALLS
@@ -1633,7 +2247,7 @@ static long int uwrap_syscall (long int sysno, va_list vp)
                        break;
                default:
                        UWRAP_LOG(UWRAP_LOG_DEBUG,
-                                 "UID_WRAPPER calling non-wrapped syscall %lu\n",
+                                 "UID_WRAPPER calling non-wrapped syscall %lu",
                                  sysno);
 
                        rc = libc_vsyscall(sysno, vp);