X-Git-Url: https://git.saurik.com/apple/launchd.git/blobdiff_plain/62123c11d73b074206859f8a24f4e65c1d1e0061..dcace88fe6cde929a524daa9e1f1495bf1e24cfe:/launchd/src/launchd_core_logic.c diff --git a/launchd/src/launchd_core_logic.c b/launchd/src/launchd_core_logic.c index 5f6a5b7..c04a496 100644 --- a/launchd/src/launchd_core_logic.c +++ b/launchd/src/launchd_core_logic.c @@ -16,10 +16,12 @@ * @APPLE_APACHE_LICENSE_HEADER_END@ */ -static const char *const __rcs_file_version__ = "$Revision: 24208 $"; +static const char *const __rcs_file_version__ = "$Revision: 24984 $"; #include "config.h" #include "launchd_core_logic.h" +#include "launch_internal.h" +#include "launchd_helper.h" #include #include @@ -75,10 +77,14 @@ static const char *const __rcs_file_version__ = "$Revision: 24208 $"; #include #include #include +#include #include +#include + #include #include #include +#include #if HAVE_SANDBOX #define __APPLE_API_PRIVATE #include @@ -92,6 +98,7 @@ static const char *const __rcs_file_version__ = "$Revision: 24208 $"; /* To make my life easier. */ typedef struct jetsam_priority_entry { pid_t pid; + uint32_t priority; uint32_t flags; int32_t hiwat_pages; int32_t hiwat_reserved1; @@ -123,20 +130,32 @@ enum { #include "protocol_job_reply.h" #include "protocol_job_forward.h" #include "mach_excServer.h" +#if !TARGET_OS_EMBEDDED +#include "domainServer.h" +#include "init.h" +#endif +#include "eventsServer.h" -/* - * LAUNCHD_SAMPLE_TIMEOUT - * If the job hasn't exited in the given number of seconds after sending - * it a SIGTERM, start sampling it. - * LAUNCHD_DEFAULT_EXIT_TIMEOUT +#ifndef POSIX_SPAWN_OSX_TALAPP_START +#define POSIX_SPAWN_OSX_TALAPP_START 0x0400 +#endif + +#ifndef POSIX_SPAWN_OSX_WIDGET_START +#define POSIX_SPAWN_OSX_WIDGET_START 0x0800 +#endif + +#ifndef POSIX_SPAWN_IOS_APP_START +#define POSIX_SPAWN_IOS_APP_START 0x1000 +#endif + +/* LAUNCHD_DEFAULT_EXIT_TIMEOUT * If the job hasn't exited in the given number of seconds after sending * it a SIGTERM, SIGKILL it. Can be overriden in the job plist. */ #define LAUNCHD_MIN_JOB_RUN_TIME 10 -#define LAUNCHD_SAMPLE_TIMEOUT 2 #define LAUNCHD_DEFAULT_EXIT_TIMEOUT 20 -#define LAUNCHD_SIGKILL_TIMER 5 -#define LAUNCHD_CLEAN_KILL_TIMER 1 +#define LAUNCHD_SIGKILL_TIMER 2 +#define LAUNCHD_LOG_FAILED_EXEC_FREQ 10 #define SHUTDOWN_LOG_DIR "/var/log/shutdown" @@ -156,39 +175,34 @@ struct waiting_for_removal { static bool waiting4removal_new(job_t j, mach_port_t rp); static void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r); -struct waiting_for_exit { - LIST_ENTRY(waiting_for_exit) sle; - mach_port_t rp; - bool legacy; -}; - -static bool waiting4exit_new(job_t j, mach_port_t rp, bool legacy); -static void waiting4exit_delete(job_t j, struct waiting_for_exit *w4e); - struct machservice { SLIST_ENTRY(machservice) sle; SLIST_ENTRY(machservice) special_port_sle; LIST_ENTRY(machservice) name_hash_sle; LIST_ENTRY(machservice) port_hash_sle; + struct machservice *alias; job_t job; unsigned int gen_num; mach_port_name_t port; - unsigned int isActive :1, - reset :1, - recv :1, - hide :1, - kUNCServer :1, - per_user_hack :1, - debug_on_close :1, - per_pid :1, - delete_on_destruction :1, - drain_one_on_crash :1, - drain_all_on_crash :1, - /* Don't let the size of this field to get too small. It has to be large enough - * to represent the reasonable range of special port numbers. - */ - special_port_num :20; - + unsigned int + isActive :1, + reset :1, + recv :1, + hide :1, + kUNCServer :1, + per_user_hack :1, + debug_on_close :1, + per_pid :1, + delete_on_destruction :1, + drain_one_on_crash :1, + drain_all_on_crash :1, + event_update_port :1, /* The job which owns this port is the event monitor. */ + upfront :1, /* This service was declared in the plist. */ + event_channel :1, /* The job is to receive events on this channel. */ + /* Don't let the size of this field to get too small. It has to be large enough + * to represent the reasonable range of special port numbers. + */ + special_port_num :18; const char name[0]; }; @@ -203,6 +217,9 @@ static void machservice_setup(launch_data_t obj, const char *key, void *context) static void machservice_setup_options(launch_data_t obj, const char *key, void *context); static void machservice_resetport(job_t j, struct machservice *ms); static struct machservice *machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local); +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +static struct machservice *machservice_new_alias(job_t aj, struct machservice *orig); +#endif static void machservice_ignore(job_t j, struct machservice *ms); static void machservice_watch(job_t j, struct machservice *ms); static void machservice_delete(job_t j, struct machservice *, bool port_died); @@ -214,6 +231,7 @@ static bool machservice_active(struct machservice *); static const char *machservice_name(struct machservice *); static bootstrap_status_t machservice_status(struct machservice *); void machservice_drain_port(struct machservice *); +static struct machservice *xpc_events_find_channel(job_t j, event_name_t stream, mach_port_t *p); struct socketgroup { SLIST_ENTRY(socketgroup) sle; @@ -286,6 +304,8 @@ typedef enum { NETWORK_DOWN, SUCCESSFUL_EXIT, FAILED_EXIT, + CRASHED, + DID_NOT_CRASH, PATH_EXISTS, PATH_MISSING, OTHER_JOB_ENABLED, @@ -302,6 +322,7 @@ struct semaphoreitem { semaphore_reason_t why; bool watching_parent; int fd; + union { const char what[0]; char what_init[0]; @@ -323,63 +344,121 @@ static void semaphoreitem_watch(job_t j, struct semaphoreitem *si); static void semaphoreitem_ignore(job_t j, struct semaphoreitem *si); static void semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add); -#define ACTIVE_JOB_HASH_SIZE 32 -#define ACTIVE_JOB_HASH(x) (IS_POWER_OF_TWO(ACTIVE_JOB_HASH_SIZE) ? (x & (ACTIVE_JOB_HASH_SIZE - 1)) : (x % ACTIVE_JOB_HASH_SIZE)) - -#define MACHSERVICE_HASH_SIZE 37 +struct externalevent { + LIST_ENTRY(externalevent) sys_le; + LIST_ENTRY(externalevent) job_le; + struct eventsystem *sys; + + uint64_t id; + job_t job; + bool state; + bool wanted_state; + launch_data_t event; + + char name[0]; +}; -enum { - JOBMGR_PHASE_HOPEFULLY_EXITS_FIRST, - JOBMGR_PHASE_NORMAL, - JOBMGR_PHASE_HOPEFULLY_EXITS_LAST, - JOBMGR_PHASE_LAST, +struct externalevent_iter_ctx { + job_t j; + struct eventsystem *sys; }; -static char *s_phases[JOBMGR_PHASE_LAST + 1] = { - "HopefullyExitsFirst", - "Normal", - "HopefullyExitsLast", - "Finalized", +static bool externalevent_new(job_t j, struct eventsystem *sys, char *evname, launch_data_t event); +static void externalevent_delete(struct externalevent *ee); +static void externalevent_setup(launch_data_t obj, const char *key, void *context); +static struct externalevent *externalevent_find(const char *sysname, uint64_t id); + +struct eventsystem { + LIST_ENTRY(eventsystem) global_le; + LIST_HEAD(, externalevent) events; + uint64_t curid; + bool has_updates; + char name[0]; }; +static struct eventsystem *eventsystem_new(const char *name); +static void eventsystem_delete(struct eventsystem *sys); +static void eventsystem_setup(launch_data_t obj, const char *key, void *context); +static struct eventsystem *eventsystem_find(const char *name); +static void eventsystem_ping(void); + +#define ACTIVE_JOB_HASH_SIZE 32 +#define ACTIVE_JOB_HASH(x) (IS_POWER_OF_TWO(ACTIVE_JOB_HASH_SIZE) ? (x & (ACTIVE_JOB_HASH_SIZE - 1)) : (x % ACTIVE_JOB_HASH_SIZE)) + +#define MACHSERVICE_HASH_SIZE 37 + +#define LABEL_HASH_SIZE 53 struct jobmgr_s { kq_callback kqjobmgr_callback; + LIST_ENTRY(jobmgr_s) xpc_le; SLIST_ENTRY(jobmgr_s) sle; SLIST_HEAD(, jobmgr_s) submgrs; LIST_HEAD(, job_s) jobs; LIST_HEAD(, job_s) jetsam_jobs; + + /* For legacy reasons, we keep all job labels that are imported in the + * root job manager's label hash. If a job manager is an XPC domain, then + * it gets its own label hash that is separate from the "global" one + * stored in the root job manager. + */ + LIST_HEAD(, job_s) label_hash[LABEL_HASH_SIZE]; LIST_HEAD(, job_s) active_jobs[ACTIVE_JOB_HASH_SIZE]; LIST_HEAD(, machservice) ms_hash[MACHSERVICE_HASH_SIZE]; LIST_HEAD(, job_s) global_env_jobs; - STAILQ_HEAD(, job_s) pending_samples; mach_port_t jm_port; mach_port_t req_port; - mach_port_t init_audit_session; jobmgr_t parentmgr; int reboot_flags; - int shutdown_phase; + time_t shutdown_time; unsigned int global_on_demand_cnt; - unsigned int hopefully_first_cnt; unsigned int normal_active_cnt; unsigned int jetsam_jobs_cnt; - unsigned int shutting_down :1, - session_initialized :1, - killed_hopefully_first_jobs :1, - killed_normal_jobs :1, - killed_hopefully_last_jobs :1, - killed_stray_jobs :1; - char sample_log_file[PATH_MAX]; + unsigned int + shutting_down :1, + session_initialized :1, + killed_stray_jobs :1, + monitor_shutdown :1, + shutdown_jobs_dirtied :1, + shutdown_jobs_cleaned :1, + xpc_singleton :1; uint32_t properties; + /* XPC-specific properties. */ + char owner[MAXCOMLEN]; + char *shortdesc; + mach_port_t req_bsport; + mach_port_t req_excport; + mach_port_t req_asport; + pid_t req_pid; + uid_t req_euid; + gid_t req_egid; + au_asid_t req_asid; + vm_offset_t req_ctx; + mach_msg_type_number_t req_ctx_sz; + mach_port_t req_rport; + kern_return_t error; union { const char name[0]; char name_init[0]; }; }; +/* Global XPC domains. */ +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +static jobmgr_t _s_xpc_system_domain; +static LIST_HEAD(, jobmgr_s) _s_xpc_user_domains; +static LIST_HEAD(, jobmgr_s) _s_xpc_session_domains; +#endif + #define jobmgr_assumes(jm, e) \ (unlikely(!(e)) ? jobmgr_log_bug(jm, __LINE__), false : true) -static jobmgr_t jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool no_init, mach_port_t session_port); +static jobmgr_t jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool no_init, mach_port_t asport); +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +static jobmgr_t jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name); +static jobmgr_t jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid); +static jobmgr_t jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid); +static job_t xpc_domain_import_service(jobmgr_t jm, launch_data_t pload); +#endif static job_t jobmgr_import2(jobmgr_t jm, launch_data_t pload); static jobmgr_t jobmgr_parent(jobmgr_t jm); static jobmgr_t jobmgr_do_garbage_collection(jobmgr_t jm); @@ -389,13 +468,12 @@ static void jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays); static void jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np); static void jobmgr_remove(jobmgr_t jm); static void jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack); -static void jobmgr_dequeue_next_sample(jobmgr_t jm); static job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag); static job_t jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay); static job_t jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon); static jobmgr_t jobmgr_find_by_name(jobmgr_t jm, const char *where); static job_t job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid); -static job_t jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch, mach_port_t *mp); +static job_t jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp); static void job_export_all2(jobmgr_t jm, launch_data_t where); static void jobmgr_callback(void *obj, struct kevent *kev); static void jobmgr_setup_env_from_other_jobs(jobmgr_t jm); @@ -408,6 +486,7 @@ static void jobmgr_log_bug(jobmgr_t jm, unsigned int line); #define AUTO_PICK_LEGACY_LABEL (const char *)(~0) #define AUTO_PICK_ANONYMOUS_LABEL (const char *)(~1) +#define AUTO_PICK_XPC_LABEL (const char *)(~2) struct suspended_peruser { LIST_ENTRY(suspended_peruser) sle; @@ -417,15 +496,17 @@ struct suspended_peruser { struct job_s { kq_callback kqjob_callback; /* MUST be first element of this structure for benefit of launchd's run loop. */ LIST_ENTRY(job_s) sle; + LIST_ENTRY(job_s) subjob_sle; LIST_ENTRY(job_s) needing_session_sle; LIST_ENTRY(job_s) jetsam_sle; LIST_ENTRY(job_s) pid_hash_sle; LIST_ENTRY(job_s) label_hash_sle; LIST_ENTRY(job_s) global_env_sle; - STAILQ_ENTRY(job_s) pending_samples_sle; SLIST_ENTRY(job_s) curious_jobs_sle; LIST_HEAD(, suspended_peruser) suspended_perusers; LIST_HEAD(, waiting_for_exit) exit_watchers; + LIST_HEAD(, job_s) subjobs; + LIST_HEAD(, externalevent) events; SLIST_HEAD(, socketgroup) sockets; SLIST_HEAD(, calendarinterval) cal_intervals; SLIST_HEAD(, envitem) global_env; @@ -434,11 +515,14 @@ struct job_s { SLIST_HEAD(, machservice) machservices; SLIST_HEAD(, semaphoreitem) semaphores; SLIST_HEAD(, waiting_for_removal) removal_watchers; + job_t alias; struct rusage ru; cpu_type_t *j_binpref; size_t j_binpref_cnt; mach_port_t j_port; - mach_port_t wait_reply_port; /* we probably should switch to a list of waiters */ + mach_port_t exit_status_dest; + mach_port_t exit_status_port; + mach_port_t spawn_reply_port; uid_t mach_uid; jobmgr_t mgr; size_t argc; @@ -470,8 +554,10 @@ struct job_s { int log_redirect_fd; int nice; int stdout_err_fd; + uint32_t pstype; int32_t jetsam_priority; int32_t jetsam_memlimit; + int32_t jetsam_seq; int32_t main_thread_priority; uint32_t timeout; uint32_t exit_timeout; @@ -480,6 +566,8 @@ struct job_s { uint32_t min_run_time; uint32_t start_interval; uint32_t peruser_suspend_count; /* The number of jobs that have disabled this per-user launchd. */ + uuid_t instance_id; + uint32_t fail_cnt; #if 0 /* someday ... */ enum { @@ -489,76 +577,81 @@ struct job_s { J_TYPE_INETD, } j_type; #endif - bool debug :1, /* man launchd.plist --> Debug */ - ondemand :1, /* man launchd.plist --> KeepAlive == false */ - session_create :1, /* man launchd.plist --> SessionCreate */ - low_pri_io :1, /* man launchd.plist --> LowPriorityIO */ - no_init_groups :1, /* man launchd.plist --> InitGroups */ - priv_port_has_senders :1, /* a legacy mach_init concept to make bootstrap_create_server/service() work */ - importing_global_env :1, /* a hack during job importing */ - importing_hard_limits :1, /* a hack during job importing */ - setmask :1, /* man launchd.plist --> Umask */ - anonymous :1, /* a process that launchd knows about, but isn't managed by launchd */ - checkedin :1, /* a legacy mach_init concept to detect sick jobs */ - legacy_mach_job :1, /* a job created via bootstrap_create_server() */ - legacy_LS_job :1, /* a job created via spawn_via_launchd() */ - inetcompat :1, /* a legacy job that wants inetd compatible semantics */ - inetcompat_wait :1, /* a twist on inetd compatibility */ - start_pending :1, /* an event fired and the job should start, but not necessarily right away */ - globargv :1, /* man launchd.plist --> EnableGlobbing */ - wait4debugger :1, /* man launchd.plist --> WaitForDebugger */ - wait4debugger_oneshot :1, /* One-shot WaitForDebugger. */ - internal_exc_handler :1, /* MachExceptionHandler == true */ - stall_before_exec :1, /* a hack to support an option of spawn_via_launchd() */ - only_once :1, /* man launchd.plist --> LaunchOnlyOnce. Note: 5465184 Rename this to "HopefullyNeverExits" */ - currently_ignored :1, /* Make job_ignore() / job_watch() work. If these calls were balanced, then this wouldn't be necessarily. */ - forced_peers_to_demand_mode :1, /* A job that forced all other jobs to be temporarily launch-on-demand */ - setnice :1, /* man launchd.plist --> Nice */ - hopefully_exits_last :1, /* man launchd.plist --> HopefullyExitsLast */ - removal_pending :1, /* a job was asked to be unloaded/removed while running, we'll remove it after it exits */ - sent_sigkill :1, /* job_kill() was called */ - sampling_complete :1, /* job_force_sampletool() was called (or is disabled) */ - debug_before_kill :1, /* enter the kernel debugger before killing a job */ - weird_bootstrap :1, /* a hack that launchd+launchctl use during jobmgr_t creation */ - start_on_mount :1, /* man launchd.plist --> StartOnMount */ - per_user :1, /* This job is a per-user launchd managed by the PID 1 launchd */ - hopefully_exits_first :1, /* man launchd.plist --> HopefullyExitsFirst */ - deny_unknown_mslookups :1, /* A flag for changing the behavior of bootstrap_look_up() */ - unload_at_mig_return :1, /* A job thoroughly confused launchd. We need to unload it ASAP */ - abandon_pg :1, /* man launchd.plist --> AbandonProcessGroup */ - ignore_pg_at_shutdown :1, /* During shutdown, do not send SIGTERM to stray processes in the process group of this job. */ - poll_for_vfs_changes :1, /* a hack to work around the fact that kqueues don't work on all filesystems */ - deny_job_creation :1, /* Don't let this job create new 'job_t' objects in launchd */ - kill_via_shmem :1, /* man launchd.plist --> EnableTransactions */ - sent_kill_via_shmem :1, /* We need to 'kill_via_shmem' once-and-only-once */ - clean_kill :1, /* The job was sent SIGKILL because it was clean. */ - pending_sample :1, /* This job needs to be sampled for some reason. */ - kill_after_sample :1, /* The job is to be killed after sampling. */ - is_being_sampled :1, /* We've spawned a sample tool to sample the job. */ - reap_after_trace :1, /* The job exited before sample did, so we should reap it after sample is done. */ - nosy :1, /* The job has an OtherJobEnabled KeepAlive criterion. */ - crashed :1, /* The job is the default Mach exception handler, and it crashed. */ - reaped :1, /* We've received NOTE_EXIT for the job. */ - stopped :1, /* job_stop() was called. */ - jetsam_frontmost :1, /* The job is considered "frontmost" by Jetsam. */ - needs_kickoff :1, /* The job is to be kept alive continuously, but it must be initially kicked off. */ - 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_special_privileges :1, /* The job runs as a non-root user on embedded but has select privileges of the root user. */ - did_exec :1, /* The job exec(2)ed successfully. */ - holds_ref :1, /* The (anonymous) job called vprocmgr_switch_to_session(). */ - jetsam_properties :1; /* The job has Jetsam limits in place. */ + bool + debug :1, /* man launchd.plist --> Debug */ + ondemand :1, /* man launchd.plist --> KeepAlive == false */ + session_create :1, /* man launchd.plist --> SessionCreate */ + low_pri_io :1, /* man launchd.plist --> LowPriorityIO */ + no_init_groups :1, /* man launchd.plist --> InitGroups */ + priv_port_has_senders :1, /* a legacy mach_init concept to make bootstrap_create_server/service() work */ + importing_global_env :1, /* a hack during job importing */ + importing_hard_limits :1, /* a hack during job importing */ + setmask :1, /* man launchd.plist --> Umask */ + anonymous :1, /* a process that launchd knows about, but isn't managed by launchd */ + checkedin :1, /* a legacy mach_init concept to detect sick jobs */ + legacy_mach_job :1, /* a job created via bootstrap_create_server() */ + legacy_LS_job :1, /* a job created via spawn_via_launchd() */ + inetcompat :1, /* a legacy job that wants inetd compatible semantics */ + inetcompat_wait :1, /* a twist on inetd compatibility */ + start_pending :1, /* an event fired and the job should start, but not necessarily right away */ + globargv :1, /* man launchd.plist --> EnableGlobbing */ + wait4debugger :1, /* man launchd.plist --> WaitForDebugger */ + wait4debugger_oneshot :1, /* One-shot WaitForDebugger. */ + internal_exc_handler :1, /* MachExceptionHandler == true */ + stall_before_exec :1, /* a hack to support an option of spawn_via_launchd() */ + only_once :1, /* man launchd.plist --> LaunchOnlyOnce. Note: 5465184 Rename this to "HopefullyNeverExits" */ + currently_ignored :1, /* Make job_ignore() / job_watch() work. If these calls were balanced, then this wouldn't be necessarily. */ + forced_peers_to_demand_mode :1, /* A job that forced all other jobs to be temporarily launch-on-demand */ + setnice :1, /* man launchd.plist --> Nice */ + removal_pending :1, /* a job was asked to be unloaded/removed while running, we'll remove it after it exits */ + sent_sigkill :1, /* job_kill() was called */ + debug_before_kill :1, /* enter the kernel debugger before killing a job */ + weird_bootstrap :1, /* a hack that launchd+launchctl use during jobmgr_t creation */ + start_on_mount :1, /* man launchd.plist --> StartOnMount */ + per_user :1, /* This job is a per-user launchd managed by the PID 1 launchd */ + unload_at_mig_return :1, /* A job thoroughly confused launchd. We need to unload it ASAP */ + abandon_pg :1, /* man launchd.plist --> AbandonProcessGroup */ + ignore_pg_at_shutdown :1, /* During shutdown, do not send SIGTERM to stray processes in the process group of this job. */ + poll_for_vfs_changes :1, /* a hack to work around the fact that kqueues don't work on all filesystems */ + deny_job_creation :1, /* Don't let this job create new 'job_t' objects in launchd */ + kill_via_shmem :1, /* man launchd.plist --> EnableTransactions */ + sent_kill_via_shmem :1, /* We need to 'kill_via_shmem' once-and-only-once */ + clean_kill :1, /* The job was sent SIGKILL because it was clean. */ + kill_after_sample :1, /* The job is to be killed after sampling. */ + reap_after_trace :1, /* The job exited before sample did, so we should reap it after sample is done. */ + nosy :1, /* The job has an OtherJobEnabled KeepAlive criterion. */ + crashed :1, /* The job is the default Mach exception handler, and it crashed. */ + reaped :1, /* We've received NOTE_EXIT for the job. */ + stopped :1, /* job_stop() was called. */ + jetsam_frontmost :1, /* The job is considered "frontmost" by Jetsam. */ + needs_kickoff :1, /* The job is to be kept alive continuously, but it must be initially kicked off. */ + is_bootstrapper :1, /* The job is a bootstrapper. */ + has_console :1, /* The job owns the console. */ + embedded_special_privileges :1, /* The job runs as a non-root user on embedded but has select privileges of the root user. */ + did_exec :1, /* The job exec(2)ed successfully. */ + xpcproxy_did_exec :1, /* The job is an XPC service, and XPC proxy successfully exec(3)ed. */ + holds_ref :1, /* The (anonymous) job called vprocmgr_switch_to_session(). */ + jetsam_properties :1, /* The job has Jetsam limits in place. */ + dedicated_instance :1, /* This job was created as the result of a look up of a service provided by a per-lookup job. */ + multiple_instances :1, /* The job supports creating additional instances of itself. */ + former_subjob :1, /* The sub-job was already removed from the parent's list of sub-jobs. */ + event_monitor :1, /* The job is responsible for monitoring external events for this launchd. */ + removing :1, /* A lame hack. */ + disable_aslr :1, /* Disable ASLR when launching this job. */ + xpc_service :1, /* The job is an XPC Service. */ + shutdown_monitor :1, /* The job is the Performance team's shutdown monitor. */ + dirty_at_shutdown :1, /* We should open a transaction for the job when shutdown begins. */ + workaround9359725 :1; /* The job was sent SIGKILL but did not exit in a timely fashion, indicating a kernel bug. */ + mode_t mask; pid_t tracing_pid; - mach_port_t audit_session; + mach_port_t asport; + /* Only set for per-user launchd's. */ + au_asid_t asid; uuid_t expected_audit_uuid; const char label[0]; }; -#define LABEL_HASH_SIZE 53 - -static LIST_HEAD(, job_s) label_hash[LABEL_HASH_SIZE]; static size_t hash_label(const char *label) __attribute__((pure)); static size_t hash_ms(const char *msstr) __attribute__((pure)); static SLIST_HEAD(, job_s) s_curious_jobs; @@ -586,6 +679,7 @@ static void job_start(job_t j); static void job_start_child(job_t j) __attribute__((noreturn)); static void job_setup_attributes(job_t j); static bool job_setup_machport(job_t j); +static kern_return_t job_setup_exit_port(job_t j); static void job_setup_fd(job_t j, int target_fd, const char *path, int flags); static void job_postfork_become_user(job_t j); static void job_postfork_test_user(job_t j); @@ -599,7 +693,11 @@ static void job_log_stray_pg(job_t j); static void job_log_children_without_exec(job_t j); static job_t job_new_anonymous(jobmgr_t jm, pid_t anonpid) __attribute__((malloc, nonnull, warn_unused_result)); static job_t job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv) __attribute__((malloc, nonnull(1,2), warn_unused_result)); +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +static job_t job_new_alias(jobmgr_t jm, job_t src); +#endif static job_t job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond) __attribute__((malloc, nonnull, warn_unused_result)); +static job_t job_new_subjob(job_t j, uuid_t identifier); static void job_kill(job_t j); static void job_uncork_fork(job_t j); static void job_log_stdouterr(job_t j); @@ -608,7 +706,9 @@ static void job_log_error(job_t j, int pri, const char *msg, ...) __attribute__( static void job_log_bug(job_t j, unsigned int line); static void job_log_stdouterr2(job_t j, const char *msg, ...); static void job_set_exception_port(job_t j, mach_port_t port); -static kern_return_t job_handle_mpm_wait(job_t j, mach_port_t srp, int *waitstatus); +static kern_return_t job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj); +static void job_open_shutdown_transaction(job_t ji); +static void job_close_shutdown_transaction(job_t ji); static const struct { const char *key; @@ -632,15 +732,20 @@ static bool cronemu_mday(struct tm *wtm, int mday, int hour, int min); static bool cronemu_hour(struct tm *wtm, int hour, int min); static bool cronemu_min(struct tm *wtm, int min); +/* These functions are a total nightmare to get to through headers. + * See rdar://problem/8223092. + */ +typedef __darwin_mach_port_t fileport_t; +#define FILEPORT_NULL ((fileport_t)0) +extern int fileport_makeport(int, fileport_t *); +extern int fileport_makefd(fileport_t); + /* miscellaneous file local functions */ static size_t get_kern_max_proc(void); static int dir_has_files(job_t j, const char *path); static char **mach_cmd2argv(const char *string); static size_t our_strhash(const char *s) __attribute__((pure)); static void extract_rcsid_substr(const char *i, char *o, size_t osz); -static void simulate_pid1_crash(void); -static pid_t basic_spawn(job_t j, void (*what_to_do)(job_t)); -static void take_sample(job_t j); void eliminate_double_reboot(void); @@ -650,7 +755,12 @@ static size_t total_anon_children; static mach_port_t the_exception_server; static job_t workaround_5477111; static LIST_HEAD(, job_s) s_needing_sessions; +static LIST_HEAD(, eventsystem) _s_event_systems; +static job_t _s_event_monitor; +static job_t _s_shutdown_monitor; +static mach_port_t _s_event_update_port; mach_port_t g_audit_session_port = MACH_PORT_NULL; +static uint32_t s_jetsam_sequence_id; #if !TARGET_OS_EMBEDDED static job_t s_embedded_privileged_job = (job_t)&root_jobmgr; @@ -687,7 +797,7 @@ job_ignore(job_t j) if (j->poll_for_vfs_changes) { j->poll_for_vfs_changes = false; - job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); } SLIST_FOREACH(sg, &j->sockets, sle) { @@ -737,73 +847,53 @@ job_stop(job_t j) char extralog[100]; int32_t newval = 1; - if (unlikely(!j->p || j->anonymous)) { + if (unlikely(!j->p || j->stopped || j->anonymous)) { return; } - -#if !TARGET_OS_EMBEDDED - if (j->kill_via_shmem && !g_force_old_kill_path) { - if (j->shmem) { - if (!j->sent_kill_via_shmem) { - j->shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING; - newval = __sync_sub_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1); - j->sent_kill_via_shmem = true; - } else { - newval = j->shmem->vp_shmem_transaction_cnt; - } - } else { - newval = -1; - } - } else if( j->kill_via_shmem ) { - job_log(j, LOG_DEBUG, "Stopping transactional job the old-fashioned way."); - } -#endif #if TARGET_OS_EMBEDDED - if( g_embedded_privileged_action && s_embedded_privileged_job ) { - if( !job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL) ) { + if (g_embedded_privileged_action && s_embedded_privileged_job) { + if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) { errno = EPERM; return; } - if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + if (strcmp(j->username, s_embedded_privileged_job->username) != 0) { errno = EPERM; return; } - } else if( g_embedded_privileged_action ) { + } else if (g_embedded_privileged_action) { errno = EINVAL; return; } #endif + if (j->kill_via_shmem) { + if (j->shmem) { + if (!j->sent_kill_via_shmem) { + j->shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING; + newval = __sync_sub_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1); + j->sent_kill_via_shmem = true; + } else { + newval = j->shmem->vp_shmem_transaction_cnt; + } + } else { + newval = -1; + } + } + j->sent_signal_time = runtime_get_opaque_time(); if (newval < 0) { j->clean_kill = true; job_kill(j); } else { - /* - * If sampling is enabled and SAMPLE_TIMEOUT is earlier than the job exit_timeout, - * then set a timer for SAMPLE_TIMEOUT seconds after killing - */ - unsigned int exit_timeout = j->exit_timeout; - bool do_sample = do_apple_internal_logging; - unsigned int timeout = exit_timeout; + (void)job_assumes(j, runtime_kill(j->p, SIGTERM) != -1); - if (do_sample && (!exit_timeout || (LAUNCHD_SAMPLE_TIMEOUT < exit_timeout))) { - timeout = LAUNCHD_SAMPLE_TIMEOUT; - } - - job_assumes(j, runtime_kill(j->p, SIGTERM) != -1); - - if (timeout) { - j->sampling_complete = !do_sample; - job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, - EV_ADD|EV_ONESHOT, NOTE_SECONDS, timeout, j) != -1); - } - - if (!exit_timeout) { - job_log(j, LOG_DEBUG, "This job has an infinite exit timeout"); + if (j->exit_timeout) { + (void)job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, j->exit_timeout, j) != -1); + } else { + job_log(j, LOG_NOTICE, "This job has an infinite exit timeout"); } if (j->kill_via_shmem) { @@ -961,8 +1051,10 @@ jobmgr_log_active_jobs(jobmgr_t jm) } LIST_FOREACH(ji, &jm->jobs, sle) { - if( (why_active = job_active(ji)) ) { - job_log(ji, LOG_DEBUG | LOG_CONSOLE, "%s", why_active); + if ((why_active = job_active(ji))) { + if (ji->p != 1) { + job_log(ji, LOG_DEBUG | LOG_CONSOLE, "%s", why_active); + } } } } @@ -970,7 +1062,7 @@ jobmgr_log_active_jobs(jobmgr_t jm) static void jobmgr_still_alive_with_check(jobmgr_t jm) { - jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Still alive with %lu/%lu (normal/anonymous) children. In %s phase of shutdown.", total_children, total_anon_children, s_phases[jm->shutdown_phase]); + jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Still alive with %lu/%lu (normal/anonymous) children.", total_children, total_anon_children); jobmgr_log_active_jobs(jm); } @@ -980,6 +1072,22 @@ jobmgr_shutdown(jobmgr_t jm) jobmgr_t jmi, jmn; jobmgr_log(jm, LOG_DEBUG, "Beginning job manager shutdown with flags: %s", reboot_flags_to_C_names(jm->reboot_flags)); + jm->shutdown_time = runtime_get_wall_time() / USEC_PER_SEC; + + struct tm curtime; + (void)localtime_r(&jm->shutdown_time, &curtime); + + char date[26]; + (void)asctime_r(&curtime, date); + /* Trim the new line that asctime_r(3) puts there for some reason. */ + date[24] = 0; + + if (jm == root_jobmgr && pid1_magic) { + jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown begun at: %s", date); + } else { + jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown begun at: %s", date); + } + jm->shutting_down = true; SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { @@ -987,11 +1095,13 @@ jobmgr_shutdown(jobmgr_t jm) } if (jm->parentmgr == NULL && pid1_magic) { - jobmgr_assumes(jm, kevent_mod((uintptr_t)jm, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 5, jm)); - #if !TARGET_OS_EMBEDDED - /* Kill the update thread. */ - jobmgr_assumes(jm, __sync_sub_and_fetch(&g_sync_frequency, 30) == 0); - #endif + (void)jobmgr_assumes(jm, kevent_mod((uintptr_t)jm, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 5, jm)); + + /* Spawn the shutdown monitor. */ + if (_s_shutdown_monitor && !_s_shutdown_monitor->p) { + job_log(_s_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Starting shutdown monitor."); + job_dispatch(_s_shutdown_monitor, true); + } } return jobmgr_do_garbage_collection(jm); @@ -1010,20 +1120,60 @@ jobmgr_remove(jobmgr_t jm) } } - while( (ji = LIST_FIRST(&jm->jobs)) ) { - if( !ji->anonymous && ji->p ) { - job_log(ji, LOG_WARNING | LOG_CONSOLE, "Job has overstayed its welcome. Forcing removal."); + while ((ji = LIST_FIRST(&jm->jobs))) { + if (!ji->anonymous && !job_assumes(ji, ji->p == 0)) { ji->p = 0; } job_remove(ji); } if (jm->req_port) { - jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_port) == KERN_SUCCESS); + (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_port) == KERN_SUCCESS); } - if (jm->jm_port) { - jobmgr_assumes(jm, launchd_mport_close_recv(jm->jm_port) == KERN_SUCCESS); + (void)jobmgr_assumes(jm, launchd_mport_close_recv(jm->jm_port) == KERN_SUCCESS); + } + + if (jm->req_bsport) { + (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_bsport) == KERN_SUCCESS); + } + if (jm->req_excport) { + (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_excport) == KERN_SUCCESS); + } + if (jm->req_asport) { + (void)jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_asport) == KERN_SUCCESS); + } +#if !TARGET_OS_EMBEDDED + if (jm->req_rport) { + kern_return_t kr = xpc_call_wakeup(jm->req_rport, jm->error); + if (!(kr == KERN_SUCCESS || kr == MACH_SEND_INVALID_DEST)) { + /* If the originator went away, the reply port will be a dead name, + * and we expect this to fail. + */ + errno = kr; + (void)jobmgr_assumes(jm, kr == KERN_SUCCESS); + } + } +#endif + if (jm->req_ctx) { + (void)jobmgr_assumes(jm, vm_deallocate(mach_task_self(), jm->req_ctx, jm->req_ctx_sz) == KERN_SUCCESS); + } + + time_t ts = runtime_get_wall_time() / USEC_PER_SEC; + struct tm curtime; + (void)localtime_r(&ts, &curtime); + + char date[26]; + (void)asctime_r(&curtime, date); + date[24] = 0; + + time_t delta = ts - jm->shutdown_time; + if (jm == root_jobmgr && pid1_magic) { + jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown finished at: %s", date); + jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Userspace shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : ""); + } else { + jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown finished at: %s", date); + jobmgr_log(jm, LOG_DEBUG, "Job manager shutdown took approximately %ld second%s.", delta, (delta != 1) ? "s" : ""); } if (jm->parentmgr) { @@ -1032,9 +1182,10 @@ jobmgr_remove(jobmgr_t jm) } else if (pid1_magic) { eliminate_double_reboot(); launchd_log_vm_stats(); + jobmgr_log_stray_children(jm, true); jobmgr_log(root_jobmgr, LOG_NOTICE | LOG_CONSOLE, "About to call: reboot(%s).", reboot_flags_to_C_names(jm->reboot_flags)); runtime_closelog(); - jobmgr_assumes(jm, reboot(jm->reboot_flags) != -1); + (void)jobmgr_assumes(jm, reboot(jm->reboot_flags) != -1); } else { jobmgr_log(jm, LOG_DEBUG, "About to exit"); runtime_closelog(); @@ -1054,24 +1205,49 @@ job_remove(job_t j) struct machservice *ms; struct limititem *li; struct envitem *ei; - + + if (j->alias) { + /* HACK: Egregious code duplication. But as with machservice_delete(), + * job aliases can't (and shouldn't) have any complex behaviors + * associated with them. + */ + while ((ms = SLIST_FIRST(&j->machservices))) { + machservice_delete(j, ms, false); + } + + LIST_REMOVE(j, sle); + LIST_REMOVE(j, label_hash_sle); + free(j); + return; + } + #if TARGET_OS_EMBEDDED - if( g_embedded_privileged_action && s_embedded_privileged_job ) { - if( !job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL) ) { + if (g_embedded_privileged_action && s_embedded_privileged_job) { + if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) { errno = EPERM; return; } - if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + if (strcmp(j->username, s_embedded_privileged_job->username) != 0) { errno = EPERM; return; } - } else if( g_embedded_privileged_action ) { + } else if (g_embedded_privileged_action) { errno = EINVAL; return; } #endif + /* Do this BEFORE we check and see whether the job is still active. If we're a + * sub-job, we're being removed due to the parent job removing us. Therefore, the + * parent job will free itself after this call completes. So if we defer removing + * ourselves from the parent's list, we'll crash when we finally get around to it. + */ + if (j->dedicated_instance && !j->former_subjob) { + LIST_REMOVE(j, subjob_sle); + j->former_subjob = true; + } + if (unlikely(j->p)) { if (j->anonymous) { job_reap(j); @@ -1082,11 +1258,15 @@ job_remove(job_t j) j->removal_pending = true; job_stop(j); } + return; } } - job_dispatch_curious_jobs(j); + if (!j->removing) { + j->removing = true; + job_dispatch_curious_jobs(j); + } ipc_close_all_with_job(j); @@ -1104,23 +1284,19 @@ job_remove(job_t j) } if (!job_assumes(j, j->fork_fd == 0)) { - job_assumes(j, runtime_close(j->fork_fd) != -1); + (void)job_assumes(j, runtime_close(j->fork_fd) != -1); } if (j->stdin_fd) { - job_assumes(j, runtime_close(j->stdin_fd) != -1); + (void)job_assumes(j, runtime_close(j->stdin_fd) != -1); } if (!job_assumes(j, j->log_redirect_fd == 0)) { - job_assumes(j, runtime_close(j->log_redirect_fd) != -1); + (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1); } if (j->j_port) { - job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); - } - - if (!job_assumes(j, j->wait_reply_port == MACH_PORT_NULL)) { - job_assumes(j, launchd_mport_deallocate(j->wait_reply_port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); } while ((sg = SLIST_FIRST(&j->sockets))) { @@ -1147,6 +1323,37 @@ job_remove(job_t j) while ((w4r = SLIST_FIRST(&j->removal_watchers))) { waiting4removal_delete(j, w4r); } + + struct externalevent *eei = NULL; + while ((eei = LIST_FIRST(&j->events))) { + eventsystem_ping(); + externalevent_delete(eei); + } + +#if 0 + /* Event systems exist independently of an actual monitor job. They're + * created on-demand when a job has a LaunchEvents dictionary. So we + * really don't need to get rid of them. + */ + if (j->event_monitor) { + struct eventsystem *esi = NULL; + while ((esi = LIST_FIRST(&_s_event_systems))) { + eventsystem_delete(esi); + } + } +#else + if (false) { + /* Make gcc happy. */ + eventsystem_delete(NULL); + } + if (j->event_monitor) { + if (_s_event_update_port != MACH_PORT_NULL) { + (void)job_assumes(j, launchd_mport_deallocate(_s_event_update_port) == KERN_SUCCESS); + _s_event_update_port = MACH_PORT_NULL; + } + _s_event_monitor = NULL; + } +#endif if (j->prog) { free(j->prog); @@ -1193,34 +1400,50 @@ job_remove(job_t j) } if (j->start_interval) { runtime_del_weak_ref(); - job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); } if (j->poll_for_vfs_changes) { - job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); } - if( j->exit_timeout ) { + if (j->exit_timeout) { /* Not a big deal if this fails. It means that the timer's already been freed. */ kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); } - if( j->jetsam_properties ) { + if (j->jetsam_properties) { LIST_REMOVE(j, jetsam_sle); j->mgr->jetsam_jobs_cnt--; } - if( j->audit_session != MACH_PORT_NULL ) { - job_assumes(j, mach_port_deallocate(mach_task_self(), j->audit_session) == KERN_SUCCESS); + if (j->asport != MACH_PORT_NULL) { + (void)job_assumes(j, launchd_mport_deallocate(j->asport) == KERN_SUCCESS); } - if( !uuid_is_null(j->expected_audit_uuid) ) { + if (!uuid_is_null(j->expected_audit_uuid)) { LIST_REMOVE(j, needing_session_sle); } - if( j->embedded_special_privileges ) { + if (j->embedded_special_privileges) { s_embedded_privileged_job = NULL; } + if (j->shutdown_monitor) { + _s_shutdown_monitor = NULL; + } + if (j->workaround9359725) { + /* We may have forcibly removed this job by simulating an exit. If this + * is the case, we don't want to hear about these events anymore, lest + * we get a stale context pointer and crash trying to dereference it. + */ + kevent_mod((uintptr_t)j->p, EVFILT_PROC, EV_DELETE, 0, 0, NULL); + } kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); LIST_REMOVE(j, sle); LIST_REMOVE(j, label_hash_sle); + job_t ji = NULL; + job_t jit = NULL; + LIST_FOREACH_SAFE(ji, &j->subjobs, subjob_sle, jit) { + job_remove(ji); + } + job_log(j, LOG_DEBUG, "Removed"); free(j); @@ -1297,17 +1520,40 @@ job_setup_machport(job_t j) } if (!job_assumes(j, launchd_mport_notify_req(j->j_port, MACH_NOTIFY_NO_SENDERS) == KERN_SUCCESS)) { - job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); goto out_bad; } return true; out_bad2: - job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); out_bad: return false; } +kern_return_t +job_setup_exit_port(job_t j) +{ + kern_return_t kr = launchd_mport_create_recv(&j->exit_status_port); + if (!job_assumes(j, kr == KERN_SUCCESS)) { + return MACH_PORT_NULL; + } + + struct mach_port_limits limits = { + .mpl_qlimit = 1, + }; + kr = mach_port_set_attributes(mach_task_self(), j->exit_status_port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, sizeof(limits)); + (void)job_assumes(j, kr == KERN_SUCCESS); + + kr = launchd_mport_make_send_once(j->exit_status_port, &j->exit_status_dest); + if (!job_assumes(j, kr == KERN_SUCCESS)) { + (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS); + j->exit_status_port = MACH_PORT_NULL; + } + + return kr; +} + job_t job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond) { @@ -1348,25 +1594,10 @@ out_bad: return NULL; } -kern_return_t -job_handle_mpm_wait(job_t j, mach_port_t srp, int *waitstatus) -{ - if (j->p) { - j->wait_reply_port = srp; - return MIG_NO_REPLY; - } - - *waitstatus = j->last_exit_status; - - return 0; -} - job_t job_new_anonymous(jobmgr_t jm, pid_t anonpid) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, anonpid }; - struct kinfo_proc kp; - size_t len = sizeof(kp); + struct proc_bsdshortinfo proc; bool shutdown_state; job_t jp = NULL, jr = NULL; uid_t kp_euid, kp_uid, kp_svuid; @@ -1383,62 +1614,59 @@ job_new_anonymous(jobmgr_t jm, pid_t anonpid) return NULL; } - if (!jobmgr_assumes(jm, sysctl(mib, 4, &kp, &len, NULL, 0) != -1)) { - return NULL; - } - - if (unlikely(len != sizeof(kp))) { - jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for nonexistent PID: %u", anonpid); - errno = ESRCH; + /* libproc returns the number of bytes written into the buffer upon success, + * zero on failure. + */ + if (proc_pidinfo(anonpid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + (void)jobmgr_assumes(jm, errno == 0); + } return NULL; } - if (!jobmgr_assumes(jm, kp.kp_proc.p_comm[0] != '\0')) { + if (!jobmgr_assumes(jm, proc.pbsi_comm[0] != '\0')) { errno = EINVAL; return NULL; } - if (unlikely(kp.kp_proc.p_stat == SZOMB)) { - jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for zombie PID %u: %s", anonpid, kp.kp_proc.p_comm); + if (unlikely(proc.pbsi_status == SZOMB)) { + jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for zombie PID %u: %s", anonpid, proc.pbsi_comm); } - if (unlikely(kp.kp_proc.p_flag & P_SUGID)) { - jobmgr_log(jm, LOG_DEBUG, "Inconsistency: P_SUGID is set on PID %u: %s", anonpid, kp.kp_proc.p_comm); + if (unlikely(proc.pbsi_flags & P_SUGID)) { + jobmgr_log(jm, LOG_DEBUG, "Inconsistency: P_SUGID is set on PID %u: %s", anonpid, proc.pbsi_comm); } - kp_euid = kp.kp_eproc.e_ucred.cr_uid; - kp_uid = kp.kp_eproc.e_pcred.p_ruid; - kp_svuid = kp.kp_eproc.e_pcred.p_svuid; - kp_egid = kp.kp_eproc.e_ucred.cr_gid; - kp_gid = kp.kp_eproc.e_pcred.p_rgid; - kp_svgid = kp.kp_eproc.e_pcred.p_svgid; + kp_euid = proc.pbsi_uid; + kp_uid = proc.pbsi_ruid; + kp_svuid = proc.pbsi_svuid; + kp_egid = proc.pbsi_gid; + kp_gid = proc.pbsi_rgid; + kp_svgid = proc.pbsi_svgid; if (unlikely(kp_euid != kp_uid || kp_euid != kp_svuid || kp_uid != kp_svuid || kp_egid != kp_gid || kp_egid != kp_svgid || kp_gid != kp_svgid)) { jobmgr_log(jm, LOG_DEBUG, "Inconsistency: Mixed credentials (e/r/s UID %u/%u/%u GID %u/%u/%u) detected on PID %u: %s", - kp_euid, kp_uid, kp_svuid, kp_egid, kp_gid, kp_svgid, anonpid, kp.kp_proc.p_comm); + kp_euid, kp_uid, kp_svuid, kp_egid, kp_gid, kp_svgid, anonpid, proc.pbsi_comm); } /* "Fix" for a problem that shouldn't even exist. * See rdar://problem/7264615 for the symptom and rdar://problem/5020256 * as to why this can happen. */ - if( !jobmgr_assumes(jm, kp.kp_eproc.e_ppid != anonpid) ) { - jobmgr_log(jm, LOG_WARNING, "Process has become its own parent through ptrace(3). It should find a different way to do whatever it's doing. Setting PPID to 0: %s", kp.kp_proc.p_comm); + if (!jobmgr_assumes(jm, (pid_t)proc.pbsi_ppid != anonpid)) { + jobmgr_log(jm, LOG_WARNING, "Process has become its own parent through ptrace(3). It should find a different way to do whatever it's doing. Setting PPID to 0: %s", proc.pbsi_comm); errno = EINVAL; return NULL; } - if (jp && !jp->anonymous && unlikely(!(kp.kp_proc.p_flag & P_EXEC))) { - job_log(jp, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", - kp.kp_proc.p_pid); - } - /* A total hack: Normally, job_new() returns an error during shutdown, but anonymous jobs are special. */ if (unlikely(shutdown_state = jm->shutting_down)) { jm->shutting_down = false; } - if (jobmgr_assumes(jm, (jr = job_new(jm, AUTO_PICK_ANONYMOUS_LABEL, kp.kp_proc.p_comm, NULL)) != NULL)) { + /* We only set requestor_pid for XPC domains. */ + const char *whichlabel = (jm->req_pid == anonpid) ? AUTO_PICK_XPC_LABEL : AUTO_PICK_ANONYMOUS_LABEL; + if (jobmgr_assumes(jm, (jr = job_new(jm, whichlabel, proc.pbsi_comm, NULL)) != NULL)) { u_int proc_fflags = NOTE_EXEC|NOTE_FORK|NOTE_EXIT; total_anon_children++; @@ -1454,11 +1682,11 @@ job_new_anonymous(jobmgr_t jm, pid_t anonpid) jr->unload_at_mig_return = true; } - if (unlikely(shutdown_state && jm->hopefully_first_cnt == 0)) { + if (unlikely(shutdown_state)) { job_log(jr, LOG_SCOLDING, "This process showed up to the party while all the guests were leaving. Odds are that it will have a miserable time."); } - job_log(jr, LOG_DEBUG, "Created PID %u anonymously by PPID %u%s%s", anonpid, kp.kp_eproc.e_ppid, jp ? ": " : "", jp ? jp->label : ""); + job_log(jr, LOG_DEBUG, "Created PID %u anonymously by PPID %u%s%s", anonpid, proc.pbsi_ppid, jp ? ": " : "", jp ? jp->label : ""); } if (unlikely(shutdown_state)) { @@ -1471,7 +1699,7 @@ job_new_anonymous(jobmgr_t jm, pid_t anonpid) * tree (thereby making it not a tree anymore), we'll find the tracing parent PID of the * parent process, which is the child, when we go looking for it in jobmgr_find_by_pid(). */ - switch (kp.kp_eproc.e_ppid) { + switch (proc.pbsi_ppid) { case 0: /* the kernel */ break; @@ -1482,14 +1710,177 @@ job_new_anonymous(jobmgr_t jm, pid_t anonpid) } /* fall through */ default: - jp = jobmgr_find_by_pid(jm, kp.kp_eproc.e_ppid, true); - jobmgr_assumes(jm, jp != NULL); + jp = jobmgr_find_by_pid(jm, proc.pbsi_ppid, true); + if (jobmgr_assumes(jm, jp != NULL)) { + if (jp && !jp->anonymous && unlikely(!(proc.pbsi_flags & P_EXEC))) { + job_log(jp, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", proc.pbsi_pid); + } + } break; } return jr; } +job_t +job_new_subjob(job_t j, uuid_t identifier) +{ + char label[0]; + uuid_string_t idstr; + uuid_unparse(identifier, idstr); + size_t label_sz = snprintf(label, 0, "%s.%s", j->label, idstr); + + job_t nj = (struct job_s *)calloc(1, sizeof(struct job_s) + label_sz + 1); + if (launchd_assumes(nj != NULL)) { + nj->kqjob_callback = job_callback; + nj->mgr = j->mgr; + nj->min_run_time = j->min_run_time; + nj->timeout = j->timeout; + nj->exit_timeout = j->exit_timeout; + + snprintf((char *)nj->label, label_sz + 1, "%s.%s", j->label, idstr); + + /* Set all our simple Booleans that are applicable. */ + nj->debug = j->debug; + nj->ondemand = j->ondemand; + nj->checkedin = true; + nj->low_pri_io = j->low_pri_io; + nj->setmask = j->setmask; + nj->wait4debugger = j->wait4debugger; + nj->internal_exc_handler = j->internal_exc_handler; + nj->setnice = j->setnice; + nj->abandon_pg = j->abandon_pg; + nj->ignore_pg_at_shutdown = j->ignore_pg_at_shutdown; + nj->deny_job_creation = j->deny_job_creation; + nj->kill_via_shmem = j->kill_via_shmem; + nj->needs_kickoff = j->needs_kickoff; + nj->currently_ignored = true; + nj->dedicated_instance = true; + nj->xpc_service = j->xpc_service; + + nj->mask = j->mask; + uuid_copy(nj->instance_id, identifier); + + /* These jobs are purely on-demand Mach jobs. */ + + /* {Hard | Soft}ResourceLimits are not supported. */ + + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &j->machservices, sle) { + /* Only copy MachServices that were actually declared in the plist. + * So skip over per-PID ones and ones that were created via + * bootstrap_register(). + */ + if (msi->upfront) { + mach_port_t mp = MACH_PORT_NULL; + struct machservice *msj = machservice_new(nj, msi->name, &mp, msi->per_pid); + if (job_assumes(nj, msj != NULL)) { + msj->reset = msi->reset; + msj->delete_on_destruction = msi->delete_on_destruction; + msj->drain_one_on_crash = msi->drain_one_on_crash; + msj->drain_all_on_crash = msi->drain_all_on_crash; + } + } + } + + if (j->prog) { + nj->prog = strdup(j->prog); + } + if (j->argv) { + size_t sz = malloc_size(j->argv); + nj->argv = (char **)malloc(sz); + if (job_assumes(nj, nj->argv != NULL)) { + /* This is the start of our strings. */ + char *p = ((char *)nj->argv) + ((j->argc + 1) * sizeof(char *)); + + size_t i = 0; + for (i = 0; i < j->argc; i++) { + (void)strcpy(p, j->argv[i]); + nj->argv[i] = p; + p += (strlen(j->argv[i]) + 1); + } + nj->argv[i] = NULL; + } + + nj->argc = j->argc; + } + + /* We ignore global environment variables. */ + struct envitem *ei = NULL; + SLIST_FOREACH(ei, &j->env, sle) { + (void)job_assumes(nj, envitem_new(nj, ei->key, ei->value, false, false)); + } + uuid_string_t val; + uuid_unparse(identifier, val); + (void)job_assumes(nj, envitem_new(nj, LAUNCH_ENV_INSTANCEID, val, false, false)); + + if (j->rootdir) { + nj->rootdir = strdup(j->rootdir); + } + if (j->workingdir) { + nj->workingdir = strdup(j->workingdir); + } + if (j->username) { + nj->username = strdup(j->username); + } + if (j->groupname) { + nj->groupname = strdup(j->groupname); + } + /* FIXME: We shouldn't redirect all the output from these jobs to the same + * file. We should uniquify the file names. + */ + if (j->stdinpath) { + nj->stdinpath = strdup(j->stdinpath); + } + if (j->stdoutpath) { + nj->stdoutpath = strdup(j->stdinpath); + } + if (j->stderrpath) { + nj->stderrpath = strdup(j->stderrpath); + } + if (j->alt_exc_handler) { + nj->alt_exc_handler = strdup(j->alt_exc_handler); + } + #if HAVE_SANDBOX + if (j->seatbelt_profile) { + nj->seatbelt_profile = strdup(j->seatbelt_profile); + } + #endif + + #if HAVE_QUARANTINE + if (j->quarantine_data) { + nj->quarantine_data = strdup(j->quarantine_data); + } + nj->quarantine_data_sz = j->quarantine_data_sz; + #endif + if (j->j_binpref) { + size_t sz = malloc_size(j->j_binpref); + nj->j_binpref = (cpu_type_t *)malloc(sz); + if (job_assumes(nj, nj->j_binpref)) { + memcpy(&nj->j_binpref, &j->j_binpref, sz); + } + } + + /* JetsamPriority is unsupported. */ + + if (j->asport != MACH_PORT_NULL) { + (void)job_assumes(nj, launchd_mport_copy_send(j->asport) == KERN_SUCCESS); + nj->asport = j->asport; + } + + LIST_INSERT_HEAD(&nj->mgr->jobs, nj, sle); + + jobmgr_t where2put = root_jobmgr; + if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) { + where2put = j->mgr; + } + LIST_INSERT_HEAD(&where2put->label_hash[hash_label(nj->label)], nj, label_hash_sle); + LIST_INSERT_HEAD(&j->subjobs, nj, subjob_sle); + } + + return nj; +} + job_t job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv) { @@ -1514,7 +1905,7 @@ job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *arg return NULL; } - char *anon_or_legacy = ( label == AUTO_PICK_ANONYMOUS_LABEL ) ? "anonymous" : "mach_init"; + char *anon_or_legacy = (label == AUTO_PICK_ANONYMOUS_LABEL) ? "anonymous" : "mach_init"; if (unlikely(label == AUTO_PICK_LEGACY_LABEL || label == AUTO_PICK_ANONYMOUS_LABEL)) { if (prog) { bn = prog; @@ -1527,7 +1918,11 @@ job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *arg /* This is so we can do gross things later. See NOTE_EXEC for anonymous jobs */ minlabel_len = strlen(label) + MAXCOMLEN; } else { - minlabel_len = strlen(label); + if (label == AUTO_PICK_XPC_LABEL) { + minlabel_len = snprintf(auto_label, sizeof(auto_label), "com.apple.xpc.domain-owner.%s", jm->owner); + } else { + minlabel_len = strlen(label); + } } j = calloc(1, sizeof(struct job_s) + minlabel_len + 1); @@ -1539,7 +1934,7 @@ job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *arg if (unlikely(label == auto_label)) { snprintf((char *)j->label, strlen(label) + 1, "%p.%s.%s", j, anon_or_legacy, bn); } else { - strcpy((char *)j->label, label); + strcpy((char *)j->label, (label == AUTO_PICK_XPC_LABEL) ? auto_label : label); } j->kqjob_callback = job_callback; j->mgr = jm; @@ -1551,6 +1946,7 @@ job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *arg j->checkedin = true; j->jetsam_priority = -1; j->jetsam_memlimit = -1; + j->jetsam_seq = -1; uuid_clear(j->expected_audit_uuid); if (prog) { @@ -1585,12 +1981,17 @@ job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *arg j->argv[i] = NULL; } - if( strcmp(j->label, "com.apple.WindowServer") == 0 ) { + if (strcmp(j->label, "com.apple.WindowServer") == 0) { j->has_console = true; } LIST_INSERT_HEAD(&jm->jobs, j, sle); - LIST_INSERT_HEAD(&label_hash[hash_label(j->label)], j, label_hash_sle); + + jobmgr_t where2put_label = root_jobmgr; + if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) { + where2put_label = j->mgr; + } + LIST_INSERT_HEAD(&where2put_label->label_hash[hash_label(j->label)], j, label_hash_sle); uuid_clear(j->expected_audit_uuid); job_log(j, LOG_DEBUG, "Conceived"); @@ -1606,6 +2007,47 @@ out_bad: return NULL; } +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +job_t +job_new_alias(jobmgr_t jm, job_t src) +{ + job_t j = NULL; + if (job_find(jm, src->label)) { + errno = EEXIST; + } else { + j = calloc(1, sizeof(struct job_s) + strlen(src->label) + 1); + if (jobmgr_assumes(jm, j != NULL)) { + strcpy((char *)j->label, src->label); + LIST_INSERT_HEAD(&jm->jobs, j, sle); + LIST_INSERT_HEAD(&jm->label_hash[hash_label(j->label)], j, label_hash_sle); + /* Bad jump address. The kqueue callback for aliases should never be + * invoked. + */ + j->kqjob_callback = (kq_callback)0xfa1afe1; + j->alias = src; + j->mgr = jm; + + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &src->machservices, sle) { + if (!machservice_new_alias(j, msi)) { + jobmgr_log(jm, LOG_ERR, "Failed to alias job: %s", src->label); + errno = EINVAL; + job_remove(j); + j = NULL; + break; + } + } + } + + if (j) { + job_log(j, LOG_DEBUG, "Aliased service into domain: %s", jm->name); + } + } + + return j; +} +#endif + job_t job_import(launch_data_t pload) { @@ -1633,7 +2075,7 @@ job_import_bulk(launch_data_t pload) ja = alloca(c * sizeof(job_t)); for (i = 0; i < c; i++) { - if( (likely(ja[i] = jobmgr_import2(root_jobmgr, launch_data_array_get_index(pload, i)))) && errno != ENEEDAUTH ) { + if ((likely(ja[i] = jobmgr_import2(root_jobmgr, launch_data_array_get_index(pload, i)))) && errno != ENEEDAUTH) { errno = 0; } launch_data_array_set_index(resp, launch_data_new_errno(errno), i); @@ -1662,6 +2104,13 @@ job_import_bool(job_t j, const char *key, bool value) found_key = true; } break; + case 'b': + case 'B': + if (strcasecmp(key, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN) == 0) { + j->dirty_at_shutdown = value; + found_key = true; + } + break; case 'k': case 'K': if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) { @@ -1682,17 +2131,18 @@ job_import_bool(job_t j, const char *key, bool value) j->debug = value; found_key = true; } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED) == 0) { - job_assumes(j, !value); + (void)job_assumes(j, !value); + found_key = true; + } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLEASLR) == 0) { + j->disable_aslr = value; found_key = true; } break; case 'h': case 'H': if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSLAST) == 0) { - j->hopefully_exits_last = value; - found_key = true; - } else if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST) == 0) { - j->hopefully_exits_first = value; + job_log(j, LOG_INFO, "%s has been deprecated. Please use the new %s key instead and add EnableTransactions to your launchd.plist.", LAUNCH_JOBKEY_HOPEFULLYEXITSLAST, LAUNCH_JOBKEY_BEGINTRANSACTIONATSHUTDOWN); + j->dirty_at_shutdown = value; found_key = true; } break; @@ -1707,6 +2157,14 @@ job_import_bool(job_t j, const char *key, bool value) } else if (strcasecmp(key, LAUNCH_JOBKEY_SERVICEIPC) == 0) { /* this only does something on Mac OS X 10.4 "Tiger" */ found_key = true; + } else if (strcasecmp(key, LAUNCH_JOBKEY_SHUTDOWNMONITOR) == 0) { + if (_s_shutdown_monitor) { + job_log(j, LOG_ERR, "Only one job may monitor shutdown."); + } else { + j->shutdown_monitor = true; + _s_shutdown_monitor = j; + } + found_key = true; } break; case 'l': @@ -1724,6 +2182,9 @@ job_import_bool(job_t j, const char *key, bool value) if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) { j->internal_exc_handler = value; found_key = true; + } else if (strcasecmp(key, LAUNCH_JOBKEY_MULTIPLEINSTANCES) == 0) { + j->multiple_instances = value; + found_key = true; } break; case 'i': @@ -1735,7 +2196,7 @@ job_import_bool(job_t j, const char *key, bool value) } j->no_init_groups = !value; found_key = true; - } else if( strcasecmp(key, LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN) == 0 ) { + } else if (strcasecmp(key, LAUNCH_JOBKEY_IGNOREPROCESSGROUPATSHUTDOWN) == 0) { j->ignore_pg_at_shutdown = value; found_key = true; } @@ -1761,14 +2222,24 @@ job_import_bool(job_t j, const char *key, bool value) } else if (strcasecmp(key, LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL) == 0) { j->debug_before_kill = value; found_key = true; - } else if( strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDPRIVILEGEDISPENSATION) == 0 ) { - if( !s_embedded_privileged_job ) { + } 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; + } else if (strcasecmp(key, LAUNCH_JOBKEY_EVENTMONITOR) == 0) { + if (job_assumes(j, _s_event_monitor == NULL)) { + j->event_monitor = value; + if (value) { + _s_event_monitor = j; + } + } else { + job_log(j, LOG_NOTICE, "Job tried to steal event monitoring responsibility!"); + } + found_key = true; } break; case 'w': @@ -1803,6 +2274,21 @@ job_import_string(job_t j, const char *key, const char *value) case 'P': if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAM) == 0) { return; + } else if (strcasecmp(key, LAUNCH_JOBKEY_POSIXSPAWNTYPE) == 0) { + if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP) == 0) { + j->pstype = POSIX_SPAWN_OSX_TALAPP_START; + } else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET) == 0) { + j->pstype = POSIX_SPAWN_OSX_WIDGET_START; + } +#if TARGET_OS_EMBEDDED + else if (strcasecmp(value, LAUNCH_KEY_POSIXSPAWNTYPE_IOSAPP) == 0) { + j->pstype = POSIX_SPAWN_IOS_APP_START; + } +#endif + else { + job_log(j, LOG_ERR, "Unknown value for key %s: %s", key, value); + } + return; } break; case 'l': @@ -1868,9 +2354,9 @@ job_import_string(job_t j, const char *key, const char *value) j->stdin_fd = _fd(open(value, O_RDONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, DEFFILEMODE)); if (job_assumes(j, j->stdin_fd != -1)) { /* open() should not block, but regular IO by the job should */ - job_assumes(j, fcntl(j->stdin_fd, F_SETFL, 0) != -1); + (void)job_assumes(j, fcntl(j->stdin_fd, F_SETFL, 0) != -1); /* XXX -- EV_CLEAR should make named pipes happy? */ - job_assumes(j, kevent_mod(j->stdin_fd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, j) != -1); + (void)job_assumes(j, kevent_mod(j->stdin_fd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, j) != -1); } else { j->stdin_fd = 0; } @@ -1880,16 +2366,22 @@ job_import_string(job_t j, const char *key, const char *value) #endif } break; + case 'X': + case 'x': + if (strcasecmp(key, LAUNCH_JOBKEY_XPCDOMAIN) == 0) { + return; + } + break; default: job_log(j, LOG_WARNING, "Unknown key for string: %s", key); break; } if (likely(where2put)) { - job_assumes(j, (*where2put = strdup(value)) != NULL); + (void)job_assumes(j, (*where2put = strdup(value)) != NULL); } else { /* See rdar://problem/5496612. These two are okay. */ - if( strncmp(key, "SHAuthorizationRight", sizeof("SHAuthorizationRight")) != 0 && strncmp(key, "ServiceDescription", sizeof("ServiceDescription")) != 0 ) { + if (strncmp(key, "SHAuthorizationRight", sizeof("SHAuthorizationRight")) != 0 && strncmp(key, "ServiceDescription", sizeof("ServiceDescription")) != 0) { job_log(j, LOG_WARNING, "Unknown key: %s", key); } } @@ -1909,17 +2401,17 @@ job_import_integer(job_t j, const char *key, long long value) } else { j->exit_timeout = (typeof(j->exit_timeout)) value; } - } else if( strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY) == 0 ) { + } else if (strcasecmp(key, LAUNCH_JOBKEY_EMBEDDEDMAINTHREADPRIORITY) == 0) { j->main_thread_priority = value; } break; case 'j': case 'J': - if( strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0 ) { + if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0) { job_log(j, LOG_WARNING | LOG_CONSOLE, "Please change the JetsamPriority key to be in a dictionary named JetsamProperties."); launch_data_t pri = launch_data_new_integer(value); - if( job_assumes(j, pri != NULL) ) { + if (job_assumes(j, pri != NULL)) { jetsam_property_setup(pri, LAUNCH_JOBKEY_JETSAMPRIORITY, j); launch_data_free(pri); } @@ -1975,7 +2467,7 @@ job_import_integer(job_t j, const char *key, long long value) runtime_add_weak_ref(); j->start_interval = (typeof(j->start_interval)) value; - job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1); } #if HAVE_SANDBOX } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) { @@ -2009,9 +2501,9 @@ job_import_opaque(job_t j __attribute__((unused)), #endif case 's': case 'S': - if( strcasecmp(key, LAUNCH_JOBKEY_SECURITYSESSIONUUID) == 0 ) { + if (strcasecmp(key, LAUNCH_JOBKEY_SECURITYSESSIONUUID) == 0) { size_t tmpsz = launch_data_get_opaque_size(value); - if( job_assumes(j, tmpsz == sizeof(uuid_t)) ) { + if (job_assumes(j, tmpsz == sizeof(uuid_t))) { memcpy(j->expected_audit_uuid, launch_data_get_opaque(value), sizeof(uuid_t)); } } @@ -2074,7 +2566,7 @@ job_import_dictionary(job_t j, const char *key, launch_data_t value) break; case 'j': case 'J': - if( strcasecmp(key, LAUNCH_JOBKEY_JETSAMPROPERTIES) == 0 ) { + if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPROPERTIES) == 0) { launch_data_dict_iterate(value, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, j); } case 'e': @@ -2119,6 +2611,19 @@ job_import_dictionary(job_t j, const char *key, launch_data_t value) launch_data_dict_iterate(value, machservice_setup, j); } break; + case 'l': + case 'L': + if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHEVENTS) == 0) { + launch_data_dict_iterate(value, eventsystem_setup, j); + } else { + if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHARDWARE) == 0) { + return; + } + if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHARDWARE) == 0) { + return; + } + } + break; default: job_log(j, LOG_WARNING, "Unknown key for dictionary: %s", key); break; @@ -2236,7 +2741,7 @@ job_import_keys(launch_data_t obj, const char *key, void *context) } } -job_t +job_t jobmgr_import2(jobmgr_t jm, launch_data_t pload) { launch_data_t tmp, ldpa; @@ -2270,30 +2775,30 @@ jobmgr_import2(jobmgr_t jm, launch_data_t pload) } #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))) ) { + 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) ) { + if (likely(tmp && launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) { username = launch_data_get_string(tmp); } else { errno = EPERM; return NULL; } - if( !jobmgr_assumes(jm, s_embedded_privileged_job->username != NULL && username != NULL) ) { + if (!jobmgr_assumes(jm, s_embedded_privileged_job->username != NULL && username != NULL)) { errno = EPERM; return NULL; } - if( unlikely(strcmp(s_embedded_privileged_job->username, username) != 0) ) { + if (unlikely(strcmp(s_embedded_privileged_job->username, username) != 0)) { errno = EPERM; return NULL; } - } else if( g_embedded_privileged_action ) { + } else if (g_embedded_privileged_action) { errno = EINVAL; return NULL; } @@ -2304,6 +2809,7 @@ jobmgr_import2(jobmgr_t jm, launch_data_t pload) prog = launch_data_get_string(tmp); } + int argc = 0; if ((ldpa = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) { size_t i, c; @@ -2328,27 +2834,68 @@ jobmgr_import2(jobmgr_t jm, launch_data_t pload) } argv[i] = NULL; + argc = i; } - /* Hack to make sure the proper job manager is set the whole way through. */ + if (!prog && argc == 0) { + jobmgr_log(jm, LOG_ERR, "Job specifies neither Program nor ProgramArguments: %s", label); + errno = EINVAL; + return NULL; + } + + /* Find the requested session. You cannot load services into XPC domains in + * this manner. + */ launch_data_t session = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE); - if( session ) { - jm = jobmgr_find_by_name(jm, launch_data_get_string(session)) ?: jm; + if (session) { + jobmgr_t jmt = NULL; + if (launch_data_get_type(session) == LAUNCH_DATA_STRING) { + jmt = jobmgr_find_by_name(jm, launch_data_get_string(session)); + if (!jmt) { + jobmgr_log(jm, LOG_ERR, "Could not find requested session: %s", launch_data_get_string(session)); + } else { + jm = jmt; + } + } else { + jobmgr_log(jm, LOG_ERR, "Session type is not a string."); + } + + if (!jmt) { + errno = EINVAL; + return NULL; + } } - - jobmgr_log(jm, LOG_DEBUG, "Importing %s.", label); - - if (unlikely((j = job_find(label)) != NULL)) { - errno = EEXIST; - return NULL; - } else if (unlikely(!jobmgr_label_test(jm, label))) { + + /* For legacy reasons, we have a global hash of all labels in all job + * managers. So rather than make it a global, we store it in the root job + * manager. But for an XPC domain, we store a local hash of all services in + * the domain. + */ + jobmgr_t where2look = (jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) ? jm : root_jobmgr; + if (unlikely((j = job_find(where2look, label)) != NULL)) { + if (jm->xpc_singleton) { + /* There can (and probably will be) multiple attemtps to import the + * same XPC service from the same framework. This is okay. It's + * treated as a singleton, so just return the existing one so that + * it may be aliased into the requesting process' XPC domain. + */ + return j; + } else { + /* If we're not a global XPC domain, then it's an error to try + * importing the same job/service multiple times. + */ + errno = EEXIST; + return NULL; + } + } else if (unlikely(!jobmgr_label_test(where2look, label))) { errno = EINVAL; return NULL; } + jobmgr_log(jm, LOG_DEBUG, "Importing %s.", label); if (likely(j = job_new(jm, label, prog, argv))) { launch_data_dict_iterate(pload, job_import_keys, j); - if( !uuid_is_null(j->expected_audit_uuid) ) { + if (!uuid_is_null(j->expected_audit_uuid)) { uuid_string_t uuid_str; uuid_unparse(j->expected_audit_uuid, uuid_str); job_log(j, LOG_DEBUG, "Imported job. Waiting for session for UUID %s.", uuid_str); @@ -2356,7 +2903,30 @@ jobmgr_import2(jobmgr_t jm, launch_data_t pload) errno = ENEEDAUTH; } else { job_log(j, LOG_DEBUG, "No security session specified."); - j->audit_session = MACH_PORT_NULL; + j->asport = MACH_PORT_NULL; + } + + if (j->event_monitor) { + if (job_assumes(j, LIST_FIRST(&j->events) == NULL)) { + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &j->machservices, sle) { + if (msi->event_update_port) { + break; + } + } + + if (job_assumes(j, msi != NULL)) { + /* Create our send-once right so we can kick things off. */ + (void)job_assumes(j, launchd_mport_make_send_once(msi->port, &_s_event_update_port) == KERN_SUCCESS); + if (!LIST_EMPTY(&_s_event_systems)) { + eventsystem_ping(); + } + } + } else { + job_log(j, LOG_ERR, "The event monitor job may not have a LaunchEvents dictionary."); + job_remove(j); + j = NULL; + } } } @@ -2398,11 +2968,15 @@ jobmgr_label_test(jobmgr_t jm, const char *str) } job_t -job_find(const char *label) +job_find(jobmgr_t jm, const char *label) { job_t ji; - - LIST_FOREACH(ji, &label_hash[hash_label(label)], label_hash_sle) { + + if (!jm) { + jm = root_jobmgr; + } + + LIST_FOREACH(ji, &jm->label_hash[hash_label(label)], label_hash_sle) { if (unlikely(ji->removal_pending || ji->mgr->shutting_down)) { continue; /* 5351245 and 5488633 respectively */ } @@ -2421,15 +2995,15 @@ job_t jobmgr_find_by_pid_deep(jobmgr_t jm, pid_t p, bool anon_okay) { job_t ji = NULL; - LIST_FOREACH( ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle ) { + LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) { if (ji->p == p && (!ji->anonymous || (ji->anonymous && anon_okay)) ) { return ji; } } jobmgr_t jmi = NULL; - SLIST_FOREACH( jmi, &jm->submgrs, sle ) { - if( (ji = jobmgr_find_by_pid_deep(jmi, p, anon_okay)) ) { + SLIST_FOREACH(jmi, &jm->submgrs, sle) { + if ((ji = jobmgr_find_by_pid_deep(jmi, p, anon_okay))) { break; } } @@ -2487,15 +3061,13 @@ job_mig_intran(mach_port_t p) jr = job_mig_intran2(root_jobmgr, p, ldc->pid); if (!jobmgr_assumes(root_jobmgr, jr != NULL)) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; - struct kinfo_proc kp; - size_t len = sizeof(kp); - - mib[3] = ldc->pid; - - if (jobmgr_assumes(root_jobmgr, sysctl(mib, 4, &kp, &len, NULL, 0) != -1) - && jobmgr_assumes(root_jobmgr, len == sizeof(kp))) { - jobmgr_log(root_jobmgr, LOG_ERR, "%s() was confused by PID %u UID %u EUID %u Mach Port 0x%x: %s", __func__, ldc->pid, ldc->uid, ldc->euid, p, kp.kp_proc.p_comm); + struct proc_bsdshortinfo proc; + if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + (void)jobmgr_assumes(root_jobmgr, errno == 0); + } else { + jobmgr_log(root_jobmgr, LOG_ERR, "%s() was confused by PID %u UID %u EUID %u Mach Port 0x%x: %s", __func__, ldc->pid, ldc->uid, ldc->euid, p, proc.pbsi_comm); + } } } @@ -2569,42 +3141,48 @@ job_export_all(void) void job_log_stray_pg(job_t j) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, j->p }; - size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); - struct kinfo_proc *kp; - + pid_t *pids = NULL; + size_t len = sizeof(pid_t) * get_kern_max_proc(); + int i = 0, kp_cnt = 0; + if (!do_apple_internal_logging) { return; } runtime_ktrace(RTKT_LAUNCHD_FINDING_STRAY_PG, j->p, 0, 0); - if (!job_assumes(j, (kp = malloc(len)) != NULL)) { + if (!job_assumes(j, (pids = malloc(len)) != NULL)) { return; } - if (!job_assumes(j, sysctl(mib, 4, kp, &len, NULL, 0) != -1)) { + if (!job_assumes(j, (kp_cnt = proc_listpgrppids(j->p, pids, len)) != -1)) { goto out; } - - kp_cnt = len / sizeof(struct kinfo_proc); - + for (i = 0; i < kp_cnt; i++) { - pid_t p_i = kp[i].kp_proc.p_pid; - pid_t pp_i = kp[i].kp_eproc.e_ppid; - const char *z = (kp[i].kp_proc.p_stat == SZOMB) ? "zombie " : ""; - const char *n = kp[i].kp_proc.p_comm; - + pid_t p_i = pids[i]; if (p_i == j->p) { continue; } else if (!job_assumes(j, p_i != 0 && p_i != 1)) { continue; } + + struct proc_bsdshortinfo proc; + if (proc_pidinfo(p_i, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + job_assumes(j, errno == 0); + } + continue; + } + + pid_t pp_i = proc.pbsi_ppid; + const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : ""; + const char *n = proc.pbsi_comm; - job_log(j, LOG_WARNING, "Stray %sprocess with PGID equal to this dead job: PID %u PPID %u %s", z, p_i, pp_i, n); + job_log(j, LOG_WARNING, "Stray %sprocess with PGID equal to this dead job: PID %u PPID %u PGID %u %s", z, p_i, pp_i, proc.pbsi_pgid, n); } out: - free(kp); + free(pids); } void @@ -2618,7 +3196,7 @@ job_reap(job_t j) job_log(j, LOG_DEBUG, "Reaping"); if (j->shmem) { - job_assumes(j, vm_deallocate(mach_task_self(), (vm_address_t)j->shmem, getpagesize()) == 0); + (void)job_assumes(j, vm_deallocate(mach_task_self(), (vm_address_t)j->shmem, getpagesize()) == 0); j->shmem = NULL; } @@ -2631,13 +3209,13 @@ job_reap(job_t j) job_log_stdouterr(j); /* one last chance */ if (j->log_redirect_fd) { - job_assumes(j, runtime_close(j->log_redirect_fd) != -1); + (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1); j->log_redirect_fd = 0; } } if (j->fork_fd) { - job_assumes(j, runtime_close(j->fork_fd) != -1); + (void)job_assumes(j, runtime_close(j->fork_fd) != -1); j->fork_fd = 0; } @@ -2655,28 +3233,40 @@ job_reap(job_t j) #ifdef __LP64__ job_log(j, LOG_APPLEONLY, "Bug: 5487498"); #else - job_assumes(j, false); + (void)job_assumes(j, false); #endif } } - - /* - * 5020256 + + /* We have to work around one of two kernel bugs here. ptrace(3) may + * have abducted the child away from us and reparented it to the tracing + * process. If the process then exits, we still get NOTE_EXIT, but we + * cannot reap it because the kernel may not have restored the true + * parent/child relationship in time. + * + * See . * - * The current implementation of ptrace() causes the traced process to - * be abducted away from the true parent and adopted by the tracer. + * The other bug is if the shutdown monitor has suspended a task and not + * resumed it before exiting. In this case, the kernel will not clean up + * after the shutdown monitor. It will, instead, leave the task + * task suspended and not process any pending signals on the event loop + * for the task. * - * Once the tracing process relinquishes control, the kernel then - * restores the true parent/child relationship. + * There are a variety of other kernel bugs that could prevent a process + * from exiting, usually having to do with faulty hardware or talking to + * misbehaving drivers that mark a thread as uninterruptible and + * deadlock/hang before unmarking it as such. So we have to work around + * that too. * - * Unfortunately, the wait*() family of APIs is unaware of the temporarily - * data structures changes, and they return an error if reality hasn't - * been restored by the time they are called. + * See . */ - if (!job_assumes(j, wait4(j->p, &status, 0, &ru) != -1)) { - job_log(j, LOG_NOTICE, "Working around 5020256. Assuming the job crashed."); - - status = W_EXITCODE(0, SIGSEGV); + if (j->workaround9359725) { + job_log(j, LOG_NOTICE, "Simulated exit: "); + status = W_EXITCODE(-1, SIGSEGV); + memset(&ru, 0, sizeof(ru)); + } else if (wait4(j->p, &status, 0, &ru) == -1) { + job_log(j, LOG_NOTICE, "Assuming job exited: : %d: %s", errno, strerror(errno)); + status = W_EXITCODE(-1, SIGSEGV); memset(&ru, 0, sizeof(ru)); } } @@ -2687,18 +3277,6 @@ job_reap(job_t j) LIST_REMOVE(j, pid_hash_sle); - if (j->wait_reply_port) { - job_log(j, LOG_DEBUG, "MPM wait reply being sent"); - job_assumes(j, job_mig_wait_reply(j->wait_reply_port, 0, status) == 0); - j->wait_reply_port = MACH_PORT_NULL; - } - - if( j->pending_sample ) { - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job exited before we could sample it."); - STAILQ_REMOVE(&j->mgr->pending_samples, j, job_s, pending_samples_sle); - j->pending_sample = false; - } - if (j->sent_signal_time) { uint64_t td_sec, td_usec, td = runtime_get_nanoseconds_since(j->sent_signal_time); @@ -2726,35 +3304,42 @@ job_reap(job_t j) j->ru.ru_nivcsw += ru.ru_nivcsw; if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { - job_log(j, LOG_WARNING, "Exited with exit code: %d", WEXITSTATUS(status)); + int level = LOG_WARNING; + if (!j->did_exec && (j->fail_cnt++ % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) { + level = LOG_DEBUG; + } + + job_log(j, level, "Exited with code: %d", WEXITSTATUS(status)); + } else { + j->fail_cnt = 0; } if (WIFSIGNALED(status)) { int s = WTERMSIG(status); if ((SIGKILL == s || SIGTERM == s) && !j->stopped) { job_log(j, LOG_NOTICE, "Exited: %s", strsignal(s)); - } else if( !j->stopped && !j->clean_kill ) { - switch( s ) { - /* Signals which indicate a crash. */ - case SIGILL : - case SIGABRT : - case SIGFPE : - case SIGBUS : - case SIGSEGV : - case SIGSYS : - /* If the kernel has posted NOTE_EXIT and the signal sent to the process was - * SIGTRAP, assume that it's a crash. - */ - case SIGTRAP : - j->crashed = true; - job_log(j, LOG_WARNING, "Job appears to have crashed: %s", strsignal(s)); - break; - default : - job_log(j, LOG_WARNING, "Exited abnormally: %s", strsignal(s)); - break; + } else if (!j->stopped && !j->clean_kill) { + switch (s) { + /* Signals which indicate a crash. */ + case SIGILL: + case SIGABRT: + case SIGFPE: + case SIGBUS: + case SIGSEGV: + case SIGSYS: + /* If the kernel has posted NOTE_EXIT and the signal sent to the process was + * SIGTRAP, assume that it's a crash. + */ + case SIGTRAP: + j->crashed = true; + job_log(j, LOG_WARNING, "Job appears to have crashed: %s", strsignal(s)); + break; + default: + job_log(j, LOG_WARNING, "Exited abnormally: %s", strsignal(s)); + break; } - if( is_system_bootstrapper && j->crashed ) { + if (is_system_bootstrapper && j->crashed) { job_log(j, LOG_ERR | LOG_CONSOLE, "The %s bootstrapper has crashed: %s", j->mgr->name, strsignal(s)); } } @@ -2763,37 +3348,79 @@ job_reap(job_t j) j->reaped = true; struct machservice *msi = NULL; - if( j->crashed || !(j->did_exec || j->anonymous) ) { - SLIST_FOREACH( msi, &j->machservices, sle ) { - if( j->crashed && !msi->isActive && (msi->drain_one_on_crash || msi->drain_all_on_crash) ) { + if (j->crashed || !(j->did_exec || j->anonymous)) { + SLIST_FOREACH(msi, &j->machservices, sle) { + if (j->crashed && !msi->isActive && (msi->drain_one_on_crash || msi->drain_all_on_crash)) { machservice_drain_port(msi); } - if( !j->did_exec && msi->reset && job_assumes(j, !msi->isActive) ) { + if (!j->did_exec && msi->reset && job_assumes(j, !msi->isActive)) { machservice_resetport(j, msi); } } } - + + /* HACK: Essentially duplicating the logic directly above. But this has + * gotten really hairy, and I don't want to try consolidating it right now. + */ + if (j->xpc_service && !j->xpcproxy_did_exec) { + job_log(j, LOG_ERR, "XPC Service could not exec(3). Resetting port."); + SLIST_FOREACH(msi, &j->machservices, sle) { + /* Drain the messages but do not reset the port. If xpcproxy could + * not exec(3), then we don't want to continue trying, since there + * is very likely a serious configuration error with the service. + * + * + */ + machservice_resetport(j, msi); + } + } + struct suspended_peruser *spi = NULL; - while( (spi = LIST_FIRST(&j->suspended_perusers)) ) { + while ((spi = LIST_FIRST(&j->suspended_perusers))) { job_log(j, LOG_ERR, "Job exited before resuming per-user launchd for UID %u. Will forcibly resume.", spi->j->mach_uid); spi->j->peruser_suspend_count--; - if( spi->j->peruser_suspend_count == 0 ) { + if (spi->j->peruser_suspend_count == 0) { job_dispatch(spi->j, false); } LIST_REMOVE(spi, sle); free(spi); } - - struct waiting_for_exit *w4e = NULL; - while( (w4e = LIST_FIRST(&j->exit_watchers)) ) { - waiting4exit_delete(j, w4e); + + j->last_exit_status = status; + + if (j->exit_status_dest) { + errno = helper_downcall_wait(j->exit_status_dest, j->last_exit_status); + if (errno && errno != MACH_SEND_INVALID_DEST) { + (void)job_assumes(j, errno == 0); + } + + j->exit_status_dest = MACH_PORT_NULL; } - + + if (j->spawn_reply_port) { + /* If the child never called exec(3), we must send a spawn() reply so + * that the requestor can get exit status from it. If we fail to send + * the reply for some reason, we have to deallocate the exit status port + * ourselves. + */ + kern_return_t kr = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port); + if (kr) { + if (kr != MACH_SEND_INVALID_DEST) { + errno = kr; + (void)job_assumes(j, errno == KERN_SUCCESS); + } + + (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS); + } + + j->exit_status_port = MACH_PORT_NULL; + j->spawn_reply_port = MACH_PORT_NULL; + } + if (j->anonymous) { total_anon_children--; - if( j->holds_ref ) { + if (j->holds_ref) { runtime_del_ref(); } } else { @@ -2801,30 +3428,44 @@ job_reap(job_t j) total_children--; } - if( j->has_console ) { + if (j->has_console) { g_wsp = 0; } + + if (j->shutdown_monitor) { + job_log(j, LOG_NOTICE | LOG_CONSOLE, "Shutdown monitor has exited."); + _s_shutdown_monitor = NULL; + j->shutdown_monitor = false; + } + + if (j->event_monitor && !j->mgr->shutting_down) { + msi = NULL; + SLIST_FOREACH(msi, &j->machservices, sle) { + if (msi->event_update_port) { + break; + } + } + /* Only do this if we've gotten the port-destroyed notification already. + * If we haven't yet, the port destruction handler will do this. + */ + if (job_assumes(j, msi != NULL) && !msi->isActive) { + if (_s_event_update_port == MACH_PORT_NULL) { + (void)job_assumes(j, launchd_mport_make_send_once(msi->port, &_s_event_update_port) == KERN_SUCCESS); + } + eventsystem_ping(); + } + } - if (j->hopefully_exits_first) { - j->mgr->hopefully_first_cnt--; - } else if (!j->anonymous && !j->hopefully_exits_last) { + if (!j->anonymous) { j->mgr->normal_active_cnt--; } - j->last_exit_status = status; j->sent_signal_time = 0; j->sent_sigkill = false; j->clean_kill = false; - j->sampling_complete = false; j->sent_kill_via_shmem = false; j->lastlookup = NULL; j->lastlookup_gennum = 0; j->p = 0; - - /* - * We need to someday evaluate other jobs and find those who wish to track the - * active/inactive state of this job. The current job_dispatch() logic makes - * this messy, given that jobs can be deleted at dispatch. - */ } void @@ -2850,158 +3491,26 @@ jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack) } } -pid_t -basic_spawn(job_t j, void (*what_to_do)(job_t)) -{ - pid_t p = 0; - thread_state_flavor_t f = 0; -#if defined (__ppc__) || defined(__ppc64__) - f = PPC_THREAD_STATE64; -#elif defined(__i386__) || defined(__x86_64__) - f = x86_THREAD_STATE; -#elif defined(__arm__) - f = ARM_THREAD_STATE; -#else - #error "unknown architecture" -#endif - - int execpair[2] = { 0, 0 }; - job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execpair) != -1); - - switch( (p = fork()) ) { - case 0 : - job_assumes(j, runtime_close(execpair[0]) != -1); - /* Wait for the parent to attach a kevent. */ - read(_fd(execpair[1]), &p, sizeof(p)); - what_to_do(j); - _exit(EXIT_FAILURE); - case -1 : - job_assumes(j, runtime_close(execpair[0]) != -1); - job_assumes(j, runtime_close(execpair[1]) != -1); - execpair[0] = -1; - execpair[1] = -1; - job_log(j, LOG_NOTICE | LOG_CONSOLE, "fork(2) failed: %d", errno); - break; - default : - job_assumes(j, runtime_close(execpair[1]) != -1); - execpair[1] = -1; - break; - } - - int r = -1; - if( p != -1 ) { - /* Let us know when sample is done. ONESHOT is implicit if we're just interested in NOTE_EXIT. */ - if( job_assumes(j, (r = kevent_mod(p, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j)) != -1) ) { - if( !job_assumes(j, write(execpair[0], &p, sizeof(p)) == sizeof(p)) ) { - job_assumes(j, kevent_mod(p, EVFILT_PROC, EV_DELETE, 0, 0, NULL) != -1); - job_assumes(j, runtime_kill(p, SIGKILL) != -1); - r = -1; - p = -1; - } - } else { - job_assumes(j, runtime_kill(p, SIGKILL) != -1); - } - - int status = 0; - if( r == -1 ) { - job_assumes(j, waitpid(p, &status, WNOHANG) != -1); - } - } - - if( execpair[0] != -1 ) { - job_assumes(j, runtime_close(execpair[0]) != -1); - } - - if( execpair[1] != -1 ) { - job_assumes(j, runtime_close(execpair[0]) != -1); - } - - return p; -} - -void -take_sample(job_t j) -{ - char pidstr[32]; - snprintf(pidstr, sizeof(pidstr), "%u", j->p); -#if !TARGET_OS_EMBEDDED - /* -nodsyms so sample doesn't try to use Spotlight to find dsym files after mds has gone away. */ - char *sample_args[] = { "/usr/bin/sample", pidstr, "1", "-unsupportedShowArch", "-mayDie", "-nodsyms", "-file", j->mgr->sample_log_file, NULL }; -#else - char *sample_args[] = { "/usr/bin/sample", pidstr, "1", "-unsupportedShowArch", "-mayDie", "-file", j->mgr->sample_log_file, NULL }; -#endif - - execve(sample_args[0], sample_args, environ); - _exit(EXIT_FAILURE); -} - -void -jobmgr_dequeue_next_sample(jobmgr_t jm) -{ - if( STAILQ_EMPTY(&jm->pending_samples) ) { - jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Sample queue is empty."); - return; - } - - /* Dequeue the next in line. */ - job_t j = STAILQ_FIRST(&jm->pending_samples); - if( j->is_being_sampled ) { - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Sampling is in progress. Not dequeuing next job."); - return; - } - - if( !job_assumes(j, !j->sampling_complete) ) { - return; - } - - if (!job_assumes(j, do_apple_internal_logging)) { - return; - } - - if (!job_assumes(j, mkdir(SHUTDOWN_LOG_DIR, S_IRWXU) != -1 || errno == EEXIST)) { - return; - } - - char pidstr[32]; - snprintf(pidstr, sizeof(pidstr), "%u", j->p); - snprintf(j->mgr->sample_log_file, sizeof(j->mgr->sample_log_file), SHUTDOWN_LOG_DIR "/%s-%u.sample.txt", j->label, j->p); - - if (job_assumes(j, unlink(jm->sample_log_file) != -1 || errno == ENOENT)) { - pid_t sp = basic_spawn(j, take_sample); - - if( sp == -1 ) { - job_log(j, LOG_ERR | LOG_CONSOLE, "Sampling for job failed!"); - STAILQ_REMOVE(&jm->pending_samples, j, job_s, pending_samples_sle); - j->sampling_complete = true; - jobmgr_dequeue_next_sample(jm); - } else { - j->tracing_pid = sp; - j->is_being_sampled = true; - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Sampling job (sample PID: %i, file: %s).", sp, j->mgr->sample_log_file); - } - } else { - STAILQ_REMOVE(&jm->pending_samples, j, job_s, pending_samples_sle); - j->sampling_complete = true; - } - - j->pending_sample = false; -} - void job_dispatch_curious_jobs(job_t j) { job_t ji = NULL, jt = NULL; - SLIST_FOREACH_SAFE( ji, &s_curious_jobs, curious_jobs_sle, jt ) { + SLIST_FOREACH_SAFE(ji, &s_curious_jobs, curious_jobs_sle, jt) { struct semaphoreitem *si = NULL; - SLIST_FOREACH( si, &ji->semaphores, sle ) { - if( !(si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED) ) { + SLIST_FOREACH(si, &ji->semaphores, sle) { + if (!(si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED)) { continue; } - if( strncmp(si->what, j->label, strlen(j->label)) == 0 ) { + if (strcmp(si->what, j->label) == 0) { job_log(ji, LOG_DEBUG, "Dispatching out of interest in \"%s\".", j->label); - job_dispatch(ji, false); + if (!ji->removing) { + job_dispatch(ji, false); + } else { + job_log(ji, LOG_NOTICE, "The following job is circularly dependent upon this one: %s", j->label); + } + /* ji could be removed here, so don't do anything with it or its semaphores * after this point. */ @@ -3015,22 +3524,25 @@ job_t job_dispatch(job_t j, bool kickstart) { /* Don't dispatch a job if it has no audit session set. */ - if( !uuid_is_null(j->expected_audit_uuid) ) { + if (!uuid_is_null(j->expected_audit_uuid)) { return NULL; } + if (j->alias) { + j = j->alias; + } #if TARGET_OS_EMBEDDED - if( g_embedded_privileged_action && s_embedded_privileged_job ) { - if( !job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL) ) { + if (g_embedded_privileged_action && s_embedded_privileged_job) { + if (!job_assumes(j, s_embedded_privileged_job->username != NULL && j->username != NULL)) { errno = EPERM; return NULL; } - if( strcmp(j->username, s_embedded_privileged_job->username) != 0 ) { + if (strcmp(j->username, s_embedded_privileged_job->username) != 0) { errno = EPERM; return NULL; } - } else if( g_embedded_privileged_action ) { + } else if (g_embedded_privileged_action) { errno = EINVAL; return NULL; } @@ -3049,7 +3561,7 @@ job_dispatch(job_t j, bool kickstart) job_remove(j); return NULL; } - if( unlikely(j->per_user && j->peruser_suspend_count > 0) ) { + if (unlikely(j->per_user && j->peruser_suspend_count > 0)) { return NULL; } @@ -3107,7 +3619,7 @@ job_log_stdouterr(job_t j) job_log(j, LOG_DEBUG, "Standard out/error pipe closed"); close_log_redir = true; } else if (rsz == -1) { - if( !job_assumes(j, errno == EAGAIN) ) { + if (!job_assumes(j, errno == EAGAIN)) { close_log_redir = true; } } else { @@ -3123,7 +3635,7 @@ job_log_stdouterr(job_t j) free(buf); if (unlikely(close_log_redir)) { - job_assumes(j, runtime_close(j->log_redirect_fd) != -1); + (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1); j->log_redirect_fd = 0; job_dispatch(j, false); } @@ -3136,105 +3648,99 @@ job_kill(job_t j) return; } - job_assumes(j, runtime_kill(j->p, SIGKILL) != -1); + (void)job_assumes(j, runtime_kill(j->p, SIGKILL) != -1); j->sent_sigkill = true; - - intptr_t timer = j->clean_kill ? LAUNCHD_CLEAN_KILL_TIMER : LAUNCHD_SIGKILL_TIMER; - job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, timer, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, LAUNCHD_SIGKILL_TIMER, j) != -1); job_log(j, LOG_DEBUG, "Sent SIGKILL signal"); } void -job_log_children_without_exec(job_t j) +job_open_shutdown_transaction(job_t j) { - /* ER: Add a KERN_PROC_PPID sysctl */ -#ifdef KERN_PROC_PPID - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PPID, j->p }; -#else - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; -#endif - size_t mib_sz = sizeof(mib) / sizeof(mib[0]); - size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); - struct kinfo_proc *kp; + if (j->kill_via_shmem) { + if (j->shmem) { + job_log(j, LOG_DEBUG, "Opening shutdown transaction for job."); + (void)__sync_add_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1); + } else { + job_log(j, LOG_DEBUG, "Job wants to be dirty at shutdown, but it has not set up shared memory. Treating normally."); + j->dirty_at_shutdown = false; + } + } else { + job_log(j, LOG_DEBUG, "Job wants to be dirty at shutdown, but it is not Instant Off-compliant. Treating normally."); + j->dirty_at_shutdown = false; + } +} +void +job_close_shutdown_transaction(job_t j) +{ + if (j->dirty_at_shutdown) { + job_log(j, LOG_DEBUG, "Closing shutdown transaction for job."); + if (__sync_sub_and_fetch(&j->shmem->vp_shmem_transaction_cnt, 1) == -1) { + job_log(j, LOG_DEBUG, "Job is now clean. Killing."); + job_kill(j); + } + j->dirty_at_shutdown = false; + } +} + +void +job_log_children_without_exec(job_t j) +{ + pid_t *pids = NULL; + size_t len = sizeof(pid_t) * get_kern_max_proc(); + int i = 0, kp_cnt = 0; + if (!do_apple_internal_logging || j->anonymous || j->per_user) { return; } - if (!job_assumes(j, (kp = malloc(len)) != NULL)) { + if (!job_assumes(j, (pids = malloc(len)) != NULL)) { return; } - if (!job_assumes(j, sysctl(mib, (u_int) mib_sz, kp, &len, NULL, 0) != -1)) { + if (!job_assumes(j, (kp_cnt = proc_listchildpids(j->p, pids, len)) != -1)) { goto out; } - kp_cnt = len / sizeof(struct kinfo_proc); - for (i = 0; i < kp_cnt; i++) { -#ifndef KERN_PROC_PPID - if (kp[i].kp_eproc.e_ppid != j->p) { + struct proc_bsdshortinfo proc; + if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + job_assumes(j, errno == 0); + } continue; } -#endif - if (kp[i].kp_proc.p_flag & P_EXEC) { + if (proc.pbsi_flags & P_EXEC) { continue; } - job_log(j, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", - kp[i].kp_proc.p_pid); + job_log(j, LOG_DEBUG, "Called *fork(). Please switch to posix_spawn*(), pthreads or launchd. Child PID %u", pids[i]); } out: - free(kp); + free(pids); } void job_cleanup_after_tracer(job_t j) { - jobmgr_t jm = NULL; - if( j->is_being_sampled ) { - int wstatus = 0; - job_log(j, LOG_DEBUG | LOG_CONSOLE, "sample[%i] finished with job.", j->tracing_pid); - if( job_assumes(j, waitpid(j->tracing_pid, &wstatus, 0) != -1) ) { - job_assumes(j, WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0); - } - STAILQ_REMOVE(&j->mgr->pending_samples, j, job_s, pending_samples_sle); - - if( j->kill_after_sample ) { - if (unlikely(j->debug_before_kill)) { - job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger"); - job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); - } - - job_log(j, LOG_NOTICE, "Killing..."); - job_kill(j); - } - j->sampling_complete = true; - j->is_being_sampled = false; - jm = j->mgr; - } - j->tracing_pid = 0; - if( j->reap_after_trace ) { + if (j->reap_after_trace) { job_log(j, LOG_DEBUG | LOG_CONSOLE, "Reaping job now that attached tracer is gone."); struct kevent kev; EV_SET(&kev, j->p, 0, 0, NOTE_EXIT, 0, 0); - + /* Fake a kevent to keep our logic consistent. */ job_callback_proc(j, &kev); - + /* Normally, after getting a EVFILT_PROC event, we do garbage collection * on the root job manager. To make our fakery complete, we will do garbage * collection at the beginning of the next run loop cycle (after we're done * draining the current queue of kevents). */ - job_assumes(j, kevent_mod((uintptr_t)&root_jobmgr->reboot_flags, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_NSECONDS, 1, root_jobmgr) != -1); - } - - if( jm ) { - jobmgr_dequeue_next_sample(jm); + (void)job_assumes(j, kevent_mod((uintptr_t)&root_jobmgr->reboot_flags, EVFILT_TIMER, EV_ADD | EV_ONESHOT, NOTE_NSECONDS, 1, root_jobmgr) != -1); } } @@ -3244,18 +3750,18 @@ job_callback_proc(job_t j, struct kevent *kev) bool program_changed = false; int fflags = kev->fflags; - job_log(j, LOG_DEBUG, "EVFILT_PROC event for job:"); + job_log(j, LOG_DEBUG, "EVFILT_PROC event for job."); log_kevent_struct(LOG_DEBUG, kev, 0); - if( fflags & NOTE_EXIT ) { - 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; - size_t len = sizeof(kp); - - /* Sometimes, the kernel says it succeeded but really didn't. */ - if( job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1) && len == sizeof(kp) ) { - if( !job_assumes(j, kp.kp_eproc.e_ppid == getpid()) ) { + if (fflags & NOTE_EXIT) { + if (j->p == (pid_t)kev->ident && !j->anonymous) { + /* Note that the third argument to proc_pidinfo() is a magic argument for + * PROC_PIDT_SHORTBSDINFO. Specifically, passing 1 means "don't fail on a zombie + * PID". + */ + struct proc_bsdshortinfo proc; + if (job_assumes(j, proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0)) { + if (!job_assumes(j, (pid_t)proc.pbsi_ppid == getpid())) { /* Someone has attached to the process with ptrace(). There's a race here. * If we determine that we are not the parent process and then fail to attach * a kevent to the parent PID (who is probably using ptrace()), we can take that as an @@ -3270,23 +3776,23 @@ job_callback_proc(job_t j, struct kevent *kev) * own parent. Apparently, that is not correct. If this is the case, we forsake * the process to its own devices. Let it reap itself. */ - if( !job_assumes(j, kp.kp_eproc.e_ppid != (pid_t)kev->ident) ) { + if (!job_assumes(j, proc.pbsi_ppid != kev->ident)) { job_log(j, LOG_WARNING, "Job is its own parent and has (somehow) exited. Leaving it to waste away."); return; } - if( job_assumes(j, kevent_mod(kp.kp_eproc.e_ppid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j) != -1) ) { - j->tracing_pid = kp.kp_eproc.e_ppid; + if (job_assumes(j, kevent_mod(proc.pbsi_ppid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j) != -1)) { + j->tracing_pid = proc.pbsi_ppid; j->reap_after_trace = true; return; } } } - } else if( !j->anonymous ) { - if( j->tracing_pid == (pid_t)kev->ident ) { + } else if (!j->anonymous) { + if (j->tracing_pid == (pid_t)kev->ident) { job_cleanup_after_tracer(j); return; - } else if( j->tracing_pid && !j->reap_after_trace ) { + } else if (j->tracing_pid && !j->reap_after_trace) { /* The job exited before our sample completed. */ job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job has exited. Will reap after tracing PID %i exits.", j->tracing_pid); j->reap_after_trace = true; @@ -3299,15 +3805,11 @@ job_callback_proc(job_t j, struct kevent *kev) program_changed = true; if (j->anonymous) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, j->p }; - struct kinfo_proc kp; - size_t len = sizeof(kp); - - /* Sometimes, the kernel says it succeeded but really didn't. */ - if (job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1) && len == sizeof(kp)) { + struct proc_bsdshortinfo proc; + if (proc_pidinfo(j->p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) > 0) { char newlabel[1000]; - snprintf(newlabel, sizeof(newlabel), "%p.anonymous.%s", j, kp.kp_proc.p_comm); + snprintf(newlabel, sizeof(newlabel), "%p.anonymous.%s", j, proc.pbsi_comm); job_log(j, LOG_INFO, "Program changed. Updating the label to: %s", newlabel); j->lastlookup = NULL; @@ -3315,11 +3817,35 @@ job_callback_proc(job_t j, struct kevent *kev) LIST_REMOVE(j, label_hash_sle); strcpy((char *)j->label, newlabel); - LIST_INSERT_HEAD(&label_hash[hash_label(j->label)], j, label_hash_sle); + + jobmgr_t where2put = root_jobmgr; + if (j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) { + where2put = j->mgr; + } + LIST_INSERT_HEAD(&where2put->label_hash[hash_label(j->label)], j, label_hash_sle); + } else if (errno != ESRCH) { + job_assumes(j, errno == 0); } } else { - j->did_exec = true; - job_log(j, LOG_DEBUG, "Program changed"); + if (j->spawn_reply_port) { + errno = job_mig_spawn2_reply(j->spawn_reply_port, BOOTSTRAP_SUCCESS, j->p, j->exit_status_port); + if (errno) { + if (errno != MACH_SEND_INVALID_DEST) { + (void)job_assumes(j, errno == KERN_SUCCESS); + } + (void)job_assumes(j, launchd_mport_close_recv(j->exit_status_port) == KERN_SUCCESS); + } + + j->spawn_reply_port = MACH_PORT_NULL; + j->exit_status_port = MACH_PORT_NULL; + } + + if (j->xpc_service && j->did_exec) { + j->xpcproxy_did_exec = true; + } + + j->did_exec = true; + job_log(j, LOG_DEBUG, "Program changed"); } } @@ -3331,11 +3857,11 @@ job_callback_proc(job_t j, struct kevent *kev) if (fflags & NOTE_EXIT) { job_reap(j); - if( !j->anonymous ) { - j = job_dispatch(j, false); - } else { + if (j->anonymous) { job_remove(j); j = NULL; + } else { + j = job_dispatch(j, false); } } } @@ -3354,84 +3880,38 @@ job_callback_timer(job_t j, void *ident) j->start_pending = true; job_dispatch(j, false); } else if (&j->exit_timeout == ident) { - if( !job_assumes(j, j->p != 0) ) { - return; - } - - if( j->clean_kill ) { - job_log(j, LOG_ERR | LOG_CONSOLE, "Clean job failed to exit %u second after receiving SIGKILL.", LAUNCHD_CLEAN_KILL_TIMER); - job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL)); - j->clean_exit_timer_expired = true; - - jobmgr_do_garbage_collection(j->mgr); + if (!job_assumes(j, j->p != 0)) { return; } - - /* - * This block might be executed up to 3 times for a given (slow) job - * - once for the SAMPLE_TIMEOUT timer, at which point sampling is triggered - * - once for the exit_timeout timer, at which point: - * - sampling is performed if not triggered previously - * - SIGKILL is being sent to the job - * - once for the SIGKILL_TIMER timer, at which point we log an issue - * with the long SIGKILL - */ - - if( j->per_user ) { - /* Don't sample per-user launchd's. */ - j->sampling_complete = true; - } - bool was_is_or_will_be_sampled = ( j->sampling_complete || j->is_being_sampled || j->pending_sample ); - bool should_enqueue = ( !was_is_or_will_be_sampled && do_apple_internal_logging ); - + if (j->sent_sigkill) { uint64_t td = runtime_get_nanoseconds_since(j->sent_signal_time); td /= NSEC_PER_SEC; td -= j->clean_kill ? 0 : j->exit_timeout; - job_log(j, LOG_WARNING | LOG_CONSOLE, "Did not die after sending SIGKILL %llu seconds ago...", td); - } else if( should_enqueue && (!j->exit_timeout || (LAUNCHD_SAMPLE_TIMEOUT < j->exit_timeout)) ) { - /* This should work even if the job changes its exit_timeout midstream */ - job_log(j, LOG_NOTICE | LOG_CONSOLE, "Sampling timeout elapsed (%u seconds). Scheduling a sample...", LAUNCHD_SAMPLE_TIMEOUT); - if (j->exit_timeout) { - unsigned int ttk = (j->exit_timeout - LAUNCHD_SAMPLE_TIMEOUT); - job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, - EV_ADD|EV_ONESHOT, NOTE_SECONDS, ttk, j) != -1); - job_log(j, LOG_NOTICE | LOG_CONSOLE, "Scheduled new exit timeout for %u seconds later", ttk); + job_log(j, LOG_WARNING | LOG_CONSOLE, "Job has not died after being %skilled %llu seconds ago. Simulating exit.", j->clean_kill ? "cleanly " : "", td); + j->workaround9359725 = true; + + if (g_trap_sigkill_bugs) { + job_log(j, LOG_NOTICE | LOG_CONSOLE, "Trapping into kernel debugger. You can continue the machine after it has been debugged, and shutdown will proceed normally."); + (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); } - - STAILQ_INSERT_TAIL(&j->mgr->pending_samples, j, pending_samples_sle); - j->pending_sample = true; - jobmgr_dequeue_next_sample(j->mgr); - } else { - if( do_apple_internal_logging && !j->sampling_complete ) { - if( j->is_being_sampled || j->pending_sample ) { - char pidstr[24] = { 0 }; - snprintf(pidstr, sizeof(pidstr), "[%i] ", j->tracing_pid); - - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Will kill after sample%shas completed.", j->exit_timeout, j->tracing_pid ? pidstr : " "); - j->kill_after_sample = true; - } else { - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Will sample and then kill.", j->exit_timeout); - - STAILQ_INSERT_TAIL(&j->mgr->pending_samples, j, pending_samples_sle); - j->pending_sample = true; - } - jobmgr_dequeue_next_sample(j->mgr); - } else { - if (unlikely(j->debug_before_kill)) { - job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger"); - job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); - } - job_log(j, LOG_WARNING | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Killing", j->exit_timeout); - job_kill(j); - jobmgr_do_garbage_collection(j->mgr); + struct kevent bogus_exit; + EV_SET(&bogus_exit, j->p, EVFILT_PROC, 0, NOTE_EXIT, 0, 0); + jobmgr_callback(j->mgr, &bogus_exit); + } else { + if (unlikely(j->debug_before_kill)) { + job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger"); + (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); } + + job_log(j, LOG_WARNING | LOG_CONSOLE, "Exit timeout elapsed (%u seconds). Killing", j->exit_timeout); + job_kill(j); } } else { - job_assumes(j, false); + (void)job_assumes(j, false); } } @@ -3494,7 +3974,7 @@ jobmgr_callback(void *obj, struct kevent *kev) LIST_FOREACH(ji, &root_jobmgr->jobs, sle) { if (ji->per_user && ji->p) { - job_assumes(ji, runtime_kill(ji->p, SIGUSR2) != -1); + (void)job_assumes(ji, runtime_kill(ji->p, SIGUSR2) != -1); } } } else { @@ -3513,34 +3993,34 @@ jobmgr_callback(void *obj, struct kevent *kev) jobmgr_dispatch_all_semaphores(jm); break; case EVFILT_TIMER: - if( kev->ident == (uintptr_t)&sorted_calendar_events ) { + if (kev->ident == (uintptr_t)&sorted_calendar_events) { calendarinterval_callback(); - } else if( kev->ident == (uintptr_t)jm ) { + } else if (kev->ident == (uintptr_t)jm) { jobmgr_log(jm, LOG_DEBUG, "Shutdown timer firing."); jobmgr_still_alive_with_check(jm); - } else if( kev->ident == (uintptr_t)&jm->reboot_flags ) { + } else if (kev->ident == (uintptr_t)&jm->reboot_flags) { jobmgr_do_garbage_collection(jm); - } else if( kev->ident == (uintptr_t)&g_runtime_busy_time ) { + } else if (kev->ident == (uintptr_t)&g_runtime_busy_time) { jobmgr_log(jm, LOG_DEBUG, "Idle exit timer fired. Shutting down."); - if( jobmgr_assumes(jm, runtime_busy_cnt == 0) ) { + if (jobmgr_assumes(jm, runtime_busy_cnt == 0)) { return launchd_shutdown(); } } break; case EVFILT_VNODE: - if( kev->ident == (uintptr_t)s_no_hang_fd ) { + if (kev->ident == (uintptr_t)s_no_hang_fd) { int _no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK); - if( unlikely(_no_hang_fd != -1) ) { + if (unlikely(_no_hang_fd != -1)) { jobmgr_log(root_jobmgr, LOG_DEBUG, "/dev/autofs_nowait has appeared!"); - jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1); - jobmgr_assumes(root_jobmgr, runtime_close(s_no_hang_fd) != -1); + (void)jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1); + (void)jobmgr_assumes(root_jobmgr, runtime_close(s_no_hang_fd) != -1); s_no_hang_fd = _fd(_no_hang_fd); } - } else if( pid1_magic && g_console && kev->ident == (uintptr_t)fileno(g_console) ) { + } else if (pid1_magic && g_console && kev->ident == (uintptr_t)fileno(g_console)) { int cfd = -1; - if( launchd_assumes((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1) ) { + if (launchd_assumes((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1)) { _fd(cfd); - if( !launchd_assumes((g_console = fdopen(cfd, "w")) != NULL) ) { + if (!launchd_assumes((g_console = fdopen(cfd, "w")) != NULL)) { close(cfd); } } @@ -3605,14 +4085,18 @@ job_start(job_t j) if (j->start_time && (td < j->min_run_time) && !j->legacy_mach_job && !j->inetcompat) { time_t respawn_delta = j->min_run_time - (uint32_t)td; - + /* * We technically should ref-count throttled jobs to prevent idle exit, * but we're not directly tracking the 'throttled' state at the moment. */ - - job_log(j, LOG_WARNING, "Throttling respawn: Will start in %ld seconds", respawn_delta); - job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, respawn_delta, j) != -1); + int level = LOG_WARNING; + if (!j->did_exec && ((j->fail_cnt - 1) % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) { + level = LOG_DEBUG; + } + + job_log(j, level, "Throttling respawn: Will start in %ld seconds", respawn_delta); + (void)job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, respawn_delta, j) != -1); job_ignore(j); return; } @@ -3621,33 +4105,33 @@ job_start(job_t j) sipc = ((!SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices)) && !j->deny_job_creation) || j->embedded_special_privileges; } - if( sipc ) { - job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1); + if (sipc) { + (void)job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1); } - job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execspair) != -1); + (void)job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execspair) != -1); if (likely(!j->legacy_mach_job) && job_assumes(j, pipe(oepair) != -1)) { j->log_redirect_fd = _fd(oepair[0]); - job_assumes(j, fcntl(j->log_redirect_fd, F_SETFL, O_NONBLOCK) != -1); - job_assumes(j, kevent_mod(j->log_redirect_fd, EVFILT_READ, EV_ADD, 0, 0, j) != -1); + (void)job_assumes(j, fcntl(j->log_redirect_fd, F_SETFL, O_NONBLOCK) != -1); + (void)job_assumes(j, kevent_mod(j->log_redirect_fd, EVFILT_READ, EV_ADD, 0, 0, j) != -1); } switch (c = runtime_fork(j->weird_bootstrap ? j->j_port : j->mgr->jm_port)) { case -1: job_log_error(j, LOG_ERR, "fork() failed, will try again in one second"); - job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 1, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 1, j) != -1); job_ignore(j); - job_assumes(j, runtime_close(execspair[0]) == 0); - job_assumes(j, runtime_close(execspair[1]) == 0); + (void)job_assumes(j, runtime_close(execspair[0]) == 0); + (void)job_assumes(j, runtime_close(execspair[1]) == 0); if (sipc) { - job_assumes(j, runtime_close(spair[0]) == 0); - job_assumes(j, runtime_close(spair[1]) == 0); + (void)job_assumes(j, runtime_close(spair[0]) == 0); + (void)job_assumes(j, runtime_close(spair[1]) == 0); } if (likely(!j->legacy_mach_job)) { - job_assumes(j, runtime_close(oepair[0]) != -1); - job_assumes(j, runtime_close(oepair[1]) != -1); + (void)job_assumes(j, runtime_close(oepair[0]) != -1); + (void)job_assumes(j, runtime_close(oepair[1]) != -1); j->log_redirect_fd = 0; } break; @@ -3656,16 +4140,16 @@ job_start(job_t j) _exit(EXIT_FAILURE); } if (!j->legacy_mach_job) { - job_assumes(j, dup2(oepair[1], STDOUT_FILENO) != -1); - job_assumes(j, dup2(oepair[1], STDERR_FILENO) != -1); - job_assumes(j, runtime_close(oepair[1]) != -1); + (void)job_assumes(j, dup2(oepair[1], STDOUT_FILENO) != -1); + (void)job_assumes(j, dup2(oepair[1], STDERR_FILENO) != -1); + (void)job_assumes(j, runtime_close(oepair[1]) != -1); } - job_assumes(j, runtime_close(execspair[0]) == 0); + (void)job_assumes(j, runtime_close(execspair[0]) == 0); /* wait for our parent to say they've attached a kevent to us */ read(_fd(execspair[1]), &c, sizeof(c)); if (sipc) { - job_assumes(j, runtime_close(spair[0]) == 0); + (void)job_assumes(j, runtime_close(spair[0]) == 0); snprintf(nbuf, sizeof(nbuf), "%d", spair[1]); setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1); } @@ -3677,20 +4161,21 @@ job_start(job_t j) job_log(j, LOG_DEBUG, "Started as PID: %u", c); j->did_exec = false; + j->xpcproxy_did_exec = false; j->checkedin = false; j->start_pending = false; j->reaped = false; j->crashed = false; j->stopped = false; - if( j->needs_kickoff ) { + if (j->needs_kickoff) { j->needs_kickoff = false; - if( SLIST_EMPTY(&j->semaphores) ) { + if (SLIST_EMPTY(&j->semaphores)) { j->ondemand = false; } } - if( j->has_console ) { + if (j->has_console) { g_wsp = c; } @@ -3699,18 +4184,15 @@ job_start(job_t j) LIST_INSERT_HEAD(&j->mgr->active_jobs[ACTIVE_JOB_HASH(c)], j, pid_hash_sle); if (likely(!j->legacy_mach_job)) { - job_assumes(j, runtime_close(oepair[1]) != -1); + (void)job_assumes(j, runtime_close(oepair[1]) != -1); } j->p = c; - if (unlikely(j->hopefully_exits_first)) { - j->mgr->hopefully_first_cnt++; - } else if (likely(!j->hopefully_exits_last)) { - j->mgr->normal_active_cnt++; - } + + j->mgr->normal_active_cnt++; j->fork_fd = _fd(execspair[0]); - job_assumes(j, runtime_close(execspair[1]) == 0); + (void)job_assumes(j, runtime_close(execspair[1]) == 0); if (sipc) { - job_assumes(j, runtime_close(spair[1]) == 0); + (void)job_assumes(j, runtime_close(spair[1]) == 0); ipc_open(_fd(spair[0]), j); } if (job_assumes(j, kevent_mod(c, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr ? root_jobmgr : j->mgr) != -1)) { @@ -3722,8 +4204,8 @@ job_start(job_t j) j->wait4debugger_oneshot = false; struct envitem *ei = NULL, *et = NULL; - SLIST_FOREACH_SAFE( ei, &j->env, sle, et ) { - if( ei->one_shot ) { + SLIST_FOREACH_SAFE(ei, &j->env, sle, et) { + if (ei->one_shot) { SLIST_REMOVE(&j->env, ei, envitem, sle); } } @@ -3748,7 +4230,7 @@ job_start_child(job_t j) size_t binpref_out_cnt = 0; size_t i; - job_assumes(j, posix_spawnattr_init(&spattr) == 0); + (void)job_assumes(j, posix_spawnattr_init(&spattr) == 0); job_setup_attributes(j); @@ -3784,15 +4266,22 @@ job_start_child(job_t j) } if (unlikely(j->wait4debugger || j->wait4debugger_oneshot)) { - job_log(j, LOG_WARNING, "Spawned and waiting for the debugger to attach before continuing..."); + if (!j->legacy_LS_job) { + job_log(j, LOG_WARNING, "Spawned and waiting for the debugger to attach before continuing..."); + } spflags |= POSIX_SPAWN_START_SUSPENDED; } - job_assumes(j, posix_spawnattr_setflags(&spattr, spflags) == 0); + if (unlikely(j->disable_aslr)) { + spflags |= _POSIX_SPAWN_DISABLE_ASLR; + } + spflags |= j->pstype; + + (void)job_assumes(j, posix_spawnattr_setflags(&spattr, spflags) == 0); if (unlikely(j->j_binpref_cnt)) { - job_assumes(j, posix_spawnattr_setbinpref_np(&spattr, j->j_binpref_cnt, j->j_binpref, &binpref_out_cnt) == 0); - job_assumes(j, binpref_out_cnt == j->j_binpref_cnt); + (void)job_assumes(j, posix_spawnattr_setbinpref_np(&spattr, j->j_binpref_cnt, j->j_binpref, &binpref_out_cnt) == 0); + (void)job_assumes(j, binpref_out_cnt == j->j_binpref_cnt); } #if HAVE_QUARANTINE @@ -3801,7 +4290,7 @@ job_start_child(job_t j) if (job_assumes(j, qp = qtn_proc_alloc())) { if (job_assumes(j, qtn_proc_init_with_data(qp, j->quarantine_data, j->quarantine_data_sz) == 0)) { - job_assumes(j, qtn_proc_apply_to_self(qp) == 0); + (void)job_assumes(j, qtn_proc_apply_to_self(qp) == 0); } } } @@ -3826,13 +4315,20 @@ job_start_child(job_t j) file2exec = j->prog ? j->prog : argv[0]; } - errno = psf(NULL, file2exec, NULL, &spattr, (char *const*)argv, environ); - job_log_error(j, LOG_ERR, "posix_spawn(\"%s\", ...)", file2exec); - + errno = psf(NULL, file2exec, NULL, &spattr, (char *const *)argv, environ); + if (errno != EBADARCH) { + int level = LOG_ERR; + if ((j->fail_cnt++ % LAUNCHD_LOG_FAILED_EXEC_FREQ) != 0) { + level = LOG_DEBUG; + } + job_log_error(j, level, "posix_spawn(\"%s\", ...)", file2exec); + errno = EXIT_FAILURE; + } + #if HAVE_SANDBOX out_bad: #endif - _exit(EXIT_FAILURE); + _exit(errno); } void @@ -3885,51 +4381,71 @@ jobmgr_setup_env_from_other_jobs(jobmgr_t jm) void job_log_pids_with_weird_uids(job_t j) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; - size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); - struct kinfo_proc *kp; + size_t len = sizeof(pid_t) * get_kern_max_proc(); + pid_t *pids = NULL; uid_t u = j->mach_uid; - + int i = 0, kp_cnt = 0; + if (!do_apple_internal_logging) { return; } - kp = malloc(len); - - if (!job_assumes(j, kp != NULL)) { + pids = malloc(len); + if (!job_assumes(j, pids != NULL)) { return; } runtime_ktrace(RTKT_LAUNCHD_FINDING_WEIRD_UIDS, j->p, u, 0); - if (!job_assumes(j, sysctl(mib, 3, kp, &len, NULL, 0) != -1)) { + /* libproc actually has some serious performance drawbacks when used over sysctl(3) in + * scenarios like this. Whereas sysctl(3) can give us back all the kinfo_proc's in + * one kernel call, libproc requires that we get a list of PIDs we're interested in + * (in this case, all PIDs on the system) and then get a single proc_bsdshortinfo + * struct back in a single call for each one. + * + * This kind of thing is also more inherently racy than sysctl(3). While sysctl(3) + * returns a snapshot, it returns the whole shebang at once. Any PIDs given to us by + * libproc could go stale before we call proc_pidinfo(). + * + * Note that proc_list*() APIs return the number of PIDs given back, not the number + * of bytes written to the buffer. + */ + if (!job_assumes(j, (kp_cnt = proc_listallpids(pids, len)) != -1)) { goto out; } - kp_cnt = len / sizeof(struct kinfo_proc); - for (i = 0; i < kp_cnt; i++) { - uid_t i_euid = kp[i].kp_eproc.e_ucred.cr_uid; - uid_t i_uid = kp[i].kp_eproc.e_pcred.p_ruid; - uid_t i_svuid = kp[i].kp_eproc.e_pcred.p_svuid; - pid_t i_pid = kp[i].kp_proc.p_pid; + struct proc_bsdshortinfo proc; + /* We perhaps should not log a bug here if we get ESRCH back, due to the race + * detailed above. + */ + if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + job_assumes(j, errno == 0); + } + continue; + } + + uid_t i_euid = proc.pbsi_uid; + uid_t i_uid = proc.pbsi_ruid; + uid_t i_svuid = proc.pbsi_svuid; + pid_t i_pid = pids[i]; if (i_euid != u && i_uid != u && i_svuid != u) { continue; } - job_log(j, LOG_ERR, "PID %u \"%s\" has no account to back it! Real/effective/saved UIDs: %u/%u/%u", - i_pid, kp[i].kp_proc.p_comm, i_uid, i_euid, i_svuid); + job_log(j, LOG_ERR, "PID %u \"%s\" has no account to back it! Real/effective/saved UIDs: %u/%u/%u", i_pid, proc.pbsi_comm, i_uid, i_euid, i_svuid); /* Temporarily disabled due to 5423935 and 4946119. */ #if 0 /* Ask the accountless process to exit. */ - job_assumes(j, runtime_kill(i_pid, SIGTERM) != -1); + (void)job_assumes(j, runtime_kill(i_pid, SIGTERM) != -1); #endif } out: - free(kp); + free(pids); } void @@ -3995,7 +4511,7 @@ job_postfork_test_user(job_t j) return; out_bad: #if 0 - job_assumes(j, runtime_kill(getppid(), SIGTERM) != -1); + (void)job_assumes(j, runtime_kill(getppid(), SIGTERM) != -1); _exit(EXIT_FAILURE); #else job_log(j, LOG_WARNING, "In a future build of the OS, this error will be fatal."); @@ -4105,9 +4621,9 @@ job_postfork_become_user(job_t j) int groups[NGROUPS], ngroups; /* A failure here isn't fatal, and we'll still get data we can use. */ - job_assumes(j, getgrouplist(j->username, desired_gid, groups, &ngroups) != -1); + (void)job_assumes(j, getgrouplist(j->username, desired_gid, groups, &ngroups) != -1); - if( !job_assumes(j, syscall(SYS_initgroups, ngroups, groups, desired_uid) != -1) ) { + if (!job_assumes(j, syscall(SYS_initgroups, ngroups, groups, desired_uid) != -1)) { _exit(EXIT_FAILURE); } #endif @@ -4136,7 +4652,7 @@ job_setup_attributes(job_t j) struct envitem *ei; if (unlikely(j->setnice)) { - job_assumes(j, setpriority(PRIO_PROCESS, 0, j->nice) != -1); + (void)job_assumes(j, setpriority(PRIO_PROCESS, 0, j->nice) != -1); } SLIST_FOREACH(li, &j->limits, sle) { @@ -4158,40 +4674,22 @@ job_setup_attributes(job_t j) } } -#if !TARGET_OS_EMBEDDED - if( unlikely(j->per_user) ) { - auditinfo_addr_t auinfo = { - .ai_termid = { .at_type = AU_IPv4 }, - .ai_auid = j->mach_uid, - .ai_asid = AU_ASSIGN_ASID, - }; - (void)au_user_mask(j->username, &auinfo.ai_mask); - - if( !launchd_assumes(setaudit_addr(&auinfo, sizeof(auinfo)) != -1) ) { - runtime_syslog(LOG_WARNING, "Could not set audit session! (errno = %d)", errno); - _exit(EXIT_FAILURE); - } else { - job_log(j, LOG_DEBUG, "Created new security session for per-user launchd."); - } - } -#endif - if (unlikely(!j->inetcompat && j->session_create)) { launchd_SessionCreate(); } if (unlikely(j->low_pri_io)) { - job_assumes(j, setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE) != -1); + (void)job_assumes(j, setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE) != -1); } if (unlikely(j->rootdir)) { - job_assumes(j, chroot(j->rootdir) != -1); - job_assumes(j, chdir(".") != -1); + (void)job_assumes(j, chroot(j->rootdir) != -1); + (void)job_assumes(j, chdir(".") != -1); } job_postfork_become_user(j); if (unlikely(j->workingdir)) { - job_assumes(j, chdir(j->workingdir) != -1); + (void)job_assumes(j, chdir(j->workingdir) != -1); } if (unlikely(j->setmask)) { @@ -4199,7 +4697,7 @@ job_setup_attributes(job_t j) } if (j->stdin_fd) { - job_assumes(j, dup2(j->stdin_fd, STDIN_FILENO) != -1); + (void)job_assumes(j, dup2(j->stdin_fd, STDIN_FILENO) != -1); } else { job_setup_fd(j, STDIN_FILENO, j->stdinpath, O_RDONLY|O_CREAT); } @@ -4212,22 +4710,22 @@ job_setup_attributes(job_t j) setenv(ei->key, ei->value, 1); } - if( do_apple_internal_logging ) { + if (do_apple_internal_logging) { setenv(LAUNCHD_DO_APPLE_INTERNAL_LOGGING, "true", 1); } #if !TARGET_OS_EMBEDDED - if( j->jetsam_properties ) { - job_assumes(j, proc_setpcontrol(PROC_SETPC_TERMINATE) == 0); + if (j->jetsam_properties) { + (void)job_assumes(j, proc_setpcontrol(PROC_SETPC_TERMINATE) == 0); } #endif #if TARGET_OS_EMBEDDED - if( j->main_thread_priority != 0 ) { + if (j->main_thread_priority != 0) { struct sched_param params; bzero(¶ms, sizeof(params)); params.sched_priority = j->main_thread_priority; - job_assumes(j, pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms) != -1); + (void)job_assumes(j, pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms) != -1); } #endif @@ -4237,9 +4735,9 @@ job_setup_attributes(job_t j) * setuid children. We'll settle for process-groups. */ if (getppid() != 1) { - job_assumes(j, setpgid(0, 0) != -1); + (void)job_assumes(j, setpgid(0, 0) != -1); } else { - job_assumes(j, setsid() != -1); + (void)job_assumes(j, setsid() != -1); } } @@ -4257,8 +4755,8 @@ job_setup_fd(job_t j, int target_fd, const char *path, int flags) return; } - job_assumes(j, dup2(fd, target_fd) != -1); - job_assumes(j, runtime_close(fd) == 0); + (void)job_assumes(j, dup2(fd, target_fd) != -1); + (void)job_assumes(j, runtime_close(fd) == 0); } int @@ -4279,7 +4777,7 @@ dir_has_files(job_t j, const char *path) } } - job_assumes(j, closedir(dd) == 0); + (void)job_assumes(j, closedir(dd) == 0); return r; } @@ -4403,7 +4901,6 @@ job_log_bug(job_t j, unsigned int line) } } - /* I cannot think of any reason why 'j' should ever be NULL, nor have I ever seen the case in the wild */ if (likely(j)) { job_log(j, LOG_NOTICE, "Bug: %s:%u (%s):%u", file, line, buf, saved_errno); } else { @@ -4434,26 +4931,26 @@ job_logv(job_t j, int pri, int err, const char *msg, va_list ap) newmsg = alloca(newmsgsz); if (err) { - #if !TARGET_OS_EMBEDDED +#if !TARGET_OS_EMBEDDED snprintf(newmsg, newmsgsz, "%s: %s", msg, strerror(err)); - #else +#else snprintf(newmsg, newmsgsz, "(%s) %s: %s", label2use, msg, strerror(err)); - #endif +#endif } else { - #if !TARGET_OS_EMBEDDED +#if !TARGET_OS_EMBEDDED snprintf(newmsg, newmsgsz, "%s", msg); - #else +#else snprintf(newmsg, newmsgsz, "(%s) %s", label2use, msg); - #endif +#endif } - if( j && unlikely(j->debug) ) { + if (j && unlikely(j->debug)) { oldmask = setlogmask(LOG_UPTO(LOG_DEBUG)); } runtime_vsyslog(&attr, newmsg, ap); - if( j && unlikely(j->debug) ) { + if (j && unlikely(j->debug)) { setlogmask(oldmask); } } @@ -4540,7 +5037,7 @@ semaphoreitem_ignore(job_t j, struct semaphoreitem *si) { if (si->fd != -1) { job_log(j, LOG_DEBUG, "Ignoring Vnode: %d", si->fd); - job_assumes(j, kevent_mod(si->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1); + (void)job_assumes(j, kevent_mod(si->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1); } } @@ -4576,7 +5073,7 @@ semaphoreitem_watch(job_t j, struct semaphoreitem *si) do { if (si->fd == -1) { struct stat sb; - if( stat(si->what, &sb) == 0 ) { + if (stat(si->what, &sb) == 0) { /* If we're watching a character or block device, only watch the parent directory. * See rdar://problem/6489900 for the gory details. Basically, holding an open file * descriptor to a devnode could end up (a) blocking us on open(2) until someone else @@ -4589,12 +5086,12 @@ semaphoreitem_watch(job_t j, struct semaphoreitem *si) * for dev nodes by only watching the parent directory and stat(2)ing our desired file * each time the parent changes to see if it appeared or disappeared. */ - if( S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode) ) { + if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) { si->fd = _fd(open(si->what, O_EVTONLY | O_NOCTTY | O_NONBLOCK)); } } - if( si->fd == -1 ) { + if (si->fd == -1) { si->watching_parent = job_assumes(j, (si->fd = _fd(open(parentdir, O_EVTONLY | O_NOCTTY | O_NONBLOCK))) != -1); } else { si->watching_parent = false; @@ -4615,7 +5112,7 @@ semaphoreitem_watch(job_t j, struct semaphoreitem *si) * attached to short lived zombie processes after fork() * but before kevent(). */ - job_assumes(j, runtime_close(si->fd) == 0); + (void)job_assumes(j, runtime_close(si->fd) == 0); si->fd = -1; } } while (unlikely((si->fd == -1) && (saved_errno == ENOENT))); @@ -4630,7 +5127,7 @@ semaphoreitem_watch(job_t j, struct semaphoreitem *si) if (!j->poll_for_vfs_changes) { j->poll_for_vfs_changes = true; - job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 3, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 3, j) != -1); } } } @@ -4684,19 +5181,19 @@ semaphoreitem_callback(job_t j, struct kevent *kev) if (invalidation_reason[0]) { job_log(j, LOG_DEBUG, "Path %s: %s", invalidation_reason, si->what); - job_assumes(j, runtime_close(si->fd) == 0); + (void)job_assumes(j, runtime_close(si->fd) == 0); si->fd = -1; /* this will get fixed in semaphoreitem_watch() */ } - if( !si->watching_parent ) { + if (!si->watching_parent) { if (si->why == PATH_CHANGES) { j->start_pending = true; } else { semaphoreitem_watch(j, si); } } else { /* Something happened to the parent directory. See if our target file appeared. */ - if( !invalidation_reason[0] ) { - job_assumes(j, runtime_close(si->fd) == 0); + if (!invalidation_reason[0]) { + (void)job_assumes(j, runtime_close(si->fd) == 0); si->fd = -1; /* this will get fixed in semaphoreitem_watch() */ semaphoreitem_watch(j, si); } @@ -4730,35 +5227,35 @@ calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void if (val < 0) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is less than zero.", key); } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MINUTE) == 0) { - if( val > 59 ) { + if (val > 59) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 59 (inclusive).", key); tmptm->tm_sec = -1; } else { tmptm->tm_min = (typeof(tmptm->tm_min)) val; } } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_HOUR) == 0) { - if( val > 23 ) { + if (val > 23) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 23 (inclusive).", key); tmptm->tm_sec = -1; } else { tmptm->tm_hour = (typeof(tmptm->tm_hour)) val; } } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_DAY) == 0) { - if( val < 1 || val > 31 ) { + if (val < 1 || val > 31) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 1 and 31 (inclusive).", key); tmptm->tm_sec = -1; } else { tmptm->tm_mday = (typeof(tmptm->tm_mday)) val; } } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_WEEKDAY) == 0) { - if( val > 7 ) { + if (val > 7) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 7 (inclusive).", key); tmptm->tm_sec = -1; } else { tmptm->tm_wday = (typeof(tmptm->tm_wday)) val; } } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MONTH) == 0) { - if( val > 12 ) { + if (val > 12) { job_log(j, LOG_WARNING, "The interval for key \"%s\" is not between 0 and 12 (inclusive).", key); tmptm->tm_sec = -1; } else { @@ -4838,7 +5335,7 @@ calendarinterval_sanity_check(void) time_t now = time(NULL); if (unlikely(ci && (ci->when_next < now))) { - jobmgr_assumes(root_jobmgr, raise(SIGUSR1) != -1); + (void)jobmgr_assumes(root_jobmgr, raise(SIGUSR1) != -1); } } @@ -4905,11 +5402,11 @@ socketgroup_delete(job_t j, struct socketgroup *sg) /* 5480306 */ if (job_assumes(j, getsockname(sg->fds[i], (struct sockaddr *)&ss, &ss_len) != -1) && job_assumes(j, ss_len > 0) && (ss.ss_family == AF_UNIX)) { - job_assumes(j, unlink(sun->sun_path) != -1); + (void)job_assumes(j, unlink(sun->sun_path) != -1); /* We might conditionally need to delete a directory here */ } #endif - job_assumes(j, runtime_close(sg->fds[i]) != -1); + (void)job_assumes(j, runtime_close(sg->fds[i]) != -1); } SLIST_REMOVE(&j->sockets, sg, socketgroup, sle); @@ -4938,12 +5435,12 @@ socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add) job_log(j, LOG_DEBUG, "%s Sockets:%s", do_add ? "Watching" : "Ignoring", buf); - job_assumes(j, kevent_bulk_mod(kev, sg->fd_cnt) != -1); + (void)job_assumes(j, kevent_bulk_mod(kev, sg->fd_cnt) != -1); for (i = 0; i < sg->fd_cnt; i++) { - job_assumes(j, kev[i].flags & EV_ERROR); + (void)job_assumes(j, kev[i].flags & EV_ERROR); errno = (typeof(errno)) kev[i].data; - job_assumes(j, kev[i].data == 0); + (void)job_assumes(j, kev[i].data == 0); } } @@ -5017,7 +5514,7 @@ envitem_setup(launch_data_t obj, const char *key, void *context) return; } - if( strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0 ) { + if (strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0) { envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env, false); } else { job_log(j, LOG_DEBUG, "Ignoring reserved environmental variable: %s", key); @@ -5033,7 +5530,7 @@ envitem_setup_one_shot(launch_data_t obj, const char *key, void *context) return; } - if( strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0 ) { + if (strncmp(LAUNCHD_TRUSTED_FD_ENV, key, sizeof(LAUNCHD_TRUSTED_FD_ENV) - 1) != 0) { envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env, true); } else { job_log(j, LOG_DEBUG, "Ignoring reserved environmental variable: %s", key); @@ -5141,12 +5638,12 @@ job_useless(job_t j) } else if (j->removal_pending) { job_log(j, LOG_DEBUG, "Exited while removal was pending."); return true; - } else if (j->mgr->shutting_down && (j->hopefully_exits_first || j->mgr->hopefully_first_cnt == 0)) { + } else if (j->shutdown_monitor) { + return false; + } else if (j->mgr->shutting_down) { job_log(j, LOG_DEBUG, "Exited while shutdown in progress. Processes remaining: %lu/%lu", total_children, total_anon_children); - if( total_children == 0 && !j->anonymous ) { - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job was last (non-anonymous) to exit during %s shutdown.", (pid1_magic && j->mgr == root_jobmgr) ? "system" : "job manager"); - } else if( total_anon_children == 0 && j->anonymous ) { - job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job was last (anonymous) to exit during %s shutdown.", (pid1_magic && j->mgr == root_jobmgr) ? "system" : "job manager"); + if (total_children == 0 && !j->anonymous) { + job_log(j, LOG_DEBUG | LOG_CONSOLE, "Job was last to exit during shutdown of: %s.", j->mgr->name); } return true; } else if (j->legacy_mach_job) { @@ -5157,6 +5654,18 @@ job_useless(job_t j) job_log(j, LOG_WARNING, "Failed to check-in!"); return true; } + } else { + /* If the job's executable does not have any valid architectures (for + * example, if it's a PowerPC-only job), then we don't even bother + * trying to relaunch it, as we have no reasonable expectation that + * the situation will change. + * + * + */ + if (!j->did_exec && WEXITSTATUS(j->last_exit_status) == EBADARCH) { + job_log(j, LOG_ERR, "Job executable does not contain supported architectures. Unloading it. Its plist should be removed."); + return true; + } } return false; @@ -5187,7 +5696,7 @@ job_keepalive(job_t j) return false; } - if( unlikely(j->needs_kickoff) ) { + if (unlikely(j->needs_kickoff)) { job_log(j, LOG_DEBUG, "KeepAlive check: Job needs to be kicked off on-demand before KeepAlive sets in."); return false; } @@ -5214,8 +5723,17 @@ job_keepalive(job_t j) return true; } } - - + + /* TODO: Coalesce external events and semaphore items, since they're basically + * the same thing. + */ + struct externalevent *ei = NULL; + LIST_FOREACH(ei, &j->events, job_le) { + if (ei->state == ei->wanted_state) { + return true; + } + } + SLIST_FOREACH(si, &j->semaphores, sle) { bool wanted_state = false; int qdir_file_cnt; @@ -5238,10 +5756,17 @@ job_keepalive(job_t j) return true; } break; + case CRASHED: + wanted_state = true; + case DID_NOT_CRASH: + if (j->crashed == wanted_state) { + return true; + } + break; case OTHER_JOB_ENABLED: wanted_state = true; case OTHER_JOB_DISABLED: - if ((bool)job_find(si->what) == wanted_state) { + if ((bool)job_find(NULL, si->what) == wanted_state) { job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "enabled" : "disabled", si->what); return true; } @@ -5249,7 +5774,7 @@ job_keepalive(job_t j) case OTHER_JOB_ACTIVE: wanted_state = true; case OTHER_JOB_INACTIVE: - if ((other_j = job_find(si->what))) { + if ((other_j = job_find(NULL, si->what))) { if ((bool)other_j->p == wanted_state) { job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "active" : "inactive", si->what); return true; @@ -5263,15 +5788,15 @@ job_keepalive(job_t j) job_log(j, LOG_DEBUG, "KeepAlive: The following path %s: %s", wanted_state ? "exists" : "is missing", si->what); return true; } else { - if( wanted_state ) { /* File is not there but we wish it was. */ - if( si->fd != -1 && !si->watching_parent ) { /* Need to be watching the parent now. */ - job_assumes(j, runtime_close(si->fd) == 0); + if (wanted_state) { /* File is not there but we wish it was. */ + if (si->fd != -1 && !si->watching_parent) { /* Need to be watching the parent now. */ + (void)job_assumes(j, runtime_close(si->fd) == 0); si->fd = -1; semaphoreitem_watch(j, si); } } else { /* File is there but we wish it wasn't. */ - if( si->fd != -1 && si->watching_parent ) { /* Need to watch the file now. */ - job_assumes(j, runtime_close(si->fd) == 0); + if (si->fd != -1 && si->watching_parent) { /* Need to watch the file now. */ + (void)job_assumes(j, runtime_close(si->fd) == 0); si->fd = -1; semaphoreitem_watch(j, si); } @@ -5298,12 +5823,15 @@ const char * job_active(job_t j) { struct machservice *ms; + if (j->p && j->shutdown_monitor) { + return "Monitoring shutdown"; + } if (j->p) { return "PID is still valid"; } if (j->mgr->shutting_down && j->log_redirect_fd) { - job_assumes(j, runtime_close(j->log_redirect_fd) != -1); + (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1); j->log_redirect_fd = 0; } @@ -5311,7 +5839,7 @@ job_active(job_t j) if (job_assumes(j, j->legacy_LS_job)) { return "Standard out/error is still valid"; } else { - job_assumes(j, runtime_close(j->log_redirect_fd) != -1); + (void)job_assumes(j, runtime_close(j->log_redirect_fd) != -1); j->log_redirect_fd = 0; } } @@ -5333,25 +5861,25 @@ void machservice_watch(job_t j, struct machservice *ms) { if (ms->recv) { - job_assumes(j, runtime_add_mport(ms->port, NULL, 0) == KERN_SUCCESS); + (void)job_assumes(j, runtime_add_mport(ms->port, NULL, 0) == KERN_SUCCESS); } } void machservice_ignore(job_t j, struct machservice *ms) { - job_assumes(j, runtime_remove_mport(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, runtime_remove_mport(ms->port) == KERN_SUCCESS); } void machservice_resetport(job_t j, struct machservice *ms) { LIST_REMOVE(ms, port_hash_sle); - job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); - job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); ms->gen_num++; - job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS); - job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS); LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle); } @@ -5385,28 +5913,57 @@ machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_lo } SLIST_INSERT_HEAD(&j->machservices, ms, sle); - - jobmgr_t jm_to_insert = j->mgr; - if( g_flat_mach_namespace ) { - jm_to_insert = (j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) ? j->mgr : root_jobmgr; + + jobmgr_t where2put = j->mgr; + /* XPC domains are separate from Mach bootstraps. */ + if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + if (g_flat_mach_namespace && !(j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) { + where2put = root_jobmgr; + } } - LIST_INSERT_HEAD(&jm_to_insert->ms_hash[hash_ms(ms->name)], ms, name_hash_sle); + /* Don't allow MachServices added by multiple-instance jobs to be looked up by others. + * We could just do this with a simple bit, but then we'd have to uniquify the + * names ourselves to avoid collisions. This is just easier. + */ + if (!j->dedicated_instance) { + LIST_INSERT_HEAD(&where2put->ms_hash[hash_ms(ms->name)], ms, name_hash_sle); + } LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle); job_log(j, LOG_DEBUG, "Mach service added%s: %s", (j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) ? " to private namespace" : "", name); return ms; out_bad2: - job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); out_bad: free(ms); return NULL; } +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +struct machservice * +machservice_new_alias(job_t j, struct machservice *orig) +{ + struct machservice *ms = calloc(1, sizeof(struct machservice) + strlen(orig->name) + 1); + if (job_assumes(j, ms != NULL)) { + strcpy((char *)ms->name, orig->name); + ms->alias = orig; + ms->job = j; + + LIST_INSERT_HEAD(&j->mgr->ms_hash[hash_ms(ms->name)], ms, name_hash_sle); + SLIST_INSERT_HEAD(&j->machservices, ms, sle); + jobmgr_log(j->mgr, LOG_DEBUG, "Service aliased into job manager: %s", orig->name); + } + + return ms; +} +#endif + bootstrap_status_t machservice_status(struct machservice *ms) { + ms = ms->alias ? ms->alias : ms; if (ms->isActive) { return BOOTSTRAP_STATUS_ACTIVE; } else if (ms->job->ondemand) { @@ -5447,10 +6004,10 @@ job_setup_exception_port(job_t j, task_t target_task) #endif if (likely(target_task)) { - job_assumes(j, task_set_exception_ports(target_task, EXC_MASK_CRASH, exc_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); + (void)job_assumes(j, task_set_exception_ports(target_task, EXC_MASK_CRASH, exc_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); } else if (pid1_magic && the_exception_server) { mach_port_t mhp = mach_host_self(); - job_assumes(j, host_set_exception_ports(mhp, EXC_MASK_CRASH, the_exception_server, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); + (void)job_assumes(j, host_set_exception_ports(mhp, EXC_MASK_CRASH, the_exception_server, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); job_assumes(j, launchd_mport_deallocate(mhp) == KERN_SUCCESS); } } @@ -5499,7 +6056,7 @@ machservice_setup_options(launch_data_t obj, const char *key, void *context) } } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT) == 0 && pid1_magic) { if (which_port > HOST_MAX_SPECIAL_KERNEL_PORT) { - job_assumes(ms->job, (errno = host_set_special_port(mhp, which_port, ms->port)) == KERN_SUCCESS); + (void)job_assumes(ms->job, (errno = host_set_special_port(mhp, which_port, ms->port)) == KERN_SUCCESS); } else { job_log(ms->job, LOG_WARNING, "Tried to set a reserved host special port: %d", which_port); } @@ -5516,15 +6073,17 @@ machservice_setup_options(launch_data_t obj, const char *key, void *context) job_set_exception_port(ms->job, ms->port); } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_KUNCSERVER) == 0) { ms->kUNCServer = b; - job_assumes(ms->job, host_set_UNDServer(mhp, ms->port) == KERN_SUCCESS); + (void)job_assumes(ms->job, host_set_UNDServer(mhp, ms->port) == KERN_SUCCESS); + } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_PINGEVENTUPDATES) == 0) { + ms->event_update_port = b; } break; case LAUNCH_DATA_STRING: - if( strcasecmp(key, LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH) == 0 ) { + if (strcasecmp(key, LAUNCH_JOBKEY_MACH_DRAINMESSAGESONCRASH) == 0) { const char *option = launch_data_get_string(obj); - if( strcasecmp(option, "One") == 0 ) { + if (strcasecmp(option, "One") == 0) { ms->drain_one_on_crash = true; - } else if( strcasecmp(option, "All") == 0 ) { + } else if (strcasecmp(option, "All") == 0) { ms->drain_all_on_crash = true; } } @@ -5556,7 +6115,8 @@ machservice_setup(launch_data_t obj, const char *key, void *context) } ms->isActive = false; - + ms->upfront = true; + if (launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY) { launch_data_dict_iterate(obj, machservice_setup_options, ms); } @@ -5570,96 +6130,94 @@ jobmgr_do_garbage_collection(jobmgr_t jm) jobmgr_do_garbage_collection(jmi); } - if( !jm->shutting_down ) { + if (!jm->shutting_down) { return jm; } - if( SLIST_EMPTY(&jm->submgrs) ) { + if (SLIST_EMPTY(&jm->submgrs)) { jobmgr_log(jm, LOG_DEBUG, "No submanagers left."); } else { jobmgr_log(jm, LOG_DEBUG, "Still have submanagers."); } - - int phase = -1; - for( phase = jm->shutdown_phase; phase < JOBMGR_PHASE_LAST; phase++ ) { - if( phase == JOBMGR_PHASE_HOPEFULLY_EXITS_LAST ) { - if( jm == root_jobmgr ) { - simulate_pid1_crash(); - } - - if( jm == root_jobmgr && pid1_magic && !jm->killed_stray_jobs ) { - jobmgr_log_stray_children(jm, true); - jm->killed_stray_jobs = true; - } + + size_t actives = 0; + job_t ji = NULL, jn = NULL; + LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) { + if (ji->anonymous) { + continue; + } + + /* Let the shutdown monitor be up until the very end. */ + if (ji->shutdown_monitor) { + continue; } - uint32_t unkilled_cnt = 0; - job_t ji = NULL, jn = NULL; - LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) { - if( phase == JOBMGR_PHASE_HOPEFULLY_EXITS_FIRST && !ji->hopefully_exits_first ) { - continue; - } else if( phase == JOBMGR_PHASE_NORMAL ) { - if( ji->holds_ref ) { - /* If we're shutting down, release the hold holds_ref jobs - * have on us. - */ - job_remove(ji); - } - - if( ji->hopefully_exits_first || ji->hopefully_exits_last ) { - continue; - } - } else if( phase == JOBMGR_PHASE_HOPEFULLY_EXITS_LAST && !ji->hopefully_exits_last ) { - continue; - } - - if( ji->anonymous ) { - continue; + /* On our first pass through, open a transaction for all the jobs that + * need to be dirty at shutdown. We'll close these transactions once the + * jobs that do not need to be dirty at shutdown have all exited. + */ + if (ji->dirty_at_shutdown && !jm->shutdown_jobs_dirtied) { + job_open_shutdown_transaction(ji); + } + + const char *active = job_active(ji); + if (!active) { + job_remove(ji); + } else { + job_log(ji, LOG_DEBUG, "Job is active: %s", active); + job_stop(ji); + + if (ji->p && !ji->dirty_at_shutdown) { + /* We really only care if the job has not yet been reaped. + * There's no reason to delay shutdown if a Mach port has not + * yet been sent back to us, for example. While we're shutting + * all the "normal" jobs down, do not count the + * dirty-at-shutdown jobs toward the total of actives. + * + * Note that there's a potential race here where we may not get + * a port back in time, so that when we hit jobmgr_remove(), we + * end up removing the job and then our attempt to close the + * Mach port will fail. But at that point, the failure won't + * even make it to the syslog, so not a big deal. + */ + actives++; } - - const char *active = job_active(ji); - if( !active ) { - job_log(ji, LOG_DEBUG, "Job is inactive. Removing."); - job_remove(ji); + + if (ji->clean_kill) { + job_log(ji, LOG_DEBUG, "Job was killed cleanly."); } else { - if( ji->p ) { - if( !ji->stopped ) { - job_log(ji, LOG_DEBUG, "Stopping job."); - job_stop(ji); - unkilled_cnt++; - } else { - if( ji->clean_kill ) { - job_log(ji, LOG_DEBUG, "Job was clean and sent SIGKILL."); - if( !ji->clean_exit_timer_expired ) { - /* Give jobs that were clean and sent SIGKILL 1 second to exit after receipt. */ - unkilled_cnt++; - } else { - job_log(ji, LOG_ERR, "Job was clean, killed and has not exited after 1 second. Moving on."); - } - } else { - job_log(ji, LOG_DEBUG, "Job was sent SIGTERM%s.", ji->sent_sigkill ? " and SIGKILL" : ""); - unkilled_cnt += !ji->sent_sigkill; - } - } - } else { - job_log(ji, LOG_DEBUG, "Job is active: %s", active); + job_log(ji, LOG_DEBUG, "Job was sent SIGTERM%s.", ji->sent_sigkill ? " and SIGKILL" : ""); + } + } + } + + jm->shutdown_jobs_dirtied = true; + if (actives == 0) { + if (!jm->shutdown_jobs_cleaned) { + LIST_FOREACH(ji, &jm->jobs, sle) { + if (!ji->anonymous) { + job_close_shutdown_transaction(ji); + actives++; } } - } /* LIST_FOREACH_SAFE */ - - if( unkilled_cnt == 0 ) { - jobmgr_log(jm, LOG_DEBUG, "Done with the %s bucket, advancing.", s_phases[jm->shutdown_phase]); - jm->shutdown_phase++; - } else { - jobmgr_log(jm, LOG_DEBUG, "Still %u unkilled job%s in %s bucket.", unkilled_cnt, unkilled_cnt > 1 ? "s" : "", s_phases[jm->shutdown_phase]); - phase = JOBMGR_PHASE_LAST; + + jm->shutdown_jobs_cleaned = true; + } else if (jm->monitor_shutdown && _s_shutdown_monitor) { + /* The rest of shutdown has completed, so we can kill the shutdown + * monitor now like it was any other job. + */ + _s_shutdown_monitor->shutdown_monitor = false; + actives = 1; + + job_log(_s_shutdown_monitor, LOG_NOTICE | LOG_CONSOLE, "Stopping shutdown monitor."); + job_stop(_s_shutdown_monitor); + _s_shutdown_monitor = NULL; } - } /* for */ - + } + jobmgr_t r = jm; - if( jm->shutdown_phase > JOBMGR_PHASE_HOPEFULLY_EXITS_LAST && SLIST_EMPTY(&jm->submgrs) ) { + if (SLIST_EMPTY(&jm->submgrs) && actives == 0) { jobmgr_log(jm, LOG_DEBUG, "Removing."); - jobmgr_log_stray_children(jm, false); jobmgr_remove(jm); r = NULL; } @@ -5670,7 +6228,6 @@ jobmgr_do_garbage_collection(jobmgr_t jm) void jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np) { -#if 1 /* I maintain that stray processes should be at the mercy of launchd during shutdown, * but nevertheless, things like diskimages-helper can stick around, and SIGKILLing * them can result in data loss. So we send SIGTERM to all the strays and don't wait @@ -5679,125 +6236,71 @@ jobmgr_kill_stray_children(jobmgr_t jm, pid_t *p, size_t np) * See rdar://problem/6562592 */ size_t i = 0; - for( i = 0; i < np; i++ ) { - if( p[i] != 0 ) { + for (i = 0; i < np; i++) { + if (p[i] != 0) { jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Sending SIGTERM to PID %u and continuing...", p[i]); - jobmgr_assumes(jm, runtime_kill(p[i], SIGTERM) != -1); - } - } -#else - struct timespec tts = { 2, 0 }; /* Wait 2 seconds for stray children to die after being SIGTERM'ed. */ - struct timespec kts = { 1, 0 }; /* Wait 1 second for stray children to die after being SIGKILL'ed. */ - uint64_t start, end, nanosec; - struct kevent kev; - int r, kq = kqueue(); - - if (!jobmgr_assumes(jm, kq != -1)) { - return; - } - - start = runtime_get_opaque_time(); - size_t i = 0, n2t = 0; - for( i = 0; i < np; i++ ) { - if( p[i] != 0 ) { - EV_SET(&kev, p[i], EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); - - if( jobmgr_assumes(jm, kevent(kq, &kev, 1, NULL, 0, NULL) != -1) ) { - jobmgr_assumes(jm, runtime_kill(p[i], SIGTERM) != -1); - n2t++; - } else { - jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "Disregarding PID %u and continuing.", p[i]); - p[i] = 0; - } - } - } - - while( n2t > 0 && (r = kevent(kq, NULL, 0, &kev, 1, &tts)) ) { - int status = 0; - waitpid((pid_t)kev.ident, &status, WNOHANG); - - end = runtime_get_opaque_time(); - nanosec = runtime_opaque_time_to_nano(end - start); - jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "PID %u died after %llu nanoseconds.", (pid_t)kev.ident, nanosec); - - for( i = 0; i < np; i++ ) { - p[i] = ( p[i] == (pid_t)kev.ident ) ? 0 : p[i]; - } - } - - size_t n2k = 0; - for( i = 0; i < np; i++ ) { - if( p[i] != 0 ) { - jobmgr_assumes(jm, runtime_kill(p[i], SIGKILL) != -1); - n2k++; - } - } - - while( n2k > 0 && (r = kevent(kq, NULL, 0, &kev, 1, &kts)) ) { - int status = 0; - waitpid((pid_t)kev.ident, &status, WNOHANG); - - end = runtime_get_opaque_time(); - nanosec = runtime_opaque_time_to_nano(end - start); - jobmgr_log(jm, LOG_DEBUG | LOG_CONSOLE, "PID %u was killed and died after %llu nanoseconds.", (pid_t)kev.ident, nanosec); - - for( i = 0; i < np; i++ ) { - p[i] = ( p[i] == (pid_t)kev.ident ) ? 0 : p[i]; - } - } - - for( i = 0; i < np; i++ ) { - if( p[i] != 0 ) { - jobmgr_log(jm, LOG_NOTICE | LOG_CONSOLE, "PID %u did not die after being SIGKILL'ed 1 second ago.", p[i]); + (void)jobmgr_assumes(jm, runtime_kill(p[i], SIGTERM) != -1); } } -#endif } void jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; - size_t i, kp_cnt = 0, kp_skipped = 0, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); - struct kinfo_proc *kp; - + size_t kp_skipped = 0, len = sizeof(pid_t) * get_kern_max_proc(); + pid_t *pids = NULL; + int i = 0, kp_cnt = 0; + if (likely(jm->parentmgr || !pid1_magic)) { return; } - if (!jobmgr_assumes(jm, (kp = malloc(len)) != NULL)) { + if (!jobmgr_assumes(jm, (pids = malloc(len)) != NULL)) { return; } runtime_ktrace0(RTKT_LAUNCHD_FINDING_ALL_STRAYS); - if (!jobmgr_assumes(jm, sysctl(mib, 3, kp, &len, NULL, 0) != -1)) { + if (!jobmgr_assumes(jm, (kp_cnt = proc_listallpids(pids, len)) != -1)) { goto out; } - kp_cnt = len / sizeof(struct kinfo_proc); pid_t *ps = (pid_t *)calloc(sizeof(pid_t), kp_cnt); - for (i = 0; i < kp_cnt; i++) { - pid_t p_i = kp[i].kp_proc.p_pid; - pid_t pp_i = kp[i].kp_eproc.e_ppid; - pid_t pg_i = kp[i].kp_eproc.e_pgid; - const char *z = (kp[i].kp_proc.p_stat == SZOMB) ? "zombie " : ""; - const char *n = kp[i].kp_proc.p_comm; + struct proc_bsdshortinfo proc; + if (proc_pidinfo(pids[i], PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + jobmgr_assumes(jm, errno == 0); + } + + kp_skipped++; + continue; + } + + pid_t p_i = pids[i]; + pid_t pp_i = proc.pbsi_ppid; + pid_t pg_i = proc.pbsi_pgid; + const char *z = (proc.pbsi_status == SZOMB) ? "zombie " : ""; + const char *n = proc.pbsi_comm; if (unlikely(p_i == 0 || p_i == 1)) { kp_skipped++; continue; } + + if (_s_shutdown_monitor && pp_i == _s_shutdown_monitor->p) { + kp_skipped++; + continue; + } /* We might have some jobs hanging around that we've decided to shut down in spite of. */ job_t j = jobmgr_find_by_pid(jm, p_i, false); - if( !j || (j && j->anonymous) ) { + if (!j || (j && j->anonymous)) { jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Stray %s%s at shutdown: PID %u PPID %u PGID %u %s", z, j ? "anonymous job" : "process", p_i, pp_i, pg_i, n); int status = 0; - if( pp_i == getpid() && !jobmgr_assumes(jm, kp[i].kp_proc.p_stat != SZOMB) ) { - if( jobmgr_assumes(jm, waitpid(p_i, &status, WNOHANG) == 0) ) { + if (pp_i == getpid() && !jobmgr_assumes(jm, proc.pbsi_status != SZOMB)) { + if (jobmgr_assumes(jm, waitpid(p_i, &status, WNOHANG) == 0)) { jobmgr_log(jm, LOG_INFO | LOG_CONSOLE, "Unreaped zombie stray exited with status %i.", WEXITSTATUS(status)); } kp_skipped++; @@ -5808,7 +6311,7 @@ jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays) * hints to the kernel along the way, so that it could shutdown certain subsystems when * their userspace emissaries go away, before the call to reboot(2). */ - if( leader && leader->ignore_pg_at_shutdown ) { + if (leader && leader->ignore_pg_at_shutdown) { kp_skipped++; } else { ps[i] = p_i; @@ -5819,13 +6322,13 @@ jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays) } } - if( (kp_cnt - kp_skipped > 0) && kill_strays ) { - jobmgr_kill_stray_children(jm, ps, kp_cnt); + if ((kp_cnt - kp_skipped > 0) && kill_strays) { + jobmgr_kill_stray_children(jm, ps, kp_cnt - kp_skipped); } free(ps); out: - free(kp); + free(pids); } jobmgr_t @@ -5842,13 +6345,13 @@ job_uncork_fork(job_t j) job_log(j, LOG_DEBUG, "Uncorking the fork()."); /* this unblocks the child and avoids a race * between the above fork() and the kevent_mod() */ - job_assumes(j, write(j->fork_fd, &c, sizeof(c)) == sizeof(c)); - job_assumes(j, runtime_close(j->fork_fd) != -1); + (void)job_assumes(j, write(j->fork_fd, &c, sizeof(c)) == sizeof(c)); + (void)job_assumes(j, runtime_close(j->fork_fd) != -1); j->fork_fd = 0; } jobmgr_t -jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool no_init, mach_port_t session_port) +jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name, bool skip_init, mach_port_t asport) { mach_msg_size_t mxmsgsz; job_t bootstrapper = NULL; @@ -5867,6 +6370,10 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo return NULL; } + if (jm == NULL) { + root_jobmgr = jmr; + } + jmr->kqjobmgr_callback = jobmgr_callback; strcpy(jmr->name_init, name ? name : "Under construction"); @@ -5881,7 +6388,7 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo } if (transfer_port != MACH_PORT_NULL) { - jobmgr_assumes(jmr, jm != NULL); + (void)jobmgr_assumes(jmr, jm != NULL); jmr->jm_port = transfer_port; } else if (!jm && !pid1_magic) { char *trusted_fd = getenv(LAUNCHD_TRUSTED_FD_ENV); @@ -5897,8 +6404,8 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo int dfd, lfd = (int) strtol(trusted_fd, NULL, 10); if ((dfd = dup(lfd)) >= 0) { - jobmgr_assumes(jmr, runtime_close(dfd) != -1); - jobmgr_assumes(jmr, runtime_close(lfd) != -1); + (void)jobmgr_assumes(jmr, runtime_close(dfd) != -1); + (void)jobmgr_assumes(jmr, runtime_close(lfd) != -1); } unsetenv(LAUNCHD_TRUSTED_FD_ENV); @@ -5925,14 +6432,25 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize; } + /* Total hacks. But the MIG server loop is too generic, and the more dynamic + * parts of it haven't been tested, or if they have, it was a very long time + * ago. + */ + if (xpc_events_xpc_events_subsystem.maxsize > mxmsgsz) { + mxmsgsz = xpc_events_xpc_events_subsystem.maxsize; + } + if (xpc_domain_xpc_domain_subsystem.maxsize > mxmsgsz) { + mxmsgsz = xpc_domain_xpc_domain_subsystem.maxsize; + } + if (!jm) { - jobmgr_assumes(jmr, kevent_mod(SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); - jobmgr_assumes(jmr, kevent_mod(SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); - jobmgr_assumes(jmr, kevent_mod(SIGUSR2, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); - jobmgr_assumes(jmr, kevent_mod(0, EVFILT_FS, EV_ADD, VQ_MOUNT|VQ_UNMOUNT|VQ_UPDATE, 0, jmr) != -1); + (void)jobmgr_assumes(jmr, kevent_mod(SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); + (void)jobmgr_assumes(jmr, kevent_mod(SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); + (void)jobmgr_assumes(jmr, kevent_mod(SIGUSR2, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); + (void)jobmgr_assumes(jmr, kevent_mod(0, EVFILT_FS, EV_ADD, VQ_MOUNT|VQ_UNMOUNT|VQ_UPDATE, 0, jmr) != -1); } - if (name && !no_init) { + if (name && !skip_init) { bootstrapper = jobmgr_init_session(jmr, name, sflag); } @@ -5942,18 +6460,19 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo } } - STAILQ_INIT(&jmr->pending_samples); - jobmgr_log(jmr, LOG_DEBUG, "Created job manager%s%s", jm ? " with parent: " : ".", jm ? jm->name : ""); if (bootstrapper) { - bootstrapper->audit_session = session_port; - if( session_port != MACH_PORT_NULL ) { - mach_port_mod_refs(mach_task_self(), session_port, MACH_PORT_RIGHT_SEND, 1); - } + bootstrapper->asport = asport; - jobmgr_log(jmr, LOG_DEBUG, "Bootstrapping new job manager with audit session %u", session_port); - jobmgr_assumes(jmr, job_dispatch(bootstrapper, true) != NULL); + jobmgr_log(jmr, LOG_DEBUG, "Bootstrapping new job manager with audit session %u", asport); + (void)jobmgr_assumes(jmr, job_dispatch(bootstrapper, true) != NULL); + } else { + jmr->req_asport = asport; + } + + if (asport != MACH_PORT_NULL) { + (void)jobmgr_assumes(jmr, launchd_mport_copy_send(asport) == KERN_SUCCESS); } if (jmr->parentmgr) { @@ -5965,10 +6484,102 @@ jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bo out_bad: if (jmr) { jobmgr_remove(jmr); + if (jm == NULL) { + root_jobmgr = NULL; + } } return NULL; } +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +jobmgr_t +jobmgr_new_xpc_singleton_domain(jobmgr_t jm, name_t name) +{ + jobmgr_t new = NULL; + + /* These job managers are basically singletons, so we use the root Mach + * bootstrap port as their requestor ports so they'll never go away. + */ + mach_port_t req_port = root_jobmgr->jm_port; + if (jobmgr_assumes(jm, launchd_mport_make_send(req_port) == KERN_SUCCESS)) { + new = jobmgr_new(root_jobmgr, req_port, MACH_PORT_NULL, false, name, true, MACH_PORT_NULL); + if (new) { + new->properties |= BOOTSTRAP_PROPERTY_XPC_SINGLETON; + new->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN; + new->xpc_singleton = true; + } + } + + return new; +} + +jobmgr_t +jobmgr_find_xpc_per_user_domain(jobmgr_t jm, uid_t uid) +{ + jobmgr_t jmi = NULL; + LIST_FOREACH(jmi, &_s_xpc_user_domains, xpc_le) { + if (jmi->req_euid == uid) { + return jmi; + } + } + + name_t name; + (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.peruser.%u", uid); + jmi = jobmgr_new_xpc_singleton_domain(jm, name); + if (jobmgr_assumes(jm, jmi != NULL)) { + /* We need to create a per-user launchd for this UID if there isn't one + * already so we can grab the bootstrap port. + */ + job_t puj = jobmgr_lookup_per_user_context_internal(NULL, uid, &jmi->req_bsport); + if (jobmgr_assumes(jmi, puj != NULL)) { + (void)jobmgr_assumes(jmi, launchd_mport_copy_send(puj->asport) == KERN_SUCCESS); + (void)jobmgr_assumes(jmi, launchd_mport_copy_send(jmi->req_bsport) == KERN_SUCCESS); + jmi->shortdesc = "per-user"; + jmi->req_asport = puj->asport; + jmi->req_asid = puj->asid; + jmi->req_euid = uid; + jmi->req_egid = -1; + + LIST_INSERT_HEAD(&_s_xpc_user_domains, jmi, xpc_le); + } else { + jobmgr_remove(jmi); + } + } + + return jmi; +} + +jobmgr_t +jobmgr_find_xpc_per_session_domain(jobmgr_t jm, au_asid_t asid) +{ + jobmgr_t jmi = NULL; + LIST_FOREACH(jmi, &_s_xpc_session_domains, xpc_le) { + if (jmi->req_asid == asid) { + return jmi; + } + } + + name_t name; + (void)snprintf(name, sizeof(name), "com.apple.xpc.domain.persession.%i", asid); + jmi = jobmgr_new_xpc_singleton_domain(jm, name); + if (jobmgr_assumes(jm, jmi != NULL)) { + (void)jobmgr_assumes(jmi, launchd_mport_make_send(root_jobmgr->jm_port) == KERN_SUCCESS); + jmi->shortdesc = "per-session"; + jmi->req_bsport = root_jobmgr->jm_port; + (void)jobmgr_assumes(jmi, audit_session_port(asid, &jmi->req_asport) == 0); + jmi->req_asid = asid; + jmi->req_euid = -1; + jmi->req_egid = -1; + + LIST_INSERT_HEAD(&_s_xpc_session_domains, jmi, xpc_le); + } else { + jobmgr_remove(jmi); + } + + return jmi; +} +#endif + job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag) { @@ -5979,7 +6590,7 @@ jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag) snprintf(thelabel, sizeof(thelabel), "com.apple.launchctl.%s", session_type); bootstrapper = job_new(jm, thelabel, NULL, bootstrap_tool); - if( jobmgr_assumes(jm, bootstrapper != NULL) && (jm->parentmgr || !pid1_magic) ) { + if (jobmgr_assumes(jm, bootstrapper != NULL) && (jm->parentmgr || !pid1_magic)) { bootstrapper->is_bootstrapper = true; char buf[100]; @@ -5987,22 +6598,21 @@ jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag) snprintf(buf, sizeof(buf), "0x%X:0:0", getuid()); envitem_new(bootstrapper, "__CF_USER_TEXT_ENCODING", buf, false, false); bootstrapper->weird_bootstrap = true; - jobmgr_assumes(jm, job_setup_machport(bootstrapper)); - } else if( bootstrapper && strncmp(session_type, VPROCMGR_SESSION_SYSTEM, sizeof(VPROCMGR_SESSION_SYSTEM)) == 0 ) { + (void)jobmgr_assumes(jm, job_setup_machport(bootstrapper)); + } else if (bootstrapper && strncmp(session_type, VPROCMGR_SESSION_SYSTEM, sizeof(VPROCMGR_SESSION_SYSTEM)) == 0) { bootstrapper->is_bootstrapper = true; - if( jobmgr_assumes(jm, pid1_magic) ) { + if (jobmgr_assumes(jm, pid1_magic)) { /* Have our system bootstrapper print out to the console. */ bootstrapper->stdoutpath = strdup(_PATH_CONSOLE); bootstrapper->stderrpath = strdup(_PATH_CONSOLE); - if( g_console ) { - jobmgr_assumes(jm, kevent_mod((uintptr_t)fileno(g_console), EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_REVOKE, 0, jm) != -1); + if (g_console) { + (void)jobmgr_assumes(jm, kevent_mod((uintptr_t)fileno(g_console), EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_REVOKE, 0, jm) != -1); } } } jm->session_initialized = true; - return bootstrapper; } @@ -6024,7 +6634,7 @@ jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port) if (jm == root_jobmgr) { if (port == inherited_bootstrap_port) { - jobmgr_assumes(jm, launchd_mport_deallocate(port) == KERN_SUCCESS); + (void)jobmgr_assumes(jm, launchd_mport_deallocate(port) == KERN_SUCCESS); inherited_bootstrap_port = MACH_PORT_NULL; return jobmgr_shutdown(jm); @@ -6063,7 +6673,7 @@ jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t ta */ /* Start in the given bootstrap. */ - if( unlikely((target_j = jobmgr_find_by_pid(jm, target_pid, false)) == NULL) ) { + if (unlikely((target_j = jobmgr_find_by_pid(jm, target_pid, false)) == NULL)) { /* If we fail, do a deep traversal. */ if (unlikely((target_j = jobmgr_find_by_pid_deep(root_jobmgr, target_pid, true)) == NULL)) { jobmgr_log(jm, LOG_DEBUG, "Didn't find PID %i", target_pid); @@ -6081,10 +6691,17 @@ jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t ta return NULL; } - jobmgr_t jm_to_search = ( g_flat_mach_namespace && !(jm->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) ) ? root_jobmgr : jm; - LIST_FOREACH(ms, &jm_to_search->ms_hash[hash_ms(name)], name_hash_sle) { - if (!ms->per_pid && strcmp(name, ms->name) == 0) { - return ms; + jobmgr_t where2look = jm; + /* XPC domains are separate from Mach bootstraps. */ + if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + if (g_flat_mach_namespace && !(jm->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET)) { + where2look = root_jobmgr; + } + } + + LIST_FOREACH(ms, &where2look->ms_hash[hash_ms(name)], name_hash_sle) { + if (!ms->per_pid && strcmp(name, ms->name) == 0) { + return ms; } } @@ -6131,7 +6748,7 @@ machservice_drain_port(struct machservice *ms) bool drain_one = ms->drain_one_on_crash; bool drain_all = ms->drain_all_on_crash; - if( !job_assumes(ms->job, (drain_one || drain_all) == true) ) { + if (!job_assumes(ms->job, (drain_one || drain_all) == true)) { return; } @@ -6149,43 +6766,54 @@ machservice_drain_port(struct machservice *ms) * port, and it will break things if ReportCrash or SafetyNet start advertising other * Mach services. But for now, it should be okay. */ - if( ms->job->alt_exc_handler || ms->job->internal_exc_handler ) { + if (ms->job->alt_exc_handler || ms->job->internal_exc_handler) { mr = launchd_exc_runtime_once(ms->port, sizeof(req_buff), sizeof(rep_buff), req_hdr, rep_hdr, 0); } else { mach_msg_options_t options = MACH_RCV_MSG | MACH_RCV_TIMEOUT ; mr = mach_msg((mach_msg_header_t *)req_hdr, options, 0, sizeof(req_buff), ms->port, 0, MACH_PORT_NULL); - switch( mr ) { - case MACH_MSG_SUCCESS : - mach_msg_destroy((mach_msg_header_t *)req_hdr); - break; - case MACH_RCV_TIMED_OUT : - break; - case MACH_RCV_TOO_LARGE : - runtime_syslog(LOG_WARNING, "Tried to receive message that was larger than %lu bytes", sizeof(req_buff)); - break; - default : - break; + switch (mr) { + case MACH_MSG_SUCCESS: + mach_msg_destroy((mach_msg_header_t *)req_hdr); + break; + case MACH_RCV_TIMED_OUT: + break; + case MACH_RCV_TOO_LARGE: + runtime_syslog(LOG_WARNING, "Tried to receive message that was larger than %lu bytes", sizeof(req_buff)); + break; + default: + break; } } - } while( drain_all && mr != MACH_RCV_TIMED_OUT ); + } while (drain_all && mr != MACH_RCV_TIMED_OUT); } void machservice_delete(job_t j, struct machservice *ms, bool port_died) { + if (ms->alias) { + /* HACK: Egregious code duplication. But dealing with aliases is a + * pretty simple affair since they can't and shouldn't have any complex + * behaviors associated with them. + */ + LIST_REMOVE(ms, name_hash_sle); + SLIST_REMOVE(&j->machservices, ms, machservice, sle); + free(ms); + return; + } + if (unlikely(ms->debug_on_close)) { job_log(j, LOG_NOTICE, "About to enter kernel debugger because of Mach port: 0x%x", ms->port); - job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); + (void)job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); } if (ms->recv && job_assumes(j, !machservice_active(ms))) { job_log(j, LOG_DEBUG, "Closing receive right for %s", ms->name); - job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); } - job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); if (unlikely(ms->port == the_exception_server)) { the_exception_server = 0; @@ -6196,9 +6824,11 @@ machservice_delete(job_t j, struct machservice *ms, bool port_died) if (ms->special_port_num) { SLIST_REMOVE(&special_ports, ms, machservice, special_port_sle); } - SLIST_REMOVE(&j->machservices, ms, machservice, sle); - LIST_REMOVE(ms, name_hash_sle); + + if (!(j->dedicated_instance || ms->event_channel)) { + LIST_REMOVE(ms, name_hash_sle); + } LIST_REMOVE(ms, port_hash_sle); free(ms); @@ -6216,7 +6846,7 @@ machservice_request_notifications(struct machservice *ms) job_checkin(ms->job); } - job_assumes(ms->job, launchd_mport_notify_req(ms->port, which) == KERN_SUCCESS); + (void)job_assumes(ms->job, launchd_mport_notify_req(ms->port, which) == KERN_SUCCESS); } #define NELEM(x) (sizeof(x)/sizeof(x[0])) @@ -6323,15 +6953,25 @@ job_ack_port_destruction(mach_port_t p) * ReceiveRight(N - 1)Returned */ - if( ms->drain_one_on_crash || ms->drain_all_on_crash ) { - if( j->crashed && j->reaped ) { + if (ms->drain_one_on_crash || ms->drain_all_on_crash) { + if (j->crashed && j->reaped) { job_log(j, LOG_DEBUG, "Job has crashed. Draining port..."); machservice_drain_port(ms); - } else if( !(j->crashed || j->reaped) ) { + } else if (!(j->crashed || j->reaped)) { job_log(j, LOG_DEBUG, "Job's exit status is still unknown. Deferring drain."); } } + /* If we get this notification after the job has been reaped, then we want to ping + * the event port to keep things going. + */ + if (ms->event_update_port && !j->p && job_assumes(j, j->event_monitor)) { + if (_s_event_update_port == MACH_PORT_NULL) { + (void)job_assumes(j, launchd_mport_make_send_once(ms->port, &_s_event_update_port) == KERN_SUCCESS); + } + eventsystem_ping(); + } + ms->isActive = false; if (ms->delete_on_destruction) { machservice_delete(j, ms, false); @@ -6351,7 +6991,7 @@ job_ack_no_senders(job_t j) { j->priv_port_has_senders = false; - job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); j->j_port = 0; job_log(j, LOG_DEBUG, "No more senders on privileged Mach bootstrap port"); @@ -6382,7 +7022,7 @@ semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what) SLIST_INSERT_HEAD(&j->semaphores, si, sle); - if( (why == OTHER_JOB_ENABLED || why == OTHER_JOB_DISABLED) && !j->nosy ) { + if ((why == OTHER_JOB_ENABLED || why == OTHER_JOB_DISABLED) && !j->nosy) { job_log(j, LOG_DEBUG, "Job is interested in \"%s\".", what); SLIST_INSERT_HEAD(&s_curious_jobs, j, curious_jobs_sle); j->nosy = true; @@ -6428,15 +7068,15 @@ semaphoreitem_delete(job_t j, struct semaphoreitem *si) SLIST_REMOVE(&j->semaphores, si, semaphoreitem, sle); if (si->fd != -1) { - job_assumes(j, runtime_close(si->fd) != -1); + (void)job_assumes(j, runtime_close(si->fd) != -1); } /* We'll need to rethink this if it ever becomes possible to dynamically add or remove semaphores. */ - if( (si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED) && j->nosy ) { + if ((si->why == OTHER_JOB_ENABLED || si->why == OTHER_JOB_DISABLED) && j->nosy) { j->nosy = false; SLIST_REMOVE(&s_curious_jobs, j, job_s, curious_jobs_sle); } - + free(si); } @@ -6467,10 +7107,14 @@ semaphoreitem_setup(launch_data_t obj, const char *key, void *context) why = launch_data_get_bool(obj) ? SUCCESSFUL_EXIT : FAILED_EXIT; semaphoreitem_new(j, why, NULL); j->start_pending = true; - } else if( strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND) == 0 ) { + } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_AFTERINITIALDEMAND) == 0) { j->needs_kickoff = launch_data_get_bool(obj); + } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_CRASHED) == 0) { + why = launch_data_get_bool(obj) ? CRASHED : DID_NOT_CRASH; + semaphoreitem_new(j, why, NULL); + j->start_pending = true; } else { - job_assumes(j, false); + (void)job_assumes(j, false); } break; case LAUNCH_DATA_DICTIONARY: @@ -6484,18 +7128,163 @@ semaphoreitem_setup(launch_data_t obj, const char *key, void *context) sdic.why_true = OTHER_JOB_ENABLED; sdic.why_false = OTHER_JOB_DISABLED; } else { - job_assumes(j, false); + (void)job_assumes(j, false); break; } launch_data_dict_iterate(obj, semaphoreitem_setup_dict_iter, &sdic); break; default: - job_assumes(j, false); + (void)job_assumes(j, false); break; } } +bool +externalevent_new(job_t j, struct eventsystem *sys, char *evname, launch_data_t event) +{ + struct externalevent *ee = (struct externalevent *)calloc(1, sizeof(struct externalevent) + strlen(evname) + 1); + if (job_assumes(j, ee != NULL)) { + ee->event = launch_data_copy(event); + if (job_assumes(j, ee->event != NULL)) { + strcpy(ee->name, evname); + ee->job = j; + ee->id = sys->curid; + ee->sys = sys; + ee->state = false; + ee->wanted_state = true; + sys->curid++; + + LIST_INSERT_HEAD(&j->events, ee, job_le); + LIST_INSERT_HEAD(&sys->events, ee, sys_le); + + job_log(j, LOG_DEBUG, "New event: %s:%s", sys->name, evname); + } else { + free(ee); + ee = NULL; + } + } + + eventsystem_ping(); + return ee; +} + +void +externalevent_delete(struct externalevent *ee) +{ + launch_data_free(ee->event); + LIST_REMOVE(ee, job_le); + LIST_REMOVE(ee, sys_le); + + free(ee); + + eventsystem_ping(); +} + +void +externalevent_setup(launch_data_t obj, const char *key, void *context) +{ + struct externalevent_iter_ctx *ctx = (struct externalevent_iter_ctx *)context; + (void)job_assumes(ctx->j, externalevent_new(ctx->j, ctx->sys, (char *)key, obj)); +} + +struct externalevent * +externalevent_find(const char *sysname, uint64_t id) +{ + struct externalevent *ei = NULL; + + struct eventsystem *es = eventsystem_find(sysname); + if (launchd_assumes(es != NULL)) { + LIST_FOREACH(ei, &es->events, sys_le) { + if (ei->id == id) { + break; + } + } + } + + return ei; +} + +struct eventsystem * +eventsystem_new(const char *name) +{ + struct eventsystem *es = (struct eventsystem *)calloc(1, sizeof(struct eventsystem) + strlen(name) + 1); + if (launchd_assumes(es != NULL)) { + strcpy(es->name, name); + LIST_INSERT_HEAD(&_s_event_systems, es, global_le); + } + + return es; +} + +void +eventsystem_delete(struct eventsystem *es) +{ + struct externalevent *ei = NULL; + while ((ei = LIST_FIRST(&es->events))) { + externalevent_delete(ei); + } + + LIST_REMOVE(es, global_le); + + free(es); +} + +void +eventsystem_setup(launch_data_t obj, const char *key, void *context) +{ + job_t j = (job_t)context; + if (!job_assumes(j, launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY)) { + return; + } + + struct eventsystem *sys = eventsystem_find(key); + if (unlikely(sys == NULL)) { + sys = eventsystem_new(key); + job_log(j, LOG_DEBUG, "New event system: %s", key); + } + + if (job_assumes(j, sys != NULL)) { + struct externalevent_iter_ctx ctx = { + .j = j, + .sys = sys, + }; + launch_data_dict_iterate(obj, externalevent_setup, &ctx); + sys->has_updates = true; + } +} + +struct eventsystem * +eventsystem_find(const char *name) +{ + struct eventsystem *esi = NULL; + LIST_FOREACH(esi, &_s_event_systems, global_le) { + if (strcmp(name, esi->name) == 0) { + break; + } + } + + return esi; +} + +void +eventsystem_ping(void) +{ + /* We don't wrap this in an assumes() macro because we could potentially + * call this function many times before the helper job gets back to us + * and gives us another send-once right. So if it's MACH_PORT_NULL, that + * means that we've sent a ping, but the helper hasn't yet checked in to + * get the new set of notifications. + */ + if (_s_event_update_port != MACH_PORT_NULL) { + kern_return_t kr = helper_downcall_ping(_s_event_update_port); + if (kr != KERN_SUCCESS) { + runtime_syslog(LOG_NOTICE, "helper_downcall_ping(): kr = 0x%x", kr); + } + _s_event_update_port = MACH_PORT_NULL; + } +} + void jobmgr_dispatch_all_semaphores(jobmgr_t jm) { @@ -6722,11 +7511,11 @@ job_mig_setup_shmem(job_t j, mach_port_t *shmem_port) (memory_object_offset_t)vm_addr, VM_PROT_READ|VM_PROT_WRITE, shmem_port, 0); if (job_assumes(j, kr == 0)) { - job_assumes(j, size_of_page == size_of_page_orig); + (void)job_assumes(j, size_of_page == size_of_page_orig); } /* no need to inherit this in child processes */ - job_assumes(j, vm_inherit(mach_task_self(), (vm_address_t)j->shmem, size_of_page_orig, VM_INHERIT_NONE) == 0); + (void)job_assumes(j, vm_inherit(mach_task_self(), (vm_address_t)j->shmem, size_of_page_orig, VM_INHERIT_NONE) == 0); return kr; } @@ -6792,13 +7581,9 @@ job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig) return BOOTSTRAP_NO_MEMORY; } - 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 (unlikely(ldc->euid != 0 && ldc->euid != getuid()) || j->deny_job_creation) { #if TARGET_OS_EMBEDDED - if( !j->embedded_special_privileges ) { + if (!j->embedded_special_privileges) { return BOOTSTRAP_NOT_PRIVILEGED; } #else @@ -6812,12 +7597,12 @@ job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig) } #endif - if (unlikely(!(otherj = job_find(targetlabel)))) { + if (unlikely(!(otherj = job_find(NULL, targetlabel)))) { return BOOTSTRAP_UNKNOWN_SERVICE; } #if TARGET_OS_EMBEDDED - if( j->embedded_special_privileges && strcmp(j->username, otherj->username) != 0 ) { + if (j->embedded_special_privileges && strcmp(j->username, otherj->username) != 0) { return BOOTSTRAP_NOT_PRIVILEGED; } #endif @@ -6834,7 +7619,7 @@ job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig) if (do_block) { job_log(j, LOG_DEBUG, "Blocking MIG return of job_remove(): %s", otherj->label); /* this is messy. We shouldn't access 'otherj' after job_remove(), but we check otherj->p first... */ - job_assumes(otherj, waiting4removal_new(otherj, srp)); + (void)job_assumes(otherj, waiting4removal_new(otherj, srp)); return MIG_NO_REPLY; } else { return 0; @@ -6846,20 +7631,21 @@ job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig) if (!j->shmem) { j->sent_kill_via_shmem = true; - job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1); + (void)job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1); return 0; } - #if !TARGET_OS_EMBEDDED + +#if !TARGET_OS_EMBEDDED if (__sync_bool_compare_and_swap(&j->shmem->vp_shmem_transaction_cnt, 0, -1)) { j->shmem->vp_shmem_flags |= VPROC_SHMEM_EXITING; j->sent_kill_via_shmem = true; - job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1); + (void)job_assumes(j, runtime_kill(otherj->p, SIGKILL) != -1); return 0; } - #endif +#endif return BOOTSTRAP_NOT_PRIVILEGED; } else if (otherj->p) { - job_assumes(j, runtime_kill(otherj->p, sig) != -1); + (void)job_assumes(j, runtime_kill(otherj->p, sig) != -1); } return 0; @@ -6898,9 +7684,7 @@ job_mig_log_drain(job_t j, mach_port_t srp, vm_offset_t *outval, mach_msg_type_n } kern_return_t -job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, - vm_offset_t inval, mach_msg_type_number_t invalCnt, - vm_offset_t *outval, mach_msg_type_number_t *outvalCnt) +job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, vm_offset_t inval, mach_msg_type_number_t invalCnt, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt) { const char *action; launch_data_t input_obj = NULL, output_obj = NULL; @@ -6911,11 +7695,9 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } - if (unlikely(inkey && ldc->euid && ldc->euid != getuid())) { return BOOTSTRAP_NOT_PRIVILEGED; } - if (unlikely(inkey && outkey && !job_assumes(j, inkey == outkey))) { return 1; } @@ -6936,6 +7718,9 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, return 1; } + /* Note to future maintainers: launch_data_unpack() does NOT return a heap object. The data + * is decoded in-place. So do not call launch_data_free() on input_obj. + */ runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK); if (unlikely(invalCnt && !job_assumes(j, (input_obj = launch_data_unpack((void *)inval, invalCnt, NULL, 0, &data_offset, NULL)) != NULL))) { goto out_bad; @@ -6966,18 +7751,18 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, launch_data_free(output_obj); break; case VPROC_GSK_MGR_NAME: - if( !job_assumes(j, (output_obj = launch_data_new_string(j->mgr->name)) != NULL) ) { + if (!job_assumes(j, (output_obj = launch_data_new_string(j->mgr->name)) != NULL)) { goto out_bad; } packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL); if (!job_assumes(j, packed_size != 0)) { goto out_bad; } - + launch_data_free(output_obj); break; case VPROC_GSK_JOB_OVERRIDES_DB: - if( !job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_OVERRIDES))) != NULL) ) { + if (!job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_OVERRIDES))) != NULL)) { goto out_bad; } packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL); @@ -6988,7 +7773,7 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, launch_data_free(output_obj); break; case VPROC_GSK_JOB_CACHE_DB: - if( !job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_JOBCACHE))) != NULL) ) { + if (!job_assumes(j, (output_obj = launch_data_new_string(launchd_data_base_path(LAUNCHD_DB_TYPE_JOBCACHE))) != NULL)) { goto out_bad; } packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL); @@ -6997,7 +7782,7 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, } job_log(j, LOG_DEBUG, "Location of job cache database: %s", launch_data_get_string(output_obj)); - + launch_data_free(output_obj); break; case 0: @@ -7010,28 +7795,32 @@ job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, } if (invalCnt) switch (inkey) { - case VPROC_GSK_ENVIRONMENT: - if( launch_data_get_type(input_obj) == LAUNCH_DATA_DICTIONARY ) { - if( j->p ) { - job_log(j, LOG_INFO, "Setting environment for a currently active job. This environment will take effect on the next invocation of the job."); + case VPROC_GSK_ENVIRONMENT: + if (launch_data_get_type(input_obj) == LAUNCH_DATA_DICTIONARY) { + if (j->p) { + job_log(j, LOG_INFO, "Setting environment for a currently active job. This environment will take effect on the next invocation of the job."); + } + launch_data_dict_iterate(input_obj, envitem_setup_one_shot, j); } - launch_data_dict_iterate(input_obj, envitem_setup_one_shot, j); - } - break; - case 0: - break; - default: - goto out_bad; + break; + case 0: + break; + default: + goto out_bad; } - + mig_deallocate(inval, invalCnt); - return 0; - + out_bad: + mig_deallocate(inval, invalCnt); if (*outval) { mig_deallocate(*outval, *outvalCnt); } + if (output_obj) { + launch_data_free(output_obj); + } + return 1; } @@ -7140,9 +7929,9 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv runtime_add_weak_ref(); } j->start_interval = (typeof(j->start_interval)) inval; - job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1); } else if (j->start_interval) { - job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); + (void)job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); if (j->start_interval != 0) { runtime_del_weak_ref(); } @@ -7175,21 +7964,25 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv if (inval < 0 || inval > UINT16_MAX) { kr = 1; } else { - umask((mode_t) inval); +#if HAVE_SANDBOX + if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) { + kr = 1; + } else { + umask((mode_t) inval); + } +#endif } break; case VPROC_GSK_TRANSACTIONS_ENABLED: - if( !job_assumes(j, inval != 0) ) { + if (!job_assumes(j, inval != 0)) { job_log(j, LOG_WARNING, "Attempt to unregister from transaction model. This is not supported."); kr = 1; } else { - job_log(j, LOG_DEBUG, "Now participating in transaction model."); j->kill_via_shmem = (bool)inval; - job_log(j, LOG_DEBUG, "j->kill_via_shmem = %s", j->kill_via_shmem ? "true" : "false"); } break; case VPROC_GSK_WEIRD_BOOTSTRAP: - if( job_assumes(j, j->weird_bootstrap) ) { + if (job_assumes(j, j->weird_bootstrap)) { job_log(j, LOG_DEBUG, "Unsetting weird bootstrap."); mach_msg_size_t mxmsgsz = (typeof(mxmsgsz)) sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem); @@ -7198,7 +7991,7 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize; } - job_assumes(j, runtime_add_mport(j->mgr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS); + (void)job_assumes(j, runtime_add_mport(j->mgr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS); j->weird_bootstrap = false; } break; @@ -7206,26 +7999,35 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv j->wait4debugger_oneshot = inval; break; case VPROC_GSK_PERUSER_SUSPEND: - if( job_assumes(j, pid1_magic && ldc->euid == 0) ) { + if (job_assumes(j, pid1_magic && ldc->euid == 0)) { mach_port_t junk = MACH_PORT_NULL; - job_t jpu = jobmgr_lookup_per_user_context_internal(j, (uid_t)inval, false, &junk); - if( job_assumes(j, jpu != NULL) ) { + job_t jpu = jobmgr_lookup_per_user_context_internal(j, (uid_t)inval, &junk); + if (job_assumes(j, jpu != NULL)) { struct suspended_peruser *spi = NULL; - LIST_FOREACH( spi, &j->suspended_perusers, sle ) { - if( (int64_t)(spi->j->mach_uid) == inval ) { + LIST_FOREACH(spi, &j->suspended_perusers, sle) { + if ((int64_t)(spi->j->mach_uid) == inval) { job_log(j, LOG_WARNING, "Job tried to suspend per-user launchd for UID %lli twice.", inval); break; } } - if( spi == NULL ) { + if (spi == NULL) { job_log(j, LOG_INFO, "Job is suspending the per-user launchd for UID %lli.", inval); spi = (struct suspended_peruser *)calloc(sizeof(struct suspended_peruser), 1); - if( job_assumes(j, spi != NULL) ) { + if (job_assumes(j, spi != NULL)) { + /* Stop listening for events. + * + * See . + */ + if (jpu->peruser_suspend_count == 0) { + job_ignore(jpu); + } + spi->j = jpu; spi->j->peruser_suspend_count++; LIST_INSERT_HEAD(&j->suspended_perusers, spi, sle); job_stop(spi->j); + *outval = jpu->p; } else { kr = BOOTSTRAP_NO_MEMORY; } @@ -7236,10 +8038,10 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv } break; case VPROC_GSK_PERUSER_RESUME: - if( job_assumes(j, pid1_magic == true) ) { + if (job_assumes(j, pid1_magic == true)) { struct suspended_peruser *spi = NULL, *spt = NULL; - LIST_FOREACH_SAFE( spi, &j->suspended_perusers, sle, spt ) { - if( (int64_t)(spi->j->mach_uid) == inval ) { + LIST_FOREACH_SAFE(spi, &j->suspended_perusers, sle, spt) { + if ((int64_t)(spi->j->mach_uid) == inval) { spi->j->peruser_suspend_count--; LIST_REMOVE(spi, sle); job_log(j, LOG_INFO, "Job is resuming the per-user launchd for UID %lli.", inval); @@ -7247,10 +8049,11 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv } } - if( !job_assumes(j, spi != NULL) ) { + if (!job_assumes(j, spi != NULL)) { job_log(j, LOG_WARNING, "Job tried to resume per-user launchd for UID %lli that it did not suspend.", inval); kr = BOOTSTRAP_NOT_PRIVILEGED; - } else if( spi->j->peruser_suspend_count == 0 ) { + } else if (spi->j->peruser_suspend_count == 0) { + job_watch(spi->j); job_dispatch(spi->j, false); free(spi); } @@ -7269,7 +8072,7 @@ job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inv } kern_return_t -job_mig_post_fork_ping(job_t j, task_t child_task, mach_port_t *audit_session) +job_mig_post_fork_ping(job_t j, task_t child_task, mach_port_t *asport) { struct machservice *ms; @@ -7306,15 +8109,22 @@ job_mig_post_fork_ping(job_t j, task_t child_task, mach_port_t *audit_session) } } - mach_port_t _session = MACH_PORT_NULL; + /* MIG will not zero-initialize this pointer, so we must always do so. See + * . + */ + *asport = MACH_PORT_NULL; #if !TARGET_OS_EMBEDDED - if( !j->anonymous && !j->per_user ) { - job_log(j, LOG_DEBUG, "Returning session port %u", j->audit_session); - _session = j->audit_session; + if (!j->anonymous) { + /* XPC services will spawn into the root security session by default. + * xpcproxy will switch them away if needed. + */ + if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + job_log(j, LOG_DEBUG, "Returning j->asport: %u", j->asport); + *asport = j->asport; + } } -#endif - *audit_session = _session; - job_assumes(j, launchd_mport_deallocate(child_task) == KERN_SUCCESS); +#endif + (void)job_assumes(j, launchd_mport_deallocate(child_task) == KERN_SUCCESS); return 0; } @@ -7323,7 +8133,7 @@ kern_return_t job_mig_reboot2(job_t j, uint64_t flags) { char who_started_the_reboot[2048] = ""; - struct kinfo_proc kp; + struct proc_bsdshortinfo proc; struct ldcred *ldc = runtime_get_caller_creds(); pid_t pid_to_log; @@ -7338,35 +8148,34 @@ job_mig_reboot2(job_t j, uint64_t flags) #if !TARGET_OS_EMBEDDED if (unlikely(ldc->euid)) { #else - if( unlikely(ldc->euid) && !j->embedded_special_privileges ) { + if (unlikely(ldc->euid) && !j->embedded_special_privileges) { #endif return BOOTSTRAP_NOT_PRIVILEGED; } - for (pid_to_log = ldc->pid; pid_to_log; pid_to_log = kp.kp_eproc.e_ppid) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid_to_log }; - size_t who_offset, len = sizeof(kp); - - if (!job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1)) { + for (pid_to_log = ldc->pid; pid_to_log; pid_to_log = proc.pbsi_ppid) { + size_t who_offset; + if (proc_pidinfo(pid_to_log, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + job_assumes(j, errno == 0); + } return 1; } - if( !job_assumes(j, pid_to_log != kp.kp_eproc.e_ppid) ) { + if (!job_assumes(j, pid_to_log != (pid_t)proc.pbsi_ppid)) { job_log(j, LOG_WARNING, "Job which is its own parent started reboot."); - snprintf(who_started_the_reboot, sizeof(who_started_the_reboot), "%s[%u]->%s[%u]->%s[%u]->...", kp.kp_proc.p_comm, pid_to_log, kp.kp_proc.p_comm, pid_to_log, kp.kp_proc.p_comm, pid_to_log); + snprintf(who_started_the_reboot, sizeof(who_started_the_reboot), "%s[%u]->%s[%u]->%s[%u]->...", proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log, proc.pbsi_comm, pid_to_log); break; } who_offset = strlen(who_started_the_reboot); snprintf(who_started_the_reboot + who_offset, sizeof(who_started_the_reboot) - who_offset, - " %s[%u]%s", kp.kp_proc.p_comm, pid_to_log, kp.kp_eproc.e_ppid ? " ->" : ""); + " %s[%u]%s", proc.pbsi_comm, pid_to_log, proc.pbsi_ppid ? " ->" : ""); } root_jobmgr->reboot_flags = (int)flags; - - launchd_shutdown(); - job_log(j, LOG_DEBUG, "reboot2() initiated by:%s", who_started_the_reboot); + launchd_shutdown(); return 0; } @@ -7378,9 +8187,16 @@ job_mig_getsocket(job_t j, name_t spr) return BOOTSTRAP_NO_MEMORY; } - if( j->deny_job_creation ) { + if (j->deny_job_creation) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + +#if HAVE_SANDBOX + struct ldcred *ldc = runtime_get_caller_creds(); + if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) { return BOOTSTRAP_NOT_PRIVILEGED; } +#endif ipc_server_init(); @@ -7410,7 +8226,7 @@ job_mig_log(job_t j, int pri, int err, logmsg_t msg) } job_t -jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch, mach_port_t *mp) +jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, mach_port_t *mp) { job_t ji = NULL; LIST_FOREACH(ji, &root_jobmgr->jobs, sle) { @@ -7429,7 +8245,7 @@ jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch break; } - if( unlikely(ji == NULL) ) { + if (unlikely(ji == NULL)) { struct machservice *ms; char lbuf[1024]; @@ -7439,39 +8255,60 @@ jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch ji = job_new(root_jobmgr, lbuf, "/sbin/launchd", NULL); - if( ji != NULL ) { + if (ji != NULL) { + auditinfo_addr_t auinfo = { + .ai_termid = { .at_type = AU_IPv4 }, + .ai_auid = which_user, + .ai_asid = AU_ASSIGN_ASID, + }; + + if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) { + job_log(ji, LOG_DEBUG, "Created new security session for per-user launchd: %u", auinfo.ai_asid); + (void)job_assumes(ji, (ji->asport = audit_session_self()) != MACH_PORT_NULL); + + /* Kinda lame that we have to do this, but we can't create an + * audit session without joining it. + */ + (void)job_assumes(ji, audit_session_join(g_audit_session_port)); + ji->asid = auinfo.ai_asid; + } else { + job_log(ji, LOG_WARNING, "Could not set audit session!"); + job_remove(ji); + return NULL; + } + ji->mach_uid = which_user; ji->per_user = true; ji->kill_via_shmem = true; - + struct stat sb; char pu_db[PATH_MAX]; snprintf(pu_db, sizeof(pu_db), LAUNCHD_DB_PREFIX "/%s", lbuf); bool created = false; int err = stat(pu_db, &sb); - if( (err == -1 && errno == ENOENT) || (err == 0 && !S_ISDIR(sb.st_mode)) ) { - if( err == 0 ) { + if ((err == -1 && errno == ENOENT) || (err == 0 && !S_ISDIR(sb.st_mode))) { + if (err == 0) { char move_aside[PATH_MAX]; snprintf(move_aside, sizeof(move_aside), LAUNCHD_DB_PREFIX "/%s.movedaside", lbuf); - job_assumes(ji, rename(pu_db, move_aside) != -1); + (void)job_assumes(ji, rename(pu_db, move_aside) != -1); } - job_assumes(ji, mkdir(pu_db, S_IRWXU) != -1); - job_assumes(ji, chown(pu_db, which_user, 0) != -1); + (void)job_assumes(ji, mkdir(pu_db, S_IRWXU) != -1); + (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1); created = true; } - if( !created ) { - if( !job_assumes(ji, sb.st_uid == which_user) ) { - job_assumes(ji, chown(pu_db, which_user, 0) != -1); + if (!created) { + if (!job_assumes(ji, sb.st_uid == which_user)) { + (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1); } - if( !job_assumes(ji, sb.st_gid == 0) ) { - job_assumes(ji, chown(pu_db, which_user, 0) != -1); + if (!job_assumes(ji, sb.st_gid == 0)) { + (void)job_assumes(ji, chown(pu_db, which_user, 0) != -1); } - if( !job_assumes(ji, sb.st_mode == (S_IRWXU | S_IFDIR)) ) { - job_assumes(ji, chmod(pu_db, S_IRWXU) != -1); + if (!job_assumes(ji, sb.st_mode == (S_IRWXU | S_IFDIR))) { + (void)job_assumes(ji, chmod(pu_db, S_IRWXU) != -1); } } @@ -7481,8 +8318,8 @@ jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch } else { ms->per_user_hack = true; ms->hide = true; - - ji = dispatch ? job_dispatch(ji, false) : ji; + + ji = job_dispatch(ji, false); } } } else { @@ -7528,40 +8365,66 @@ job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont) *up_cont = MACH_PORT_NULL; - jpu = jobmgr_lookup_per_user_context_internal(j, which_user, true, up_cont); + jpu = jobmgr_lookup_per_user_context_internal(j, which_user, up_cont); return 0; } kern_return_t -job_mig_check_in2(job_t j, name_t servicename, mach_port_t *serviceportp, uint64_t flags) +job_mig_check_in2(job_t j, name_t servicename, mach_port_t *serviceportp, uuid_t instance_id, uint64_t flags) { bool per_pid_service = flags & BOOTSTRAP_PER_PID_SERVICE; + bool strict = flags & BOOTSTRAP_STRICT_CHECKIN; struct ldcred *ldc = runtime_get_caller_creds(); - struct machservice *ms; + struct machservice *ms = NULL; job_t jo; if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } - ms = jobmgr_lookup_service(j->mgr, servicename, false, per_pid_service ? ldc->pid : 0); - - if (ms == NULL) { - *serviceportp = MACH_PORT_NULL; - - if (unlikely((ms = machservice_new(j, servicename, serviceportp, per_pid_service)) == NULL)) { - return BOOTSTRAP_NO_MEMORY; - } - - /* Treat this like a legacy job. */ - if( !j->legacy_mach_job ) { - ms->isActive = true; - ms->recv = false; + if (j->dedicated_instance) { + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &j->machservices, sle) { + if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) { + uuid_copy(instance_id, j->instance_id); + ms = msi; + break; + } } + } else { + ms = jobmgr_lookup_service(j->mgr, servicename, false, per_pid_service ? ldc->pid : 0); + } - if (!(j->anonymous || j->legacy_LS_job || j->legacy_mach_job)) { - job_log(j, LOG_SCOLDING, "Please add the following service to the configuration file for this job: %s", servicename); + if (strict) { + if (likely(ms != NULL)) { + if (ms->job != j) { + return BOOTSTRAP_NOT_PRIVILEGED; + } else if (ms->isActive) { + return BOOTSTRAP_SERVICE_ACTIVE; + } + } else { + return BOOTSTRAP_UNKNOWN_SERVICE; + } + } else if (ms == NULL) { + if (job_assumes(j, !j->dedicated_instance)) { + *serviceportp = MACH_PORT_NULL; + + if (unlikely((ms = machservice_new(j, servicename, serviceportp, per_pid_service)) == NULL)) { + return BOOTSTRAP_NO_MEMORY; + } + + /* Treat this like a legacy job. */ + if (!j->legacy_mach_job) { + ms->isActive = true; + ms->recv = false; + } + + if (!(j->anonymous || j->legacy_LS_job || j->legacy_mach_job)) { + job_log(j, LOG_SCOLDING, "Please add the following service to the configuration file for this job: %s", servicename); + } + } else { + return BOOTSTRAP_UNKNOWN_SERVICE; } } else { if (unlikely((jo = machservice_job(ms)) != j)) { @@ -7652,17 +8515,22 @@ job_mig_register2(job_t j, name_t servicename, mach_port_t serviceport, uint64_t } kern_return_t -job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serviceportp, pid_t target_pid, uint64_t flags) +job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serviceportp, pid_t target_pid, uuid_t instance_id, uint64_t flags) { - struct machservice *ms; + struct machservice *ms = NULL; struct ldcred *ldc = runtime_get_caller_creds(); kern_return_t kr; bool per_pid_lookup = flags & BOOTSTRAP_PER_PID_SERVICE; + bool specific_instance = flags & BOOTSTRAP_SPECIFIC_INSTANCE; + bool strict_lookup = flags & BOOTSTRAP_STRICT_LOOKUP; + bool privileged = flags & BOOTSTRAP_PRIVILEGED_SERVER; if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } + bool xpc_req = j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN; + /* 5641783 for the embedded hack */ #if !TARGET_OS_EMBEDDED if (unlikely(pid1_magic && j->anonymous && j->mgr->parentmgr == NULL && ldc->uid != 0 && ldc->euid != 0)) { @@ -7671,7 +8539,10 @@ job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serv #endif #if HAVE_SANDBOX - if (unlikely(sandbox_check(ldc->pid, "mach-lookup", per_pid_lookup ? SANDBOX_FILTER_LOCAL_NAME : SANDBOX_FILTER_GLOBAL_NAME, servicename) > 0)) { + /* We don't do sandbox checking for XPC domains because, by definition, all + * the services within your domain should be accessibly to you. + */ + if (!xpc_req && unlikely(sandbox_check(ldc->pid, "mach-lookup", per_pid_lookup ? SANDBOX_FILTER_LOCAL_NAME : SANDBOX_FILTER_GLOBAL_NAME, servicename) > 0)) { return BOOTSTRAP_NOT_PRIVILEGED; } #endif @@ -7679,19 +8550,81 @@ job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serv if (per_pid_lookup) { ms = jobmgr_lookup_service(j->mgr, servicename, false, target_pid); } else { - ms = jobmgr_lookup_service(j->mgr, servicename, true, 0); + if (xpc_req) { + /* Requests from XPC domains stay local. */ + ms = jobmgr_lookup_service(j->mgr, servicename, false, 0); + } else { + /* A strict lookup which is privileged won't even bother trying to + * find a service if we're not hosting the root Mach bootstrap. + */ + if (strict_lookup && privileged) { + if (inherited_bootstrap_port == MACH_PORT_NULL) { + ms = jobmgr_lookup_service(j->mgr, servicename, true, 0); + } + } else { + ms = jobmgr_lookup_service(j->mgr, servicename, true, 0); + } + } } if (likely(ms)) { - if (machservice_hidden(ms) && !machservice_active(ms)) { - ms = NULL; - } else if (unlikely(ms->per_user_hack)) { + ms = ms->alias ? ms->alias : ms; + if (unlikely(specific_instance && ms->job->multiple_instances)) { + job_t ji = NULL; + job_t instance = NULL; + LIST_FOREACH(ji, &ms->job->subjobs, subjob_sle) { + if (uuid_compare(instance_id, ji->instance_id) == 0) { + instance = ji; + break; + } + } + + if (unlikely(instance == NULL)) { + job_log(ms->job, LOG_DEBUG, "Creating new instance of job based on lookup of service %s", ms->name); + instance = job_new_subjob(ms->job, instance_id); + if (job_assumes(j, instance != NULL)) { + /* Disable this support for now. We only support having + * multi-instance jobs within private XPC domains. + */ +#if 0 + /* If the job is multi-instance, in a singleton XPC domain + * and the request is not coming from within that singleton + * domain, we need to alias the new job into the requesting + * domain. + */ + if (!j->mgr->xpc_singleton && xpc_req) { + (void)job_assumes(instance, job_new_alias(j->mgr, instance)); + } +#endif + job_dispatch(instance, false); + } + } + ms = NULL; + if (job_assumes(j, instance != NULL)) { + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &instance->machservices, sle) { + /* sizeof(servicename) will return the size of a pointer, even though it's + * an array type, because when passing arrays as parameters in C, they + * implicitly degrade to pointers. + */ + if (strncmp(servicename, msi->name, sizeof(name_t) - 1) == 0) { + ms = msi; + break; + } + } + } + } else { + if (machservice_hidden(ms) && !machservice_active(ms)) { + ms = NULL; + } else if (unlikely(ms->per_user_hack)) { + ms = NULL; + } } } if (likely(ms)) { - job_assumes(j, machservice_port(ms) != MACH_PORT_NULL); + (void)job_assumes(j, machservice_port(ms) != MACH_PORT_NULL); job_log(j, LOG_DEBUG, "%sMach service lookup: %s", per_pid_lookup ? "Per PID " : "", servicename); if (unlikely(!per_pid_lookup && j->lastlookup == ms && j->lastlookup_gennum == ms->gen_num && !j->per_user)) { @@ -7705,10 +8638,18 @@ job_mig_look_up2(job_t j, mach_port_t srp, name_t servicename, mach_port_t *serv *serviceportp = machservice_port(ms); kr = BOOTSTRAP_SUCCESS; - } else if (!per_pid_lookup && (inherited_bootstrap_port != MACH_PORT_NULL)) { + } else if (strict_lookup && !privileged) { + /* Hack: We need to simulate XPC's desire not to establish a hierarchy. So if + * XPC is doing the lookup, and it's not a privileged lookup, we won't forward. + * But if it is a privileged lookup (that is, was looked up in XPC_DOMAIN_LOCAL_SYSTEM) + * then we must forward. + */ + return BOOTSTRAP_UNKNOWN_SERVICE; + } else if (inherited_bootstrap_port != MACH_PORT_NULL) { + /* Requests from within an XPC domain don't get forwarded. */ job_log(j, LOG_DEBUG, "Mach service lookup forwarded: %s", servicename); /* Clients potentially check the audit token of the reply to verify that the returned send right is trustworthy. */ - job_assumes(j, vproc_mig_look_up2_forward(inherited_bootstrap_port, srp, servicename, 0, 0) == 0); + (void)job_assumes(j, vproc_mig_look_up2_forward(inherited_bootstrap_port, srp, servicename, target_pid, instance_id, flags) == 0); /* The previous routine moved the reply port, we're forced to return MIG_NO_REPLY now */ return MIG_NO_REPLY; } else if (pid1_magic && j->anonymous && ldc->euid >= 500 && strcasecmp(j->mgr->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) { @@ -7742,7 +8683,7 @@ job_mig_parent(job_t j, mach_port_t srp, mach_port_t *parentport) } else if (MACH_PORT_NULL == inherited_bootstrap_port) { *parentport = jm->jm_port; } else { - job_assumes(j, vproc_mig_parent_forward(inherited_bootstrap_port, srp) == 0); + (void)job_assumes(j, vproc_mig_parent_forward(inherited_bootstrap_port, srp) == 0); /* The previous routine moved the reply port, we're forced to return MIG_NO_REPLY now */ return MIG_NO_REPLY; } @@ -7750,10 +8691,21 @@ job_mig_parent(job_t j, mach_port_t srp, mach_port_t *parentport) } kern_return_t -job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cnt, - name_array_t *servicejobsp, unsigned int *servicejobs_cnt, - bootstrap_status_array_t *serviceactivesp, unsigned int *serviceactives_cnt, - uint64_t flags) +job_mig_get_root_bootstrap(job_t j, mach_port_t *rootbsp) +{ + if (inherited_bootstrap_port == MACH_PORT_NULL) { + *rootbsp = root_jobmgr->jm_port; + (void)job_assumes(j, launchd_mport_make_send(root_jobmgr->jm_port) == KERN_SUCCESS); + } else { + *rootbsp = inherited_bootstrap_port; + (void)job_assumes(j, launchd_mport_copy_send(inherited_bootstrap_port) == KERN_SUCCESS); + } + + return BOOTSTRAP_SUCCESS; +} + +kern_return_t +job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cnt, name_array_t *servicejobsp, unsigned int *servicejobs_cnt, bootstrap_status_array_t *serviceactivesp, unsigned int *serviceactives_cnt, uint64_t flags) { name_array_t service_names = NULL; name_array_t service_jobs = NULL; @@ -7765,8 +8717,8 @@ job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cn return BOOTSTRAP_NO_MEMORY; } - if( g_flat_mach_namespace ) { - if( (j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) || (flags & BOOTSTRAP_FORCE_LOCAL) ) { + if (g_flat_mach_namespace) { + if ((j->mgr->properties & BOOTSTRAP_PROPERTY_EXPLICITSUBSET) || (flags & BOOTSTRAP_FORCE_LOCAL)) { jm = j->mgr; } else { jm = root_jobmgr; @@ -7777,8 +8729,8 @@ job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cn unsigned int i = 0; struct machservice *msi = NULL; - for( i = 0; i < MACHSERVICE_HASH_SIZE; i++ ) { - LIST_FOREACH( msi, &jm->ms_hash[i], name_hash_sle ) { + for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) { + LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) { cnt += !msi->per_pid ? 1 : 0; } } @@ -7802,18 +8754,23 @@ job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cn goto out_bad; } - for( i = 0; i < MACHSERVICE_HASH_SIZE; i++ ) { - LIST_FOREACH( msi, &jm->ms_hash[i], name_hash_sle ) { - if( !msi->per_pid ) { + for (i = 0; i < MACHSERVICE_HASH_SIZE; i++) { + LIST_FOREACH(msi, &jm->ms_hash[i], name_hash_sle) { + if (!msi->per_pid) { strlcpy(service_names[cnt2], machservice_name(msi), sizeof(service_names[0])); - strlcpy(service_jobs[cnt2], msi->job->label, sizeof(service_jobs[0])); + msi = msi->alias ? msi->alias : msi; + if (msi->job->mgr->shortdesc) { + strlcpy(service_jobs[cnt2], msi->job->mgr->shortdesc, sizeof(service_jobs[0])); + } else { + strlcpy(service_jobs[cnt2], msi->job->label, sizeof(service_jobs[0])); + } service_actives[cnt2] = machservice_status(msi); cnt2++; } } } - job_assumes(j, cnt == cnt2); + (void)job_assumes(j, cnt == cnt2); out: *servicenamesp = service_names; @@ -7838,12 +8795,10 @@ out_bad: } kern_return_t -job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_type_number_t *child_ports_cnt, - name_array_t *child_names, mach_msg_type_number_t *child_names_cnt, - bootstrap_property_array_t *child_properties, mach_msg_type_number_t *child_properties_cnt) +job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_type_number_t *child_ports_cnt, name_array_t *child_names, mach_msg_type_number_t *child_names_cnt, bootstrap_property_array_t *child_properties,mach_msg_type_number_t *child_properties_cnt) { kern_return_t kr = BOOTSTRAP_NO_MEMORY; - if( !launchd_assumes(j != NULL) ) { + if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } @@ -7853,7 +8808,7 @@ job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_ty * Otherwise, this could be used to cross sessions, which counts as a security vulnerability * in a non-flat namespace. */ - if( ldc->euid != 0 ) { + if (ldc->euid != 0) { job_log(j, LOG_WARNING, "Attempt to look up children of bootstrap by unprivileged job."); return BOOTSTRAP_NOT_PRIVILEGED; } @@ -7862,46 +8817,46 @@ job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_ty jobmgr_t jmr = j->mgr; jobmgr_t jmi = NULL; - SLIST_FOREACH( jmi, &jmr->submgrs, sle ) { + SLIST_FOREACH(jmi, &jmr->submgrs, sle) { cnt++; } /* Find our per-user launchds if we're PID 1. */ job_t ji = NULL; - if( pid1_magic ) { - LIST_FOREACH( ji, &jmr->jobs, sle ) { + if (pid1_magic) { + LIST_FOREACH(ji, &jmr->jobs, sle) { cnt += ji->per_user ? 1 : 0; } } - if( cnt == 0 ) { + if (cnt == 0) { return BOOTSTRAP_NO_CHILDREN; } mach_port_array_t _child_ports = NULL; mig_allocate((vm_address_t *)&_child_ports, cnt * sizeof(_child_ports[0])); - if( !job_assumes(j, _child_ports != NULL) ) { + if (!job_assumes(j, _child_ports != NULL)) { kr = BOOTSTRAP_NO_MEMORY; goto out_bad; } name_array_t _child_names = NULL; mig_allocate((vm_address_t *)&_child_names, cnt * sizeof(_child_names[0])); - if( !job_assumes(j, _child_names != NULL) ) { + if (!job_assumes(j, _child_names != NULL)) { kr = BOOTSTRAP_NO_MEMORY; goto out_bad; } bootstrap_property_array_t _child_properties = NULL; mig_allocate((vm_address_t *)&_child_properties, cnt * sizeof(_child_properties[0])); - if( !job_assumes(j, _child_properties != NULL) ) { + if (!job_assumes(j, _child_properties != NULL)) { kr = BOOTSTRAP_NO_MEMORY; goto out_bad; } unsigned int cnt2 = 0; - SLIST_FOREACH( jmi, &jmr->submgrs, sle ) { - if( jobmgr_assumes(jmi, launchd_mport_make_send(jmi->jm_port) == KERN_SUCCESS) ) { + SLIST_FOREACH(jmi, &jmr->submgrs, sle) { + if (jobmgr_assumes(jmi, launchd_mport_make_send(jmi->jm_port) == KERN_SUCCESS)) { _child_ports[cnt2] = jmi->jm_port; } else { _child_ports[cnt2] = MACH_PORT_NULL; @@ -7913,12 +8868,12 @@ job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_ty cnt2++; } - if( pid1_magic ) LIST_FOREACH( ji, &jmr->jobs, sle ) { - if( ji->per_user ) { - if( job_assumes(ji, SLIST_FIRST(&ji->machservices)->per_user_hack == true) ) { + if (pid1_magic) LIST_FOREACH( ji, &jmr->jobs, sle) { + if (ji->per_user) { + if (job_assumes(ji, SLIST_FIRST(&ji->machservices)->per_user_hack == true)) { mach_port_t port = machservice_port(SLIST_FIRST(&ji->machservices)); - if( job_assumes(ji, launchd_mport_copy_send(port) == KERN_SUCCESS) ) { + if (job_assumes(ji, launchd_mport_copy_send(port) == KERN_SUCCESS)) { _child_ports[cnt2] = port; } else { _child_ports[cnt2] = MACH_PORT_NULL; @@ -7943,21 +8898,21 @@ job_mig_lookup_children(job_t j, mach_port_array_t *child_ports, mach_msg_ty *child_properties = _child_properties; unsigned int i = 0; - for( i = 0; i < cnt; i++ ) { + for (i = 0; i < cnt; i++) { job_log(j, LOG_DEBUG, "child_names[%u] = %s", i, (char *)_child_names[i]); } return BOOTSTRAP_SUCCESS; out_bad: - if( _child_ports ) { + if (_child_ports) { mig_deallocate((vm_address_t)_child_ports, cnt * sizeof(_child_ports[0])); } - if( _child_names ) { + if (_child_names) { mig_deallocate((vm_address_t)_child_names, cnt * sizeof(_child_ports[0])); } - if( _child_properties ) { + if (_child_properties) { mig_deallocate((vm_address_t)_child_properties, cnt * sizeof(_child_properties[0])); } @@ -7969,14 +8924,14 @@ job_mig_transaction_count_for_pid(job_t j, pid_t p, int32_t *cnt, boolean_t *con { kern_return_t kr = KERN_FAILURE; struct ldcred *ldc = runtime_get_caller_creds(); - if( (ldc->euid != geteuid()) && (ldc->euid != 0) ) { + if ((ldc->euid != geteuid()) && (ldc->euid != 0)) { return BOOTSTRAP_NOT_PRIVILEGED; } job_t j_for_pid = jobmgr_find_by_pid_deep(j->mgr, p, false); - if( j_for_pid ) { - if( j_for_pid->kill_via_shmem ) { - if( j_for_pid->shmem ) { + if (j_for_pid) { + if (j_for_pid->kill_via_shmem) { + if (j_for_pid->shmem) { *cnt = j_for_pid->shmem->vp_shmem_transaction_cnt; *condemned = j_for_pid->shmem->vp_shmem_flags & VPROC_SHMEM_EXITING; *cnt += *condemned ? 1 : 0; @@ -8000,7 +8955,7 @@ kern_return_t job_mig_pid_is_managed(job_t j __attribute__((unused)), pid_t p, boolean_t *managed) { struct ldcred *ldc = runtime_get_caller_creds(); - if( (ldc->euid != geteuid()) && (ldc->euid != 0) ) { + if ((ldc->euid != geteuid()) && (ldc->euid != 0)) { return BOOTSTRAP_NOT_PRIVILEGED; } @@ -8008,7 +8963,7 @@ job_mig_pid_is_managed(job_t j __attribute__((unused)), pid_t p, boolean_t *mana * directly by launchd as agents. */ job_t j_for_pid = jobmgr_find_by_pid_deep(root_jobmgr, p, false); - if( j_for_pid && !j_for_pid->anonymous && !j_for_pid->legacy_LS_job ) { + if (j_for_pid && !j_for_pid->anonymous && !j_for_pid->legacy_LS_job) { *managed = true; } @@ -8020,13 +8975,19 @@ job_mig_port_for_label(job_t j __attribute__((unused)), name_t label, mach_port_ { struct ldcred *ldc = runtime_get_caller_creds(); kern_return_t kr = BOOTSTRAP_NOT_PRIVILEGED; - + +#if HAVE_SANDBOX + if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) { + return BOOTSTRAP_NOT_PRIVILEGED; + } +#endif + mach_port_t _mp = MACH_PORT_NULL; - if( !j->deny_job_creation && (ldc->euid == 0 || ldc->euid == geteuid()) ) { - job_t target_j = job_find(label); - if( jobmgr_assumes(root_jobmgr, target_j != NULL) ) { - if( target_j->j_port == MACH_PORT_NULL ) { - job_assumes(target_j, job_setup_machport(target_j) == true); + if (!j->deny_job_creation && (ldc->euid == 0 || ldc->euid == geteuid())) { + job_t target_j = job_find(NULL, label); + if (jobmgr_assumes(root_jobmgr, target_j != NULL)) { + if (target_j->j_port == MACH_PORT_NULL) { + (void)job_assumes(target_j, job_setup_machport(target_j) == true); } _mp = target_j->j_port; @@ -8042,27 +9003,27 @@ job_mig_port_for_label(job_t j __attribute__((unused)), name_t label, mach_port_ #if !TARGET_OS_EMBEDDED kern_return_t -job_mig_set_security_session(job_t j, uuid_t uuid, mach_port_t session) +job_mig_set_security_session(job_t j, uuid_t uuid, mach_port_t asport) { uuid_string_t uuid_str; uuid_unparse(uuid, uuid_str); - job_log(j, LOG_DEBUG, "Setting session %u for UUID %s...", session, uuid_str); + job_log(j, LOG_DEBUG, "Setting session %u for UUID %s...", asport, uuid_str); job_t ji = NULL, jt = NULL; - LIST_FOREACH_SAFE( ji, &s_needing_sessions, sle, jt ) { + LIST_FOREACH_SAFE(ji, &s_needing_sessions, sle, jt) { uuid_string_t uuid_str2; uuid_unparse(ji->expected_audit_uuid, uuid_str2); - if( uuid_compare(uuid, ji->expected_audit_uuid) == 0 ) { + if (uuid_compare(uuid, ji->expected_audit_uuid) == 0) { uuid_clear(ji->expected_audit_uuid); - if( session != MACH_PORT_NULL ) { - job_log(ji, LOG_DEBUG, "Job should join session with port %u", session); - mach_port_mod_refs(mach_task_self(), session, MACH_PORT_RIGHT_SEND, 1); + if (asport != MACH_PORT_NULL ) { + job_log(ji, LOG_DEBUG, "Job should join session with port %u", asport); + (void)job_assumes(j, launchd_mport_copy_send(asport) == KERN_SUCCESS); } else { job_log(ji, LOG_DEBUG, "No session to set for job. Using our session."); } - ji->audit_session = session; + ji->asport = asport; LIST_REMOVE(ji, needing_session_sle); job_dispatch(ji, false); } @@ -8073,7 +9034,7 @@ job_mig_set_security_session(job_t j, uuid_t uuid, mach_port_t session) * We need to release it so that the session goes away when all the jobs * referencing it are unloaded. */ - mach_port_deallocate(mach_task_self(), session); + (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS); return KERN_SUCCESS; } @@ -8103,7 +9064,7 @@ jobmgr_find_by_name(jobmgr_t jm, const char *where) return jm; } - if( strcasecmp(where, VPROCMGR_SESSION_BACKGROUND) == 0 && !pid1_magic ) { + if (strcasecmp(where, VPROCMGR_SESSION_BACKGROUND) == 0 && !pid1_magic) { jmi = root_jobmgr; goto jm_found; } @@ -8111,6 +9072,8 @@ jobmgr_find_by_name(jobmgr_t jm, const char *where) SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) { if (unlikely(jmi->shutting_down)) { continue; + } else if (jmi->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN) { + continue; } else if (strcasecmp(jmi->name, where) == 0) { goto jm_found; } else if (strcasecmp(jmi->name, VPROCMGR_SESSION_BACKGROUND) == 0 && pid1_magic) { @@ -8128,7 +9091,7 @@ jm_found: } kern_return_t -job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mach_port_t audit_session, uint64_t flags) +job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mach_port_t asport, uint64_t flags) { mach_msg_type_number_t l2l_i, l2l_port_cnt = 0; mach_port_array_t l2l_ports = NULL; @@ -8159,7 +9122,7 @@ job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mac launchd_assert(launch_data_array_get_count(out_obj_array) == l2l_port_cnt); - if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, reqport, rcvright, false, session_type, false, audit_session)) != NULL)) { + if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, reqport, rcvright, false, session_type, false, asport)) != NULL)) { kr = BOOTSTRAP_NO_MEMORY; goto out; } @@ -8170,7 +9133,7 @@ job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mac * processing an IPC request, we'll do this action before the new job manager can get any IPC * requests. This serialization is guaranteed since we are single-threaded in that respect. */ - if( flags & LAUNCH_GLOBAL_ON_DEMAND ) { + if (flags & LAUNCH_GLOBAL_ON_DEMAND) { /* This is so awful. */ /* Remove the job from its current job manager. */ LIST_REMOVE(j, sle); @@ -8183,7 +9146,7 @@ job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mac j->mgr = jmr; job_set_global_on_demand(j, true); - if( !j->holds_ref ) { + if (!j->holds_ref) { j->holds_ref = true; runtime_add_ref(); } @@ -8197,19 +9160,19 @@ job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type, mac pid_t target_pid; bool serv_perpid; - job_assumes(j, obj_at_idx = launch_data_array_get_index(out_obj_array, l2l_i)); - job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PID)); + (void)job_assumes(j, obj_at_idx = launch_data_array_get_index(out_obj_array, l2l_i)); + (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PID)); target_pid = (pid_t)launch_data_get_integer(tmp); - job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PERPID)); + (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PERPID)); serv_perpid = launch_data_get_bool(tmp); - job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_NAME)); + (void)job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_NAME)); serv_name = launch_data_get_string(tmp); j_for_service = jobmgr_find_by_pid(jmr, target_pid, true); if (unlikely(!j_for_service)) { /* The PID probably exited */ - job_assumes(j, launchd_mport_deallocate(l2l_ports[l2l_i]) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_deallocate(l2l_ports[l2l_i]) == KERN_SUCCESS); continue; } @@ -8232,7 +9195,10 @@ out: if (kr == 0) { if (target_subset) { - job_assumes(j, launchd_mport_deallocate(target_subset) == KERN_SUCCESS); + (void)job_assumes(j, launchd_mport_deallocate(target_subset) == KERN_SUCCESS); + } + if (asport) { + (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS); } } else if (jmr) { jobmgr_shutdown(jmr); @@ -8242,7 +9208,7 @@ out: } kern_return_t -job_mig_init_session(job_t j, name_t session_type, mach_port_t audit_session) +job_mig_init_session(job_t j, name_t session_type, mach_port_t asport) { job_t j2; @@ -8281,8 +9247,8 @@ job_mig_init_session(job_t j, name_t session_type, mach_port_t audit_session) strcpy(j->mgr->name_init, session_type); if (job_assumes(j, (j2 = jobmgr_init_session(j->mgr, session_type, false)))) { - j2->audit_session = audit_session; - job_assumes(j, job_dispatch(j2, true)); + j2->asport = asport; + (void)job_assumes(j, job_dispatch(j2, true)); kr = BOOTSTRAP_SUCCESS; } @@ -8290,7 +9256,7 @@ job_mig_init_session(job_t j, name_t session_type, mach_port_t audit_session) } kern_return_t -job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_name, mach_port_t audit_session, mach_port_t *new_bsport) +job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_name, mach_port_t asport, mach_port_t *new_bsport) { struct ldcred *ldc = runtime_get_caller_creds(); if (!jobmgr_assumes(root_jobmgr, j != NULL)) { @@ -8300,33 +9266,32 @@ job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_na job_log(j, LOG_DEBUG, "Job wants to move to %s session.", session_name); - if( !job_assumes(j, pid1_magic == false) ) { + if (!job_assumes(j, pid1_magic == false)) { job_log(j, LOG_WARNING, "Switching sessions is not allowed in the system Mach bootstrap."); return BOOTSTRAP_NOT_PRIVILEGED; } - if( !j->anonymous ) { + if (!j->anonymous) { job_log(j, LOG_NOTICE, "Non-anonymous job tried to switch sessions. Please use LimitLoadToSessionType instead."); return BOOTSTRAP_NOT_PRIVILEGED; } jobmgr_t target_jm = jobmgr_find_by_name(root_jobmgr, session_name); - if( target_jm == j->mgr ) { + if (target_jm == j->mgr) { job_log(j, LOG_DEBUG, "Job is already in its desired session (%s).", session_name); *new_bsport = target_jm->jm_port; return BOOTSTRAP_SUCCESS; } - if( !target_jm ) { - target_jm = jobmgr_new(j->mgr, requestor_port, MACH_PORT_NULL, false, session_name, false, audit_session); - if( !target_jm ) { - mach_port_deallocate(mach_task_self(), audit_session); - } else { + if (!target_jm) { + target_jm = jobmgr_new(j->mgr, requestor_port, MACH_PORT_NULL, false, session_name, false, asport); + if (target_jm) { target_jm->properties |= BOOTSTRAP_PROPERTY_IMPLICITSUBSET; + (void)job_assumes(j, launchd_mport_deallocate(asport) == KERN_SUCCESS); } } - if( !job_assumes(j, target_jm != NULL) ) { + if (!job_assumes(j, target_jm != NULL)) { job_log(j, LOG_WARNING, "Could not find %s session!", session_name); return BOOTSTRAP_NO_MEMORY; } @@ -8336,8 +9301,8 @@ job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_na LIST_REMOVE(j, pid_hash_sle); job_t ji = NULL, jit = NULL; - LIST_FOREACH_SAFE( ji, &j->mgr->global_env_jobs, global_env_sle, jit ) { - if( ji == j ) { + LIST_FOREACH_SAFE(ji, &j->mgr->global_env_jobs, global_env_sle, jit) { + if (ji == j) { LIST_REMOVE(ji, global_env_sle); break; } @@ -8347,14 +9312,14 @@ job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_na LIST_INSERT_HEAD(&target_jm->jobs, j, sle); LIST_INSERT_HEAD(&target_jm->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle); - if( ji ) { + if (ji) { LIST_INSERT_HEAD(&target_jm->global_env_jobs, j, global_env_sle); } /* Move our Mach services over if we're not in a flat namespace. */ - if( !g_flat_mach_namespace && !SLIST_EMPTY(&j->machservices) ) { + if (!g_flat_mach_namespace && !SLIST_EMPTY(&j->machservices)) { struct machservice *msi = NULL, *msit = NULL; - SLIST_FOREACH_SAFE( msi, &j->machservices, sle, msit ) { + SLIST_FOREACH_SAFE(msi, &j->machservices, sle, msit) { LIST_REMOVE(msi, name_hash_sle); LIST_INSERT_HEAD(&target_jm->ms_hash[hash_ms(msi->name)], msi, name_hash_sle); } @@ -8362,7 +9327,7 @@ job_mig_switch_to_session(job_t j, mach_port_t requestor_port, name_t session_na j->mgr = target_jm; - if( !j->holds_ref ) { + if (!j->holds_ref) { /* Anonymous jobs which move around are particularly interesting to us, so we want to * stick around while they're still around. * For example, login calls into the PAM launchd module, which moves the process into @@ -8448,25 +9413,25 @@ job_mig_take_subset(job_t j, mach_port_t *reqport, mach_port_t *rcvright, SLIST_FOREACH(ms, &ji->machservices, sle) { if (job_assumes(j, (tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) { - job_assumes(j, launch_data_array_set_index(outdata_obj_array, tmp_dict, cnt2)); + (void)job_assumes(j, launch_data_array_set_index(outdata_obj_array, tmp_dict, cnt2)); } else { goto out_bad; } if (job_assumes(j, (tmp_obj = launch_data_new_string(machservice_name(ms))))) { - job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_NAME)); + (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_NAME)); } else { goto out_bad; } if (job_assumes(j, (tmp_obj = launch_data_new_integer((ms->job->p))))) { - job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PID)); + (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PID)); } else { goto out_bad; } if (job_assumes(j, (tmp_obj = launch_data_new_bool((ms->per_pid))))) { - job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PERPID)); + (void)job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PERPID)); } else { goto out_bad; } @@ -8474,12 +9439,12 @@ job_mig_take_subset(job_t j, mach_port_t *reqport, mach_port_t *rcvright, ports[cnt2] = machservice_port(ms); /* Increment the send right by one so we can shutdown the jobmgr cleanly */ - jobmgr_assumes(jm, (errno = mach_port_mod_refs(mach_task_self(), ports[cnt2], MACH_PORT_RIGHT_SEND, 1)) == 0); + (void)jobmgr_assumes(jm, (errno = launchd_mport_copy_send(ports[cnt2])) == KERN_SUCCESS); cnt2++; } } - job_assumes(j, cnt == cnt2); + (void)job_assumes(j, cnt == cnt2); runtime_ktrace0(RTKT_LAUNCHD_DATA_PACK); packed_size = launch_data_pack(outdata_obj_array, (void *)*outdata, *outdataCnt, NULL, NULL); @@ -8543,7 +9508,7 @@ job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp) char name[NAME_MAX]; snprintf(name, sizeof(name), "%s[%i].subset.%i", j->anonymous ? j->prog : j->label, j->p, MACH_PORT_INDEX(requestorport)); - if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, name, true, j->audit_session)) != NULL)) { + if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, name, true, j->asport)) != NULL)) { if (unlikely(requestorport == MACH_PORT_NULL)) { return BOOTSTRAP_NOT_PRIVILEGED; } @@ -8556,7 +9521,7 @@ job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp) /* A job could create multiple subsets, so only add a reference the first time * it does so we don't have to keep a count. */ - if( j->anonymous && !j->holds_ref ) { + if (j->anonymous && !j->holds_ref) { j->holds_ref = true; runtime_add_ref(); } @@ -8565,169 +9530,507 @@ job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp) return BOOTSTRAP_SUCCESS; } -kern_return_t -job_mig_embedded_wait(job_t j, name_t targetlabel, integer_t *waitstatus) -{ - job_t otherj; - - if (!launchd_assumes(j != NULL)) { - return BOOTSTRAP_NO_MEMORY; - } - - if (unlikely(!(otherj = job_find(targetlabel)))) { - return BOOTSTRAP_UNKNOWN_SERVICE; - } - - *waitstatus = j->last_exit_status; +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ +job_t +xpc_domain_import_service(jobmgr_t jm, launch_data_t pload) +{ + jobmgr_t where2put = NULL; + + launch_data_t destname = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_XPCDOMAIN); + if (destname) { + if (launch_data_get_type(destname) == LAUNCH_DATA_STRING) { + const char *str = launch_data_get_string(destname); + if (strcmp(str, XPC_DOMAIN_TYPE_SYSTEM) == 0) { + where2put = _s_xpc_system_domain; + } else if (strcmp(str, XPC_DOMAIN_TYPE_PERUSER) == 0) { + where2put = jobmgr_find_xpc_per_user_domain(jm, jm->req_euid); + } else if (strcmp(str, XPC_DOMAIN_TYPE_PERSESSION) == 0) { + where2put = jobmgr_find_xpc_per_session_domain(jm, jm->req_asid); + } else { + jobmgr_log(jm, LOG_ERR, "Invalid XPC domain type: %s", str); + errno = EINVAL; + } + } else { + jobmgr_log(jm, LOG_ERR, "XPC domain type is not a string."); + errno = EINVAL; + } - return 0; + if (where2put) { + launch_data_t mi = NULL; + if ((mi = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_MULTIPLEINSTANCES))) { + if (launch_data_get_type(mi) == LAUNCH_DATA_BOOL && launch_data_get_bool(mi)) { + jobmgr_log(where2put, LOG_ERR, "Multiple-instance services are not supported in this domain."); + where2put = NULL; + errno = EINVAL; + } + } + } + } else { + where2put = jm; + } + + job_t j = NULL; + if (where2put) { + jobmgr_log(where2put, LOG_DEBUG, "Importing service..."); + j = jobmgr_import2(where2put, pload); + if (j) { + j->xpc_service = true; + if (where2put->xpc_singleton) { + /* If the service was destined for one of the global domains, + * then we have to alias it into our local domain to reserve the + * name. + */ + job_t ja = job_new_alias(jm, j); + if (!ja) { + /* If we failed to alias the job because of a conflict over + * the label, then we remove it from the global domain. We + * don't want to risk having imported a malicious job into + * one of the global domains. + */ + if (errno != EEXIST) { + job_assumes(j, errno == 0); + } else { + job_log(j, LOG_ERR, "Failed to alias job into: %s", where2put->name); + } + + job_remove(j); + } else { + ja->xpc_service = true; + j = ja; + } + } + } + } + + return j; } kern_return_t -job_mig_kickstart(job_t j, name_t targetlabel, pid_t *out_pid, mach_port_t *out_name_port, mach_port_t *obsrvr_port, unsigned int flags) +xpc_domain_import2(job_t j, mach_port_t reqport, mach_port_t dport) { - struct ldcred *ldc = runtime_get_caller_creds(); - job_t otherj; + if (unlikely(!pid1_magic)) { + job_log(j, LOG_ERR, "XPC domains may only reside in PID 1."); + return BOOTSTRAP_NOT_PRIVILEGED; + } + if (!MACH_PORT_VALID(reqport)) { + return BOOTSTRAP_UNKNOWN_SERVICE; + } - if (!launchd_assumes(j != NULL)) { - return BOOTSTRAP_NO_MEMORY; + kern_return_t kr = BOOTSTRAP_NO_MEMORY; + /* All XPC domains are children of the root job manager. What we're creating + * here is really just a skeleton. By creating it, we're adding reqp to our + * port set. It will have two messages on it. The first specifies the + * environment of the originator. This is so we can cache it and hand it to + * xpcproxy to bootstrap our services. The second is the set of jobs that is + * to be bootstrapped in. + */ + jobmgr_t jm = jobmgr_new(root_jobmgr, reqport, dport, false, NULL, true, MACH_PORT_NULL); + if (job_assumes(j, jm != NULL)) { + jm->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN; + jm->shortdesc = "private"; + kr = BOOTSTRAP_SUCCESS; } - if (unlikely(!(otherj = job_find(targetlabel)))) { + return kr; +} + +kern_return_t +xpc_domain_set_environment(job_t j, mach_port_t rp, mach_port_t bsport, mach_port_t excport, vm_offset_t ctx, mach_msg_type_number_t ctx_sz) +{ + if (!j) { + /* Due to the whacky nature of XPC service bootstrapping, we can end up + * getting this message long after the requesting process has gone away. + * See . + */ return BOOTSTRAP_UNKNOWN_SERVICE; } -#if TARGET_OS_EMBEDDED - bool allow_non_root_kickstart = j->username && otherj->username && ( strcmp(j->username, otherj->username) == 0 ); -#else - bool allow_non_root_kickstart = false; -#endif + jobmgr_t jm = j->mgr; + if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + if (jm->req_asport != MACH_PORT_NULL) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + struct ldcred *ldc = runtime_get_caller_creds(); + struct proc_bsdshortinfo proc; + if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) { + if (errno != ESRCH) { + jobmgr_assumes(jm, errno == 0); + } - if( ldc->euid != 0 && ldc->euid != geteuid() && !allow_non_root_kickstart ) { + jm->error = errno; + jobmgr_remove(jm); + return BOOTSTRAP_NO_MEMORY; + } + + if (!jobmgr_assumes(jm, audit_session_port(ldc->asid, &jm->req_asport) == 0)) { + jm->error = EPERM; + jobmgr_remove(jm); + job_log(j, LOG_ERR, "Failed to get port for ASID: %u", ldc->asid); return BOOTSTRAP_NOT_PRIVILEGED; } - if( otherj->p && (flags & VPROCFLAG_STALL_JOB_EXEC) ) { - return BOOTSTRAP_SERVICE_ACTIVE; + (void)snprintf(jm->name_init, NAME_MAX, "com.apple.xpc.domain.%s[%i]", proc.pbsi_comm, ldc->pid); + strlcpy(jm->owner, proc.pbsi_comm, sizeof(jm->owner)); + jm->req_bsport = bsport; + jm->req_excport = excport; + jm->req_rport = rp; + jm->req_ctx = ctx; + jm->req_ctx_sz = ctx_sz; + jm->req_pid = ldc->pid; + jm->req_euid = ldc->euid; + jm->req_egid = ldc->egid; + jm->req_asid = ldc->asid; + + return KERN_SUCCESS; +} + +kern_return_t +xpc_domain_load_services(job_t j, vm_offset_t services_buff, mach_msg_type_number_t services_sz) +{ + if (!j) { + return BOOTSTRAP_UNKNOWN_SERVICE; } - otherj->stall_before_exec = ( flags & VPROCFLAG_STALL_JOB_EXEC ); - otherj = job_dispatch(otherj, true); + /* This is just for XPC domains (for now). */ + if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + if (j->mgr->session_initialized) { + jobmgr_log(j->mgr, LOG_ERR, "Attempt to initialize an already-initialized XPC domain."); + return BOOTSTRAP_NOT_PRIVILEGED; + } - if (!job_assumes(j, otherj && otherj->p)) { - /* Clear this flag if we failed to start the job. */ - otherj->stall_before_exec = false; + size_t offset = 0; + launch_data_t services = launch_data_unpack((void *)services_buff, services_sz, NULL, 0, &offset, NULL); + if (!jobmgr_assumes(j->mgr, services != NULL)) { return BOOTSTRAP_NO_MEMORY; } - /* If any of these proceeding steps fail, we return an error to the client. - * the problem is that, if the client has requested the job be stalled before - * exec(2), the client won't be able to uncork the fork(2), leaving the job - * forever stalled until the client tries again and we successfully start - * the job. - * - * See for more about the implications. - * - * Fortunately, these next actions should pretty much never fail. In the - * future, we should look at cleaning up after these failures if the job - * was started in a stalled state. - */ + size_t i = 0; + size_t c = launch_data_array_get_count(services); + for (i = 0; i < c; i++) { + job_t nj = NULL; + launch_data_t ploadi = launch_data_array_get_index(services, i); + if (!(nj = xpc_domain_import_service(j->mgr, ploadi))) { + /* If loading one job fails, just fail the whole thing. At this + * point, xpchelper should receive the failure and then just refuse + * to launch the application, since its XPC services could not be + * fully bootstrapped. + * + * Take care to not reference the job or its manager after this + * point. + */ + if (errno == EINVAL) { + jobmgr_log(j->mgr, LOG_ERR, "Service at index is not valid: %lu", i); + } else if (errno == EEXIST) { + /* If we get back EEXIST, we know that the payload was a + * dictionary with a label. But, well, I guess it never hurts to + * check. + */ + char *label = "(bogus)"; + if (launch_data_get_type(ploadi) == LAUNCH_DATA_DICTIONARY) { + launch_data_t llabel = launch_data_dict_lookup(ploadi, LAUNCH_JOBKEY_LABEL); + if (launch_data_get_type(llabel) == LAUNCH_DATA_STRING) { + label = (char *)launch_data_get_string(llabel); + } + } + jobmgr_log(j->mgr, LOG_ERR, "Service name conflict: %s", label); + } - kern_return_t kr = task_name_for_pid(mach_task_self(), otherj->p, out_name_port); - if (!job_assumes(j, kr == 0)) { - return kr; + j->mgr->error = errno; + jobmgr_log(j->mgr, LOG_ERR, "Obliterating domain."); + jobmgr_remove(j->mgr); + break; + } else { + jobmgr_log(j->mgr, LOG_DEBUG, "Imported service %s", nj->label); + job_dispatch(nj, false); + } } - if (!job_setup_machport(otherj)) { - return BOOTSTRAP_NO_MEMORY; + kern_return_t result = BOOTSTRAP_NO_MEMORY; + if (i == c) { + j->mgr->session_initialized = true; + (void)jobmgr_assumes(j->mgr, xpc_call_wakeup(j->mgr->req_rport, BOOTSTRAP_SUCCESS) == KERN_SUCCESS); + j->mgr->req_rport = MACH_PORT_NULL; + + /* Returning a failure code will destroy the message, whereas returning + * success will not, so we need to clean up here. + */ + mig_deallocate(services_buff, services_sz); + result = BOOTSTRAP_SUCCESS; } - - *obsrvr_port = otherj->j_port; - *out_pid = otherj->p; - return 0; + return result; } kern_return_t -job_mig_wait(job_t j, mach_port_t srp, integer_t *waitstatus) +xpc_domain_check_in(job_t j, mach_port_t *bsport, mach_port_t *sbsport, mach_port_t *excport, mach_port_t *asport, uint32_t *uid, uint32_t *gid, int32_t *asid, vm_offset_t *ctx, mach_msg_type_number_t *ctx_sz) { -#if 0 - if (!launchd_assumes(j != NULL)) { - return BOOTSTRAP_NO_MEMORY; + if (!jobmgr_assumes(root_jobmgr, j != NULL)) { + return BOOTSTRAP_UNKNOWN_SERVICE; } - return job_handle_mpm_wait(j, srp, waitstatus); -#else - if( false ) { - /* To make the compiler happy. */ - job_handle_mpm_wait(NULL, MACH_PORT_NULL, NULL); + jobmgr_t jm = j->mgr; + if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) { + return BOOTSTRAP_NOT_PRIVILEGED; } - struct ldcred *ldc = runtime_get_caller_creds(); - job_t calling_j = jobmgr_find_by_pid(j->mgr, ldc->pid, true); - return job_mig_wait2(calling_j, j, srp, waitstatus, true); -#endif + if (jm->req_asport == MACH_PORT_NULL) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + *bsport = jm->req_bsport; + *sbsport = root_jobmgr->jm_port; + *excport = jm->req_excport; + *asport = jm->req_asport; + *uid = jm->req_euid; + *gid = jm->req_egid; + *asid = jm->req_asid; + + *ctx = jm->req_ctx; + *ctx_sz = jm->req_ctx_sz; + + return KERN_SUCCESS; } kern_return_t -job_mig_wait2(job_t j, job_t target_j, mach_port_t srp, integer_t *status, boolean_t legacy) +xpc_domain_get_service_name(job_t j, event_name_t name) { - if( !launchd_assumes(j != NULL) ) { + if (!j) { return BOOTSTRAP_NO_MEMORY; } - if( !launchd_assumes(target_j != NULL) ) { - return BOOTSTRAP_NO_MEMORY; + if (!j->xpc_service) { + jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name by non-XPC service: %s", j->label); + return BOOTSTRAP_NOT_PRIVILEGED; } - if( !launchd_assumes(status != NULL) ) { - return BOOTSTRAP_NO_MEMORY; + + struct machservice * ms = SLIST_FIRST(&j->machservices); + if (!ms) { + jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name of job with no machservices: %s", j->label); + return BOOTSTRAP_UNKNOWN_SERVICE; } - - /* See rdar://problem/7084138 for why we do the second part of this check. - * Basically, since Finder, Dock and SystemUIServer are now real launchd - * jobs, they don't get removed after exiting, like legacy LaunchServices - * jobs do. So there's a race. coreservicesd came in asking for the exit - * status after we'd relaunched Finder, so Finder's PID isn't 0. - * - * So we check to make sure the target job isn't a LaunchServices job and - * that the request is coming through the legacy path (mpm_wait()). If so, - * we return the last exit status, regardless of the current PID value. - */ - if( target_j->p == 0 || (!target_j->legacy_LS_job && legacy) ) { - *status = target_j->last_exit_status; - return BOOTSTRAP_SUCCESS; + + (void)strlcpy(name, ms->name, sizeof(event_name_t)); + return BOOTSTRAP_SUCCESS; +} +#endif + +kern_return_t +xpc_events_get_channel_name(job_t j __attribute__((unused)), event_name_t stream __attribute__((unused)), uint64_t token __attribute__((unused)), event_name_t name __attribute__((unused))) +{ + return KERN_FAILURE; +} + +kern_return_t +xpc_events_get_event_name(job_t j, event_name_t stream, uint64_t token, event_name_t name) +{ + struct externalevent *event = externalevent_find(stream, token); + if (event && j->event_monitor) { + (void)strcpy(name, event->name); + } else { + event = NULL; } + + return event ? BOOTSTRAP_SUCCESS : BOOTSTRAP_UNKNOWN_SERVICE; +} - if( !job_assumes(j, waiting4exit_new(target_j, srp, legacy) == true) ) { - return BOOTSTRAP_NO_MEMORY; +kern_return_t +xpc_events_set_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t event, mach_msg_type_number_t eventCnt) +{ + if (j->anonymous) { + return BOOTSTRAP_NOT_PRIVILEGED; } - - return MIG_NO_REPLY; + + struct externalevent *eei = NULL; + LIST_FOREACH(eei, &j->events, job_le) { + if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) { + externalevent_delete(eei); + eventsystem_ping(); + break; + } + } + + bool success = false; + struct eventsystem *es = eventsystem_find(stream); + if (!es) { + es = eventsystem_new(stream); + (void)job_assumes(j, es != NULL); + } + + if (es) { + size_t offset = 0; + launch_data_t unpacked = launch_data_unpack((void *)event, eventCnt, NULL, 0, &offset, 0); + if (unpacked && launch_data_get_type(unpacked) == LAUNCH_DATA_DICTIONARY) { + success = externalevent_new(j, es, key, unpacked); + } + } + + if (!success) { + mig_deallocate(event, eventCnt); + } + + return KERN_SUCCESS; } kern_return_t -job_mig_uncork_fork(job_t j) +xpc_events_get_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t *event, mach_msg_type_number_t *eventCnt) +{ + struct externalevent *eei = NULL; + LIST_FOREACH(eei, &j->events, job_le) { + if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) { + /* Big enough. */ + *eventCnt = 10 * 1024; + mig_allocate(event, *eventCnt); + + size_t sz = launch_data_pack(eei->event, (void *)*event, *eventCnt, NULL, NULL); + if (!job_assumes(j, sz != 0)) { + mig_deallocate(*event, *eventCnt); + return BOOTSTRAP_NO_MEMORY; + } + + return BOOTSTRAP_SUCCESS; + } + } + + return BOOTSTRAP_UNKNOWN_SERVICE; +} + +struct machservice * +xpc_events_find_channel(job_t j, event_name_t stream, mach_port_t *p) { + struct machservice *msi = NULL; + SLIST_FOREACH(msi, &j->machservices, sle) { + if (strcmp(stream, msi->name) == 0) { + break; + } + } + + if (!msi) { + mach_port_t sp = MACH_PORT_NULL; + msi = machservice_new(j, stream, &sp, false); + if (job_assumes(j, msi)) { + /* Hack to keep this from being publicly accessible through + * bootstrap_look_up(). + */ + LIST_REMOVE(msi, name_hash_sle); + msi->event_channel = true; + *p = sp; + + machservice_watch(j, msi); + } else { + errno = BOOTSTRAP_NO_MEMORY; + } + } else { + if (!msi->event_channel) { + job_log(j, LOG_ERR, "This job registered a MachService name identical to the requested event channel name: %s", stream); + msi = NULL; + errno = BOOTSTRAP_NAME_IN_USE; + } else { + *p = msi->port; + } + } + + return msi; +} + +kern_return_t +xpc_events_channel_check_in(job_t j, event_name_t stream, uint64_t flags __attribute__((unused)), mach_port_t *p) +{ + struct machservice *ms = xpc_events_find_channel(j, stream, p); + if (ms) { + if (ms->isActive) { + job_log(j, LOG_ERR, "Attempt to check in on event channel multiple times: %s", stream); + *p = MACH_PORT_NULL; + errno = BOOTSTRAP_SERVICE_ACTIVE; + } else { + job_checkin(j); + machservice_request_notifications(ms); + errno = BOOTSTRAP_SUCCESS; + } + } + + return errno; +} + +kern_return_t +xpc_events_channel_look_up(job_t j, event_name_t stream, event_token_t token, uint64_t flags __attribute__((unused)), mach_port_t *p) +{ + if (!j->event_monitor) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + struct externalevent *ee = externalevent_find(stream, token); + if (!ee) { + return BOOTSTRAP_UNKNOWN_SERVICE; + } + + struct machservice *ms = xpc_events_find_channel(ee->job, stream, p); + if (ms) { + errno = BOOTSTRAP_SUCCESS; + } + + return errno; +} + +kern_return_t +job_mig_kickstart(job_t j, name_t targetlabel, pid_t *out_pid, unsigned int flags) +{ + struct ldcred *ldc = runtime_get_caller_creds(); + job_t otherj; + if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } - if (unlikely(!j->stall_before_exec)) { - job_log(j, LOG_WARNING, "Attempt to uncork a job that isn't in the middle of a fork()."); - return 1; + if (unlikely(!(otherj = job_find(NULL, targetlabel)))) { + return BOOTSTRAP_UNKNOWN_SERVICE; } - job_uncork_fork(j); - j->stall_before_exec = false; +#if TARGET_OS_EMBEDDED + bool allow_non_root_kickstart = j->username && otherj->username && (strcmp(j->username, otherj->username) == 0); +#else + bool allow_non_root_kickstart = false; +#endif + + if (ldc->euid != 0 && ldc->euid != geteuid() && !allow_non_root_kickstart) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + +#if HAVE_SANDBOX + if (unlikely(sandbox_check(ldc->pid, "job-creation", SANDBOX_FILTER_NONE) > 0)) { + return BOOTSTRAP_NOT_PRIVILEGED; + } +#endif + + if (otherj->p && (flags & VPROCFLAG_STALL_JOB_EXEC)) { + return BOOTSTRAP_SERVICE_ACTIVE; + } + + otherj->stall_before_exec = (flags & VPROCFLAG_STALL_JOB_EXEC); + otherj = job_dispatch(otherj, true); + + if (!job_assumes(j, otherj && otherj->p)) { + /* Clear this flag if we failed to start the job. */ + otherj->stall_before_exec = false; + return BOOTSTRAP_NO_MEMORY; + } + + *out_pid = otherj->p; + return 0; } kern_return_t -job_mig_spawn(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t audit_session, pid_t *child_pid, mach_port_t *obsvr_port) +job_mig_spawn_internal(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, job_t *outj) { - launch_data_t input_obj = NULL; + launch_data_t jobdata = NULL; size_t data_offset = 0; struct ldcred *ldc = runtime_get_caller_creds(); job_t jr; - + if (!launchd_assumes(j != NULL)) { return BOOTSTRAP_NO_MEMORY; } @@ -8741,7 +10044,7 @@ job_mig_spawn(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mac return BOOTSTRAP_NOT_PRIVILEGED; } #endif - + if (unlikely(pid1_magic && ldc->euid && ldc->uid)) { job_log(j, LOG_DEBUG, "Punting spawn to per-user-context"); return VPROC_ERR_TRY_PER_USER; @@ -8752,21 +10055,44 @@ job_mig_spawn(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mac } runtime_ktrace0(RTKT_LAUNCHD_DATA_UNPACK); - if (!job_assumes(j, (input_obj = launch_data_unpack((void *)indata, indataCnt, NULL, 0, &data_offset, NULL)) != NULL)) { + if (!job_assumes(j, (jobdata = launch_data_unpack((void *)indata, indataCnt, NULL, 0, &data_offset, NULL)) != NULL)) { return 1; } jobmgr_t target_jm = jobmgr_find_by_name(j->mgr, NULL); - if( !jobmgr_assumes(j->mgr, target_jm != NULL) ) { - jobmgr_log(j->mgr, LOG_NOTICE, "%s() can't find its session!", __func__); + if (!jobmgr_assumes(j->mgr, target_jm != NULL)) { + jobmgr_log(j->mgr, LOG_ERR, "This API can only be used by a process running within an Aqua session."); return 1; } - jr = jobmgr_import2(target_jm ?: j->mgr, input_obj); - - if (!job_assumes(j, jr != NULL)) { + jr = jobmgr_import2(target_jm ?: j->mgr, jobdata); + + launch_data_t label = NULL; + launch_data_t wait4debugger = NULL; + if (!jr) { switch (errno) { case EEXIST: + /* If EEXIST was returned, we know that there is a label string in + * the dictionary. So we don't need to check the types here; that + * has already been done. + */ + label = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_LABEL); + jr = job_find(NULL, launch_data_get_string(label)); + if (job_assumes(j, jr != NULL) && !jr->p) { + wait4debugger = launch_data_dict_lookup(jobdata, LAUNCH_JOBKEY_WAITFORDEBUGGER); + if (wait4debugger && launch_data_get_type(wait4debugger) == LAUNCH_DATA_BOOL) { + if (launch_data_get_bool(wait4debugger)) { + /* If the job exists, we're going to kick-start it, but + * we need to give the caller the opportunity to start + * it suspended if it so desires. But this will only + * take effect if the job isn't running. + */ + jr->wait4debugger_oneshot = true; + } + } + } + + *outj = jr; return BOOTSTRAP_NAME_IN_USE; default: return BOOTSTRAP_NO_MEMORY; @@ -8779,34 +10105,157 @@ job_mig_spawn(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, mac jr->legacy_LS_job = true; jr->abandon_pg = true; - jr->stall_before_exec = jr->wait4debugger; - jr->wait4debugger = false; - jr->audit_session = audit_session; + jr->asport = asport; uuid_clear(jr->expected_audit_uuid); - jr = job_dispatch(jr, true); if (!job_assumes(j, jr != NULL)) { - return BOOTSTRAP_NO_MEMORY; - } - - if (!job_assumes(jr, jr->p)) { job_remove(jr); return BOOTSTRAP_NO_MEMORY; } - if (!job_setup_machport(jr)) { + if (!job_assumes(jr, jr->p)) { job_remove(jr); return BOOTSTRAP_NO_MEMORY; } job_log(jr, LOG_DEBUG, "Spawned by PID %u: %s", j->p, j->label); + *outj = jr; - *child_pid = jr->p; - *obsvr_port = jr->j_port; + return BOOTSTRAP_SUCCESS; +} + +kern_return_t +job_mig_spawn2(job_t j, mach_port_t rp, vm_offset_t indata, mach_msg_type_number_t indataCnt, mach_port_t asport, pid_t *child_pid, mach_port_t *obsvr_port) +{ + job_t nj = NULL; + kern_return_t kr = job_mig_spawn_internal(j, indata, indataCnt, asport, &nj); + if (likely(kr == KERN_SUCCESS)) { + if (job_setup_exit_port(nj) != KERN_SUCCESS) { + job_remove(nj); + kr = BOOTSTRAP_NO_MEMORY; + } else { + /* Do not return until the job has called exec(3), thereby making it + * safe for the caller to send it SIGCONT. + * + * + */ + nj->spawn_reply_port = rp; + kr = MIG_NO_REPLY; + } + } else if (kr == BOOTSTRAP_NAME_IN_USE) { + bool was_running = nj->p; + if (job_dispatch(nj, true)) { + if (!was_running) { + job_log(nj, LOG_DEBUG, "Job exists but is not running. Kick-starting."); + + if (job_setup_exit_port(nj) == KERN_SUCCESS) { + nj->spawn_reply_port = rp; + kr = MIG_NO_REPLY; + } else { + kr = BOOTSTRAP_NO_MEMORY; + } + } else { + *obsvr_port = MACH_PORT_NULL; + *child_pid = nj->p; + kr = KERN_SUCCESS; + } + } else { + job_log(nj, LOG_ERR, "Failed to dispatch job, requestor: %s", j->label); + kr = BOOTSTRAP_UNKNOWN_SERVICE; + } + } mig_deallocate(indata, indataCnt); + return kr; +} + +kern_return_t +job_mig_event_source_check_in(job_t j, name_t name, mach_port_t ping_port, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt, uint64_t *tokens) +{ + if (!j || !j->event_monitor) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + /* Update our ping-port. One ping will force all the notification systems + * to check in, so they'll all give us send-once rights. It doesn't really + * matter which one we keep around. It's not the most efficient thing ever, + * but keep in mind that, by doing this over one channel, we can do it over + * the job's MachService. This means that we'll get it back when the job dies, + * and we can create ourselves a send-once right if we didn't have one already, + * and we can just keep the helper alive without it needing to bootstrap + * communication. + * + * So we're trading efficiency for robustness. In this case, the checkins + * should happen pretty infrequently, so it's pretty worth it. + */ + if (_s_event_update_port != MACH_PORT_NULL) { + (void)job_assumes(j, launchd_mport_deallocate(_s_event_update_port) == KERN_SUCCESS); + } + _s_event_update_port = ping_port; + + kern_return_t result = BOOTSTRAP_NO_MEMORY; + launch_data_t arr = launch_data_alloc(LAUNCH_DATA_ARRAY); + if (job_assumes(j, arr != NULL)) { + struct eventsystem *es = eventsystem_find(name); + if (unlikely(es == NULL)) { + es = eventsystem_new(name); + } + + if (job_assumes(j, es != NULL)) { + struct externalevent *ei = NULL; + size_t i = 0; + LIST_FOREACH(ei, &es->events, sys_le) { + (void)job_assumes(j, launch_data_array_set_index(arr, ei->event, i)); + if (job_assumes(j, i < 1024)) { + tokens[i] = ei->id; + } else { + break; + } + i++; + } + + /* Big enough. */ + *outvalCnt = 10 * 1024; + mig_allocate(outval, *outvalCnt); + + size_t sz = launch_data_pack(arr, (void *)*outval, *outvalCnt, NULL, NULL); + if (job_assumes(j, sz != 0)) { + result = BOOTSTRAP_SUCCESS; + } else { + mig_deallocate(*outval, *outvalCnt); + } + } + + /* Total hack, but launch_data doesn't do ref-counting. */ + struct _launch_data *hack = (struct _launch_data *)arr; + free(hack->_array); + free(arr); + } + + return result; +} +kern_return_t +job_mig_event_set_state(job_t j, name_t name, uint64_t token, boolean_t state) +{ + if (!j->event_monitor) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + + struct externalevent *ei = externalevent_find(name, token); + if (job_assumes(j, ei != NULL)) { + ei->state = state; + if(job_dispatch(ei->job, false) == NULL) { + if (errno == EPERM) { + return BOOTSTRAP_NOT_PRIVILEGED; + } + return BOOTSTRAP_NO_MEMORY; + } + } else { + return BOOTSTRAP_NO_MEMORY; + } + return BOOTSTRAP_SUCCESS; } @@ -8818,12 +10267,21 @@ jobmgr_init(bool sflag) LIST_INIT(&s_needing_sessions); launchd_assert((root_jobmgr = jobmgr_new(NULL, MACH_PORT_NULL, MACH_PORT_NULL, sflag, root_session_type, false, MACH_PORT_NULL)) != NULL); - +#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__ + launchd_assert((_s_xpc_system_domain = jobmgr_new_xpc_singleton_domain(root_jobmgr, "com.apple.xpc.system")) != NULL); + _s_xpc_system_domain->req_asid = g_audit_session; + _s_xpc_system_domain->req_asport = g_audit_session_port; + _s_xpc_system_domain->shortdesc = "system"; +#endif + if (pid1_magic) { + root_jobmgr->monitor_shutdown = true; + } + uint32_t fflags = NOTE_ATTRIB | NOTE_LINK | NOTE_REVOKE | NOTE_EXTEND | NOTE_WRITE; s_no_hang_fd = open("/dev/autofs_nowait", O_EVTONLY | O_NONBLOCK); - if( likely(s_no_hang_fd == -1) ) { - if( jobmgr_assumes(root_jobmgr, (s_no_hang_fd = open("/dev", O_EVTONLY | O_NONBLOCK)) != -1) ) { - jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_ADD, fflags, 0, root_jobmgr) != -1); + if (likely(s_no_hang_fd == -1)) { + if (jobmgr_assumes(root_jobmgr, (s_no_hang_fd = open("/dev", O_EVTONLY | O_NONBLOCK)) != -1)) { + (void)jobmgr_assumes(root_jobmgr, kevent_mod((uintptr_t)s_no_hang_fd, EVFILT_VNODE, EV_ADD, fflags, 0, root_jobmgr) != -1); } } s_no_hang_fd = _fd(s_no_hang_fd); @@ -8876,42 +10334,13 @@ waiting4removal_new(job_t j, mach_port_t rp) void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r) { - job_assumes(j, job_mig_send_signal_reply(w4r->reply_port, 0) == 0); + (void)job_assumes(j, job_mig_send_signal_reply(w4r->reply_port, 0) == 0); SLIST_REMOVE(&j->removal_watchers, w4r, waiting_for_removal, sle); free(w4r); } -bool -waiting4exit_new(job_t j, mach_port_t rp, bool legacy) -{ - struct waiting_for_exit *w4e = NULL; - if( !job_assumes(j, (w4e = malloc(sizeof(struct waiting_for_exit))) != NULL) ) { - return false; - } - - w4e->rp = rp; - w4e->legacy = legacy; - LIST_INSERT_HEAD(&j->exit_watchers, w4e, sle); - - return true; -} - -void -waiting4exit_delete(job_t j, struct waiting_for_exit *w4e) -{ - if( !w4e->legacy ) { - job_assumes(j, job_mig_wait2_reply(w4e->rp, KERN_SUCCESS, j->last_exit_status, false) == KERN_SUCCESS); - } else { - job_assumes(j, job_mig_wait_reply(w4e->rp, KERN_SUCCESS, j->last_exit_status) == KERN_SUCCESS); - } - - LIST_REMOVE(w4e, sle); - - free(w4e); -} - size_t get_kern_max_proc(void) { @@ -8919,7 +10348,7 @@ get_kern_max_proc(void) int max = 100; size_t max_sz = sizeof(max); - launchd_assumes(sysctl(mib, 2, &max, &max_sz, NULL, 0) != -1); + (void)launchd_assumes(sysctl(mib, 2, &max, &max_sz, NULL, 0) != -1); return max; } @@ -8928,7 +10357,7 @@ get_kern_max_proc(void) void eliminate_double_reboot(void) { - if( unlikely(!pid1_magic) ) { + if (unlikely(!pid1_magic)) { return; } @@ -8937,26 +10366,26 @@ eliminate_double_reboot(void) char *try_again = "Will try again at next boot."; int result = ~0; - if( unlikely(stat(argv[1], &sb) != -1) ) { + if (unlikely(stat(argv[1], &sb) != -1)) { jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Going to run deferred install script."); int wstatus; pid_t p; - jobmgr_assumes(root_jobmgr, (errno = posix_spawnp(&p, argv[0], NULL, NULL, (char **)argv, environ)) == 0); + (void)jobmgr_assumes(root_jobmgr, (errno = posix_spawnp(&p, argv[0], NULL, NULL, (char **)argv, environ)) == 0); if (errno) { jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Couldn't run deferred install script! %s", try_again); goto out; } - if( !jobmgr_assumes(root_jobmgr, waitpid(p, &wstatus, 0) != -1) ) { + if (!jobmgr_assumes(root_jobmgr, waitpid(p, &wstatus, 0) != -1)) { jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Couldn't confirm that deferred install script exited successfully! %s", try_again); goto out; } - if( jobmgr_assumes(root_jobmgr, WIFEXITED(wstatus) != 0) ) { - if( jobmgr_assumes(root_jobmgr, (result = WEXITSTATUS(wstatus)) == EXIT_SUCCESS) ) { + if (jobmgr_assumes(root_jobmgr, WIFEXITED(wstatus) != 0)) { + if (jobmgr_assumes(root_jobmgr, (result = WEXITSTATUS(wstatus)) == EXIT_SUCCESS)) { jobmgr_log(root_jobmgr, LOG_DEBUG | LOG_CONSOLE, "Deferred install script completed successfully."); } else { jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Deferred install script exited with status %d. %s", WEXITSTATUS(wstatus), try_again); @@ -8966,42 +10395,33 @@ eliminate_double_reboot(void) } } out: - if( result == 0 ) { + if (result == 0) { /* If the unlink(2) was to fail, it would be most likely fail with EBUSY. All the other * failure cases for unlink(2) don't apply when we're running under PID 1 and have verified * that the file exists. Outside of someone deliberately messing with us (like if /etc/rc.deferredinstall * is actually a looping sym-link or a mount point for a filesystem) and I/O errors, we should be good. */ - if( !jobmgr_assumes(root_jobmgr, unlink(argv[1]) != -1) ) { + if (!jobmgr_assumes(root_jobmgr, unlink(argv[1]) != -1)) { jobmgr_log(root_jobmgr, LOG_WARNING | LOG_CONSOLE, "Deferred install script couldn't be removed!"); } } } -static void -simulate_pid1_crash(void) -{ - if( pid1_magic && g_simulate_pid1_crash ) { - runtime_syslog(LOG_EMERG | LOG_CONSOLE, "About to simulate a crash."); - raise(SIGSEGV); - } -} - void jetsam_property_setup(launch_data_t obj, const char *key, job_t j) { job_log(j, LOG_DEBUG, "Setting Jetsam properties for job..."); - if( strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER ) { + if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMPRIORITY) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) { j->jetsam_priority = (typeof(j->jetsam_priority))launch_data_get_integer(obj); job_log(j, LOG_DEBUG, "Priority: %d", j->jetsam_priority); - } else if( strcasecmp(key, LAUNCH_JOBKEY_JETSAMMEMORYLIMIT) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER ) { + } else if (strcasecmp(key, LAUNCH_JOBKEY_JETSAMMEMORYLIMIT) == 0 && launch_data_get_type(obj) == LAUNCH_DATA_INTEGER) { j->jetsam_memlimit = (typeof(j->jetsam_memlimit))launch_data_get_integer(obj); job_log(j, LOG_DEBUG, "Memory limit: %d", j->jetsam_memlimit); - } else if( strcasecmp(key, LAUNCH_KEY_JETSAMFRONTMOST) == 0 ) { + } else if (strcasecmp(key, LAUNCH_KEY_JETSAMFRONTMOST) == 0) { /* Ignore. We only recognize this key so we don't complain when we get SpringBoard's request. * You can't set this in a plist. */ - } else if( strcasecmp(key, LAUNCH_KEY_JETSAMLABEL) == 0 ) { + } else if (strcasecmp(key, LAUNCH_KEY_JETSAMLABEL) == 0) { /* Ignore. This key is present in SpringBoard's request dictionary, so we don't want to * complain about it. */ @@ -9009,17 +10429,19 @@ jetsam_property_setup(launch_data_t obj, const char *key, job_t j) job_log(j, LOG_ERR, "Unknown Jetsam key: %s", key); } - if( unlikely(!j->jetsam_properties) ) { + if (unlikely(!j->jetsam_properties)) { j->jetsam_properties = true; LIST_INSERT_HEAD(&j->mgr->jetsam_jobs, j, jetsam_sle); j->mgr->jetsam_jobs_cnt++; } + + j->jetsam_seq = s_jetsam_sequence_id++; } int launchd_set_jetsam_priorities(launch_data_t priorities) { - if( !launchd_assumes(launch_data_get_type(priorities) == LAUNCH_DATA_ARRAY) ) { + if (!launchd_assumes(launch_data_get_type(priorities) == LAUNCH_DATA_ARRAY)) { return EINVAL; } @@ -9027,14 +10449,14 @@ launchd_set_jetsam_priorities(launch_data_t priorities) #if !TARGET_OS_EMBEDDED /* For testing. */ jm = jobmgr_find_by_name(root_jobmgr, VPROCMGR_SESSION_AQUA); - if( !launchd_assumes(jm != NULL) ) { + if (!launchd_assumes(jm != NULL)) { return EINVAL; } #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 ) { + if (!g_embedded_privileged_action) { return EPERM; } #endif @@ -9043,36 +10465,36 @@ launchd_set_jetsam_priorities(launch_data_t priorities) job_t ji = NULL; size_t i = 0; - for( i = 0; i < npris; i++ ) { + for (i = 0; i < npris; i++) { launch_data_t ldi = launch_data_array_get_index(priorities, i); - if( !launchd_assumes(launch_data_get_type(ldi) == LAUNCH_DATA_DICTIONARY) ) { + if (!launchd_assumes(launch_data_get_type(ldi) == LAUNCH_DATA_DICTIONARY)) { continue; } launch_data_t label = NULL; - if( !launchd_assumes(label = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMLABEL)) ) { + if (!launchd_assumes(label = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMLABEL))) { continue; } const char *_label = launch_data_get_string(label); - ji = job_find(_label); - if( !launchd_assumes(ji != NULL) ) { + ji = job_find(NULL, _label); + if (!launchd_assumes(ji != NULL)) { continue; } launch_data_dict_iterate(ldi, (void (*)(launch_data_t, const char *, void *))jetsam_property_setup, ji); launch_data_t frontmost = NULL; - if( (frontmost = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMFRONTMOST)) && launch_data_get_type(frontmost) == LAUNCH_DATA_BOOL ) { + if ((frontmost = launch_data_dict_lookup(ldi, LAUNCH_KEY_JETSAMFRONTMOST)) && launch_data_get_type(frontmost) == LAUNCH_DATA_BOOL) { ji->jetsam_frontmost = launch_data_get_bool(frontmost); } } i = 0; job_t *jobs = (job_t *)calloc(jm->jetsam_jobs_cnt, sizeof(job_t)); - if( launchd_assumes(jobs != NULL) ) { - LIST_FOREACH( ji, &jm->jetsam_jobs, jetsam_sle ) { - if( ji->p ) { + if (launchd_assumes(jobs != NULL)) { + LIST_FOREACH(ji, &jm->jetsam_jobs, jetsam_sle) { + if (ji->p) { jobs[i] = ji; i++; } @@ -9084,39 +10506,45 @@ launchd_set_jetsam_priorities(launch_data_t priorities) int result = EINVAL; /* It is conceivable that there could be no Jetsam jobs running. */ - if( totalpris > 0 ) { + if (totalpris > 0) { /* Yay blocks! */ qsort_b((void *)jobs, totalpris, sizeof(job_t), ^ int (const void *lhs, const void *rhs) { job_t _lhs = *(job_t *)lhs; job_t _rhs = *(job_t *)rhs; /* Sort in descending order. (Priority correlates to the soonishness with which you will be killed.) */ - if( _lhs->jetsam_priority > _rhs->jetsam_priority ) { + if (_lhs->jetsam_priority > _rhs->jetsam_priority) { return -1; - } else if( _lhs->jetsam_priority < _rhs->jetsam_priority ) { + } else if (_lhs->jetsam_priority < _rhs->jetsam_priority) { return 1; } + /* Priority is equal, so sort by sequence ID to maintain LRU order */ + if( (int)(_lhs->jetsam_seq - _rhs->jetsam_seq) > 0 ) { + return 1; + } else if( (int)(_lhs->jetsam_seq - _rhs->jetsam_seq) < 0 ) { + return -1; + } return 0; }); jetsam_priority_entry_t *jpris = (jetsam_priority_entry_t *)calloc(totalpris, sizeof(jetsam_priority_entry_t)); - if( !launchd_assumes(jpris != NULL) ) { + if (!launchd_assumes(jpris != NULL)) { result = ENOMEM; } else { - for( i = 0; i < totalpris; i++ ) { + for (i = 0; i < totalpris; i++) { jpris[i].pid = jobs[i]->p; /* Subject to time-of-use vs. time-of-check, obviously. */ jpris[i].flags |= jobs[i]->jetsam_frontmost ? kJetsamFlagsFrontmost : 0; jpris[i].hiwat_pages = jobs[i]->jetsam_memlimit; } - launchd_assumes((result = sysctlbyname("kern.memorystatus_priority_list", NULL, NULL, &jpris[0], totalpris * sizeof(jetsam_priority_entry_t))) != -1); + (void)launchd_assumes((result = sysctlbyname("kern.memorystatus_priority_list", NULL, NULL, &jpris[0], totalpris * sizeof(jetsam_priority_entry_t))) != -1); result = result != 0 ? errno : 0; free(jpris); } } - if( jobs ) { + if (jobs) { free(jobs); }