Path: blob/main/tools/regression/security/proc_to_proc/scenario.c
48266 views
/*-1* Copyright (c) 2001 Robert N. M. Watson2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND14* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE15* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE17* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF23* SUCH DAMAGE.24*/2526#include <sys/param.h>27#include <sys/uio.h>28#include <sys/ptrace.h>29#include <sys/time.h>30#include <sys/resource.h>31#include <sys/syscall.h>32#include <sys/wait.h>33#include <sys/ktrace.h>3435#include <assert.h>36#include <errno.h>37#include <signal.h>38#include <stdio.h>39#include <string.h>40#include <unistd.h>4142/*43* Relevant parts of a process credential.44*/45struct cred {46uid_t cr_euid, cr_ruid, cr_svuid;47int cr_issetugid;48};4950/*51* Description of a scenario.52*/53struct scenario {54struct cred *sc_cred1, *sc_cred2; /* credentials of p1 and p2 */55int sc_canptrace_errno; /* desired ptrace failure */56int sc_canktrace_errno; /* desired ktrace failure */57int sc_cansighup_errno; /* desired SIGHUP failure */58int sc_cansigsegv_errno; /* desired SIGSEGV failure */59int sc_cansee_errno; /* desired getprio failure */60int sc_cansched_errno; /* desired setprio failure */61char *sc_name; /* test name */62};6364/*65* Table of relevant credential combinations.66*/67static struct cred creds[] = {68/* euid ruid svuid issetugid */69/* 0 */ { 0, 0, 0, 0 }, /* privileged */70/* 1 */ { 0, 0, 0, 1 }, /* privileged + issetugid */71/* 2 */ { 1000, 1000, 1000, 0 }, /* unprivileged1 */72/* 3 */ { 1000, 1000, 1000, 1 }, /* unprivileged1 + issetugid */73/* 4 */ { 1001, 1001, 1001, 0 }, /* unprivileged2 */74/* 5 */ { 1001, 1001, 1001, 1 }, /* unprivileged2 + issetugid */75/* 6 */ { 1000, 0, 0, 0 }, /* daemon1 */76/* 7 */ { 1000, 0, 0, 1 }, /* daemon1 + issetugid */77/* 8 */ { 1001, 0, 0, 0 }, /* daemon2 */78/* 9 */ { 1001, 0, 0, 1 }, /* daemon2 + issetugid */79/* 10 */{ 0, 1000, 1000, 0 }, /* setuid1 */80/* 11 */{ 0, 1000, 1000, 1 }, /* setuid1 + issetugid */81/* 12 */{ 0, 1001, 1001, 0 }, /* setuid2 */82/* 13 */{ 0, 1001, 1001, 1 }, /* setuid2 + issetugid */83};8485/*86* Table of scenarios.87*/88static const struct scenario scenarios[] = {89/* cred1 cred2 ptrace ktrace, sighup sigsegv see sched name */90/* privileged on privileged */91{ &creds[0], &creds[0], 0, 0, 0, 0, 0, 0, "0. priv on priv"},92{ &creds[0], &creds[1], 0, 0, 0, 0, 0, 0, "1. priv on priv"},93{ &creds[1], &creds[0], 0, 0, 0, 0, 0, 0, "2. priv on priv"},94{ &creds[1], &creds[1], 0, 0, 0, 0, 0, 0, "3. priv on priv"},95/* privileged on unprivileged */96{ &creds[0], &creds[2], 0, 0, 0, 0, 0, 0, "4. priv on unpriv1"},97{ &creds[0], &creds[3], 0, 0, 0, 0, 0, 0, "5. priv on unpriv1"},98{ &creds[1], &creds[2], 0, 0, 0, 0, 0, 0, "6. priv on unpriv1"},99{ &creds[1], &creds[3], 0, 0, 0, 0, 0, 0, "7. priv on unpriv1"},100/* unprivileged on privileged */101{ &creds[2], &creds[0], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "8. unpriv1 on priv"},102{ &creds[2], &creds[1], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "9. unpriv1 on priv"},103{ &creds[3], &creds[0], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "10. unpriv1 on priv"},104{ &creds[3], &creds[1], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "11. unpriv1 on priv"},105/* unprivileged on same unprivileged */106{ &creds[2], &creds[2], 0, 0, 0, 0, 0, 0, "12. unpriv1 on unpriv1"},107{ &creds[2], &creds[3], EPERM, EPERM, 0, EPERM, 0, 0, "13. unpriv1 on unpriv1"},108{ &creds[3], &creds[2], 0, 0, 0, 0, 0, 0, "14. unpriv1 on unpriv1"},109{ &creds[3], &creds[3], EPERM, EPERM, 0, EPERM, 0, 0, "15. unpriv1 on unpriv1"},110/* unprivileged on different unprivileged */111{ &creds[2], &creds[4], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "16. unpriv1 on unpriv2"},112{ &creds[2], &creds[5], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "17. unpriv1 on unpriv2"},113{ &creds[3], &creds[4], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "18. unpriv1 on unpriv2"},114{ &creds[3], &creds[5], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "19. unpriv1 on unpriv2"},115/* unprivileged on daemon, same */116{ &creds[2], &creds[6], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "20. unpriv1 on daemon1"},117{ &creds[2], &creds[7], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "21. unpriv1 on daemon1"},118{ &creds[3], &creds[6], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "22. unpriv1 on daemon1"},119{ &creds[3], &creds[7], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "23. unpriv1 on daemon1"},120/* unprivileged on daemon, different */121{ &creds[2], &creds[8], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "24. unpriv1 on daemon2"},122{ &creds[2], &creds[9], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "25. unpriv1 on daemon2"},123{ &creds[3], &creds[8], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "26. unpriv1 on daemon2"},124{ &creds[3], &creds[9], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "27. unpriv1 on daemon2"},125/* unprivileged on setuid, same */126{ &creds[2], &creds[10], EPERM, EPERM, 0, 0, 0, 0, "28. unpriv1 on setuid1"},127{ &creds[2], &creds[11], EPERM, EPERM, 0, EPERM, 0, 0, "29. unpriv1 on setuid1"},128{ &creds[3], &creds[10], EPERM, EPERM, 0, 0, 0, 0, "30. unpriv1 on setuid1"},129{ &creds[3], &creds[11], EPERM, EPERM, 0, EPERM, 0, 0, "31. unpriv1 on setuid1"},130/* unprivileged on setuid, different */131{ &creds[2], &creds[12], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "32. unpriv1 on setuid2"},132{ &creds[2], &creds[13], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "33. unpriv1 on setuid2"},133{ &creds[3], &creds[12], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "34. unpriv1 on setuid2"},134{ &creds[3], &creds[13], EPERM, EPERM, EPERM, EPERM, 0, EPERM, "35. unpriv1 on setuid2"},135};136int scenarios_count = sizeof(scenarios) / sizeof(struct scenario);137138/*139* Convert an error number to a compact string representation. For now,140* implement only the error numbers we are likely to see.141*/142static char *143errno_to_string(int error)144{145146switch (error) {147case EPERM:148return ("EPERM");149case EACCES:150return ("EACCES");151case EINVAL:152return ("EINVAL");153case ENOSYS:154return ("ENOSYS");155case ESRCH:156return ("ESRCH");157case EOPNOTSUPP:158return ("EOPNOTSUPP");159case 0:160return ("0");161default:162printf("%d\n", error);163return ("unknown");164}165}166167/*168* Return a process credential describing the current process.169*/170static int171cred_get(struct cred *cred)172{173int error;174175error = getresuid(&cred->cr_ruid, &cred->cr_euid, &cred->cr_svuid);176if (error)177return (error);178179cred->cr_issetugid = issetugid();180181return (0);182}183184/*185* Userland stub for __setsugid() to take into account possible presence186* in C library, kernel, et al.187*/188int189setugid(int flag)190{191192#ifdef SETSUGID_SUPPORTED193return (__setugid(flag));194#else195#ifdef SETSUGID_SUPPORTED_BUT_NO_LIBC_STUB196return (syscall(374, flag));197#else198return (ENOSYS);199#endif200#endif201}202203/*204* Set the current process's credentials to match the passed credential.205*/206static int207cred_set(struct cred *cred)208{209int error;210211error = setresuid(cred->cr_ruid, cred->cr_euid, cred->cr_svuid);212if (error)213return (error);214215error = setugid(cred->cr_issetugid);216if (error) {217perror("__setugid");218return (error);219}220221#ifdef CHECK_CRED_SET222{223uid_t ruid, euid, svuid;224error = getresuid(&ruid, &euid, &svuid);225if (error) {226perror("getresuid");227return (-1);228}229assert(ruid == cred->cr_ruid);230assert(euid == cred->cr_euid);231assert(svuid == cred->cr_svuid);232assert(cred->cr_issetugid == issetugid());233}234#endif /* !CHECK_CRED_SET */235236return (0);237}238239/*240* Print the passed process credential to the passed I/O stream.241*/242static void243cred_print(FILE *output, struct cred *cred)244{245246fprintf(output, "(e:%d r:%d s:%d P_SUGID:%d)", cred->cr_euid,247cred->cr_ruid, cred->cr_svuid, cred->cr_issetugid);248}249250#define LOOP_PTRACE 0251#define LOOP_KTRACE 1252#define LOOP_SIGHUP 2253#define LOOP_SIGSEGV 3254#define LOOP_SEE 4255#define LOOP_SCHED 5256#define LOOP_MAX LOOP_SCHED257258/*259* Enact a scenario by looping through the four test cases for the scenario,260* spawning off pairs of processes with the desired credentials, and261* reporting results to stdout.262*/263static int264enact_scenario(int scenario)265{266pid_t pid1, pid2;267char *name, *tracefile;268int error, desirederror, loop;269270for (loop = 0; loop < LOOP_MAX+1; loop++) {271/*272* Spawn the first child, target of the operation.273*/274pid1 = fork();275switch (pid1) {276case -1:277return (-1);278case 0:279/* child */280error = cred_set(scenarios[scenario].sc_cred2);281if (error) {282perror("cred_set");283return (error);284}285/* 200 seconds should be plenty of time. */286sleep(200);287exit(0);288default:289/* parent */290break;291}292293/*294* XXX295* This really isn't ideal -- give proc 1 a chance to set296* its credentials, or we may get spurious errors. Really,297* some for of IPC should be used to allow the parent to298* wait for the first child to be ready before spawning299* the second child.300*/301sleep(1);302303/*304* Spawn the second child, source of the operation.305*/306pid2 = fork();307switch (pid2) {308case -1:309return (-1);310311case 0:312/* child */313error = cred_set(scenarios[scenario].sc_cred1);314if (error) {315perror("cred_set");316return (error);317}318319/*320* Initialize errno to zero so as to catch any321* generated errors. In each case, perform the322* operation. Preserve the error number for later323* use so it doesn't get stomped on by any I/O.324* Determine the desired error for the given case325* by extracting it from the scenario table.326* Initialize a function name string for output327* prettiness.328*/329errno = 0;330switch (loop) {331case LOOP_PTRACE:332error = ptrace(PT_ATTACH, pid1, NULL, 0);333error = errno;334name = "ptrace";335desirederror =336scenarios[scenario].sc_canptrace_errno;337break;338case LOOP_KTRACE:339tracefile = mktemp("/tmp/testuid_ktrace.XXXXXX");340if (tracefile == NULL) {341error = errno;342perror("mktemp");343break;344}345error = ktrace(tracefile, KTROP_SET,346KTRFAC_SYSCALL, pid1);347error = errno;348name = "ktrace";349desirederror =350scenarios[scenario].sc_canktrace_errno;351unlink(tracefile);352break;353case LOOP_SIGHUP:354error = kill(pid1, SIGHUP);355error = errno;356name = "sighup";357desirederror =358scenarios[scenario].sc_cansighup_errno;359break;360case LOOP_SIGSEGV:361error = kill(pid1, SIGSEGV);362error = errno;363name = "sigsegv";364desirederror =365scenarios[scenario].sc_cansigsegv_errno;366break;367case LOOP_SEE:368getpriority(PRIO_PROCESS, pid1);369error = errno;370name = "see";371desirederror =372scenarios[scenario].sc_cansee_errno;373break;374case LOOP_SCHED:375error = setpriority(PRIO_PROCESS, pid1,3760);377error = errno;378name = "sched";379desirederror =380scenarios[scenario].sc_cansched_errno;381break;382default:383name = "broken";384}385386if (error != desirederror) {387fprintf(stdout,388"[%s].%s: expected %s, got %s\n ",389scenarios[scenario].sc_name, name,390errno_to_string(desirederror),391errno_to_string(error));392cred_print(stdout,393scenarios[scenario].sc_cred1);394cred_print(stdout,395scenarios[scenario].sc_cred2);396fprintf(stdout, "\n");397}398399exit(0);400401default:402/* parent */403break;404}405406error = waitpid(pid2, NULL, 0);407/*408* Once pid2 has died, it's safe to kill pid1, if it's still409* alive. Mask signal failure in case the test actually410* killed pid1 (not unlikely: can occur in both signal and411* ptrace cases).412*/413kill(pid1, SIGKILL);414error = waitpid(pid2, NULL, 0);415}416417return (0);418}419420void421enact_scenarios(void)422{423int i, error;424425for (i = 0; i < scenarios_count; i++) {426error = enact_scenario(i);427if (error)428perror("enact_scenario");429}430}431432433