#define EXEC_RESTART 102 /* to PM: final part of exec for RS */
#define PROCSTAT 103 /* to PM */
#define GETPROCNR 104 /* to PM */
-
+#define ISSETUGID 106 /* to PM: ask if process is tainted */
#define GETEPINFO_O 107 /* to PM: get pid/uid/gid of an endpoint */
#define ADDDMA 108 /* to PM: inform PM about a region of memory
* that is used for bus-master DMA
time_t enst_ctime; /* Last changed time of executable file */
uid_t new_uid; /* Process UID after exec */
gid_t new_gid; /* Process GID after exec */
+ int setugid; /* Process is setuid or setgid */
char progname[16]; /* Should be at least PROC_NAME_LEN */
};
_PROTOTYPE( pid_t getppid, (void) );
_PROTOTYPE( uid_t getuid, (void) );
_PROTOTYPE( int isatty, (int _fd) );
+_PROTOTYPE( int issetugid, (void) );
_PROTOTYPE( int link, (const char *_existing, const char *_new) );
_PROTOTYPE( off_t lseek, (int _fd, off_t _offset, int _whence) );
_PROTOTYPE( long pathconf, (const char *_path, int _name) );
hypot.c \
index.c \
initgroups.c \
+ issetugid.c \
itoa.c \
loadname.c \
lock.c \
--- /dev/null
+#include <sys/cdefs.h>
+#include <lib.h>
+
+#include <unistd.h>
+
+int issetugid(void)
+{
+ int r;
+ message m;
+
+ r = _syscall(PM_PROC_NR, ISSETUGID, &m);
+ if (r == -1) return 0; /* Default to old behavior */
+ return(r);
+}
int issetugid(void)
{
-#warning Unsecure. Implement me.
- return 0;
+ int r;
+ message m;
+
+ r = _syscall(PM_PROC_NR, ISSETUGID, &m);
+ if (r == -1) return 0; /* Default to old behavior */
+ return(r);
}
int is_elf, dev_t st_dev, ino_t st_ino, time_t ctime,
char *progname, int new_uid, int new_gid,
vir_bytes *stack_topp, int *load_textp,
- int *allow_setuidp) );
+ int *setugidp) );
FORWARD _PROTOTYPE( int is_script, (const char *exec_hdr, size_t exec_len));
FORWARD _PROTOTYPE( int patch_stack, (struct vnode *vp, char stack[ARG_MAX],
vir_bytes *stk_bytes, char path[PATH_MAX]) );
strncpy(execi.progname, cp, PROC_NAME_LEN-1);
execi.progname[PROC_NAME_LEN-1] = '\0';
+ execi.setugid = 0;
/* Open executable */
if ((vp = eat_path(&resolve, fp)) == NULL) {
if (r != OK) goto pm_execfinal;
if (round == 0) {
- /* Deal with setuid/setgid executables */
- if (vp->v_mode & I_SET_UID_BIT) execi.new_uid = vp->v_uid;
- if (vp->v_mode & I_SET_GID_BIT) execi.new_gid = vp->v_gid;
+ /* Deal with setuid/setgid executables */
+ if (vp->v_mode & I_SET_UID_BIT) {
+ execi.new_uid = vp->v_uid;
+ execi.setugid = 1;
+ }
+ if (vp->v_mode & I_SET_GID_BIT) {
+ execi.new_gid = vp->v_gid;
+ execi.setugid = 1;
+ }
}
r = map_header(&execi.hdr, execi.vp);
if (r != OK) goto pm_execfinal;
clo_exec(rfp);
- if (execi.allow_setuid) {
+ if (execi.setugid) {
+ /* If after loading the image we're still allowed to run with
+ * setuid or setgid, change credentials now */
rfp->fp_effuid = execi.new_uid;
rfp->fp_effgid = execi.new_gid;
}
execi->frame_len, sep_id, 0 /* is_elf */, vp->v_dev, vp->v_inode_nr,
execi->sb.st_ctime,
execi->progname, execi->new_uid, execi->new_gid,
- &execi->stack_top, &execi->load_text, &execi->allow_setuid);
+ &execi->stack_top, &execi->load_text, &execi->setugid);
if (r != OK) {
printf("VFS: load_aout: exec_newmem failed: %d\n", r);
tot_bytes, execi->frame_len, sep_id, is_elf,
vp->v_dev, vp->v_inode_nr, execi->sb.st_ctime,
execi->progname, execi->new_uid, execi->new_gid,
- &execi->stack_top, &execi->load_text, &execi->allow_setuid);
+ &execi->stack_top, &execi->load_text, &execi->setugid);
if (r != OK) {
printf("VFS: load_elf: exec_newmem failed: %d\n", r);
int new_gid,
vir_bytes *stack_topp,
int *load_textp,
- int *allow_setuidp
+ int *setugidp
)
{
/* Allocate a new memory map for a process that tries to exec */
struct exec_newmem e;
message m;
+ assert(setugidp != NULL);
+
e.text_addr = text_addr;
e.text_bytes = text_bytes;
e.data_addr = data_addr;
e.enst_ctime = ctime;
e.new_uid = new_uid;
e.new_gid = new_gid;
+ e.setugid = *setugidp;
strncpy(e.progname, progname, sizeof(e.progname)-1);
e.progname[sizeof(e.progname)-1] = '\0';
*stack_topp = m.m1_i1;
*load_textp = !!(m.m1_i2 & EXC_NM_RF_LOAD_TEXT);
- *allow_setuidp = !!(m.m1_i2 & EXC_NM_RF_ALLOW_SETUID);
+ *setugidp = !!(m.m1_i2 & EXC_NM_RF_ALLOW_SETUID);
return(m.m_type);
}
uid_t new_uid; /* Process UID after exec */
gid_t new_gid; /* Process GID after exec */
int load_text; /* Load text section? */
- int allow_setuid; /* Allow setuid execution? */
+ int setugid; /* Allow set{u,g}id execution? */
struct vnode *vp; /* Exec file's vnode */
struct stat sb; /* Exec file's stat structure */
char progname[PROC_NAME_LEN]; /* Program name */
if (r != OK)
panic("do_exec_newmem: sys_datacopy failed: %d", r);
- if((r=vm_exec_newmem(proc_e, &args, sizeof(args), &stack_top, &flags)) == OK) {
- allow_setuid= 0; /* Do not allow setuid execution */
+ if ((r = vm_exec_newmem(proc_e, &args, sizeof(args), &stack_top,
+ &flags)) == OK) {
+ allow_setuid = 0; /* Do not allow setuid execution */
+ rmp->mp_flags &= ~TAINTED; /* By default not tainted */
if (rmp->mp_tracer == NO_TRACER) {
/* Okay, setuid execution is allowed */
- allow_setuid= 1;
+ allow_setuid = 1;
+
rmp->mp_effuid = args.new_uid;
rmp->mp_effgid = args.new_gid;
}
+ /* 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. */
+ if (allow_setuid && args.setugid) {
+ /* Program has setuid and/or setgid bits set */
+ rmp->mp_flags |= TAINTED;
+ } else if (rmp->mp_effuid != rmp->mp_realuid ||
+ rmp->mp_effgid != rmp->mp_realgid) {
+ rmp->mp_flags |= TAINTED;
+ }
+
/* System will save command line for debugging, ps(1) output, etc. */
strncpy(rmp->mp_name, args.progname, PROC_NAME_LEN-1);
rmp->mp_name[PROC_NAME_LEN-1] = '\0';
}
/* Inherit only these flags. In normal fork(), PRIV_PROC is not inherited. */
- rmc->mp_flags &= (IN_USE|DELAY_CALL);
+ rmc->mp_flags &= (IN_USE|DELAY_CALL|TAINTED);
rmc->mp_child_utime = 0; /* reset administration */
rmc->mp_child_stime = 0; /* reset administration */
rmc->mp_exitstatus = 0;
*===========================================================================*/
PUBLIC int do_get()
{
-/* Handle GETUID, GETGID, GETGROUPS, GETGROUPS_O, GETPID, GETPGRP, GETSID.
+/* Handle GETUID, GETGID, GETGROUPS, GETGROUPS_O, GETPID, GETPGRP, GETSID,
+ ISSETUGID.
*/
register struct mproc *rmp = mp;
r = target->mp_procgrp;
break;
}
+ case ISSETUGID:
+ r = !!(rmp->mp_flags & TAINTED);
+ break;
+
default:
r = EINVAL;
break;
#define TRACE_EXIT 0x08000 /* tracer is forcing this process to exit */
#define TRACE_ZOMBIE 0x10000 /* waiting for tracer to issue WAIT call */
#define DELAY_CALL 0x20000 /* waiting for call before sending signal */
+#define TAINTED 0x40000 /* process is 'tainted' */
#define MP_MAGIC 0xC0FFEE0
do_procstat, /* 103 = procstat */
do_getprocnr, /* 104 = getprocnr */
no_sys, /* 105 = unused */
- no_sys, /* 106 = unused */
+ do_get, /* 106 = issetugid */
do_getepinfo_o, /* 107 = getepinfo XXX: old implementation*/
do_adddma, /* 108 = adddma */
do_deldma, /* 109 = deldma */
int is_elf, dev_t st_dev, ino_t st_ino, time_t ctime,
char *progname, int new_uid, int new_gid,
vir_bytes *stack_topp, int *load_textp,
- int *allow_setuidp);
+ int *setugidp);
static int is_script(const char *exec_hdr, size_t exec_len);
static int patch_stack(struct vnode *vp, char stack[ARG_MAX],
vir_bytes *stk_bytes);
strncpy(execi.progname, cp, PROC_NAME_LEN-1);
execi.progname[PROC_NAME_LEN-1] = '\0';
+ execi.setugid = 0;
/* Open executable */
if ((vp = eat_path(PATH_NOFLAGS, fp)) == NULL) return(err_code);
}
if (round == 0) {
- /* Deal with setuid/setgid executables */
- if (vp->v_mode & I_SET_UID_BIT) execi.new_uid = vp->v_uid;
- if (vp->v_mode & I_SET_GID_BIT) execi.new_gid = vp->v_gid;
+ /* Deal with setuid/setgid executables */
+ if (vp->v_mode & I_SET_UID_BIT) {
+ execi.new_uid = vp->v_uid;
+ execi.setugid = 1;
+ }
+ if (vp->v_mode & I_SET_GID_BIT) {
+ execi.new_gid = vp->v_gid;
+ execi.setugid = 1;
+ }
}
r = map_header(&execi.hdr, execi.vp);
if (r != OK) return(r);
clo_exec(rfp);
- if (execi.allow_setuid) {
+ if (execi.setugid) {
+ /* If after loading the image we're still allowed to run with
+ * setuid or setgid, change the credentials now */
rfp->fp_effuid = execi.new_uid;
rfp->fp_effgid = execi.new_gid;
}
execi->frame_len, sep_id, 0 /* is_elf */, vp->v_dev, vp->v_inode_nr,
execi->sb.st_ctime,
execi->progname, execi->new_uid, execi->new_gid,
- &execi->stack_top, &execi->load_text, &execi->allow_setuid);
+ &execi->stack_top, &execi->load_text, &execi->setugid);
if (r != OK) {
printf("VFS: load_aout: exec_newmem failed: %d\n", r);
tot_bytes, execi->frame_len, sep_id, is_elf,
vp->v_dev, vp->v_inode_nr, execi->sb.st_ctime,
execi->progname, execi->new_uid, execi->new_gid,
- &execi->stack_top, &execi->load_text, &execi->allow_setuid);
+ &execi->stack_top, &execi->load_text, &execi->setugid);
if (r != OK) {
printf("VFS: load_elf: exec_newmem failed: %d\n", r);
int new_gid,
vir_bytes *stack_topp,
int *load_textp,
- int *allow_setuidp
+ int *setugidp
)
{
int r;
struct exec_newmem e;
message m;
+ assert(setugidp != NULL);
+
e.text_addr = text_addr;
e.text_bytes = text_bytes;
e.data_addr = data_addr;
e.enst_ctime = ctime;
e.new_uid = new_uid;
e.new_gid = new_gid;
+ e.setugid = *setugidp;
strncpy(e.progname, progname, sizeof(e.progname)-1);
e.progname[sizeof(e.progname)-1] = '\0';
*stack_topp = m.m1_i1;
*load_textp = !!(m.m1_i2 & EXC_NM_RF_LOAD_TEXT);
- *allow_setuidp = !!(m.m1_i2 & EXC_NM_RF_ALLOW_SETUID);
+ *setugidp = !!(m.m1_i2 & EXC_NM_RF_ALLOW_SETUID);
return(m.m_type);
}
uid_t new_uid; /* Process UID after exec */
gid_t new_gid; /* Process GID after exec */
int load_text; /* Load text section? */
- int allow_setuid; /* Allow setuid execution? */
+ int setugid; /* Allow set{u,g}id execution? */
struct vnode *vp; /* Exec file's vnode */
struct stat sb; /* Exec file's stat structure */
char progname[PROC_NAME_LEN]; /* Program name */
test30 test31 test32 test34 test35 test36 test37 test38 \
test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \
test42 test45 test47 test49 test50 test51 test52 test53 \
- test54 test56 test58
+ test54 test56 test58 t60a t60b
BIGOBJ= test20 test24
-ROOTOBJ= test11 test33 test43 test44 test46
+ROOTOBJ= test11 test33 test43 test44 test46 test60
GCCOBJ= test45-gcc test48 test49-gcc test55
GCCFPUOBJ= test51-gcc test52-gcc
OTHEROBJ= test57 test59
clean:
$(MAKE) -C select clean
-rm -rf *.o *.s *.bak test? test?? test??-gcc t10a t11a t11b \
- t40a t40b t40c t40d t40e t40f t43 DIR*
+ t40a t40b t40c t40d t40e t40f t43 \
+ t60a t60b \
+ DIR*
test1: test1.c
test2: test2.c
test58: test58.c
test59: test59.c
$(CC) $(CFLAGS) -o $@ $@.c -lmthread
+test60: test60.c
+t60a: t60a.c
+t60b: t60b.c
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 45-gcc 46 47 48 49 49-gcc 50 \
51 51-gcc 52 52-gcc 53 54 55 56 57 58 59\
+ 60 \
sh1.sh sh2.sh"
tests_no=`expr 0`
--- /dev/null
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc, char *argv[])
+{
+/* Return our tainted state to the parent */
+ int newmode;
+ char cmd[30];
+
+ if (argc < 2) return(-2);
+ if ((newmode = atoi(argv[1])) > 0) {
+ snprintf(cmd, sizeof(cmd), "chmod %o %s", newmode, argv[0]);
+ system(cmd);
+ }
+
+ return(issetugid());
+}
--- /dev/null
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+/* Return the tainted state of our child to our parent */
+ pid_t childpid;
+ int status;
+
+ childpid = fork();
+ if (childpid == (pid_t) -1) exit(-2);
+ else if (childpid == 0) {
+ exit(issetugid());
+ } else {
+ wait(&status);
+ }
+
+ return(WEXITSTATUS(status));
+}
+
--- /dev/null
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define MAX_ERROR 5
+#include "common.c"
+
+int subtest = -1;
+
+void test_self(void);
+void test_setnone(void);
+void test_setuid(void);
+void test_setgid(void);
+void test_effugid(void);
+int execute(const char *prog, const char *arg);
+
+int execute(const char *prog, const char *arg)
+{
+ pid_t childpid;
+ int status;
+ char cmd[30];
+
+ snprintf(cmd, sizeof(cmd), "./%s", prog);
+
+ childpid = fork();
+ if (childpid == (pid_t) -1) {
+ return(-2);
+ } else if (childpid == 0) {
+ if (execl(cmd, prog, arg, NULL) == -1) {
+ exit(-2);
+ }
+ return(-2); /* Never reached */
+ } else {
+ wait(&status);
+ }
+
+ return(WEXITSTATUS(status));
+}
+
+void test_setgid(void)
+{
+/* Execve a new process that has setgid bits set */
+ subtest = 3;
+
+ /* When we exec a new process which has setgid set, that process should
+ * be tainted.
+ */
+ system("chmod 2755 setgid");
+ if (execute("setgid", "0000") != 1) e(2);
+
+ /* When we exec a new process which has setgid set, but unsets that bit
+ * before calling issetugid() should still be tainted
+ */
+ system("chmod 2755 setgid");
+ if (execute("setgid", "0755") != 1) e(3);
+
+ /* When we exec a new process which has setgid set, and then also sets
+ * setuid before calling issetugid() should still be tainted
+ */
+ system("chmod 2755 setgid");
+ if (execute("setgid", "06755") != 1) e(4);
+
+ /* When we exec a new process that has setgid set, and which upon
+ * execution forks, the forked child should also be tainted */
+ system("chmod 2755 setgidfork");
+ if (execute("setgidfork", "0000") != 1) e(5);
+}
+
+void test_setuid(void)
+{
+/* Execve a new process that has setuid bits set */
+ subtest = 4;
+
+ /* When we exec a new process which has setuid set, that process should
+ * be tainted.
+ */
+ system("chmod 4755 setuid");
+ if (execute("setuid", "0000") != 1) e(1);
+
+ /* When we exec a new process which has setuid set, but unsets that bit
+ * before calling issetugid() should still be tainted
+ */
+ system("chmod 4755 setuid");
+ if (execute("setuid", "0755") != 1) e(2);
+
+ /* When we exec a new process which has setuid set, and then also sets
+ * setgid before calling issetugid() should still be tainted
+ */
+ system("chmod 4755 setuid");
+ if (execute("setuid", "06755") != 1) e(3);
+
+ /* When we exec a new process that has setgid set, and which upon
+ * execution forks, the forked child should also be tainted */
+ system("chmod 4755 setuidfork");
+ if (execute("setuidfork", "0000") != 1) e(4);
+
+}
+
+void test_setugid(void)
+{
+/* Execve a new process that has setuid and setgid bits set */
+ subtest = 5;
+
+ /* When we exec a new process which has setugid set, that
+ * process should be tainted.
+ */
+ system("chmod 6755 setugid");
+ if (execute("setugid", "0000") != 1) e(1);
+
+ /* When we exec a new process which has setugid set, but unsets those bits
+ * before calling issetugid() should still be tainted
+ */
+ system("chmod 6755 setugid");
+ if (execute("setugid", "0755") != 1) e(2);
+
+ /* When we exec a new process that has setugid set, and which upon
+ * execution forks, the forked child should also be tainted */
+ system("chmod 6755 setugidfork");
+ if (execute("setugidfork", "0000") != 1) e(4);
+
+}
+
+void test_effugid(void)
+{
+/* Test taint status with different effective uid and gid */
+ pid_t childpid;
+ int status;
+
+ subtest = 6;
+
+ /* Start with effective uid */
+ childpid = fork();
+ if (childpid == (pid_t) -1) e(1);
+ else if (childpid == 0) {
+ /* We're the child */
+
+ /* We should be tainted */
+ if (issetugid() != 1) e(2);
+
+ /* Now execute a program without set{u,g}id; should not be tainted */
+ system("chmod 755 nobits");
+ if (execute("nobits", "0000") != 0) e(3);
+
+ /* Change effective uid into current+42 and try nobits again. This time
+ * it should be tainted */
+ if (seteuid(geteuid() + 42) != 0) e(4);
+ if (execute("nobits", "0000") != 1) e(5);
+ exit(EXIT_SUCCESS);
+ } else {
+ /* We're the parent, wait for the child to finish */
+ wait(&status);
+ }
+
+ /* Now test effective gid */
+ childpid = fork();
+ if (childpid == (pid_t) -1) e(1);
+ else if (childpid == 0) {
+ /* We're the child */
+
+ /* We should be tainted */
+ if (issetugid() != 1) e(2);
+
+ /* Now execute a program without set{u,g}id; should not be tainted */
+ system("chmod 755 nobits");
+ if (execute("nobits", "0000") != 0) e(3);
+
+ /* Change effective gid into current+42 and try nobits again. This time
+ * it should be tainted */
+ if (seteuid(getegid() + 42) != 0) e(4);
+ if (execute("nobits", "0000") != 1) e(5);
+ exit(EXIT_SUCCESS);
+ } else {
+ /* We're the parent, wait for the child to finish */
+ wait(&status);
+ }
+}
+
+void test_setnone(void)
+{
+/* Execve a new process that does not have setuid or setgid bits set */
+ subtest = 2;
+
+ /* When we exec a new process which doesn't have set{u,g}id set, that
+ * process should not be tainted */
+ system("chmod 755 nobits");
+ if (execute("nobits", "0000") != 0) e(2);
+
+ /* When we exec a new process which doesn't have set{u,g}id set, but
+ * sets them after execution, the process should still not be tainted
+ */
+ system("chmod 755 nobits");
+ if (execute("nobits", "02755") != 0) e(4);
+ system("chmod 755 nobits");
+ if (execute("nobits", "04755") != 0) e(3);
+ system("chmod 755 nobits");
+ if (execute("nobits", "06755") != 0) e(5);
+
+ /* When we exec a new process that doesn't have setugid set, and which upon
+ * execution forks, the forked child should not be tainted either */
+ system("chmod 755 nobitsfork");
+ if (execute("nobitsfork", "0000") != 0) e(6);
+}
+
+void test_self(void)
+{
+/* We're supposed to be setuid. Verify. */
+
+ int status;
+ pid_t childpid;
+
+ subtest = 1;
+
+ if (issetugid() != 1) e(1);
+ childpid = fork();
+ if (childpid == -1) e(2);
+ else if (childpid == 0) {
+ /* We're the child and should inherit the tainted status of the parent
+ */
+ if (issetugid() != 1) e(3);
+
+ /* Let's change to the bin user */
+ if (setuid((uid_t) 2) != 0) e(4);
+ if (getuid() != (uid_t) 2) e(5);
+
+ /* At this point, taint status should not have changed. */
+ if (issetugid() != 1) e(6);
+
+ exit(EXIT_SUCCESS);
+ } else {
+ /* We're the parent. Wait for the child to finish */
+ wait(&status);
+ }
+}
+
+void switch_to_su(void)
+{
+ subtest = 0;
+ if (setuid(0) != 0) e(1);
+}
+
+int main(int argc, char **argv)
+{
+ start(60);
+ system("cp ../t60a nobits");
+ system("cp ../t60a setgid");
+ system("cp ../t60a setuid");
+ system("cp ../t60a setugid");
+ system("cp ../t60b nobitsfork");
+ system("cp ../t60b setuidfork");
+ system("cp ../t60b setgidfork");
+ system("cp ../t60b setugidfork");
+
+ switch_to_su(); /* We have to be root to perform this test */
+ test_self();
+ test_setnone();
+ test_setuid();
+ test_setgid();
+ test_setugid();
+ test_effugid();
+
+ quit();
+
+ return(-1); /* Never reached */
+}