From: David van Moolenbroek Date: Sat, 12 Mar 2016 10:11:36 +0000 (+0000) Subject: PM: add support for saved user/group IDs X-Git-Url: http://zhaoyanbai.com/repos/%22http:/www.isc.org/icons/doc/howto.html?a=commitdiff_plain;h=refs%2Fchanges%2F09%2F3309%2F2;p=minix.git PM: add support for saved user/group IDs This patch aims to synchronize the basic process user and group ID management, as well as the set[ug]id(2) and sete[ug]id(2) behavior, with NetBSD. As it turns out, the main issue was missing support for saved user and group IDs. This support is now added. Since NetBSD's userland, which we are importing, may rely on NetBSD specifics when it comes to security, we choose not to deviate from NetBSD's behavior in any way here. A new test, test89, verifies the correct behavior - it has been confirmed to pass on NetBSD as is. Change-Id: I023935546d97ed01ffd8090f7793d336cceb0f4a --- diff --git a/distrib/sets/lists/minix-tests/mi b/distrib/sets/lists/minix-tests/mi index 2c1ccb440..8e20fe782 100644 --- a/distrib/sets/lists/minix-tests/mi +++ b/distrib/sets/lists/minix-tests/mi @@ -188,6 +188,7 @@ ./usr/tests/minix-posix/test86 minix-tests ./usr/tests/minix-posix/test87 minix-tests ./usr/tests/minix-posix/test88 minix-tests +./usr/tests/minix-posix/test89 minix-tests ./usr/tests/minix-posix/test9 minix-tests ./usr/tests/minix-posix/testinterp minix-tests ./usr/tests/minix-posix/testisofs minix-tests diff --git a/minix/servers/mib/proc.c b/minix/servers/mib/proc.c index f103b483a..d2c63782b 100644 --- a/minix/servers/mib/proc.c +++ b/minix/servers/mib/proc.c @@ -750,6 +750,8 @@ fill_proc2_user(struct kinfo_proc2 * p, int mslot) p->p_uctime_usec = tv.tv_usec; p->p_realflag = p->p_flag; p->p_nlwps = (zombie) ? 0 : 1; + p->p_svuid = mp->mp_svuid; + p->p_svgid = mp->mp_svgid; p->p_stat = get_lwp_stat(mslot, &p->p_wchan, p->p_wmesg, sizeof(p->p_wmesg), &p->p_flag); diff --git a/minix/servers/pm/exec.c b/minix/servers/pm/exec.c index 123534e0d..1323ce46a 100644 --- a/minix/servers/pm/exec.c +++ b/minix/servers/pm/exec.c @@ -92,6 +92,10 @@ int do_newexec(void) rmp->mp_effgid = args.new_gid; } + /* Always update the saved user and group ID at this point. */ + rmp->mp_svuid = rmp->mp_effuid; + rmp->mp_svgid = rmp->mp_effgid; + /* A process is considered 'tainted' when it's executing with * setuid or setgid bit set, or when the real{u,g}id doesn't * match the eff{u,g}id, respectively. */ diff --git a/minix/servers/pm/forkexit.c b/minix/servers/pm/forkexit.c index 4752d4984..f4524e225 100644 --- a/minix/servers/pm/forkexit.c +++ b/minix/servers/pm/forkexit.c @@ -203,8 +203,10 @@ int do_srv_fork() rmc->mp_endpoint = child_ep; /* passed back by VM */ rmc->mp_realuid = m_in.m_lsys_pm_srv_fork.uid; rmc->mp_effuid = m_in.m_lsys_pm_srv_fork.uid; + rmc->mp_svuid = m_in.m_lsys_pm_srv_fork.uid; rmc->mp_realgid = m_in.m_lsys_pm_srv_fork.gid; rmc->mp_effgid = m_in.m_lsys_pm_srv_fork.gid; + rmc->mp_svgid = m_in.m_lsys_pm_srv_fork.gid; for (i = 0; i < NR_ITIMERS; i++) rmc->mp_interval[i] = 0; /* reset timer intervals */ rmc->mp_started = getticks(); /* remember start time, for ps(1) */ diff --git a/minix/servers/pm/getset.c b/minix/servers/pm/getset.c index 2ed157c24..7f2a9b580 100644 --- a/minix/servers/pm/getset.c +++ b/minix/servers/pm/getset.c @@ -107,11 +107,28 @@ int do_set() switch(call_nr) { case PM_SETUID: - case PM_SETEUID: uid = m_in.m_lc_pm_setuid.uid; + /* NetBSD specific semantics: setuid(geteuid()) may fail. */ if (rmp->mp_realuid != uid && rmp->mp_effuid != SUPER_USER) return(EPERM); - if(call_nr == PM_SETUID) rmp->mp_realuid = uid; + /* BSD semantics: always update all three fields. */ + rmp->mp_realuid = uid; + rmp->mp_effuid = uid; + rmp->mp_svuid = uid; + + m.m_type = VFS_PM_SETUID; + m.VFS_PM_ENDPT = rmp->mp_endpoint; + m.VFS_PM_EID = rmp->mp_effuid; + m.VFS_PM_RID = rmp->mp_realuid; + + break; + + case PM_SETEUID: + uid = m_in.m_lc_pm_setuid.uid; + /* BSD semantics: seteuid(geteuid()) may fail. */ + if (rmp->mp_realuid != uid && rmp->mp_svuid != uid && + rmp->mp_effuid != SUPER_USER) + return(EPERM); rmp->mp_effuid = uid; m.m_type = VFS_PM_SETUID; @@ -122,12 +139,12 @@ int do_set() break; case PM_SETGID: - case PM_SETEGID: gid = m_in.m_lc_pm_setgid.gid; if (rmp->mp_realgid != gid && rmp->mp_effuid != SUPER_USER) return(EPERM); - if(call_nr == PM_SETGID) rmp->mp_realgid = gid; + rmp->mp_realgid = gid; rmp->mp_effgid = gid; + rmp->mp_svgid = gid; m.m_type = VFS_PM_SETGID; m.VFS_PM_ENDPT = rmp->mp_endpoint; @@ -135,6 +152,21 @@ int do_set() m.VFS_PM_RID = rmp->mp_realgid; break; + + case PM_SETEGID: + gid = m_in.m_lc_pm_setgid.gid; + if (rmp->mp_realgid != gid && rmp->mp_svgid != gid && + rmp->mp_effuid != SUPER_USER) + return(EPERM); + rmp->mp_effgid = gid; + + m.m_type = VFS_PM_SETGID; + m.VFS_PM_ENDPT = rmp->mp_endpoint; + m.VFS_PM_EID = rmp->mp_effgid; + m.VFS_PM_RID = rmp->mp_realgid; + + break; + case PM_SETGROUPS: if (rmp->mp_effuid != SUPER_USER) return(EPERM); diff --git a/minix/servers/pm/mproc.h b/minix/servers/pm/mproc.h index d279aa2b7..ec76bcbda 100644 --- a/minix/servers/pm/mproc.h +++ b/minix/servers/pm/mproc.h @@ -37,11 +37,13 @@ EXTERN struct mproc { clock_t mp_child_utime; /* cumulative user time of children */ clock_t mp_child_stime; /* cumulative sys time of children */ - /* Real and effective uids and gids. */ + /* Real, effective, and saved user and group IDs. */ uid_t mp_realuid; /* process' real uid */ uid_t mp_effuid; /* process' effective uid */ + uid_t mp_svuid; /* process' saved uid */ gid_t mp_realgid; /* process' real gid */ gid_t mp_effgid; /* process' effective gid */ + gid_t mp_svgid; /* process' saved gid */ /* Supplemental groups. */ int mp_ngroups; /* number of supplemental groups */ diff --git a/minix/tests/Makefile b/minix/tests/Makefile index fbe00c29c..c4de4425d 100644 --- a/minix/tests/Makefile +++ b/minix/tests/Makefile @@ -59,7 +59,7 @@ MINIX_TESTS= \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 48 49 50 52 53 54 55 56 58 59 60 \ 61 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ -81 82 83 84 85 86 87 88 +81 82 83 84 85 86 87 88 89 FILES += t84_h_nonexec.sh diff --git a/minix/tests/run b/minix/tests/run index 499df1923..f70172123 100755 --- a/minix/tests/run +++ b/minix/tests/run @@ -22,7 +22,7 @@ export USENETWORK # set to "yes" for test48+82 to use the network # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69 test73 test74 test78 test83 test85 test87 test88" + test69 test73 test74 test78 test83 test85 test87 test88 test89" # Scripts that require to be run as root rootscripts="testisofs testvnd testrelpol" @@ -30,7 +30,7 @@ alltests="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \ 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ - 81 82 83 84 85 86 87 88 sh1 sh2 interp mfs isofs vnd" + 81 82 83 84 85 86 87 88 89 sh1 sh2 interp mfs isofs vnd" tests_no=`expr 0` # If root, make sure the setuid tests have the correct permissions diff --git a/minix/tests/test87.c b/minix/tests/test87.c index 3c68fa40c..7a0b774a1 100644 --- a/minix/tests/test87.c +++ b/minix/tests/test87.c @@ -43,9 +43,7 @@ test_nonroot(void (* proc)(void)) if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0); - /* FIXME: this may rely on a MINIXism. */ if (setuid(pw->pw_uid) != 0) e(0); - if (seteuid(pw->pw_uid) != 0) e(0); proc(); diff --git a/minix/tests/test88.c b/minix/tests/test88.c index 921827e07..1d803a646 100644 --- a/minix/tests/test88.c +++ b/minix/tests/test88.c @@ -105,9 +105,7 @@ spawn(struct link * link, void (* proc)(), int drop) case DROP_USER: if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0); - /* FIXME: this may rely on a MINIXism. */ if (setuid(pw->pw_uid) != 0) e(0); - if (seteuid(pw->pw_uid) != 0) e(0); } proc(link); diff --git a/minix/tests/test89.c b/minix/tests/test89.c new file mode 100644 index 000000000..00fef6a85 --- /dev/null +++ b/minix/tests/test89.c @@ -0,0 +1,1011 @@ +/* Tests for set[ug]id, sete[ug]id, and saved IDs - by D.C. van Moolenbroek */ +/* This test must be run as root, as it tests privileged operations. */ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define ITERATIONS 2 + +/* These are in a specific order. */ +enum { + SUB_REAL, /* test set[ug]id(2) */ + SUB_EFF, /* test sete[ug]id(2) */ + SUB_REAL_E0, /* test setgid(2) with euid=0 */ + SUB_EFF_E0, /* test setegid(2) with euid=0 */ + SUB_RETAIN, /* test r/e/s preservation across fork(2), exec(2) */ +}; + +static const char *executable; + +/* + * The table below is exhaustive in terms of different combinations of real, + * effective, and saved user IDs (with 0 being a special value, but 1 and 2 + * being interchangeable), but not all these combinations can actually be + * established in practice. The results for which there is no way to create + * the initial condition are set to -1. If we ever implement setresuid(2), + * these results can be filled in and tested as well. + */ +static const struct uid_set { + uid_t ruid; + uid_t euid; + uid_t suid; + uid_t uid; + int res; + int eres; +} uid_sets[] = { + { 0, 0, 0, 0, 1, 1 }, + { 0, 0, 0, 1, 1, 1 }, + { 0, 0, 1, 0, 1, 1 }, + { 0, 0, 1, 1, 1, 1 }, + { 0, 0, 1, 2, 1, 1 }, + { 0, 1, 0, 0, 1, 1 }, + { 0, 1, 0, 1, 0, 0 }, + { 0, 1, 0, 2, 0, 0 }, + { 0, 1, 1, 0, 1, 1 }, + { 0, 1, 1, 1, 0, 1 }, + { 0, 1, 1, 2, 0, 0 }, + { 0, 1, 2, 0, -1, -1 }, + { 0, 1, 2, 1, -1, -1 }, + { 0, 1, 2, 2, -1, -1 }, + { 1, 0, 0, 0, 1, 1 }, + { 1, 0, 0, 1, 1, 1 }, + { 1, 0, 0, 2, 1, 1 }, + { 1, 0, 1, 0, -1, -1 }, + { 1, 0, 1, 1, -1, -1 }, + { 1, 0, 1, 2, -1, -1 }, + { 1, 0, 2, 0, -1, -1 }, + { 1, 0, 2, 1, -1, -1 }, + { 1, 0, 2, 2, -1, -1 }, + { 1, 1, 0, 0, 0, 1 }, + { 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 2, 0, 0 }, + { 1, 1, 1, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 2, 0, 0 }, + { 1, 1, 2, 0, 0, 0 }, + { 1, 1, 2, 1, 1, 1 }, + { 1, 1, 2, 2, 0, 1 }, + { 1, 2, 0, 0, 0, 1 }, + { 1, 2, 0, 1, 1, 1 }, + { 1, 2, 0, 2, 0, 0 }, + { 1, 2, 1, 0, -1, -1 }, + { 1, 2, 1, 1, -1, -1 }, + { 1, 2, 1, 2, -1, -1 }, + { 1, 2, 2, 0, 0, 0 }, + { 1, 2, 2, 1, 1, 1 }, + { 1, 2, 2, 2, 0, 1 }, +}; + +/* + * The same type of table but now for group identifiers. In this case, all + * combinations are possible to establish in practice, because the effective + * UID, not the GID, is used for the privilege check. GID 0 does not have any + * special meaning, but we still test it as though it does, in order to ensure + * that it in fact does not. + */ +static const struct gid_set { + gid_t rgid; + gid_t egid; + gid_t sgid; + gid_t gid; + int res; + int eres; +} gid_sets[] = { + { 0, 0, 0, 0, 1, 1 }, + { 0, 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 1, 1 }, + { 0, 0, 1, 1, 0, 1 }, + { 0, 0, 1, 2, 0, 0 }, + { 0, 1, 0, 0, 1, 1 }, + { 0, 1, 0, 1, 0, 0 }, + { 0, 1, 0, 2, 0, 0 }, + { 0, 1, 1, 0, 1, 1 }, + { 0, 1, 1, 1, 0, 1 }, + { 0, 1, 1, 2, 0, 0 }, + { 0, 1, 2, 0, 1, 1 }, + { 0, 1, 2, 1, 0, 0 }, + { 0, 1, 2, 2, 0, 1 }, + { 1, 0, 0, 0, 0, 1 }, + { 1, 0, 0, 1, 1, 1 }, + { 1, 0, 0, 2, 0, 0 }, + { 1, 0, 1, 0, 0, 0 }, + { 1, 0, 1, 1, 1, 1 }, + { 1, 0, 1, 2, 0, 0 }, + { 1, 0, 2, 0, 0, 0 }, + { 1, 0, 2, 1, 1, 1 }, + { 1, 0, 2, 2, 0, 1 }, + { 1, 1, 0, 0, 0, 1 }, + { 1, 1, 0, 1, 1, 1 }, + { 1, 1, 0, 2, 0, 0 }, + { 1, 1, 1, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 2, 0, 0 }, + { 1, 1, 2, 0, 0, 0 }, + { 1, 1, 2, 1, 1, 1 }, + { 1, 1, 2, 2, 0, 1 }, + { 1, 2, 0, 0, 0, 1 }, + { 1, 2, 0, 1, 1, 1 }, + { 1, 2, 0, 2, 0, 0 }, + { 1, 2, 1, 0, 0, 0 }, + { 1, 2, 1, 1, 1, 1 }, + { 1, 2, 1, 2, 0, 0 }, + { 1, 2, 2, 0, 0, 0 }, + { 1, 2, 2, 1, 1, 1 }, + { 1, 2, 2, 2, 0, 1 }, +}; + +/* + * Obtain the kinfo_proc2 data for the given process ID. Return 0 on success, + * or -1 with errno set appropriately on failure. + */ +static int +get_proc2(pid_t pid, struct kinfo_proc2 * proc2) +{ + int mib[6]; + size_t oldlen; + + /* + * FIXME: for performance reasons, the MIB service updates it process + * tables only every clock tick. As a result, we may not be able to + * obtain accurate process details right away, and we need to wait. + * Eventually, the MIB service should retrieve more targeted subsets of + * the process tables, and this problem should go away at least for + * specific queries such as this one, which queries only a single PID. + */ + usleep((2000000 + sysconf(_SC_CLK_TCK)) / sysconf(_SC_CLK_TCK)); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC2; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + mib[4] = sizeof(*proc2); + mib[5] = 1; + + oldlen = sizeof(*proc2); + if (sysctl(mib, __arraycount(mib), proc2, &oldlen, NULL, 0) == -1) + return -1; + if (oldlen != sizeof(*proc2)) { + errno = ESRCH; + return -1; + } + return 0; +} + +/* + * Verify that the current process's real, effective, and saved user IDs are + * set to the given respective value. + */ +static void +test_uids(uid_t ruid, uid_t euid, uid_t suid) +{ + struct kinfo_proc2 proc2; + + if (getuid() != ruid) e(0); + if (geteuid() != euid) e(0); + + /* + * There is no system call specifically to retrieve the saved user ID, + * so we use sysctl(2) to obtain process information. This allows us + * to verify the real and effective user IDs once more, too. + */ + if (get_proc2(getpid(), &proc2) != 0) e(0); + + if (proc2.p_ruid != ruid) e(0); + if (proc2.p_uid != euid) e(0); + if (proc2.p_svuid != suid) e(0); +} + +/* + * Verify that the real and effective user IDs are kept as is after an exec(2) + * call on a non-setuid binary, and that the saved user ID is set to the + * effective user ID. + */ +static void +exec89b(const char * param1, const char * param2 __unused) +{ + const struct uid_set *set; + int setnum; + + setnum = atoi(param1); + if (setnum < 0 || setnum >= __arraycount(uid_sets)) { + e(setnum); + return; + } + set = &uid_sets[setnum]; + + test_uids(set->ruid, set->euid, set->euid); +} + +/* + * The real, effective, and saved user IDs have been set up as indicated by the + * current set. Verify that fork(2) and exec(2) do not change the real and + * effective UIDs, and that only exec(2) sets the saved UID to the effective + * UID. + */ +static void +sub89b(int setnum) +{ + const struct uid_set *set; + char param1[32]; + pid_t pid; + int status; + + set = &uid_sets[setnum]; + + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + break; + + case 0: + /* + * Verify that all the UIDs were retained across the fork(2) + * call. + */ + test_uids(set->ruid, set->euid, set->suid); + + snprintf(param1, sizeof(param1), "%d", setnum); + + (void)execl(executable, executable, "DO CHECK", "b", param1, + "", NULL); + + e(setnum); + break; + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } +} + +/* + * The real, effective, and saved user IDs have been set up as indicated by the + * current set. Test one particular case for test A or B, and verify the + * result. + */ +static void +test_one_uid(int setnum, int sub) +{ + const struct uid_set *set; + int res, exp; + + set = &uid_sets[setnum]; + + /* Verify that the pre-call process state is as expected. */ + test_uids(set->ruid, set->euid, set->suid); + + /* Perform the call, and check whether the result is as expected. */ + switch (sub) { + case SUB_REAL: + res = setuid(set->uid); + exp = set->res - 1; + break; + + case SUB_EFF: + res = seteuid(set->uid); + exp = set->eres - 1; + break; + + case SUB_RETAIN: + sub89b(setnum); + + return; + } + + if (res != 0 && (res != -1 || errno != EPERM)) e(setnum); + + if (res != exp) e(setnum); + + /* Verify that the post-call process state is as expected as well. */ + if (res == 0) { + if (sub == SUB_EFF) + test_uids(set->ruid, set->uid, set->suid); + else + test_uids(set->uid, set->uid, set->uid); + } else + test_uids(set->ruid, set->euid, set->suid); +} + +/* + * Test setuid(2) or seteuid(2) after a successful execve(2) call, which should + * have set the process's effective and saved user ID. + */ +static void +exec89a(const char * param1, const char * param2) +{ + const struct uid_set *set; + int setnum, sub; + + setnum = atoi(param1); + if (setnum < 0 || setnum >= __arraycount(uid_sets)) { + e(setnum); + return; + } + set = &uid_sets[setnum]; + + sub = atoi(param2); + + if (sub == SUB_RETAIN) { + /* Clear the set-uid bit before dropping more privileges. */ + if (chmod(executable, S_IXUSR | S_IXGRP | S_IXOTH) != 0) + e(setnum); + } + + /* Finish setting up the initial condition. */ + if (set->euid != set->suid) { + if (set->euid != set->ruid && set->suid != 0) { + test_uids(set->ruid, set->suid, set->suid); + + return; /* skip test */ + } + + if (seteuid(set->euid) != 0) e(setnum); + } + + /* Perform the actual test. */ + test_one_uid(setnum, sub); +} + +/* + * Test setuid(2) or seteuid(2) with a certain value starting from a certain + * initial condition, as identified by the given uid_sets[] array element. As + * a side effect, test that in particular exec(2) properly sets the effective + * and saved user ID. + */ +static void +sub89a(int setnum, int sub) +{ + const struct uid_set *set; + char param1[32], param2[32]; + + set = &uid_sets[setnum]; + + /* + * Figure out how to set the real, effective, and saved UIDs to those + * of the set structure. Without setresuid(2), not all combinations + * are possible to achieve. We silently skip the tests for which we + * cannot create the requested initial condition. + */ + if (set->ruid != set->suid) { + /* + * In order to set the saved UID to something other than the + * real UID, we must exec(2) a set-uid binary. + */ + if (chown(executable, set->suid, 0 /*anything*/) != 0) e(0); + if (chmod(executable, + S_ISUID | S_IXUSR | S_IXGRP | S_IXOTH) != 0) e(0); + + if (setuid(set->ruid) != 0) e(setnum); + + snprintf(param1, sizeof(param1), "%d", setnum); + snprintf(param2, sizeof(param2), "%d", sub); + + (void)execl(executable, executable, "DO CHECK", "a", param1, + param2, NULL); + + e(0); + } else { + /* + * If the real and saved user ID are to be set to the same + * value, we need not use exec(2). Still, we cannot achieve + * all combinations here either. + */ + if (set->ruid != 0 && set->ruid != set->euid) + return; /* skip test */ + + if (sub == SUB_RETAIN) { + /* Clear the set-uid bit before dropping privileges. */ + if (chmod(executable, + S_IXUSR | S_IXGRP | S_IXOTH) != 0) e(setnum); + } + + if (setuid(set->ruid) != 0) e(setnum); + if (seteuid(set->euid) != 0) e(setnum); + + /* Perform the actual test. */ + test_one_uid(setnum, sub); + } +} + +/* + * Test setuid(2) and seteuid(2) calls with various initial conditions, by + * setting the real, effective, and saved UIDs to different values before + * performing the setuid(2) or seteuid(2) call. + */ +static void +test89a(void) +{ + unsigned int setnum; + int sub, status; + pid_t pid; + + subtest = 1; + + for (setnum = 0; setnum < __arraycount(uid_sets); setnum++) { + for (sub = SUB_REAL; sub <= SUB_EFF; sub++) { + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + + break; + + case 0: + errct = 0; + + sub89a((int)setnum, sub); + + exit(errct); + /* NOTREACHED */ + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } + } + } +} + +/* + * Ensure that the real, effective, and saved UIDs are fully preserved across + * fork(2) and non-setuid-binary exec(2) calls. + */ +static void +test89b(void) +{ + unsigned int setnum; + int status; + pid_t pid; + + subtest = 2; + + for (setnum = 0; setnum < __arraycount(uid_sets); setnum++) { + if (uid_sets[setnum].uid != 0) + continue; /* no need to do the same test >1 times */ + + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + + break; + + case 0: + errct = 0; + + /* + * Test B uses some of the A-test code. While rather + * ugly, this avoids duplication of some of test A's + * important UID logic. + */ + sub89a((int)setnum, SUB_RETAIN); + + exit(errct); + /* NOTREACHED */ + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } + } +} + +/* + * Verify that the current process's real, effective, and saved group IDs are + * set to the given respective value. + */ +static void +test_gids(gid_t rgid, gid_t egid, gid_t sgid) +{ + struct kinfo_proc2 proc2; + + if (getgid() != rgid) e(0); + if (getegid() != egid) e(0); + + /* As above. */ + if (get_proc2(getpid(), &proc2) != 0) e(0); + + if (proc2.p_rgid != rgid) e(0); + if (proc2.p_gid != egid) e(0); + if (proc2.p_svgid != sgid) e(0); +} + +/* + * Verify that the real and effective group IDs are kept as is after an exec(2) + * call on a non-setgid binary, and that the saved group ID is set to the + * effective group ID. + */ +static void +exec89d(const char * param1, const char * param2 __unused) +{ + const struct gid_set *set; + int setnum; + + setnum = atoi(param1); + if (setnum < 0 || setnum >= __arraycount(gid_sets)) { + e(setnum); + return; + } + set = &gid_sets[setnum]; + + test_gids(set->rgid, set->egid, set->egid); +} + +/* + * The real, effective, and saved group IDs have been set up as indicated by + * the current set. Verify that fork(2) and exec(2) do not change the real and + * effective GID, and that only exec(2) sets the saved GID to the effective + * GID. + */ +static void +sub89d(int setnum) +{ + const struct gid_set *set; + char param1[32]; + pid_t pid; + int status; + + set = &gid_sets[setnum]; + + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + break; + + case 0: + /* + * Verify that all the GIDs were retained across the fork(2) + * call. + */ + test_gids(set->rgid, set->egid, set->sgid); + + /* Clear the set-gid bit. */ + if (chmod(executable, S_IXUSR | S_IXGRP | S_IXOTH) != 0) + e(setnum); + + /* Alternate between preserving and dropping user IDs. */ + if (set->gid != 0) { + if (setuid(3) != 0) e(setnum); + } + + snprintf(param1, sizeof(param1), "%d", setnum); + + (void)execl(executable, executable, "DO CHECK", "d", param1, + "", NULL); + + e(setnum); + break; + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } +} + +/* + * The real, effective, and saved group IDs have been set up as indicated by + * the current set. Test one particular case for test C or D, and verify the + * result. + */ +static void +test_one_gid(int setnum, int sub) +{ + const struct gid_set *set; + int res, exp; + + set = &gid_sets[setnum]; + + /* Verify that the pre-call process state is as expected. */ + test_gids(set->rgid, set->egid, set->sgid); + + /* Perform the call, and check whether the result is as expected. */ + switch (sub) { + case SUB_REAL: + case SUB_REAL_E0: + if (sub != SUB_REAL_E0 && seteuid(1) != 0) e(0); + + res = setgid(set->gid); + exp = (sub != SUB_REAL_E0) ? (set->res - 1) : 0; + break; + + case SUB_EFF: + case SUB_EFF_E0: + if (sub != SUB_EFF_E0 && seteuid(1) != 0) e(0); + + res = setegid(set->gid); + exp = (sub != SUB_EFF_E0) ? (set->eres - 1) : 0; + break; + + case SUB_RETAIN: + sub89d(setnum); + + return; + } + + if (res != 0 && (res != -1 || errno != EPERM)) e(setnum); + + if (res != exp) e(setnum); + + /* Verify that the post-call process state is as expected as well. */ + if (res == 0) { + if (sub == SUB_EFF || sub == SUB_EFF_E0) + test_gids(set->rgid, set->gid, set->sgid); + else + test_gids(set->gid, set->gid, set->gid); + } else + test_gids(set->rgid, set->egid, set->sgid); +} + +/* + * Test setgid(2) or setegid(2) after a successful execve(2) call, which should + * have set the process's effective and saved group ID. + */ +static void +exec89c(const char * param1, const char * param2) +{ + const struct gid_set *set; + int setnum, sub; + + setnum = atoi(param1); + if (setnum < 0 || setnum >= __arraycount(gid_sets)) { + e(setnum); + return; + } + set = &gid_sets[setnum]; + + sub = atoi(param2); + + /* Finish setting up the initial condition. */ + if (set->egid != set->sgid && setegid(set->egid) != 0) e(setnum); + + /* Perform the actual test. */ + test_one_gid(setnum, sub); +} + +/* + * Test setgid(2) or setegid(2) with a certain value starting from a certain + * initial condition, as identified by the given gid_sets[] array element. As + * a side effect, test that in particular exec(2) properly sets the effective + * and saved group ID. + */ +static void +sub89c(int setnum, int sub) +{ + const struct gid_set *set; + char param1[32], param2[32]; + + set = &gid_sets[setnum]; + + /* + * Figure out how to set the real, effective, and saved GIDs to those + * of the set structure. In this case, all combinations are possible. + */ + if (set->rgid != set->sgid) { + /* + * In order to set the saved GID to something other than the + * real GID, we must exec(2) a set-gid binary. + */ + if (chown(executable, 0 /*anything*/, set->sgid) != 0) e(0); + if (chmod(executable, + S_ISGID | S_IXUSR | S_IXGRP | S_IXOTH) != 0) e(0); + + if (setgid(set->rgid) != 0) e(setnum); + + snprintf(param1, sizeof(param1), "%d", setnum); + snprintf(param2, sizeof(param2), "%d", sub); + + (void)execl(executable, executable, "DO CHECK", "c", param1, + param2, NULL); + + e(0); + } else { + /* + * If the real and saved group ID are to be set to the same + * value, we need not use exec(2). + */ + if (setgid(set->rgid) != 0) e(setnum); + if (setegid(set->egid) != 0) e(setnum); + + /* Perform the actual test. */ + test_one_gid(setnum, sub); + } +} + +/* + * Test setgid(2) and setegid(2) calls with various initial conditions, by + * setting the real, effective, and saved GIDs to different values before + * performing the setgid(2) or setegid(2) call. At the same time, verify that + * if the caller has an effective UID of 0, all set(e)gid calls are allowed. + */ +static void +test89c(void) +{ + unsigned int setnum; + int sub, status; + pid_t pid; + + subtest = 3; + + for (setnum = 0; setnum < __arraycount(gid_sets); setnum++) { + for (sub = SUB_REAL; sub <= SUB_EFF_E0; sub++) { + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + + break; + + case 0: + errct = 0; + + sub89c((int)setnum, sub); + + exit(errct); + /* NOTREACHED */ + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } + } + } +} + +/* + * Ensure that the real, effective, and saved GIDs are fully preserved across + * fork(2) and non-setgid-binary exec(2) calls. + */ +static void +test89d(void) +{ + unsigned int setnum; + int status; + pid_t pid; + + subtest = 4; + + for (setnum = 0; setnum < __arraycount(gid_sets); setnum++) { + if (gid_sets[setnum].gid == 2) + continue; /* no need to do the same test >1 times */ + + pid = fork(); + + switch (pid) { + case -1: + e(setnum); + + break; + + case 0: + errct = 0; + + /* Similarly, test D uses some of the C-test code. */ + sub89c((int)setnum, SUB_RETAIN); + + exit(errct); + /* NOTREACHED */ + + default: + if (waitpid(pid, &status, 0) != pid) e(setnum); + if (!WIFEXITED(status)) e(setnum); + if (WEXITSTATUS(status) != 0) e(setnum); + } + } +} + +/* + * Either perform the second step of setting up user and group IDs, or check + * whether the user and/or group IDs have indeed been changed appropriately as + * the result of the second exec(2). + */ +static void +exec89e(const char * param1, const char * param2) +{ + int mask, step; + mode_t mode; + + mask = atoi(param1); + step = atoi(param2); + + if (step == 0) { + mode = S_IXUSR | S_IXGRP | S_IXOTH; + if (mask & 1) mode |= S_ISUID; + if (mask & 2) mode |= S_ISGID; + + if (chown(executable, 6, 7) != 0) e(0); + if (chmod(executable, mode) != 0) e(0); + + if (setegid(4) != 0) e(0); + if (seteuid(2) != 0) e(0); + + test_uids(1, 2, 0); + test_gids(3, 4, 5); + + (void)execl(executable, executable, "DO CHECK", "e", param1, + "1", NULL); + + e(0); + } else { + if (mask & 1) + test_uids(1, 6, 6); + else + test_uids(1, 2, 2); + + if (mask & 2) + test_gids(3, 7, 7); + else + test_gids(3, 4, 4); + } +} + +/* + * Set up for the set-uid/set-gid execution test by initializing to different + * real and effective user IDs. + */ +static void +sub89e(int mask) +{ + char param1[32]; + + if (chown(executable, 0, 5) != 0) e(0); + if (chmod(executable, + S_ISUID | S_ISGID | S_IXUSR | S_IXGRP | S_IXOTH) != 0) e(0); + + if (setgid(3) != 0) e(0); + if (setuid(1) != 0) e(0); + + snprintf(param1, sizeof(param1), "%d", mask); + (void)execl(executable, executable, "DO CHECK", "e", param1, "0", + NULL); +} + +/* + * Perform basic verification that the set-uid and set-gid bits on binaries are + * fully independent from each other. + */ +static void +test89e(void) +{ + int mask, status; + pid_t pid; + + subtest = 5; + + for (mask = 0; mask <= 3; mask++) { + pid = fork(); + + switch (pid) { + case -1: + e(0); + + break; + + case 0: + errct = 0; + + sub89e(mask); + + exit(errct); + /* NOTREACHED */ + + default: + if (waitpid(pid, &status, 0) != pid) e(mask); + if (!WIFEXITED(status)) e(mask); + if (WEXITSTATUS(status) != 0) e(mask); + } + } +} + +/* + * Call the right function after having executed myself. + */ +static void +exec89(const char * param0, const char * param1, const char * param2) +{ + + switch (param0[0]) { + case 'a': + exec89a(param1, param2); + break; + + case 'b': + exec89b(param1, param2); + break; + + case 'c': + exec89c(param1, param2); + break; + + case 'd': + exec89d(param1, param2); + break; + + case 'e': + exec89e(param1, param2); + break; + + default: + e(0); + } + + exit(errct); +} + +/* + * Initialize the test. + */ +static void +test89_init(void) +{ + char cp_cmd[PATH_MAX + 9]; + int status; + + subtest = 0; + + /* Reset all user and group IDs to known values. */ + if (setuid(0) != 0) e(0); + if (setgid(0) != 0) e(0); + if (setgroups(0, NULL) != 0) e(0); + + test_uids(0, 0, 0); + test_gids(0, 0, 0); + + /* Make a copy of the binary, which as of start() is one level up. */ + snprintf(cp_cmd, sizeof(cp_cmd), "cp ../%s .", executable); + + status = system(cp_cmd); + if (status < 0 || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) e(0); +} + +/* + * Test program for set[ug]id, sete[ug]id, and saved IDs. + */ +int +main(int argc, char ** argv) +{ + int i, m; + + executable = argv[0]; + + /* This test executes itself. Handle that case first. */ + if (argc == 5 && !strcmp(argv[1], "DO CHECK")) + exec89(argv[2], argv[3], argv[4]); + + start(89); + + test89_init(); + + if (argc == 2) + m = atoi(argv[1]); + else + m = 0xFF; + + for (i = 0; i < ITERATIONS; i++) { + if (m & 0x01) test89a(); + if (m & 0x02) test89b(); + if (m & 0x04) test89c(); + if (m & 0x08) test89d(); + if (m & 0x10) test89e(); + } + + quit(); + /* NOTREACHED */ +}