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,11 +139,25 @@ 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; + m.VFS_PM_EID = rmp->mp_effgid; + 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; @@ -135,6 +166,7 @@ int do_set() 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 */ +}