Revision: 23880 http://trac.macosforge.org/projects/launchd/changeset/23880 Author: dsorresso@apple.com Date: 2009-04-02 18:35:19 -0700 (Thu, 02 Apr 2009) Log Message: ----------- Embedded security changes. Modified Paths: -------------- trunk/launchd/src/launch_priv.h trunk/launchd/src/launchd_core_logic.c trunk/launchd/src/launchd_core_logic.h trunk/launchd/src/launchd_unix_ipc.c trunk/launchd/src/liblaunch.c trunk/launchd/src/vproc_priv.h Modified: trunk/launchd/src/launch_priv.h =================================================================== --- trunk/launchd/src/launch_priv.h 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/launch_priv.h 2009-04-03 01:35:19 UTC (rev 23880) @@ -62,6 +62,7 @@ #define LAUNCH_JOBKEY_SECURITYSESSIONUUID "SecuritySessionUUID" #define LAUNCH_JOBKEY_EMBEDDEDSHUTDOWNAUTHORITY "EmbeddedShutdownAuthority" +#define LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION "EmbeddedPrivilegeDispensation" #define LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL "EnterKernelDebuggerBeforeKill" #define LAUNCH_JOBKEY_PERJOBMACHSERVICES "PerJobMachServices" Modified: trunk/launchd/src/launchd_core_logic.c =================================================================== --- trunk/launchd/src/launchd_core_logic.c 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/launchd_core_logic.c 2009-04-03 01:35:19 UTC (rev 23880) @@ -528,7 +528,7 @@ is_bootstrapper :1, /* The job is a bootstrapper. */ has_console :1, /* The job owns the console. */ clean_exit_timer_expired :1, /* The job was clean, received SIGKILL and failed to exit after LAUNCHD_CLEAN_KILL_TIMER seconds. */ - embedded_shutdown_auth :1, /* The job is allowed to call reboot2() on embedded. */ + embedded_special_privileges :1, /* The job runs as a non-root user on embedded but has select privileges of the root user. */ migratory :1; /* The (anonymous) job called vprocmgr_switch_to_session(). */ mode_t mask; pid_t tracing_pid; @@ -637,13 +637,15 @@ static job_t workaround_5477111; static LIST_HEAD(, job_s) s_needing_sessions; mach_port_t g_audit_session_port = MACH_PORT_NULL; + #if !TARGET_OS_EMBEDDED +static job_t s_embedded_privileged_job = (job_t)&root_jobmgr; au_asid_t g_audit_session = AU_DEFAUDITSID; #else +static job_t s_embedded_privileged_job = NULL; pid_t g_audit_session = 0; #endif -static bool s_embedded_shutdown_right_claimed = false; -static pid_t s_update_pid = 0; + static int s_no_hang_fd = -1; /* process wide globals */ @@ -651,6 +653,7 @@ jobmgr_t root_jobmgr; bool g_shutdown_debugging = false; bool g_verbose_boot = false; +bool g_embedded_privileged_action = false; void job_ignore(job_t j) @@ -741,6 +744,18 @@ } #endif +#if TARGET_OS_EMBEDDED + if( g_embedded_privileged_action && s_embedded_privileged_job ) { + if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + errno = EPERM; + return; + } + } else if( g_embedded_privileged_action ) { + errno = EINVAL; + return; + } +#endif + j->sent_signal_time = runtime_get_opaque_time(); if (newval < 0) { @@ -1016,6 +1031,18 @@ struct limititem *li; struct envitem *ei; +#if TARGET_OS_EMBEDDED + if( g_embedded_privileged_action && s_embedded_privileged_job ) { + if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + errno = EPERM; + return; + } + } else if( g_embedded_privileged_action ) { + errno = EINVAL; + return; + } +#endif + if (unlikely(j->p)) { if (j->anonymous) { job_reap(j); @@ -1156,6 +1183,9 @@ if( !uuid_is_null(j->expected_audit_uuid) ) { LIST_REMOVE(j, needing_session_sle); } + if( j->embedded_special_privileges ) { + s_embedded_privileged_job = NULL; + } kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); @@ -1683,9 +1713,14 @@ } else if (strcasecmp(key, LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL) == 0) { j->debug_before_kill = value; found_key = true; - } else if( !s_embedded_shutdown_right_claimed && strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDSHUTDOWNAUTHORITY) == 0 ) { - j->embedded_shutdown_auth = true; - s_embedded_shutdown_right_claimed = true; + } else if( strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION) == 0 ) { + if( !s_embedded_privileged_job ) { + j->embedded_special_privileges = value; + s_embedded_privileged_job = j; + } else { + job_log(j, LOG_ERR, "Job tried to claim %s after it has already been claimed.", key); + } + found_key = true; } break; case 'w': @@ -2177,6 +2212,31 @@ return NULL; } +#if TARGET_OS_EMBEDDED + if( unlikely(g_embedded_privileged_action && s_embedded_privileged_job) ) { + if( unlikely(!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_USERNAME))) ) { + errno = EPERM; + return NULL; + } + + const char *username = NULL; + if( likely(tmp && launch_data_get_type(tmp) == LAUNCH_DATA_STRING) ) { + username = launch_data_get_string(tmp); + } else { + errno = EPERM; + return NULL; + } + + if( unlikely(strcmp(s_embedded_privileged_job->username, username) != 0) ) { + errno = EPERM; + return NULL; + } + } else if( g_embedded_privileged_action ) { + errno = EINVAL; + return NULL; + } +#endif + if ((tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM)) && (launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) { prog = launch_data_get_string(tmp); @@ -2886,6 +2946,18 @@ if( !uuid_is_null(j->expected_audit_uuid) ) { return NULL; } + +#if TARGET_OS_EMBEDDED + if( g_embedded_privileged_action && s_embedded_privileged_job ) { + if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + errno = EPERM; + return NULL; + } + } else if( g_embedded_privileged_action ) { + errno = EINVAL; + return NULL; + } +#endif /* * The whole job removal logic needs to be consolidated. The fact that @@ -3099,14 +3171,6 @@ log_kevent_struct(LOG_DEBUG, kev, 0); if( fflags & NOTE_EXIT ) { - if( s_update_pid == (pid_t)kev->ident ) { - int status = 0; - job_assumes(j, waitpid(s_update_pid, &status, 0) == 0); - job_log(j, LOG_NOTICE, "Reaping update job (PID %i, exit status %i)", s_update_pid, WEXITSTATUS(status)); - - s_update_pid = 0; - } - if( j->p == (pid_t)kev->ident && !j->anonymous && !j->is_being_sampled ) { int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, j->p }; struct kinfo_proc kp; @@ -3468,10 +3532,10 @@ } if (likely(!j->legacy_mach_job)) { - sipc = ( !SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices) ) && !j->deny_job_creation; + sipc = ((!SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices)) && !j->deny_job_creation) || j->embedded_special_privileges; } - if (sipc) { + if( sipc ) { job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1); } @@ -6101,6 +6165,11 @@ j->checkedin = true; } +bool job_is_god(job_t j) +{ + return j->embedded_special_privileges; +} + bool job_ack_port_destruction(mach_port_t p) { @@ -6612,7 +6681,17 @@ } if( unlikely(ldc->euid != 0 && ldc->euid != getuid()) || j->deny_job_creation ) { + + } + + if( unlikely(ldc->euid != 0 && ldc->euid != getuid()) || j->deny_job_creation ) { + #if TARGET_OS_EMBEDDED + if( !j->embedded_special_privileges ) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + #else return BOOTSTRAP_NOT_PRIVILEGED; + #endif } #if HAVE_SANDBOX @@ -6625,6 +6704,12 @@ return BOOTSTRAP_UNKNOWN_SERVICE; } +#if TARGET_OS_EMBEDDED + if( j->embedded_special_privileges && strcmp(j->username, otherj->username) != 0 ) { + return BOOTSTRAP_NOT_PRIVILEGED; + } +#endif + if (sig == VPROC_MAGIC_UNLOAD_SIGNAL) { bool do_block = otherj->p; @@ -6913,6 +6998,9 @@ case VPROC_GSK_WAITFORDEBUGGER: *outval = j->wait4debugger; break; + case VPROC_GSK_EMBEDDEDROOTEQUIVALENT: + *outval = j->embedded_special_privileges; + break; case 0: *outval = 0; break; @@ -7136,7 +7224,7 @@ #if !TARGET_OS_EMBEDDED if (unlikely(ldc->euid)) { #else - if( unlikely(ldc->euid) && !j->embedded_shutdown_auth ) { + if( unlikely(ldc->euid) && !j->embedded_special_privileges ) { #endif return BOOTSTRAP_NOT_PRIVILEGED; } @@ -8356,12 +8444,12 @@ } #if TARGET_OS_EMBEDDED - bool embedded_check = j->username && otherj->username && ( strcmp(j->username, otherj->username) != 0 ); + bool allow_non_root_kickstart = j->username && otherj->username && ( strcmp(j->username, otherj->username) == 0 ); #else - bool embedded_check = true; + bool allow_non_root_kickstart = false; #endif - if( ldc->euid != 0 && ldc->euid != geteuid() && embedded_check ) { + if( ldc->euid != 0 && ldc->euid != geteuid() && !allow_non_root_kickstart ) { return BOOTSTRAP_NOT_PRIVILEGED; } @@ -8741,6 +8829,10 @@ #else /* Since this is for embedded, we can assume that the root job manager holds the Jetsam jobs. */ jm = root_jobmgr; + + if( !g_embedded_privileged_action ) { + return EPERM; + } #endif size_t npris = launch_data_array_get_count(priorities); Modified: trunk/launchd/src/launchd_core_logic.h =================================================================== --- trunk/launchd/src/launchd_core_logic.h 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/launchd_core_logic.h 2009-04-03 01:35:19 UTC (rev 23880) @@ -32,6 +32,7 @@ extern mach_port_t g_audit_session_port; extern au_asid_t g_audit_session; extern bool g_flat_mach_namespace; +extern bool g_embedded_privileged_action; void jobmgr_init(bool); jobmgr_t jobmgr_shutdown(jobmgr_t jm); @@ -50,6 +51,7 @@ void job_stop(job_t j); void job_checkin(job_t j); void job_remove(job_t j); +bool job_is_god(job_t j); job_t job_import(launch_data_t pload); launch_data_t job_import_bulk(launch_data_t pload); job_t job_mig_intran(mach_port_t mp); Modified: trunk/launchd/src/launchd_unix_ipc.c =================================================================== --- trunk/launchd/src/launchd_unix_ipc.c 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/launchd_unix_ipc.c 2009-04-03 01:35:19 UTC (rev 23880) @@ -354,16 +354,24 @@ } // job_log(rmc->c->j, LOG_NOTICE, "Socket IPC request: %s.", cmd); + + /* Do not allow commands other than check-in to come over the trusted socket + * on the Desktop. On Embedded, allow all commands over the trusted socket if + * the job has the God Mode key set. + */ +#if TARGET_OS_EMBEDDED + bool allow_privileged_ops = ( !rmc->c->j || job_is_god(rmc->c->j) ); +#else + bool allow_privileged_ops = !rmc->c->j; +#endif - /* Do not allow commands other than check-in to come over the trusted socket. */ - if( data == NULL && rmc->c->j ) { - if( strcmp(cmd, LAUNCH_KEY_CHECKIN) == 0 ) { - resp = job_export(rmc->c->j); - job_checkin(rmc->c->j); - } else { - resp = launch_data_new_errno(EACCES); - } - } else { + if( rmc->c->j && strcmp(cmd, LAUNCH_KEY_CHECKIN) == 0 ) { + resp = job_export(rmc->c->j); + job_checkin(rmc->c->j); + } else if( allow_privileged_ops ) { + #if TARGET_OS_EMBEDDED + g_embedded_privileged_action = rmc->c->j && job_is_god(rmc->c->j); + #endif if( data == NULL ) { if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) { launchd_shutdown(); @@ -388,20 +396,20 @@ } else { if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) { if ((j = job_find(launch_data_get_string(data))) != NULL) { + errno = 0; job_dispatch(j, true); - errno = 0; } resp = launch_data_new_errno(errno); } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) { if ((j = job_find(launch_data_get_string(data))) != NULL) { + errno = 0; job_stop(j); - errno = 0; } resp = launch_data_new_errno(errno); } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) { if ((j = job_find(launch_data_get_string(data))) != NULL) { + errno = 0; job_remove(j); - errno = 0; } resp = launch_data_new_errno(errno); } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) { @@ -432,6 +440,11 @@ resp = launch_data_new_errno(launchd_set_jetsam_priorities(data)); } } + #if TARGET_OS_EMBEDDED + g_embedded_privileged_action = false; + #endif + } else { + resp = launch_data_new_errno(EACCES); } rmc->resp = resp; Modified: trunk/launchd/src/liblaunch.c =================================================================== --- trunk/launchd/src/liblaunch.c 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/liblaunch.c 2009-04-03 01:35:19 UTC (rev 23880) @@ -172,6 +172,7 @@ static launch_data_t launch_msg_internal(launch_data_t d); static void launch_mach_checkin_service(launch_data_t obj, const char *key, void *context); +static int64_t s_am_embedded_god = false; static launch_t in_flight_msg_recv_client; static pthread_once_t _lc_once = PTHREAD_ONCE_INIT; @@ -240,9 +241,12 @@ if ((lfd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) { goto out_bad; } - + +#if TARGET_OS_EMBEDDED + (void)vproc_swap_integer(NULL, VPROC_GSK_EMBEDDEDROOTEQUIVALENT, NULL, &s_am_embedded_god); +#endif if (-1 == connect(lfd, (struct sockaddr *)&sun, sizeof(sun))) { - if( cifd != -1 ) { + if( cifd != -1 || s_am_embedded_god ) { /* There is NO security enforced by this check. This is just a hint to our * library that we shouldn't error out due to failing to open this socket. If * we inherited a trusted file descriptor, we shouldn't fail. This should be @@ -1018,7 +1022,7 @@ } int fd2use = -1; - if( launch_data_get_type(d) == LAUNCH_DATA_STRING && strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) == 0 ) { + if( (launch_data_get_type(d) == LAUNCH_DATA_STRING && strcmp(launch_data_get_string(d), LAUNCH_KEY_CHECKIN) == 0) || s_am_embedded_god ) { _lc->l->which = LAUNCHD_USE_CHECKIN_FD; } else { _lc->l->which = LAUNCHD_USE_OTHER_FD; Modified: trunk/launchd/src/vproc_priv.h =================================================================== --- trunk/launchd/src/vproc_priv.h 2009-04-02 22:41:10 UTC (rev 23879) +++ trunk/launchd/src/vproc_priv.h 2009-04-03 01:35:19 UTC (rev 23880) @@ -69,6 +69,7 @@ VPROC_GSK_PERUSER_RESUME, VPROC_GSK_JOB_OVERRIDES_DB, VPROC_GSK_JOB_CACHE_DB, + VPROC_GSK_EMBEDDEDROOTEQUIVALENT, } vproc_gsk_t; typedef unsigned int vproc_flags_t;