/* -*-c-*- */ /* gateguardian-0.9.1 * * * * WARNING: This file was generated from the .in file. * Gate Guardian's ./configure overwrites any changes! * * */ /* Please report bugs and security issues with this file to * gateguardian-devel@lists.sourceforge.net */ #ifndef GATEGUARDIAN_C #define GATEGUARDIAN_C /* !!!add safe versions of fork() system() execve() */ /* ---------------------------- included header files ---------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include /* ---------------------------- interface definitions ---------------------- */ /* user macros for the environment variable table */ #define GATEG_VAR_KEEP(var) { (var), 0, GATEG_EVKEEP, 0 } #define GATEG_VAR_SET(var, val) { (var), (val), GATEG_EVSET, 0 } #define GATEG_VAR_DEL(var) { (var), 0, GATEG_EVDEL, 0 } #define GATEG_VAR_REJECT_PATH(var) { (var), 0, GATEG_EVKEEP_REJECT_PATH, 0 } #define GATEG_VAR_FILTER_LANG(var) { (var), 0, GATEG_EVKEEP_FILTER_LANG, 0 } #define GATEG_VAR_FILTER_INT(var) { (var), 0, GATEG_EVKEEP_FILTER_INT, 0 } #define GATEG_VAR_FILTER_TERM(var) { (var), 0, GATEG_EVKEEP_FILTER_TERM, 0 } #define GATEG_VAR_SET_USER_REAL(var) { (var), 0, GATEG_EVSET_USER_REAL, 0 } #define GATEG_VAR_SET_LOGINDIR_REAL(var) \ { (var), 0, GATEG_EVSET_LOGINDIR_REAL, 0 } #define GATEG_VAR_SET_USER_EFFECTIVE(var) \ { (var), 0, GATEG_EVSET_USER_EFFECTIVE, 0 } #define GATEG_VAR_SET_LOGINDIR_EFFECTIVE(var) \ { (var), 0, GATEG_EVSET_LOGINDIR_EFFECTIVE, 0 } #define GATEG_VAR_END_OF_TABLE { 0, 0, GATEG_EVEOT, 0 } /* ---------------------------- local definitions -------------------------- */ #define GATEG_CWD_DEFAULT "/" #define GATEG_CHROOT_DIR_DEFAULT ((char *)0) #define GATEG_UMASK_DEFAULT 0077 #define GATEG_MAGIC 0x5388a618 #ifdef GATEG_DEBUG #include static jmp_buf gateg_debug_jmp_buf; #define GATEG_ABORT_RET 77 #define GATEG_ABORT() \ do { \ fprintf( \ stderr, "abort in file %s line %d\n", __FILE__, \ __LINE__); \ longjmp(gateg_debug_jmp_buf, GATEG_ABORT_RET); \ abort(); \ } while (0) #define GATEG_DPRINTF(X) do { fprintf X; } while(0) #define GATEG_NO_CORES_LAST 1 #else #define GATEG_ABORT() abort() #define GATEG_DPRINTF(X) do { } while(0) #define GATEG_NO_CORES_LAST 0 #endif /* ---------------------------- feature tests ------------------------------ */ #ifdef _PATH_STDPATH #define GATEG_PATH_STDPATH _PATH_STDPATH #else #define GATEG_PATH_STDPATH "/bin:/usr/bin" #endif #ifdef _PATH_DEVNULL #define GATEG_PATH_DEVNULL _PATH_DEVNULL #else #define GATEG_PATH_DEVNULL "/dev/null" #endif #ifdef _SVR4_SOURCE #define GATEG_NSIG _sys_nsig #else #ifdef NSIG #define GATEG_NSIG NSIG #else #ifdef _NSIG #define GATEG_NSIG _NSIG #else /* conservative guess */ #define GATEG_NSIG 256 #endif #endif #endif #ifdef OPEN_MAX #define GATEG_OPEN_MAX OPEN_MAX #else /* conservative guess */ #define GATEG_OPEN_MAX 8192 #endif #ifdef FD_SETSIZE #define GATEG_FD_SETSIZE FD_SETSIZE #else #define GATEG_FD_SETSIZE GATEG_OPEN_MAX #endif #ifdef _SC_VERSION #define GATEG_SYSCONF(x) sysconf((x)) #else #undef _SC_OPEN_MAX #define _SC_OPEN_MAX 0 #define GATEG_SYSCONF(x) (-1L) #endif #if defined(__hpux) && !defined(RLIMIT_CORE) /* HPUX does have the BSD rlimits in the kernel. Officially they are * unsupported but quite a few of them like RLIMIT_CORE seem to work. * All the following are in the but made visible * only for the kernel. */ #define RLIMIT_CORE 4 #endif /* [gs]etrlimit(): SVr4, BSD 4.3 */ #ifdef RLIMIT_CORE typedef struct rlimit gateg_rlimit; #define GATEG_SETRLIMIT(x, y) setrlimit((x), (y)) #else typedef struct { long rlim_cur; long rlim_max; } gateg_rlimit; #define GATEG_SETRLIMIT(x, y) -1 #endif /* ---------------------------- local macros ------------------------------- */ /* ---------------------------- imports ------------------------------------ */ extern char **environ; /* ---------------------------- included code files ------------------------ */ /* ---------------------------- local types -------------------------------- */ typedef enum { GATEG_EVKEEP = 0, /* filters */ GATEG_EVKEEP_REJECT_PATH, GATEG_EVKEEP_FILTER_LANG, GATEG_EVKEEP_FILTER_INT, GATEG_EVKEEP_FILTER_TERM, GATEG_EVSET, /* special values */ GATEG_EVSET_USER_REAL, GATEG_EVSET_LOGINDIR_REAL, GATEG_EVSET_USER_EFFECTIVE, GATEG_EVSET_LOGINDIR_EFFECTIVE, /* do not touch these */ GATEG_EVEOT = -1, GATEG_EVSKIP = -2, GATEG_EVDEL = -3 } gateg_evaction_t; typedef struct { char *e_var; char *e_val; gateg_evaction_t e_action; /* private, don't touch this */ const int e_is_handled; } gateg_evarhandler_t; typedef enum { GATEG_OP_DFLT = 0x0, GATEG_OP_SET = 0x1, GATEG_OP_NOP = 0x2, GATEG_OP_PARANOID = 0x4, GATEG_OP_DFLT_PARANOID = GATEG_OP_DFLT | GATEG_OP_PARANOID, GATEG_OP_SET_PARANOID = GATEG_OP_SET | GATEG_OP_PARANOID, /* add to one of the above basic modes */ GATEG_OP_DELAY_1 = 0x1000, GATEG_OP_DELAY_2 = 0x2000, GATEG_OP_DELAY_3 = 0x3000, GATEG_OP_DELAY_4 = 0x4000, GATEG_OP_DELAY_5 = 0x5000, GATEG_OP_DELAY_6 = 0x6000, GATEG_OP_DELAY_7 = 0x7000, GATEG_OP_DELAY_8 = 0x8000, GATEG_OP_DELAY_9 = 0x9000, GATEG_OP_DELAY_10 = 0xa000, GATEG_OP_DELAY_11 = 0xb000, GATEG_OP_DELAY_12 = 0xc000, GATEG_OP_DELAY_13 = 0xd000, GATEG_OP_DELAY_14 = 0xe000, GATEG_OP_DELAY_15 = 0xf000, /* for internal purposes only */ GATEG_OP_MASK = 0x3, GATEG_OP_DELAY_MASK = 0xf000, GATEG_OP_DONE = 0x800 } gateg_op_t; typedef enum { GATEG_INIT_DFLT, GATEG_INIT_DFLT_PARANOID, GATEG_INIT_NOP /*,!!!GATEG_INIT_DAEMON,*/ /*GATEG_INIT_CLIENT,*/ /*GATEG_INIT_SUID,*/ /*GATEG_INIT_FORK,*/ /*GATEG_INIT_EXEC,*/ } gateg_init_t; typedef struct { struct { unsigned int evars_clean; unsigned int fds_close; unsigned int fds_std; unsigned int cwd_set; unsigned int chroot_call; unsigned int chroot_verify; unsigned int umask_set; unsigned int priv_set; unsigned int priv_drop; unsigned int sighand_reset; unsigned int sighand_verify; unsigned int core_disable; } ops; gateg_evarhandler_t *evars_handlers; char *cwd; char *chroot_dir; mode_t umask; uid_t priv_uid; gid_t priv_gid; /* private, don't touch this */ unsigned int magic; } gateg_cfg_t; /* ---------------------------- forward declarations ----------------------- */ /* ---------------------------- local variables ---------------------------- */ static gateg_evarhandler_t gateg_evars_defhandlers[] = { GATEG_VAR_SET("IFS", " \t\n"), GATEG_VAR_SET("PATH", GATEG_PATH_STDPATH), GATEG_VAR_REJECT_PATH("TERMCAP"), GATEG_VAR_FILTER_LANG("LANG"), GATEG_VAR_FILTER_LANG("LANGUAGE"), GATEG_VAR_FILTER_LANG("LC_ALL"), GATEG_VAR_FILTER_LANG("LC_CTYPE"), GATEG_VAR_FILTER_LANG("LC_NUMERIC"), GATEG_VAR_FILTER_LANG("LC_TIME"), GATEG_VAR_FILTER_LANG("LC_COLLATE"), GATEG_VAR_FILTER_LANG("LC_MONETARY"), GATEG_VAR_FILTER_LANG("LC_MESSAGES"), GATEG_VAR_FILTER_LANG("LC_PAPER"), GATEG_VAR_FILTER_LANG("LC_NAME"), GATEG_VAR_FILTER_LANG("LC_ADDRESS"), GATEG_VAR_FILTER_LANG("LC_TELEPHONE"), GATEG_VAR_FILTER_LANG("LC_MEASUREMENT"), GATEG_VAR_FILTER_LANG("LC_IDENTIFICATION"), GATEG_VAR_SET_USER_REAL("ORIG_USER"), GATEG_VAR_SET_USER_REAL("ORIG_LOGNAME"), GATEG_VAR_SET_LOGINDIR_REAL("ORIG_HOME"), GATEG_VAR_SET_USER_EFFECTIVE("USER"), GATEG_VAR_SET_USER_EFFECTIVE("LOGNAME"), GATEG_VAR_SET_LOGINDIR_EFFECTIVE("HOME"), GATEG_VAR_FILTER_INT("LINES"), GATEG_VAR_FILTER_INT("COLUMNS"), GATEG_VAR_FILTER_TERM("TERM"), GATEG_VAR_END_OF_TABLE }; static gateg_evarhandler_t gateg_evars_defhandlers_paranoid[] = { { "IFS", " \t\n", GATEG_EVSET, 0 }, { "PATH", GATEG_PATH_STDPATH, GATEG_EVSET, 0 }, GATEG_VAR_END_OF_TABLE }; /* ---------------------------- exported variables (globals) --------------- */ /* ---------------------------- local functions ---------------------------- */ /* * gateg functions */ static int __gateg_decrement_delay(unsigned int *dest, unsigned int op) { if ((op & GATEG_OP_MASK) == GATEG_OP_NOP) { return 0; } if ((op & GATEG_OP_DELAY_MASK) != 0) { *dest = op - GATEG_OP_DELAY_1; return 1; } else { *dest = GATEG_OP_NOP | GATEG_OP_DONE; return 0; } } static int __gateg_get_op_step(unsigned int op) { int step; if ((op & GATEG_OP_DONE) != 0) { step = -1; } else if ((op & GATEG_OP_MASK) == GATEG_OP_NOP) { step = -2; } else { step = (op & GATEG_OP_DELAY_MASK) / GATEG_OP_DELAY_1; } return step; } static void __gateg_check_order(unsigned int op1, unsigned int op2) { int step1; int step2; step1 = __gateg_get_op_step(op1); step2 = __gateg_get_op_step(op2); if (step1 == -2) { if (step2 != -2) { /* required first step is never executed */ abort(); } } else { if (step2 < step1) { /* second step is executed before the first step */ abort(); } } return; } static void __gateg_update_ops(gateg_cfg_t *pcfg) { int rc; __gateg_check_order( pcfg->ops.chroot_call, pcfg->ops.chroot_verify); __gateg_check_order( pcfg->ops.sighand_reset, pcfg->ops.sighand_verify); rc = 0; rc |= __gateg_decrement_delay( &pcfg->ops.evars_clean, pcfg->ops.evars_clean); rc |= __gateg_decrement_delay( &pcfg->ops.fds_close, pcfg->ops.fds_close); rc |= __gateg_decrement_delay(&pcfg->ops.fds_std, pcfg->ops.fds_std); rc |= __gateg_decrement_delay(&pcfg->ops.cwd_set, pcfg->ops.cwd_set); rc |= __gateg_decrement_delay( &pcfg->ops.chroot_call, pcfg->ops.chroot_call); rc |= __gateg_decrement_delay( &pcfg->ops.chroot_verify, pcfg->ops.chroot_verify); rc |= __gateg_decrement_delay( &pcfg->ops.umask_set, pcfg->ops.umask_set); rc |= __gateg_decrement_delay(&pcfg->ops.priv_set, pcfg->ops.priv_set); rc |= __gateg_decrement_delay( &pcfg->ops.priv_drop, pcfg->ops.priv_drop); rc |= __gateg_decrement_delay( &pcfg->ops.sighand_reset, pcfg->ops.sighand_reset); rc |= __gateg_decrement_delay( &pcfg->ops.sighand_verify, pcfg->ops.sighand_verify); rc |= __gateg_decrement_delay( &pcfg->ops.core_disable, pcfg->ops.core_disable); if (rc == 0) { /* no more operations */ *(unsigned int *)&pcfg->magic -= 1; } return; } static unsigned int __gateg_handle_delay(unsigned int op) { if ((op & GATEG_OP_DELAY_MASK) != 0) { op = GATEG_OP_NOP; } else { op &= (GATEG_OP_MASK | GATEG_OP_PARANOID); } return op; } static void __gateg_get_real_ops(gateg_cfg_t *dest, gateg_cfg_t *src) { *dest = *src; dest->ops.evars_clean = __gateg_handle_delay(src->ops.evars_clean); dest->ops.fds_close = __gateg_handle_delay(src->ops.fds_close); dest->ops.fds_std = __gateg_handle_delay(src->ops.fds_std); dest->ops.cwd_set = __gateg_handle_delay(src->ops.cwd_set); dest->ops.chroot_call = __gateg_handle_delay(src->ops.chroot_call); dest->ops.chroot_verify = __gateg_handle_delay(src->ops.chroot_verify); dest->ops.umask_set = __gateg_handle_delay(src->ops.umask_set); dest->ops.priv_set = __gateg_handle_delay(src->ops.priv_set); dest->ops.priv_drop = __gateg_handle_delay(src->ops.priv_drop); dest->ops.sighand_reset = __gateg_handle_delay(src->ops.sighand_reset); dest->ops.sighand_verify = __gateg_handle_delay(src->ops.sighand_verify); dest->ops.core_disable = __gateg_handle_delay(src->ops.core_disable); return; } /* * helper functions */ static char *__gateg_strchr(char *s, int c) { if (s == 0) { return 0; } for ( ; *s != 0 && *s != (char)c; s++) { /* nothing */ } if (*s == 0) { return 0; } return s; } static int __gateg_check_charset(char *s, char *cset) { char *r; if (s == 0) { return 1; } for ( ; *s != 0; s++) { r = __gateg_strchr(cset, *s); if (r == 0) { return 0; } } return 1; } static unsigned long __gateg_strlen(char *s) { unsigned long i; if (s == 0) { return 0; } for (i = 0; s[i] != 0; i++) { /* nothing */ } return i; } static char *__gateg_strcpy_nozero(char *dest, char *src) { while (*src != 0) { *dest++ = *src++; } return dest; } static char *__gateg_strdup(char *s) { unsigned long size; char *copy; if (s == 0) { return 0; } size = __gateg_strlen(s); copy = (char *)malloc(size + 1); if (copy == 0) { return 0; } __gateg_strcpy_nozero(copy, s); copy[size] = 0; return copy; } static int __gateg_are_strings_equal(char *s1, char *s2) { unsigned long i; if (s1 == s2) { return 1; } if (s1 == 0 || s2 == 0) { return 0; } for (i = 0; s1[i] == s2[i] && s1[i] != 0 && s2[i] != 0; i++) { /* nothing */ } return (s1[i] == 0 && s2[i] == 0); } /* * environment variable handling */ static void __gateg_evars_reset_handlers(gateg_evarhandler_t **httab) { gateg_evarhandler_t *handlers; int h; int i; for (h = 0; httab[h] != 0; h++) { handlers = httab[h]; for (i = 0; handlers[i].e_action != GATEG_EVEOT; i++) { *(int *)&(handlers[i].e_is_handled) = 0; } } return; } static char *__gateg_evars_get_username(uid_t uid) { struct passwd *pw; char *name; setpwent(); pw = getpwuid(uid); if (pw == 0) { endpwent(); return 0; } name = __gateg_strdup(pw->pw_name); if (name == 0) { GATEG_ABORT(); } endpwent(); return name; } static char *__gateg_evars_get_homedir(uid_t uid) { struct passwd *pw; char *dir; setpwent(); pw = getpwuid(uid); if (pw == 0) { endpwent(); return 0; } dir = __gateg_strdup(pw->pw_dir); if (dir == 0) { GATEG_ABORT(); } endpwent(); return dir; } static gateg_evaction_t __gateg_evars_handle_filters( char **ret_val, gateg_evaction_t act, char *var) { char *val; char *s; int rc; uid_t uid; switch (act) { case GATEG_EVKEEP_REJECT_PATH: val = getenv(var); if (val == 0) { return GATEG_EVDEL; } s = __gateg_strchr(val, '/'); return (s == 0) ? GATEG_EVKEEP : GATEG_EVDEL; case GATEG_EVKEEP_FILTER_LANG: val = getenv(var); if (val == 0) { return GATEG_EVDEL; } s = __gateg_strchr(val, '/'); if (s != 0) { return GATEG_EVDEL; } s = __gateg_strchr(val, '%'); if (s != 0) { return GATEG_EVDEL; } return (s == 0) ? GATEG_EVKEEP : GATEG_EVDEL; case GATEG_EVKEEP_FILTER_INT: val = getenv(var); if (val == 0) { return GATEG_EVDEL; } rc = __gateg_check_charset(val, "0123456789"); return (rc == 1) ? GATEG_EVKEEP : GATEG_EVDEL; case GATEG_EVKEEP_FILTER_TERM: val = getenv(var); if (val == 0) { return GATEG_EVDEL; } rc = __gateg_check_charset( val, "-/:+._0123456789" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ); return (rc == 1) ? GATEG_EVKEEP : GATEG_EVDEL; case GATEG_EVSET_USER_REAL: uid = getuid(); *ret_val = __gateg_evars_get_username(uid); return GATEG_EVSET; case GATEG_EVSET_LOGINDIR_REAL: uid = getuid(); *ret_val = __gateg_evars_get_homedir(uid); return GATEG_EVSET; case GATEG_EVSET_USER_EFFECTIVE: uid = geteuid(); *ret_val = __gateg_evars_get_username(uid); return GATEG_EVSET; case GATEG_EVSET_LOGINDIR_EFFECTIVE: uid = geteuid(); *ret_val = __gateg_evars_get_homedir(uid); return GATEG_EVSET; case GATEG_EVKEEP: case GATEG_EVSET: case GATEG_EVDEL: default: break; } return act; } static gateg_evaction_t __gateg_evars_handle_one_entry( unsigned long *ret_size, char **dest, gateg_evarhandler_t **httab, int ntab, int nentry, int do_copy) { gateg_evarhandler_t *htab; gateg_evaction_t act; char *var; char *val; int h; int i; int i0; *ret_size = 0; htab = httab[ntab]; if (htab[nentry].e_is_handled == 1) { /* already handled */ return GATEG_EVSKIP; } var = htab[nentry].e_var; val = htab[nentry].e_val; /* handle the entry */ act = __gateg_evars_handle_filters(&val, htab[nentry].e_action, var); switch (act) { case GATEG_EVKEEP: val = getenv(var); if (val == 0) { *ret_size = 0; } else { *ret_size = __gateg_strlen(var) + 1 + __gateg_strlen(val) + 1; if (do_copy) { *dest = __gateg_strcpy_nozero(*dest, var); *((*dest)++) = '='; *dest = __gateg_strcpy_nozero(*dest, val); *((*dest)++) = 0; } } break; case GATEG_EVSET: if (val == 0) { *ret_size = 0; } else { *ret_size = __gateg_strlen(var) + 1 + __gateg_strlen(val) + 1; if (do_copy) { *dest = __gateg_strcpy_nozero(*dest, var); *((*dest)++) = '='; *dest = __gateg_strcpy_nozero(*dest, val); *((*dest)++) = 0; } if (val != htab[nentry].e_val) { free(val); } } break; case GATEG_EVDEL: default: break; } /* set flags to indicate that variable was handled */ for (h = ntab, i0 = nentry; httab[h] != 0; h++, i0 = 0) { gateg_evarhandler_t *handlers; handlers = httab[h]; for (i = i0; handlers[i].e_action != GATEG_EVEOT; i++) { if (__gateg_are_strings_equal(var, handlers[i].e_var)) { *(int *)&(httab[h][i].e_is_handled) = 1; } } } return act; } static unsigned long __gateg_evars_get_envsize( unsigned long *ret_nvars, gateg_evarhandler_t **httab) { unsigned long size; int h; int i; for (h = 0, size = sizeof(char *), *ret_nvars = 0; httab[h] != 0; h++) { gateg_evarhandler_t *handlers = httab[h]; unsigned long esize; for (i = 0; handlers[i].e_action != GATEG_EVEOT; i++) { __gateg_evars_handle_one_entry( &esize, 0, httab, h, i, 0); if (esize > 0) { size += esize + sizeof(char *); *ret_nvars += 1; } } } return size; } static void __gateg_evars_make_env( char **new_env, gateg_evarhandler_t **httab, unsigned long nvars) { char *p; int h; int i; int n; p = ((char *)new_env) + (nvars + 1) * sizeof(char *); for (h = 0, n = 0; httab[h] != 0; h++) { gateg_evaction_t act; gateg_evarhandler_t *handlers = httab[h]; unsigned long esize; for (i = 0; handlers[i].e_action != GATEG_EVEOT; i++) { char *old_p; old_p = p; act = __gateg_evars_handle_one_entry( &esize, &p, httab, h, i, 1); if (esize == 0) { continue; } switch (act) { case GATEG_EVKEEP: case GATEG_EVSET: if (p != old_p) { new_env[n++] = old_p; } break; case GATEG_EVDEL: default: break; } } } new_env[n] = (char *)0; return; } static int __gateg_evars_clean_up( unsigned int op, gateg_evarhandler_t *handlers) { gateg_evarhandler_t *httab[3]; unsigned long new_size; unsigned long nvars; char **new_env; int i; int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_SET: break; case GATEG_OP_DFLT: break; default: GATEG_ABORT(); } i = 0; if (handlers != 0) { httab[i++] = handlers; } httab[i++] = (is_paranoid) ? gateg_evars_defhandlers_paranoid : gateg_evars_defhandlers; httab[i] = 0; __gateg_evars_reset_handlers(httab); new_size = __gateg_evars_get_envsize(&nvars, httab); new_env = (char **)malloc(new_size); if (new_env == 0) { GATEG_ABORT(); } __gateg_evars_reset_handlers(httab); __gateg_evars_make_env(new_env, httab, nvars); environ = new_env; return 0; } /* * file descriptor handling */ static int __gateg_fds_get_max_fds(void) { int max_fds; long rc; max_fds = -1; if (max_fds < 0) { rc = GATEG_SYSCONF(_SC_OPEN_MAX); if (rc >= 0) { return (int)rc; } } if (max_fds < 0) { rc = getdtablesize(); if (rc >= 0) { max_fds = (int)rc; } } if (max_fds < 0 || max_fds < GATEG_OPEN_MAX) { max_fds = GATEG_OPEN_MAX; } if (max_fds < 0 || max_fds < GATEG_FD_SETSIZE) { max_fds = GATEG_FD_SETSIZE; } return max_fds; } static int __gateg_fds_close(unsigned int op) { int max_fds; int i; int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); /* paranoia mode is unused */ switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_SET: break; case GATEG_OP_DFLT: break; default: GATEG_ABORT(); } max_fds = __gateg_fds_get_max_fds(); /* close open fds */ for (i = 3; i < max_fds; i++) { close(i); } return 0; } static int __gateg_fds_check_reopen_fd( int fd, char* mode, FILE *of, int do_force) { struct stat sbuf; FILE *f; int rc; if (do_force == 0) { errno = 0; rc = fstat(fd, &sbuf); if (rc == 0) { return 0; } else if (errno != EBADF) { return -1; } } f = freopen(GATEG_PATH_DEVNULL, mode, of); if (f == 0 || fileno(f) != fd) { return -1; } return 0; } static int __gateg_fds_std_clean_up(unsigned int op) { int rc; int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); /* paranoia mode is unused */ switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_SET: break; case GATEG_OP_DFLT: break; default: GATEG_ABORT(); } /* reopen stdin, stdout, stderr if necessary */ rc = __gateg_fds_check_reopen_fd(0, "rb", stdin, is_paranoid); if (rc == -1) { GATEG_ABORT(); } rc = __gateg_fds_check_reopen_fd(1, "wb", stdout, is_paranoid); if (rc == -1) { GATEG_ABORT(); } rc = __gateg_fds_check_reopen_fd(2, "wb", stderr, is_paranoid); if (rc == -1) { GATEG_ABORT(); } return 0; } /* * directory handling */ static int __gateg_cwd_set(unsigned int op, const char *path) { int is_paranoid; int rc; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_SET: break; case GATEG_OP_DFLT: path = GATEG_CWD_DEFAULT; break; default: GATEG_ABORT(); } if (path == 0 || *path == 0) { GATEG_ABORT(); } rc = chdir(path); if (rc != 0) { GATEG_ABORT(); } return rc; } /* * chroot handling */ static int __gateg_chroot_internal(const char *path) { int rc; rc = chroot(path); if (rc != 0) { GATEG_ABORT(); } rc = chdir("/"); if (rc != 0) { GATEG_ABORT(); } return rc; } static int __gateg_chroot(unsigned int op, const char *path) { int is_paranoid; int rc; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_DFLT: return 0; case GATEG_OP_SET: break; default: GATEG_ABORT(); } rc = __gateg_chroot_internal(path); return rc; } static int __gateg_chroot_verify(unsigned int op) { int is_paranoid; int i; int rc; int max_fds; struct stat buf; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_DFLT: return 0; case GATEG_OP_SET: break; default: GATEG_ABORT(); } /* verify that no directories are open */ max_fds = __gateg_fds_get_max_fds(); for (i = 0; i < max_fds; i++) { rc = fstat(i, &buf); if (rc == 0 && (buf.st_mode & S_IFDIR) != 0) { /* found an open directory */ GATEG_ABORT(); } else if (rc != 0 && errno != EBADF) { /* some obscure error */ GATEG_ABORT(); } } if (setuid(0) == 0 || getuid() == 0 || geteuid() == 0) { /* must not run as root in a chroot */ GATEG_ABORT(); } return 0; } /* * umask handling */ static int __gateg_umask_set(unsigned int op, mode_t mask) { mode_t old_umask; int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_SET: break; case GATEG_OP_DFLT: mask = GATEG_UMASK_DEFAULT; break; default: GATEG_ABORT(); } old_umask = umask(mask); if (is_paranoid) { mask |= old_umask; umask(mask); } return 0; } /* * credentials */ static int __gateg_priv_set_drop_credentials( unsigned int op, unsigned int op_drop, uid_t new_uid, gid_t new_gid) { int rc; uid_t euid; gid_t egid; uid_t oeuid; gid_t oegid; int is_set_paranoid; int is_drop_paranoid; int do_set; int do_drop; euid = new_uid; egid = new_gid; is_set_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: euid = geteuid(); egid = getegid(); do_set = 0; break; case GATEG_OP_DFLT: do_set = 1; break; case GATEG_OP_SET: do_set = 1; break; default: GATEG_ABORT(); } is_drop_paranoid = !!(op & GATEG_OP_PARANOID); op_drop = (op_drop & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: do_drop = 0; break; case GATEG_OP_DFLT: do_drop = 1; break; case GATEG_OP_SET: do_drop = 1; break; default: GATEG_ABORT(); } if (do_set == 0 && do_drop == 0) { return 0; } oeuid = geteuid(); oegid = getegid(); if ((int)euid < 0) { euid = oeuid; } if ((int)egid < 0) { egid = oegid; } if (do_drop == 0 && euid == oeuid && egid == oegid) { /* nothing to do */ return 0; } /* only root may call setgroups, so do it first */ if (oeuid == 0) { rc = setgroups(1, &egid); if (rc != 0) { GATEG_ABORT(); } } /* set gid and uid */ if (do_set != 0) { /* It may not be possible to switch back, depending on the * operating system. */ rc = setegid(egid); if (rc != 0) { GATEG_ABORT(); } rc = seteuid(euid); if (rc != 0) { GATEG_ABORT(); } } if (do_drop != 0) { /* Note: The history of the set[gu]id, sete[gu]id, setre[gu]id * functions is a terrible mess. Do not expect this to work on * older systems, but it *should* work on System V, BSD 4.2, * 4.3 and 4.4, POSIX systems, or any other system that * provides some implementation of setre[gu]id() and * sete[gu]id(). */ /* BSD 4.2 and 4.3, BSD 4.4 if [gu]id == e[gu]id */ setgid(egid); setuid(euid); /* POSIX (Linux, Solaris), BSD 4.4 */ setregid(egid, egid); setreuid(euid, euid); } /* check if it worked; this is important to detect whether set...id() * works as expected on teh given platform */ if (getegid() != egid) { GATEG_ABORT(); } if (getgid() != egid) { GATEG_ABORT(); } if (geteuid() != euid) { GATEG_ABORT(); } if (getuid() != euid) { GATEG_ABORT(); } if (euid != 0 && do_drop != 0) { if (euid != oeuid && seteuid(oeuid) == 0) { /* we can still set the old uid! */ GATEG_ABORT(); } if (egid != oegid && setegid(oegid) == 0) { /* we can still set the old gid! */ GATEG_ABORT(); } } return 0; } /* * signal handlers */ static int __gateg_sighand_reset(unsigned int op) { int nsigs; int i; int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); /* paranoia mode is the default */ switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_DFLT: break; case GATEG_OP_SET: break; default: GATEG_ABORT(); } /* assume we have a lot of signals ... */ nsigs = GATEG_NSIG; for (i = 0; i <= nsigs; i++) { if (i == SIGKILL || i == SIGSTOP) { continue; } signal(i, SIG_DFL); } return 0; } static int __gateg_sighand_verify(unsigned int op) { int is_paranoid; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); /* paranoia mode is the default */ switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_DFLT: break; case GATEG_OP_SET: break; default: GATEG_ABORT(); } /* verify that the real uid is the same as the effective uid */ if (getuid() != 0 && getuid() != geteuid()) { /* some other user might send signals to this process */ GATEG_ABORT(); } return 0; } /* * core dump handling */ static int __gateg_core_disable(unsigned int op) { gateg_rlimit rlim; int is_paranoid; int rc; is_paranoid = !!(op & GATEG_OP_PARANOID); op = (op & ~(int)GATEG_OP_PARANOID); switch (op) { case GATEG_OP_NOP: return 0; case GATEG_OP_DFLT: break; case GATEG_OP_SET: break; default: GATEG_ABORT(); } rlim.rlim_cur = 0; rlim.rlim_max = 0; rc = GATEG_SETRLIMIT(RLIMIT_CORE, &rlim); if (rc == -1) { GATEG_ABORT(); } return rc; } /* ---------------------------- interface functions ------------------------ */ void gateg_init_cfg(gateg_cfg_t *argp, gateg_init_t in) { unsigned int op; unsigned int i; char *p; switch (in) { case GATEG_INIT_DFLT: op = GATEG_OP_DFLT; break; case GATEG_INIT_DFLT_PARANOID: op = GATEG_OP_PARANOID; break; case GATEG_INIT_NOP: op = GATEG_OP_NOP; break; default: GATEG_ABORT(); } for (i = 0, p = (char *)argp; i < sizeof(*argp); i++, p++) { *p = 0; } argp->ops.evars_clean = op; argp->ops.fds_close = op; argp->ops.fds_std = op; argp->ops.cwd_set = op; argp->ops.chroot_call = op; argp->ops.chroot_verify = op; argp->ops.umask_set = op; argp->ops.priv_set = op; argp->ops.priv_drop = op; argp->ops.sighand_reset = op; argp->ops.sighand_verify = op; argp->ops.core_disable = op; argp->evars_handlers = 0; argp->cwd = GATEG_CWD_DEFAULT; argp->chroot_dir = GATEG_CHROOT_DIR_DEFAULT; argp->umask = GATEG_UMASK_DEFAULT; argp->priv_uid = (uid_t)-1; argp->priv_gid = (gid_t)-1; *(unsigned int *)&(argp->magic) = GATEG_MAGIC; return; } int gateg_safe_init(gateg_cfg_t *scfg) { gateg_cfg_t cfg; int rc; /* handle op delays */ __gateg_get_real_ops(&cfg, scfg); if (cfg.magic != GATEG_MAGIC) { GATEG_ABORT(); } __gateg_update_ops(scfg); scfg = NULL; /* Notes on execution order: * * Core dumps must be disabled first because the other functions call * abort() in case of a failure (except when debugging). * Signal handlers must be reset as early as possible because they * might be used for an active attack. * The umask may be set at any time, but as umask() never fails it may * as well be called early. * The second signal handler stage must be executed after dropping * privileges (otherwise it would fail). * Environment variables must be processed before dropping privileges * because the old real uid and gid are used in the ORIG_ variables. * The working directory must be set before chroot(). * Open descriptors must be closed before chroot(). * Chroot() must be called before dropping privileges because it needs * root. * The second chroot stage must be executed after dropping privileges * and closing open descriptors. */ if (GATEG_NO_CORES_LAST == 0) { /* for debugging, switch off core files last */ rc = __gateg_core_disable(cfg.ops.core_disable); if (rc == -1) { return -1; } } rc = __gateg_sighand_reset(cfg.ops.sighand_reset); if (rc == -1) { return -1; } rc = __gateg_umask_set(cfg.ops.umask_set, cfg.umask); if (rc == -1) { return -1; } rc = __gateg_evars_clean_up(cfg.ops.evars_clean, cfg.evars_handlers); if (rc == -1) { return -1; } rc = __gateg_fds_close(cfg.ops.fds_close); if (rc == -1) { return -1; } rc = __gateg_fds_std_clean_up(cfg.ops.fds_std); if (rc == -1) { return -1; } rc = __gateg_cwd_set(cfg.ops.cwd_set, cfg.cwd); if (rc == -1) { return -1; } rc = __gateg_chroot(cfg.ops.chroot_call, cfg.chroot_dir); if (rc == -1) { return -1; } rc = __gateg_priv_set_drop_credentials( cfg.ops.priv_set, cfg.ops.priv_drop, cfg.priv_uid, cfg.priv_gid); if (rc == -1) { return -1; } rc = __gateg_sighand_verify( cfg.ops.sighand_verify); if (rc == -1) { return -1; } rc = __gateg_chroot_verify(cfg.ops.chroot_verify); if (rc == -1) { return -1; } if (GATEG_NO_CORES_LAST != 0) { /* for debugging, switch off core files last */ rc = __gateg_core_disable(cfg.ops.core_disable); if (rc == -1) { return -1; } } return rc; } int gateg_chroot(const char *path) { int rc; rc = __gateg_chroot(GATEG_OP_SET_PARANOID, path); return rc; } #endif