/* * Copyright (c) 2000-2020 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ /* * Copyright (c) 1982, 1986, 1989, 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)kern_proc.c 8.4 (Berkeley) 1/4/94 */ /* * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce * support for mandatory and extensible security protections. This notice * is included in support of clause 2.2 (b) of the Apple Public License, * Version 2.0. */ /* HISTORY * 04-Aug-97 Umesh Vaishampayan (umeshv@apple.com) * Added current_proc_EXTERNAL() function for the use of kernel * lodable modules. * * 05-Jun-95 Mac Gillon (mgillon) at NeXT * New version based on 3.3NS and 4.4 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* vm_map_switch_protect() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* IOTaskHasEntitlement() */ #include #include /* ipc_kobject_set_kobjidx() */ #include /* proc_filedesc_ast */ #include #include #include /* OS_STRINGIFY */ #if CONFIG_CSR #include #endif #if CONFIG_MACF #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef XNU_KERNEL_PRIVATE #include #endif /* XNU_KERNEL_PRIVATE */ #if SKYWALK #include #endif /* SKYWALK */ /* * Structure associated with user cacheing. */ struct uidinfo { LIST_ENTRY(uidinfo) ui_hash; uid_t ui_uid; size_t ui_proccnt; }; #define UIHASH(uid) (&uihashtbl[(uid) & uihash]) static LIST_HEAD(uihashhead, uidinfo) * uihashtbl; static u_long uihash; /* size of hash table - 1 */ /* * Other process lists */ static struct smr_hash pid_hash; static struct smr_hash pgrp_hash; SECURITY_READ_ONLY_LATE(struct sesshashhead *) sesshashtbl; SECURITY_READ_ONLY_LATE(u_long) sesshash; struct proclist allproc = LIST_HEAD_INITIALIZER(allproc); struct proclist zombproc = LIST_HEAD_INITIALIZER(zombproc); extern struct tty cons; extern size_t proc_struct_size; extern size_t proc_and_task_size; extern int cs_debug; #if DEVELOPMENT || DEBUG static TUNABLE(bool, syscallfilter_disable, "-disable_syscallfilter", false); #endif // DEVELOPMENT || DEBUG #if DEBUG #define __PROC_INTERNAL_DEBUG 1 #endif #if CONFIG_COREDUMP || CONFIG_UCOREDUMP /* Name to give to core files */ #if defined(XNU_TARGET_OS_BRIDGE) __XNU_PRIVATE_EXTERN const char * defaultcorefiledir = "/private/var/internal"; __XNU_PRIVATE_EXTERN char corefilename[MAXPATHLEN + 1] = {"/private/var/internal/%N.core"}; __XNU_PRIVATE_EXTERN const char * defaultdrivercorefiledir = "/private/var/internal"; __XNU_PRIVATE_EXTERN char drivercorefilename[MAXPATHLEN + 1] = {"/private/var/internal/%N.core"}; #elif defined(XNU_TARGET_OS_OSX) __XNU_PRIVATE_EXTERN const char * defaultcorefiledir = "/cores"; __XNU_PRIVATE_EXTERN char corefilename[MAXPATHLEN + 1] = {"/cores/core.%P"}; __XNU_PRIVATE_EXTERN const char * defaultdrivercorefiledir = "/private/var/dextcores"; __XNU_PRIVATE_EXTERN char drivercorefilename[MAXPATHLEN + 1] = {"/private/var/dextcores/%N.core"}; #else __XNU_PRIVATE_EXTERN const char * defaultcorefiledir = "/private/var/cores"; __XNU_PRIVATE_EXTERN char corefilename[MAXPATHLEN + 1] = {"/private/var/cores/%N.core"}; __XNU_PRIVATE_EXTERN const char * defaultdrivercorefiledir = "/private/var/dextcores"; __XNU_PRIVATE_EXTERN char drivercorefilename[MAXPATHLEN + 1] = {"/private/var/dextcores/%N.core"}; #endif #endif #if PROC_REF_DEBUG #include #endif static LCK_MTX_DECLARE_ATTR(proc_klist_mlock, &proc_mlock_grp, &proc_lck_attr); ZONE_DEFINE(pgrp_zone, "pgrp", sizeof(struct pgrp), ZC_ZFREE_CLEARMEM); ZONE_DEFINE(session_zone, "session", sizeof(struct session), ZC_ZFREE_CLEARMEM); ZONE_DEFINE_ID(ZONE_ID_PROC_RO, "proc_ro", struct proc_ro, ZC_READONLY | ZC_ZFREE_CLEARMEM); typedef uint64_t unaligned_u64 __attribute__((aligned(1))); static void orphanpg(struct pgrp * pg); void proc_name_kdp(proc_t t, char * buf, int size); boolean_t proc_binary_uuid_kdp(task_t task, uuid_t uuid); boolean_t current_thread_aborted(void); int proc_threadname_kdp(void * uth, char * buf, size_t size); void proc_starttime_kdp(void * p, unaligned_u64 *tv_sec, unaligned_u64 *tv_usec, unaligned_u64 *abstime); void proc_archinfo_kdp(void* p, cpu_type_t* cputype, cpu_subtype_t* cpusubtype); uint64_t proc_getcsflags_kdp(void * p); const char * proc_name_address(void * p); char * proc_longname_address(void *); static void pgrp_destroy(struct pgrp *pgrp); static void pgrp_replace(proc_t p, struct pgrp *pgrp); static int csops_internal(pid_t pid, int ops, user_addr_t uaddr, user_size_t usersize, user_addr_t uaddittoken); static boolean_t proc_parent_is_currentproc(proc_t p); #if CONFIG_PROC_RESOURCE_LIMITS extern void task_filedesc_ast(task_t task, int current_size, int soft_limit, int hard_limit); extern void task_kqworkloop_ast(task_t task, int current_size, int soft_limit, int hard_limit); #endif /* defined in bsd/kern/kern_prot.c */ extern int get_audit_token_pid(const audit_token_t *audit_token); struct fixjob_iterargs { struct pgrp * pg; struct session * mysession; int entering; }; int fixjob_callback(proc_t, void *); uint64_t get_current_unique_pid(void) { proc_t p = current_proc(); if (p) { return proc_uniqueid(p); } else { return 0; } } /* * Initialize global process hashing structures. */ static void procinit(void) { smr_hash_init(&pid_hash, maxproc / 4); smr_hash_init(&pgrp_hash, maxproc / 4); sesshashtbl = hashinit(maxproc / 4, M_PROC, &sesshash); uihashtbl = hashinit(maxproc / 16, M_PROC, &uihash); } STARTUP(EARLY_BOOT, STARTUP_RANK_FIRST, procinit); /* * Change the count associated with number of processes * a given user is using. This routine protects the uihash * with the list lock */ size_t chgproccnt(uid_t uid, int diff) { struct uidinfo *uip; struct uidinfo *newuip = NULL; struct uihashhead *uipp; size_t retval; again: proc_list_lock(); uipp = UIHASH(uid); for (uip = uipp->lh_first; uip != 0; uip = uip->ui_hash.le_next) { if (uip->ui_uid == uid) { break; } } if (uip) { uip->ui_proccnt += diff; if (uip->ui_proccnt > 0) { retval = uip->ui_proccnt; proc_list_unlock(); goto out; } LIST_REMOVE(uip, ui_hash); retval = 0; proc_list_unlock(); kfree_type(struct uidinfo, uip); goto out; } if (diff <= 0) { if (diff == 0) { retval = 0; proc_list_unlock(); goto out; } panic("chgproccnt: lost user"); } if (newuip != NULL) { uip = newuip; newuip = NULL; LIST_INSERT_HEAD(uipp, uip, ui_hash); uip->ui_uid = uid; uip->ui_proccnt = diff; retval = diff; proc_list_unlock(); goto out; } proc_list_unlock(); newuip = kalloc_type(struct uidinfo, Z_WAITOK | Z_NOFAIL); goto again; out: kfree_type(struct uidinfo, newuip); return retval; } /* * Is p an inferior of the current process? */ int inferior(proc_t p) { int retval = 0; proc_list_lock(); for (; p != current_proc(); p = p->p_pptr) { if (proc_getpid(p) == 0) { goto out; } } retval = 1; out: proc_list_unlock(); return retval; } /* * Is p an inferior of t ? */ int isinferior(proc_t p, proc_t t) { int retval = 0; int nchecked = 0; proc_t start = p; /* if p==t they are not inferior */ if (p == t) { return 0; } proc_list_lock(); for (; p != t; p = p->p_pptr) { nchecked++; /* Detect here if we're in a cycle */ if ((proc_getpid(p) == 0) || (p->p_pptr == start) || (nchecked >= nprocs)) { goto out; } } retval = 1; out: proc_list_unlock(); return retval; } int proc_isinferior(int pid1, int pid2) { proc_t p = PROC_NULL; proc_t t = PROC_NULL; int retval = 0; if (((p = proc_find(pid1)) != (proc_t)0) && ((t = proc_find(pid2)) != (proc_t)0)) { retval = isinferior(p, t); } if (p != PROC_NULL) { proc_rele(p); } if (t != PROC_NULL) { proc_rele(t); } return retval; } /* * Returns process identity of a given process. Calling this function is not * racy for a current process or if a reference to the process is held. */ struct proc_ident proc_ident_with_policy(proc_t p, proc_ident_validation_policy_t policy) { struct proc_ident ident = { .may_exit = (policy & IDENT_VALIDATION_PROC_MAY_EXIT) != 0, .may_exec = (policy & IDENT_VALIDATION_PROC_MAY_EXEC) != 0, .p_pid = proc_pid(p), .p_uniqueid = proc_uniqueid(p), .p_idversion = proc_pidversion(p), }; return ident; } /* * Function: proc_find_audit_token * * Description: Lookup a process with the provided audit_token_t * will validate that the embedded pidver matches. */ proc_t proc_find_audit_token(const audit_token_t token) { proc_t proc = PROC_NULL; pid_t pid = get_audit_token_pid(&token); if (pid <= 0) { return PROC_NULL; } if ((proc = proc_find(pid)) == PROC_NULL) { return PROC_NULL; } /* Check the target proc pidversion */ int pidversion = proc_pidversion(proc); if (pidversion != token.val[7]) { proc_rele(proc); return PROC_NULL; } return proc; } /* * Function: proc_find_ident_validated * * Description: Obtain a proc ref from the provided proc_ident. * * Returns: * - 0 on Success * - EINVAL: When the provided arguments are invalid (NULL) * - ESTALE: The process exists but is currently a zombie and * has not been reaped via wait(). Callers may choose to handle * this edge case as a non-error. * - ESRCH: When the lookup or validation fails otherwise. The process * described by the identifier no longer exists. * * Note: Caller must proc_rele() the out param when this function returns 0 */ errno_t proc_find_ident_validated(const proc_ident_t ident, proc_t *out) { if (ident == NULL || out == NULL) { return EINVAL; } proc_t proc = proc_find(ident->p_pid); if (proc == PROC_NULL) { // If the policy indicates the process may exit, we should also check // the zombie list, and return ENOENT to indicate that the process is // a zombie waiting to be reaped. if (proc_ident_has_policy(ident, IDENT_VALIDATION_PROC_MAY_EXIT) && pzfind_unique(ident->p_pid, ident->p_uniqueid)) { return ESTALE; } return ESRCH; } // If the policy indicates that the process shouldn't exec, fail the // lookup if the pidversion doesn't match if (!proc_ident_has_policy(ident, IDENT_VALIDATION_PROC_MAY_EXEC) && proc_pidversion(proc) != ident->p_idversion) { proc_rele(proc); return ESRCH; } // Check the uniqueid which is always verified if (proc_uniqueid(proc) != ident->p_uniqueid) { proc_rele(proc); return ESRCH; } *out = proc; return 0; } /* * Function: proc_find_ident * * Description: Obtain a proc ref from the provided proc_ident. * Discards the errno result from proc_find_ident_validated * for callers using the old interface. */ inline proc_t proc_find_ident(const proc_ident_t ident) { proc_t p = PROC_NULL; if (proc_find_ident_validated(ident, &p) != 0) { return PROC_NULL; } return p; } /* * Function: proc_ident_equal_token * * Description: Compare a proc_ident_t to an audit token. The * process described by the audit token must still exist (which * includes a pidver check during the lookup). But the comparison * with the proc_ident_t will respect IDENT_VALIDATION_PROC_MAY_EXEC * and only compare PID and unique ID when it is set. */ bool proc_ident_equal_token(proc_ident_t ident, audit_token_t token) { if (ident == NULL) { return false; } // If the PIDs don't match, early return if (ident->p_pid != get_audit_token_pid(&token)) { return false; } // Compare pidversion if IDENT_VALIDATION_PROC_MAY_EXEC is not set if (!proc_ident_has_policy(ident, IDENT_VALIDATION_PROC_MAY_EXEC) && ident->p_idversion != token.val[7]) { return false; } // Lookup the process described by the provided audit token proc_t proc = proc_find_audit_token(token); if (proc == PROC_NULL) { return false; } // Always validate that the uniqueid matches if (proc_uniqueid(proc) != ident->p_uniqueid) { proc_rele(proc); return false; } proc_rele(proc); return true; } /* * Function: proc_ident_equal_ref * * Description: Compare a proc_ident_t to a proc_t. Will * respect IDENT_VALIDATION_PROC_MAY_EXEC and only compare * PID and unique ID when set. */ bool proc_ident_equal_ref(proc_ident_t ident, proc_t proc) { if (ident == NULL || proc == PROC_NULL) { return false; } // Always compare PID and p_uniqueid if (proc_pid(proc) != ident->p_pid || proc_uniqueid(proc) != ident->p_uniqueid) { return false; } // Compare pidversion if IDENT_VALIDATION_PROC_MAY_EXEC is not set if (!proc_ident_has_policy(ident, IDENT_VALIDATION_PROC_MAY_EXEC) && proc_pidversion(proc) != ident->p_idversion) { return false; } return true; } /* * Function: proc_ident_equal * * Description: Compare two proc_ident_t identifiers. Will * respect IDENT_VALIDATION_PROC_MAY_EXEC and only compare * PID and unique ID when set. */ bool proc_ident_equal(proc_ident_t ident, proc_ident_t other) { if (ident == NULL || other == NULL) { return false; } // Always compare PID and p_uniqueid if (ident->p_pid != other->p_pid || ident->p_uniqueid != other->p_uniqueid) { return false; } // Compare pidversion if IDENT_VALIDATION_PROC_MAY_EXEC is not set if (!proc_ident_has_policy(ident, IDENT_VALIDATION_PROC_MAY_EXEC) && ident->p_idversion != other->p_idversion) { return false; } return true; } /* * Function: proc_ident_has_policy * * Description: Validate that a particular policy is set. * * Stored in the upper 4 bits of the 32 bit * p_pid field. */ inline bool proc_ident_has_policy(const proc_ident_t ident, enum proc_ident_validation_policy policy) { if (ident == NULL) { return false; } switch (policy) { case IDENT_VALIDATION_PROC_MAY_EXIT: return ident->may_exit; case IDENT_VALIDATION_PROC_MAY_EXEC: return ident->may_exec; case IDENT_VALIDATION_PROC_EXACT: return ident->may_exec == 0 && ident->may_exit == 0; } } void uthread_reset_proc_refcount(uthread_t uth) { uth->uu_proc_refcount = 0; #if PROC_REF_DEBUG if (kern_feature_override(KF_DISABLE_PROCREF_TRACKING_OVRD)) { return; } struct uthread_proc_ref_info *upri = uth->uu_proc_ref_info; uint32_t n = uth->uu_proc_ref_info->upri_pindex; uth->uu_proc_ref_info->upri_pindex = 0; if (n) { for (unsigned i = 0; i < n; i++) { btref_put(upri->upri_proc_stacks[i]); } bzero(upri->upri_proc_stacks, sizeof(btref_t) * n); bzero(upri->upri_proc_ps, sizeof(proc_t) * n); } #endif /* PROC_REF_DEBUG */ } #if PROC_REF_DEBUG void uthread_init_proc_refcount(uthread_t uth) { if (kern_feature_override(KF_DISABLE_PROCREF_TRACKING_OVRD)) { return; } uth->uu_proc_ref_info = kalloc_type(struct uthread_proc_ref_info, Z_ZERO | Z_WAITOK | Z_NOFAIL); } void uthread_destroy_proc_refcount(uthread_t uth) { if (kern_feature_override(KF_DISABLE_PROCREF_TRACKING_OVRD)) { return; } struct uthread_proc_ref_info *upri = uth->uu_proc_ref_info; uint32_t n = uth->uu_proc_ref_info->upri_pindex; for (unsigned i = 0; i < n; i++) { btref_put(upri->upri_proc_stacks[i]); } kfree_type(struct uthread_proc_ref_info, uth->uu_proc_ref_info); } void uthread_assert_zero_proc_refcount(uthread_t uth) { if (kern_feature_override(KF_DISABLE_PROCREF_TRACKING_OVRD)) { return; } if (__improbable(uth->uu_proc_refcount != 0)) { panic("Unexpected non zero uu_proc_refcount = %d (%p)", uth->uu_proc_refcount, uth); } } #endif /* PROC_REF_DEBUG */ bool proc_list_exited(proc_t p) { return os_ref_get_raw_mask(&p->p_refcount) & P_REF_DEAD; } #if CONFIG_DEBUG_SYSCALL_REJECTION uint64_t uthread_get_syscall_rejection_flags(void *uthread) { uthread_t uth = (uthread_t) uthread; return uth->syscall_rejection_flags; } uint64_t* uthread_get_syscall_rejection_mask(void *uthread) { uthread_t uth = (uthread_t) uthread; return uth->syscall_rejection_mask; } uint64_t* uthread_get_syscall_rejection_once_mask(void *uthread) { uthread_t uth = (uthread_t) uthread; return uth->syscall_rejection_once_mask; } bool uthread_syscall_rejection_is_enabled(void *uthread) { uthread_t uth = (uthread_t) uthread; return (debug_syscall_rejection_mode != 0) || (uth->syscall_rejection_flags & SYSCALL_REJECTION_FLAGS_FORCE_FATAL); } #endif /* CONFIG_DEBUG_SYSCALL_REJECTION */ #if PROC_REF_DEBUG __attribute__((noinline)) #endif /* PROC_REF_DEBUG */ static void record_procref(proc_t p __unused, int count) { uthread_t uth; uth = current_uthread(); uth->uu_proc_refcount += count; #if PROC_REF_DEBUG if (kern_feature_override(KF_DISABLE_PROCREF_TRACKING_OVRD)) { return; } struct uthread_proc_ref_info *upri = uth->uu_proc_ref_info; if (upri->upri_pindex < NUM_PROC_REFS_TO_TRACK) { upri->upri_proc_stacks[upri->upri_pindex] = btref_get(__builtin_frame_address(0), BTREF_GET_NOWAIT); upri->upri_proc_ps[upri->upri_pindex] = p; upri->upri_pindex++; } #endif /* PROC_REF_DEBUG */ } /*! * @function proc_ref_try_fast() * * @brief * Tries to take a proc ref, unless it is in flux (being made, or dead). * * @returns * - the new refcount value (including bits) on success, * - 0 on failure. */ static inline uint32_t proc_ref_try_fast(proc_t p) { uint32_t bits; proc_require(p, PROC_REQUIRE_ALLOW_ALL); bits = os_ref_retain_try_mask(&p->p_refcount, P_REF_BITS, P_REF_NEW | P_REF_DEAD, NULL); if (bits) { record_procref(p, 1); } return bits; } /*! * @function proc_ref_wait() * * @brief * Waits for the specified bits to clear, on the specified event. */ __attribute__((noinline)) static void proc_ref_wait(proc_t p, event_t event, proc_ref_bits_t mask, bool locked) { assert_wait(event, THREAD_UNINT | THREAD_WAIT_NOREPORT); if (os_ref_get_raw_mask(&p->p_refcount) & mask) { uthread_t uth = current_uthread(); if (locked) { proc_list_unlock(); } uth->uu_wchan = event; uth->uu_wmesg = "proc_refwait"; thread_block(THREAD_CONTINUE_NULL); uth->uu_wchan = NULL; uth->uu_wmesg = NULL; if (locked) { proc_list_lock(); } } else { clear_wait(current_thread(), THREAD_AWAKENED); } } /*! * @function proc_ref_wait_for_exec() * * @brief * Routine called by processes trying to acquire a ref while * an exec is in flight. * * @discussion * This function is called with a proc ref held on the proc, * which will be given up until the @c P_REF_*_EXEC flags clear. * * @param p the proc, the caller owns a proc ref * @param bits the result of @c proc_ref_try_fast() prior to calling this. * @param locked whether the caller holds the @c proc_list_lock(). */ __attribute__((noinline)) static proc_t proc_ref_wait_for_exec(proc_t p, uint32_t bits, int locked) { const proc_ref_bits_t mask = P_REF_WILL_EXEC | P_REF_IN_EXEC; /* * the proc is in the middle of exec, * trade our ref for a "wait ref", * and wait for the proc_refwake_did_exec() call. * * Note: it's very unlikely that we'd loop back into the wait, * it would only happen if the target proc would be * in exec again by the time we woke up. */ os_ref_retain_raw(&p->p_waitref, &p_refgrp); do { proc_rele(p); proc_ref_wait(p, &p->p_waitref, mask, locked); bits = proc_ref_try_fast(p); } while (__improbable(bits & mask)); proc_wait_release(p); return bits ? p : PROC_NULL; } static inline bool proc_ref_needs_wait_for_exec(uint32_t bits) { if (__probable((bits & (P_REF_WILL_EXEC | P_REF_IN_EXEC)) == 0)) { return false; } if (bits & P_REF_IN_EXEC) { return true; } /* * procs can't have outstanding refs while execing. * * In order to achieve, that, proc_refdrain_will_exec() * will drain outstanding references. It signals its intent * with the P_REF_WILL_EXEC flag, and moves to P_REF_IN_EXEC * when this is achieved. * * Most threads will block in proc_ref() when any of those * flags is set. However, threads that already have * an oustanding ref on this proc might want another * before dropping them. To avoid deadlocks, we need * to let threads with any oustanding reference take one * when only P_REF_WILL_EXEC is set (which causes exec * to be delayed). * * Note: the current thread will _always_ appear like it holds * one ref due to having taken one speculatively. */ assert(current_uthread()->uu_proc_refcount >= 1); return current_uthread()->uu_proc_refcount == 1; } int proc_rele(proc_t p) { uint32_t o_bits, n_bits; proc_require(p, PROC_REQUIRE_ALLOW_ALL); os_atomic_rmw_loop(&p->p_refcount, o_bits, n_bits, release, { n_bits = o_bits - (1u << P_REF_BITS); if ((n_bits >> P_REF_BITS) == 1) { n_bits &= ~P_REF_DRAINING; } }); record_procref(p, -1); /* * p might be freed after this point. */ if (__improbable((o_bits & P_REF_DRAINING) && !(n_bits & P_REF_DRAINING))) { /* * This wakeup can cause spurious ones, * but proc_refdrain() can deal with those. * * Because the proc_zone memory is sequestered, * this is safe to wakeup a possible "freed" address. */ wakeup(&p->p_refcount); } return 0; } bool proc_is_shadow(proc_t p) { return os_ref_get_raw_mask(&p->p_refcount) & P_REF_SHADOW; } proc_t proc_self(void) { proc_t p = current_proc(); /* * Do not go through the logic of "wait for exec", it is meaningless. * Only fail taking a ref for oneself if the proc is about to die. */ return proc_ref_try_fast(p) ? p : PROC_NULL; } proc_t proc_ref(proc_t p, int locked) { uint32_t bits; bits = proc_ref_try_fast(p); if (__improbable(!bits)) { return PROC_NULL; } if (__improbable(proc_ref_needs_wait_for_exec(bits))) { return proc_ref_wait_for_exec(p, bits, locked); } return p; } static void proc_wait_free(smr_node_t node) { struct proc *p = __container_of(node, struct proc, p_smr_node); proc_release_proc_task_struct(p); } void proc_wait_release(proc_t p) { if (__probable(os_ref_release_raw(&p->p_waitref, &p_refgrp) == 0)) { smr_proc_task_call(&p->p_smr_node, proc_and_task_size, proc_wait_free); } } proc_t proc_find_zombref(int pid) { proc_t p; proc_list_lock(); p = proc_find_zombref_locked(pid); proc_list_unlock(); return p; } proc_t proc_find_zombref_locked(int pid) { proc_t p; LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); again: p = phash_find_locked(pid); /* should we bail? */ if ((p == PROC_NULL) || !proc_list_exited(p)) { return PROC_NULL; } /* If someone else is controlling the (unreaped) zombie - wait */ if ((p->p_listflag & P_LIST_WAITING) != 0) { (void)msleep(&p->p_stat, &proc_list_mlock, PWAIT, "waitcoll", 0); goto again; } p->p_listflag |= P_LIST_WAITING; return p; } void proc_drop_zombref(proc_t p) { proc_list_lock(); if ((p->p_listflag & P_LIST_WAITING) == P_LIST_WAITING) { p->p_listflag &= ~P_LIST_WAITING; wakeup(&p->p_stat); } proc_list_unlock(); } void proc_refdrain(proc_t p) { uint32_t bits = os_ref_get_raw_mask(&p->p_refcount); assert(proc_list_exited(p)); while ((bits >> P_REF_BITS) > 1) { if (os_atomic_cmpxchgv(&p->p_refcount, bits, bits | P_REF_DRAINING, &bits, relaxed)) { proc_ref_wait(p, &p->p_refcount, P_REF_DRAINING, false); } } } proc_t proc_refdrain_will_exec(proc_t p) { const proc_ref_bits_t will_exec_mask = P_REF_WILL_EXEC | P_REF_DRAINING; /* * All the calls to proc_ref will wait * for the flag to get cleared before returning a ref. * * (except for the case documented in proc_ref_needs_wait_for_exec()). */ if (p == initproc) { /* Do not wait in ref drain for launchd exec */ os_atomic_or(&p->p_refcount, P_REF_IN_EXEC, relaxed); } else { for (;;) { uint32_t o_ref, n_ref; os_atomic_rmw_loop(&p->p_refcount, o_ref, n_ref, relaxed, { if ((o_ref >> P_REF_BITS) == 1) { /* * We drained successfully, * move on to P_REF_IN_EXEC */ n_ref = o_ref & ~will_exec_mask; n_ref |= P_REF_IN_EXEC; } else { /* * Outstanding refs exit, * mark our desire to stall * proc_ref() callers with * P_REF_WILL_EXEC. */ n_ref = o_ref | will_exec_mask; } }); if (n_ref & P_REF_IN_EXEC) { break; } proc_ref_wait(p, &p->p_refcount, P_REF_DRAINING, false); } } /* Return a ref to the caller */ os_ref_retain_mask(&p->p_refcount, P_REF_BITS, NULL); record_procref(p, 1); return p; } void proc_refwake_did_exec(proc_t p) { os_atomic_andnot(&p->p_refcount, P_REF_IN_EXEC, release); wakeup(&p->p_waitref); } void proc_ref_hold_proc_task_struct(proc_t proc) { os_atomic_or(&proc->p_refcount, P_REF_PROC_HOLD, relaxed); } static void proc_free(proc_t proc, proc_ro_t proc_ro) { kauth_cred_t cred; assert(proc_ro != NULL); cred = smr_serialized_load(&proc_ro->p_ucred); kauth_cred_set(&cred, NOCRED); zfree_ro(ZONE_ID_PROC_RO, proc_ro); zfree(proc_task_zone, proc); } void proc_release_proc_task_struct(proc_t proc) { uint32_t old_ref = os_atomic_andnot_orig(&proc->p_refcount, P_REF_PROC_HOLD, relaxed); if ((old_ref & P_REF_TASK_HOLD) == 0) { proc_free(proc, proc->p_proc_ro); } } void task_ref_hold_proc_task_struct(task_t task) { proc_t proc_from_task = task_get_proc_raw(task); os_atomic_or(&proc_from_task->p_refcount, P_REF_TASK_HOLD, relaxed); } void task_release_proc_task_struct(task_t task, proc_ro_t proc_ro) { proc_t proc_from_task = task_get_proc_raw(task); uint32_t old_ref = os_atomic_andnot_orig(&proc_from_task->p_refcount, P_REF_TASK_HOLD, relaxed); if ((old_ref & P_REF_PROC_HOLD) == 0) { proc_free(proc_from_task, proc_ro); } } proc_t proc_parentholdref(proc_t p) { proc_t parent = PROC_NULL; proc_t pp; proc_list_lock(); loop: pp = p->p_pptr; if ((pp == PROC_NULL) || (pp->p_stat == SZOMB) || ((pp->p_listflag & (P_LIST_CHILDDRSTART | P_LIST_CHILDDRAINED)) == (P_LIST_CHILDDRSTART | P_LIST_CHILDDRAINED))) { parent = PROC_NULL; goto out; } if ((pp->p_listflag & (P_LIST_CHILDDRSTART | P_LIST_CHILDDRAINED)) == P_LIST_CHILDDRSTART) { pp->p_listflag |= P_LIST_CHILDDRWAIT; msleep(&pp->p_childrencnt, &proc_list_mlock, 0, "proc_parent", 0); goto loop; } if ((pp->p_listflag & (P_LIST_CHILDDRSTART | P_LIST_CHILDDRAINED)) == 0) { pp->p_parentref++; parent = pp; goto out; } out: proc_list_unlock(); return parent; } int proc_parentdropref(proc_t p, int listlocked) { if (listlocked == 0) { proc_list_lock(); } if (p->p_parentref > 0) { p->p_parentref--; if ((p->p_parentref == 0) && ((p->p_listflag & P_LIST_PARENTREFWAIT) == P_LIST_PARENTREFWAIT)) { p->p_listflag &= ~P_LIST_PARENTREFWAIT; wakeup(&p->p_parentref); } } else { panic("proc_parentdropref -ve ref"); } if (listlocked == 0) { proc_list_unlock(); } return 0; } void proc_childdrainstart(proc_t p) { #if __PROC_INTERNAL_DEBUG if ((p->p_listflag & P_LIST_CHILDDRSTART) == P_LIST_CHILDDRSTART) { panic("proc_childdrainstart: childdrain already started"); } #endif p->p_listflag |= P_LIST_CHILDDRSTART; /* wait for all that hold parentrefs to drop */ while (p->p_parentref > 0) { p->p_listflag |= P_LIST_PARENTREFWAIT; msleep(&p->p_parentref, &proc_list_mlock, 0, "proc_childdrainstart", 0); } } void proc_childdrainend(proc_t p) { #if __PROC_INTERNAL_DEBUG if (p->p_childrencnt > 0) { panic("exiting: children stil hanging around"); } #endif p->p_listflag |= P_LIST_CHILDDRAINED; if ((p->p_listflag & (P_LIST_CHILDLKWAIT | P_LIST_CHILDDRWAIT)) != 0) { p->p_listflag &= ~(P_LIST_CHILDLKWAIT | P_LIST_CHILDDRWAIT); wakeup(&p->p_childrencnt); } } void proc_checkdeadrefs(__unused proc_t p) { uint32_t bits; bits = os_ref_release_raw_mask(&p->p_refcount, P_REF_BITS, NULL); bits &= ~(P_REF_SHADOW | P_REF_PROC_HOLD | P_REF_TASK_HOLD); if (bits != P_REF_DEAD) { panic("proc being freed and unexpected refcount %p:%d:0x%x", p, bits >> P_REF_BITS, bits & P_REF_MASK); } #if __PROC_INTERNAL_DEBUG if (p->p_childrencnt != 0) { panic("proc being freed and pending children cnt %p:%d", p, p->p_childrencnt); } if (p->p_parentref != 0) { panic("proc being freed and pending parentrefs %p:%d", p, p->p_parentref); } #endif } __attribute__((always_inline, visibility("hidden"))) void proc_require(proc_t proc, proc_require_flags_t flags) { if ((flags & PROC_REQUIRE_ALLOW_NULL) && proc == PROC_NULL) { return; } zone_id_require(ZONE_ID_PROC_TASK, proc_and_task_size, proc); } pid_t proc_getpid(proc_t p) { if (p == kernproc) { return 0; } return p->p_pid; } int proc_pid(proc_t p) { if (p != NULL) { proc_require(p, PROC_REQUIRE_ALLOW_ALL); return proc_getpid(p); } return -1; } int proc_ppid(proc_t p) { if (p != NULL) { proc_require(p, PROC_REQUIRE_ALLOW_ALL); return p->p_ppid; } return -1; } int proc_original_ppid(proc_t p) { if (p != NULL) { proc_require(p, PROC_REQUIRE_ALLOW_ALL); return proc_get_ro(p)->p_orig_ppid; } return -1; } int proc_orig_ppidversion(proc_t p) { if (p != NULL) { proc_require(p, PROC_REQUIRE_ALLOW_ALL); return proc_get_ro(p)->p_orig_ppidversion; } return -1; } int proc_starttime(proc_t p, struct timeval *tv) { if (p != NULL && tv != NULL) { tv->tv_sec = p->p_start.tv_sec; tv->tv_usec = p->p_start.tv_usec; return 0; } return EINVAL; } int proc_selfpid(void) { return proc_getpid(current_proc()); } int proc_selfppid(void) { return current_proc()->p_ppid; } uint64_t proc_selfcsflags(void) { return proc_getcsflags(current_proc()); } int proc_csflags(proc_t p, uint64_t *flags) { if (p && flags) { proc_require(p, PROC_REQUIRE_ALLOW_ALL); *flags = proc_getcsflags(p); return 0; } return EINVAL; } boolean_t proc_is_simulated(const proc_t p) { #ifdef XNU_TARGET_OS_OSX if (p != NULL) { switch (proc_platform(p)) { case PLATFORM_IOSSIMULATOR: case PLATFORM_TVOSSIMULATOR: case PLATFORM_WATCHOSSIMULATOR: return TRUE; default: return FALSE; } } #else /* !XNU_TARGET_OS_OSX */ (void)p; #endif return FALSE; } uint32_t proc_platform(const proc_t p) { if (p != NULL) { return proc_get_ro(p)->p_platform_data.p_platform; } return (uint32_t)-1; } uint32_t proc_min_sdk(proc_t p) { if (p != NULL) { return proc_get_ro(p)->p_platform_data.p_min_sdk; } return (uint32_t)-1; } uint32_t proc_sdk(proc_t p) { if (p != NULL) { return proc_get_ro(p)->p_platform_data.p_sdk; } return (uint32_t)-1; } void proc_setplatformdata(proc_t p, uint32_t platform, uint32_t min_sdk, uint32_t sdk) { proc_ro_t ro; struct proc_platform_ro_data platform_data; ro = proc_get_ro(p); platform_data = ro->p_platform_data; platform_data.p_platform = platform; platform_data.p_min_sdk = min_sdk; platform_data.p_sdk = sdk; zalloc_ro_update_field(ZONE_ID_PROC_RO, ro, p_platform_data, &platform_data); } #if CONFIG_DTRACE int dtrace_proc_selfpid(void) { return proc_selfpid(); } int dtrace_proc_selfppid(void) { return proc_selfppid(); } uid_t dtrace_proc_selfruid(void) { return current_proc()->p_ruid; } #endif /* CONFIG_DTRACE */ /*! * @function proc_parent() * * @brief * Returns a ref on the parent of @c p. * * @discussion * Returns a reference on the parent, or @c PROC_NULL * if both @c p and its parent are zombies. * * If the parent is currently dying, then this function waits * for the situation to be resolved. * * This function never returns @c PROC_NULL if @c p isn't * a zombie (@c p_stat is @c SZOMB) yet. */ proc_t proc_parent(proc_t p) { proc_t parent; proc_t pp; proc_list_lock(); while (1) { pp = p->p_pptr; parent = proc_ref(pp, true); /* Check if we got a proc ref and it is still the parent */ if (parent != PROC_NULL) { if (parent == p->p_pptr) { /* * We have a ref on the parent and it is still * our parent, return the ref */ proc_list_unlock(); return parent; } /* * Our parent changed while we slept on proc_ref, * drop the ref on old parent and retry. */ proc_rele(parent); continue; } if (pp != p->p_pptr) { /* * We didn't get a ref, but parent changed from what * we last saw before we slept in proc_ref, try again * with new parent. */ continue; } if ((pp->p_listflag & P_LIST_CHILDDRAINED) == 0) { /* Parent did not change, but we also did not get a * ref on parent, sleep if the parent has not drained * its children and then retry. */ pp->p_listflag |= P_LIST_CHILDLKWAIT; msleep(&pp->p_childrencnt, &proc_list_mlock, 0, "proc_parent", 0); continue; } /* Parent has died and drained its children and we still * point to it, return NULL. */ proc_list_unlock(); return PROC_NULL; } } static boolean_t proc_parent_is_currentproc(proc_t p) { boolean_t ret = FALSE; proc_list_lock(); if (p->p_pptr == current_proc()) { ret = TRUE; } proc_list_unlock(); return ret; } void proc_name(int pid, char * buf, int size) { proc_t p; if (size <= 0) { return; } bzero(buf, size); if ((p = proc_find(pid)) != PROC_NULL) { strlcpy(buf, &p->p_comm[0], MIN((int)sizeof(p->p_comm), size)); proc_rele(p); } } void proc_name_kdp(proc_t p, char * buf, int size) { if (p == PROC_NULL) { return; } if ((size_t)size > sizeof(p->p_comm)) { strlcpy(buf, &p->p_name[0], MIN((int)sizeof(p->p_name), size)); } else { strlcpy(buf, &p->p_comm[0], MIN((int)sizeof(p->p_comm), size)); } } boolean_t proc_binary_uuid_kdp(task_t task, uuid_t uuid) { proc_t p = get_bsdtask_info(task); if (p == PROC_NULL) { return FALSE; } proc_getexecutableuuid(p, uuid, sizeof(uuid_t)); return TRUE; } int proc_threadname_kdp(void * uth, char * buf, size_t size) { if (size < MAXTHREADNAMESIZE) { /* this is really just a protective measure for the future in * case the thread name size in stackshot gets out of sync with * the BSD max thread name size. Note that bsd_getthreadname * doesn't take input buffer size into account. */ return -1; } if (uth != NULL) { bsd_getthreadname(uth, buf); } return 0; } /* note that this function is generally going to be called from stackshot, * and the arguments will be coming from a struct which is declared packed * thus the input arguments will in general be unaligned. We have to handle * that here. */ void proc_starttime_kdp(void *p, unaligned_u64 *tv_sec, unaligned_u64 *tv_usec, unaligned_u64 *abstime) { proc_t pp = (proc_t)p; if (pp != PROC_NULL) { if (tv_sec != NULL) { *tv_sec = pp->p_start.tv_sec; } if (tv_usec != NULL) { *tv_usec = pp->p_start.tv_usec; } if (abstime != NULL) { if (pp->p_stats != NULL) { *abstime = pp->p_stats->ps_start; } else { *abstime = 0; } } } } void proc_archinfo_kdp(void* p, cpu_type_t* cputype, cpu_subtype_t* cpusubtype) { proc_t pp = (proc_t)p; if (pp != PROC_NULL) { *cputype = pp->p_cputype; *cpusubtype = pp->p_cpusubtype; } } void proc_memstat_data_kdp(void *p, int32_t *current_memlimit, int32_t *prio_effective, int32_t *prio_requested, int32_t *prio_assertion); void proc_memstat_data_kdp(void *p, int32_t *current_memlimit, int32_t *prio_effective, int32_t *prio_requested, int32_t *prio_assertion) { proc_t pp = (proc_t)p; if (pp != PROC_NULL) { *current_memlimit = pp->p_memstat_memlimit; *prio_effective = pp->p_memstat_effectivepriority; *prio_assertion = pp->p_memstat_assertionpriority; *prio_requested = pp->p_memstat_requestedpriority; } } const char * proc_name_address(void *p) { return &((proc_t)p)->p_comm[0]; } char * proc_longname_address(void *p) { return &((proc_t)p)->p_name[0]; } const char * proc_best_name(proc_t p) { if (p->p_name[0] != '\0') { return &p->p_name[0]; } return &p->p_comm[0]; } void proc_best_name_for_pid(int pid, char * buf, int size) { proc_t p; if (size <= 0) { return; } bzero(buf, size); if ((p = proc_find(pid)) != PROC_NULL) { if (p->p_name[0] != '\0') { strlcpy(buf, &p->p_name[0], MIN((int)sizeof(p->p_name), size)); } else { strlcpy(buf, &p->p_comm[0], MIN((int)sizeof(p->p_comm), size)); } proc_rele(p); } } void proc_selfname(char * buf, int size) { proc_t p; if (size <= 0) { return; } bzero(buf, size); if ((p = current_proc()) != (proc_t)0) { strlcpy(buf, &p->p_name[0], MIN((int)sizeof(p->p_name), size)); } } void proc_signal(int pid, int signum) { proc_t p; if ((p = proc_find(pid)) != PROC_NULL) { psignal(p, signum); proc_rele(p); } } int proc_issignal(int pid, sigset_t mask) { proc_t p; int error = 0; if ((p = proc_find(pid)) != PROC_NULL) { error = proc_pendingsignals(p, mask); proc_rele(p); } return error; } int proc_noremotehang(proc_t p) { int retval = 0; if (p) { retval = p->p_flag & P_NOREMOTEHANG; } return retval? 1: 0; } int proc_exiting(proc_t p) { int retval = 0; if (p) { retval = p->p_lflag & P_LEXIT; } return retval? 1: 0; } int proc_in_teardown(proc_t p) { int retval = 0; if (p) { retval = p->p_lflag & P_LPEXIT; } return retval? 1: 0; } int proc_lvfork(proc_t p __unused) { return 0; } int proc_increment_ru_oublock(proc_t p, long *origvalp) { long origval; if (p && p->p_stats) { origval = OSIncrementAtomicLong(&p->p_stats->p_ru.ru_oublock); if (origvalp) { *origvalp = origval; } return 0; } return EINVAL; } int proc_isabortedsignal(proc_t p) { if ((p != kernproc) && current_thread_aborted() && (!(p->p_acflag & AXSIG) || (p->exit_thread != current_thread()) || (p->p_sigacts.ps_sig < 1) || (p->p_sigacts.ps_sig >= NSIG) || !hassigprop(p->p_sigacts.ps_sig, SA_CORE))) { return 1; } return 0; } int proc_forcequota(proc_t p) { int retval = 0; if (p) { retval = p->p_flag & P_FORCEQUOTA; } return retval? 1: 0; } int proc_suser(proc_t p) { int error; smr_proc_task_enter(); error = suser(proc_ucred_smr(p), &p->p_acflag); smr_proc_task_leave(); return error; } task_t proc_task(proc_t proc) { task_t task_from_proc = proc_get_task_raw(proc); return (proc->p_lflag & P_LHASTASK) ? task_from_proc : NULL; } void proc_set_task(proc_t proc, task_t task) { task_t task_from_proc = proc_get_task_raw(proc); if (task == NULL) { proc->p_lflag &= ~P_LHASTASK; } else { if (task != task_from_proc) { panic("proc_set_task trying to set random task %p", task); } proc->p_lflag |= P_LHASTASK; } } task_t proc_get_task_raw(proc_t proc) { return (task_t)((uintptr_t)proc + proc_struct_size); } proc_t task_get_proc_raw(task_t task) { return (proc_t)((uintptr_t)task - proc_struct_size); } /* * Obtain the first thread in a process * * XXX This is a bad thing to do; it exists predominantly to support the * XXX use of proc_t's in places that should really be using * XXX thread_t's instead. This maintains historical behaviour, but really * XXX needs an audit of the context (proxy vs. not) to clean up. */ thread_t proc_thread(proc_t proc) { LCK_MTX_ASSERT(&proc->p_mlock, LCK_MTX_ASSERT_OWNED); uthread_t uth = TAILQ_FIRST(&proc->p_uthlist); if (uth != NULL) { return get_machthread(uth); } return NULL; } kauth_cred_t proc_ucred_unsafe(proc_t p) { kauth_cred_t cred = smr_serialized_load(&proc_get_ro(p)->p_ucred); return kauth_cred_require(cred); } kauth_cred_t proc_ucred_smr(proc_t p) { assert(smr_entered(&smr_proc_task)); return proc_ucred_unsafe(p); } kauth_cred_t proc_ucred_locked(proc_t p) { LCK_MTX_ASSERT(&p->p_ucred_mlock, LCK_ASSERT_OWNED); return proc_ucred_unsafe(p); } struct uthread * current_uthread(void) { return get_bsdthread_info(current_thread()); } int proc_is64bit(proc_t p) { return IS_64BIT_PROCESS(p); } int proc_is64bit_data(proc_t p) { assert(proc_task(p)); return (int)task_get_64bit_data(proc_task(p)); } int proc_isinitproc(proc_t p) { if (initproc == NULL) { return 0; } return p == initproc; } int proc_pidversion(proc_t p) { return proc_get_ro(p)->p_idversion; } void proc_setpidversion(proc_t p, int idversion) { zalloc_ro_update_field(ZONE_ID_PROC_RO, proc_get_ro(p), p_idversion, &idversion); } uint32_t proc_persona_id(proc_t p) { return (uint32_t)persona_id_from_proc(p); } uint32_t proc_getuid(proc_t p) { return p->p_uid; } uint32_t proc_getgid(proc_t p) { return p->p_gid; } uint64_t proc_uniqueid(proc_t p) { if (p == kernproc) { return 0; } return proc_get_ro(p)->p_uniqueid; } uint64_t proc_uniqueid_task(void *p_arg, void *t); /* * During exec, two tasks point at the proc. This function is used * to gives tasks a unique ID; we make the matching task have the * proc's uniqueid, and any other task gets the high-bit flipped. * (We need to try to avoid returning UINT64_MAX, which is the * which is the uniqueid of a task without a proc. (e.g. while exiting)) * * Only used by get_task_uniqueid(); do not add additional callers. */ uint64_t proc_uniqueid_task(void *p_arg, void *t __unused) { proc_t p = p_arg; uint64_t uniqueid = proc_uniqueid(p); return uniqueid ^ (__probable(!proc_is_shadow(p)) ? 0 : (1ull << 63)); } uint64_t proc_puniqueid(proc_t p) { return p->p_puniqueid; } void proc_coalitionids(__unused proc_t p, __unused uint64_t ids[COALITION_NUM_TYPES]) { #if CONFIG_COALITIONS task_coalition_ids(proc_task(p), ids); #else memset(ids, 0, sizeof(uint64_t[COALITION_NUM_TYPES])); #endif return; } uint64_t proc_was_throttled(proc_t p) { return p->was_throttled; } uint64_t proc_did_throttle(proc_t p) { return p->did_throttle; } int proc_getcdhash(proc_t p, unsigned char *cdhash) { if (p == kernproc) { return EINVAL; } return vn_getcdhash(p->p_textvp, p->p_textoff, cdhash, NULL); } uint64_t proc_getcsflags(proc_t p) { return proc_get_ro(p)->p_csflags; } /* This variant runs in stackshot context and must not take locks. */ uint64_t proc_getcsflags_kdp(void * p) { proc_t proc = (proc_t)p; if (p == PROC_NULL) { return 0; } return proc_getcsflags(proc); } void proc_csflags_update(proc_t p, uint64_t flags) { uint32_t csflags = (uint32_t)flags; if (p != kernproc) { zalloc_ro_update_field(ZONE_ID_PROC_RO, proc_get_ro(p), p_csflags, &csflags); } } void proc_csflags_set(proc_t p, uint64_t flags) { proc_csflags_update(p, proc_getcsflags(p) | (uint32_t)flags); } void proc_csflags_clear(proc_t p, uint64_t flags) { proc_csflags_update(p, proc_getcsflags(p) & ~(uint32_t)flags); } uint8_t * proc_syscall_filter_mask(proc_t p) { return proc_get_ro(p)->syscall_filter_mask; } void proc_syscall_filter_mask_set(proc_t p, uint8_t *mask) { zalloc_ro_update_field(ZONE_ID_PROC_RO, proc_get_ro(p), syscall_filter_mask, &mask); } int proc_exitstatus(proc_t p) { return p->p_xstat & 0xffff; } bool proc_is_zombie(proc_t p) { return proc_list_exited(p); } void proc_setexecutableuuid(proc_t p, const unsigned char *uuid) { memcpy(p->p_uuid, uuid, sizeof(p->p_uuid)); } const unsigned char * proc_executableuuid_addr(proc_t p) { return &p->p_uuid[0]; } void proc_getexecutableuuid(proc_t p, unsigned char *uuidbuf, unsigned long size) { if (size >= sizeof(uuid_t)) { memcpy(uuidbuf, proc_executableuuid_addr(p), sizeof(uuid_t)); } } void proc_getresponsibleuuid(proc_t p, unsigned char *__counted_by(size)uuidbuf, unsigned long size) { if (size >= sizeof(uuid_t)) { memcpy(uuidbuf, p->p_responsible_uuid, sizeof(uuid_t)); } } void proc_setresponsibleuuid(proc_t p, unsigned char *__counted_by(size)uuidbuf, unsigned long size) { if (p != NULL && uuidbuf != NULL && size >= sizeof(uuid_t)) { memcpy(p->p_responsible_uuid, uuidbuf, sizeof(uuid_t)); } return; } /* Return vnode for executable with an iocount. Must be released with vnode_put() */ vnode_t proc_getexecutablevnode(proc_t p) { vnode_t tvp = p->p_textvp; if (tvp != NULLVP) { if (vnode_getwithref(tvp) == 0) { return tvp; } } return NULLVP; } /* * Similar to proc_getexecutablevnode() but returns NULLVP if the vnode is * being reclaimed rather than blocks until reclaim is done. */ vnode_t proc_getexecutablevnode_noblock(proc_t p) { vnode_t tvp = p->p_textvp; if (tvp != NULLVP) { if (vnode_getwithref_noblock(tvp) == 0) { return tvp; } } return NULLVP; } int proc_gettty(proc_t p, vnode_t *vp) { struct session *procsp; struct pgrp *pg; int err = EINVAL; if (!p || !vp) { return EINVAL; } if ((pg = proc_pgrp(p, &procsp)) != PGRP_NULL) { session_lock(procsp); vnode_t ttyvp = procsp->s_ttyvp; int ttyvid = procsp->s_ttyvid; if (ttyvp) { vnode_hold(ttyvp); } session_unlock(procsp); if (ttyvp) { if (vnode_getwithvid(ttyvp, ttyvid) == 0) { *vp = ttyvp; err = 0; } vnode_drop(ttyvp); } else { err = ENOENT; } pgrp_rele(pg); } return err; } int proc_gettty_dev(proc_t p, dev_t *devp) { struct pgrp *pg; dev_t dev = NODEV; if ((pg = proc_pgrp(p, NULL)) != PGRP_NULL) { dev = os_atomic_load(&pg->pg_session->s_ttydev, relaxed); pgrp_rele(pg); } if (dev == NODEV) { return EINVAL; } *devp = dev; return 0; } int proc_selfexecutableargs(uint8_t *buf, size_t *buflen) { proc_t p = current_proc(); // buflen must always be provided if (buflen == NULL) { return EINVAL; } // If a buf is provided, there must be at least enough room to fit argc if (buf && *buflen < sizeof(p->p_argc)) { return EINVAL; } if (!p->user_stack) { return EINVAL; } if (buf == NULL) { *buflen = p->p_argslen + sizeof(p->p_argc); return 0; } // Copy in argc to the first 4 bytes memcpy(buf, &p->p_argc, sizeof(p->p_argc)); if (*buflen > sizeof(p->p_argc) && p->p_argslen > 0) { // See memory layout comment in kern_exec.c:exec_copyout_strings() // We want to copy starting from `p_argslen` bytes away from top of stack return copyin(p->user_stack - p->p_argslen, buf + sizeof(p->p_argc), MIN(p->p_argslen, *buflen - sizeof(p->p_argc))); } else { return 0; } } off_t proc_getexecutableoffset(proc_t p) { return p->p_textoff; } void bsd_set_dependency_capable(task_t task) { proc_t p = get_bsdtask_info(task); if (p) { OSBitOrAtomic(P_DEPENDENCY_CAPABLE, &p->p_flag); } } #ifndef __arm__ int IS_64BIT_PROCESS(proc_t p) { if (p && (p->p_flag & P_LP64)) { return 1; } else { return 0; } } #endif SMRH_TRAITS_DEFINE_SCALAR(pid_hash_traits, struct proc, p_pid, p_hash, .domain = &smr_proc_task); /* * Locate a process by number */ proc_t phash_find_locked(pid_t pid) { smrh_key_t key = SMRH_SCALAR_KEY(pid); LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); if (!pid) { return kernproc; } return smr_hash_serialized_find(&pid_hash, key, &pid_hash_traits); } void phash_replace_locked(struct proc *old_proc, struct proc *new_proc) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); smr_hash_serialized_replace(&pid_hash, &old_proc->p_hash, &new_proc->p_hash, &pid_hash_traits); } void phash_insert_locked(struct proc *p) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); smr_hash_serialized_insert(&pid_hash, &p->p_hash, &pid_hash_traits); } void phash_remove_locked(struct proc *p) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); smr_hash_serialized_remove(&pid_hash, &p->p_hash, &pid_hash_traits); } proc_t proc_find_noref_smr(int pid) { smrh_key_t key = SMRH_SCALAR_KEY(pid); if (__improbable(pid == 0)) { return kernproc; } return smr_hash_entered_find(&pid_hash, key, &pid_hash_traits); } proc_t proc_find(int pid) { smrh_key_t key = SMRH_SCALAR_KEY(pid); proc_t p; uint32_t bits; bool shadow_proc = false; LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_NOTOWNED); if (!pid) { return proc_ref(kernproc, false); } retry: p = PROC_NULL; bits = 0; shadow_proc = false; smr_proc_task_enter(); p = smr_hash_entered_find(&pid_hash, key, &pid_hash_traits); if (p) { bits = proc_ref_try_fast(p); shadow_proc = !!proc_is_shadow(p); } smr_proc_task_leave(); /* Retry if the proc is a shadow proc */ if (shadow_proc) { if (bits) { proc_rele(p); } goto retry; } if (__improbable(!bits)) { return PROC_NULL; } if (__improbable(proc_ref_needs_wait_for_exec(bits))) { p = proc_ref_wait_for_exec(p, bits, false); /* * Retry if exec was successful since the old proc * would have become a shadow proc and might be in * middle of exiting. */ if (p == PROC_NULL || proc_is_shadow(p)) { if (p != PROC_NULL) { proc_rele(p); } goto retry; } } return p; } proc_t proc_find_locked(int pid) { proc_t p = PROC_NULL; retry: p = phash_find_locked(pid); if (p != PROC_NULL) { uint32_t bits; assert(!proc_is_shadow(p)); bits = proc_ref_try_fast(p); if (__improbable(!bits)) { return PROC_NULL; } if (__improbable(proc_ref_needs_wait_for_exec(bits))) { p = proc_ref_wait_for_exec(p, bits, true); /* * Retry if exec was successful since the old proc * would have become a shadow proc and might be in * middle of exiting. */ if (p == PROC_NULL || proc_is_shadow(p)) { if (p != PROC_NULL) { proc_rele(p); } goto retry; } } } return p; } proc_t proc_findthread(thread_t thread) { proc_t p = PROC_NULL; proc_list_lock(); { p = (proc_t)(get_bsdthreadtask_info(thread)); } p = proc_ref(p, true); proc_list_unlock(); return p; } /* * Determine if the process described by the provided * PID is a zombie */ __private_extern__ bool pzfind(pid_t pid) { bool found = false; /* Enter critical section */ proc_list_lock(); /* Ensure the proc exists and is a zombie */ proc_t p = phash_find_locked(pid); if ((p == PROC_NULL) || !proc_list_exited(p)) { goto out; } found = true; out: /* Exit critical section */ proc_list_unlock(); return found; } /* * Determine if the process described by the provided * uniqueid is a zombie. The same as pzfind but with an * additional uniqueid check. */ __private_extern__ bool pzfind_unique(pid_t pid, uint64_t uniqueid) { bool found = false; /* Enter critical section */ proc_list_lock(); /* Ensure the proc exists and is a zombie */ proc_t p = phash_find_locked(pid); if ((p == PROC_NULL) || !proc_list_exited(p)) { goto out; } if (proc_uniqueid(p) != uniqueid) { goto out; } found = true; out: /* Exit critical section */ proc_list_unlock(); return found; } /* * Acquire a pgrp ref, if and only if the pgrp is non empty. */ static inline bool pg_ref_try(struct pgrp *pgrp) { return os_ref_retain_try_mask(&pgrp->pg_refcount, PGRP_REF_BITS, PGRP_REF_EMPTY, &p_refgrp); } static bool pgrp_hash_obj_try_get(void *pgrp) { return pg_ref_try(pgrp); } /* * Unconditionally acquire a pgrp ref, * regardless of whether the pgrp is empty or not. */ static inline struct pgrp * pg_ref(struct pgrp *pgrp) { os_ref_retain_mask(&pgrp->pg_refcount, PGRP_REF_BITS, &p_refgrp); return pgrp; } SMRH_TRAITS_DEFINE_SCALAR(pgrp_hash_traits, struct pgrp, pg_id, pg_hash, .domain = &smr_proc_task, .obj_try_get = pgrp_hash_obj_try_get); /* * Locate a process group by number */ bool pghash_exists_locked(pid_t pgid) { smrh_key_t key = SMRH_SCALAR_KEY(pgid); LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); return smr_hash_serialized_find(&pgrp_hash, key, &pgrp_hash_traits); } void pghash_insert_locked(struct pgrp *pgrp) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); smr_hash_serialized_insert(&pgrp_hash, &pgrp->pg_hash, &pgrp_hash_traits); } static void pghash_remove_locked(struct pgrp *pgrp) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); smr_hash_serialized_remove(&pgrp_hash, &pgrp->pg_hash, &pgrp_hash_traits); } struct pgrp * pgrp_find(pid_t pgid) { smrh_key_t key = SMRH_SCALAR_KEY(pgid); LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_NOTOWNED); return smr_hash_get(&pgrp_hash, key, &pgrp_hash_traits); } /* consumes one ref from pgrp */ static void pgrp_add_member(struct pgrp *pgrp, struct proc *parent, struct proc *p) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); pgrp_lock(pgrp); if (LIST_EMPTY(&pgrp->pg_members)) { os_atomic_andnot(&pgrp->pg_refcount, PGRP_REF_EMPTY, relaxed); } if (parent != PROC_NULL) { assert(pgrp == smr_serialized_load(&parent->p_pgrp)); } LIST_INSERT_HEAD(&pgrp->pg_members, p, p_pglist); pgrp_unlock(pgrp); p->p_pgrpid = pgrp->pg_id; p->p_sessionid = pgrp->pg_session->s_sid; smr_serialized_store(&p->p_pgrp, pgrp); } /* returns one ref from pgrp */ static void pgrp_del_member(struct pgrp *pgrp, struct proc *p) { LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); pgrp_lock(pgrp); LIST_REMOVE(p, p_pglist); if (LIST_EMPTY(&pgrp->pg_members)) { os_atomic_or(&pgrp->pg_refcount, PGRP_REF_EMPTY, relaxed); } pgrp_unlock(pgrp); } void pgrp_rele(struct pgrp * pgrp) { if (pgrp == PGRP_NULL) { return; } if (os_ref_release_mask(&pgrp->pg_refcount, PGRP_REF_BITS, &p_refgrp) == 0) { pgrp_destroy(pgrp); } } struct session * session_alloc(proc_t leader) { struct session *sess; sess = zalloc_flags(session_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL); lck_mtx_init(&sess->s_mlock, &proc_mlock_grp, &proc_lck_attr); sess->s_leader = leader; sess->s_sid = proc_getpid(leader); sess->s_ttypgrpid = NO_PID; os_atomic_init(&sess->s_ttydev, NODEV); os_ref_init_mask(&sess->s_refcount, SESSION_REF_BITS, &p_refgrp, S_DEFAULT); return sess; } struct tty * session_set_tty_locked(struct session *sessp, struct tty *tp) { struct tty *old; LCK_MTX_ASSERT(&sessp->s_mlock, LCK_MTX_ASSERT_OWNED); old = sessp->s_ttyp; ttyhold(tp); sessp->s_ttyp = tp; os_atomic_store(&sessp->s_ttydev, tp->t_dev, relaxed); return old; } struct tty * session_clear_tty_locked(struct session *sessp) { struct tty *tp = sessp->s_ttyp; LCK_MTX_ASSERT(&sessp->s_mlock, LCK_MTX_ASSERT_OWNED); sessp->s_ttyvp = NULLVP; sessp->s_ttyvid = 0; sessp->s_ttyp = TTY_NULL; sessp->s_ttypgrpid = NO_PID; os_atomic_store(&sessp->s_ttydev, NODEV, relaxed); return tp; } __attribute__((noinline)) static void session_destroy(struct session *sess) { proc_list_lock(); LIST_REMOVE(sess, s_hash); proc_list_unlock(); /* * Either the TTY was closed, * or proc_exit() destroyed it when the leader went away */ assert(sess->s_ttyp == TTY_NULL); lck_mtx_destroy(&sess->s_mlock, &proc_mlock_grp); zfree(session_zone, sess); } struct session * session_ref(struct session *sess) { os_ref_retain_mask(&sess->s_refcount, SESSION_REF_BITS, &p_refgrp); return sess; } void session_rele(struct session *sess) { if (os_ref_release_mask(&sess->s_refcount, SESSION_REF_BITS, &p_refgrp) == 0) { session_destroy(sess); } } /* * Make a new process ready to become a useful member of society by making it * visible in all the right places and initialize its own lists to empty. * * Parameters: parent The parent of the process to insert * child The child process to insert * in_exec The child process is in exec * * Returns: (void) * * Notes: Insert a child process into the parents children list, assign * the child the parent process pointer and PPID of the parent... */ void pinsertchild(proc_t parent, proc_t child, bool in_exec) { LIST_INIT(&child->p_children); proc_t sibling = parent; /* For exec case, new proc is not a child of old proc, but its replacement */ if (in_exec) { parent = proc_parent(parent); assert(parent != PROC_NULL); /* Copy the ptrace flags from sibling */ proc_lock(sibling); child->p_oppid = sibling->p_oppid; child->p_lflag |= (sibling->p_lflag & (P_LTRACED | P_LSIGEXC | P_LNOATTACH)); proc_unlock(sibling); } proc_list_lock(); child->p_pptr = parent; child->p_ppid = proc_getpid(parent); child->p_puniqueid = proc_uniqueid(parent); child->p_xhighbits = 0; #if CONFIG_MEMORYSTATUS memorystatus_add(child, TRUE); #endif /* If the parent is initproc and p_orig_ppid is not 1, then set reparent flag */ if (in_exec && parent == initproc && proc_original_ppid(child) != 1) { child->p_listflag |= P_LIST_DEADPARENT; } parent->p_childrencnt++; LIST_INSERT_HEAD(&parent->p_children, child, p_sibling); LIST_INSERT_HEAD(&allproc, child, p_list); /* mark the completion of proc creation */ os_atomic_andnot(&child->p_refcount, P_REF_NEW, relaxed); proc_list_unlock(); if (in_exec) { proc_rele(parent); } } /* * Reparent all children of old proc to new proc. * * Parameters: old process Old process. * new process New process. * * Returns: None. */ void p_reparentallchildren(proc_t old_proc, proc_t new_proc) { proc_t child; LIST_INIT(&new_proc->p_children); /* Wait for parent ref to drop */ proc_childdrainstart(old_proc); /* Reparent child from old proc to new proc */ while ((child = old_proc->p_children.lh_first) != NULL) { LIST_REMOVE(child, p_sibling); old_proc->p_childrencnt--; child->p_pptr = new_proc; LIST_INSERT_HEAD(&new_proc->p_children, child, p_sibling); new_proc->p_childrencnt++; } new_proc->si_pid = old_proc->si_pid; new_proc->si_status = old_proc->si_status; new_proc->si_code = old_proc->si_code; new_proc->si_uid = old_proc->si_uid; proc_childdrainend(old_proc); } /* * Move p to a new or existing process group (and session) * * Returns: 0 Success * ESRCH No such process */ int enterpgrp(proc_t p, pid_t pgid, int mksess) { struct pgrp *pgrp; struct pgrp *mypgrp; struct session *procsp; pgrp = pgrp_find(pgid); mypgrp = proc_pgrp(p, &procsp); #if DIAGNOSTIC if (pgrp != NULL && mksess) { /* firewalls */ panic("enterpgrp: setsid into non-empty pgrp"); } if (SESS_LEADER(p, mypgrp->pg_session)) { panic("enterpgrp: session leader attempted setpgrp"); } #endif if (pgrp == PGRP_NULL) { struct session *sess; pid_t savepid = proc_getpid(p); proc_t np = PROC_NULL; /* * new process group */ #if DIAGNOSTIC if (proc_getpid(p) != pgid) { panic("enterpgrp: new pgrp and pid != pgid"); } #endif if ((np = proc_find(savepid)) == NULL || np != p) { if (np != PROC_NULL) { proc_rele(np); } pgrp_rele(mypgrp); return ESRCH; } proc_rele(np); pgrp = pgrp_alloc(pgid, PGRP_REF_EMPTY); if (mksess) { /* * new session */ sess = session_alloc(p); bcopy(mypgrp->pg_session->s_login, sess->s_login, sizeof(sess->s_login)); os_atomic_andnot(&p->p_flag, P_CONTROLT, relaxed); } else { sess = session_ref(procsp); } proc_list_lock(); pgrp->pg_session = sess; p->p_sessionid = sess->s_sid; pghash_insert_locked(pgrp); if (mksess) { LIST_INSERT_HEAD(SESSHASH(sess->s_sid), sess, s_hash); } proc_list_unlock(); } else if (pgrp == mypgrp) { pgrp_rele(pgrp); pgrp_rele(mypgrp); return 0; } /* * Adjust eligibility of affected pgrps to participate in job control. * Increment eligibility counts before decrementing, otherwise we * could reach 0 spuriously during the first call. */ fixjobc(p, pgrp, 1); fixjobc(p, mypgrp, 0); pgrp_rele(mypgrp); pgrp_replace(p, pgrp); return 0; } /* * remove process from process group */ struct pgrp * pgrp_leave_locked(proc_t p) { struct pgrp *pg; LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); pg = smr_serialized_load(&p->p_pgrp); pgrp_del_member(pg, p); p->p_pgrpid = PGRPID_DEAD; smr_clear_store(&p->p_pgrp); return pg; } struct pgrp * pgrp_enter_locked(struct proc *parent, struct proc *child) { struct pgrp *pgrp; LCK_MTX_ASSERT(&proc_list_mlock, LCK_MTX_ASSERT_OWNED); pgrp = pg_ref(smr_serialized_load(&parent->p_pgrp)); pgrp_add_member(pgrp, parent, child); return pgrp; } /* * delete a process group */ static void pgrp_free(smr_node_t node) { struct pgrp *pgrp = __container_of(node, struct pgrp, pg_smr_node); zfree(pgrp_zone, pgrp); } __attribute__((noinline)) static void pgrp_destroy(struct pgrp *pgrp) { struct session *sess; assert(LIST_EMPTY(&pgrp->pg_members)); assert(os_ref_get_raw_mask(&pgrp->pg_refcount) & PGRP_REF_EMPTY); proc_list_lock(); pghash_remove_locked(pgrp); proc_list_unlock(); sess = pgrp->pg_session; pgrp->pg_session = SESSION_NULL; session_rele(sess); lck_mtx_destroy(&pgrp->pg_mlock, &proc_mlock_grp); if (os_ref_release_raw(&pgrp->pg_hashref, &p_refgrp) == 0) { smr_proc_task_call(&pgrp->pg_smr_node, sizeof(*pgrp), pgrp_free); } } /* * Adjust pgrp jobc counters when specified process changes process group. * We count the number of processes in each process group that "qualify" * the group for terminal job control (those with a parent in a different * process group of the same session). If that count reaches zero, the * process group becomes orphaned. Check both the specified process' * process group and that of its children. * entering == 0 => p is leaving specified group. * entering == 1 => p is entering specified group. */ int fixjob_callback(proc_t p, void * arg) { struct fixjob_iterargs *fp; struct pgrp * pg, *hispg; struct session * mysession, *hissess; int entering; fp = (struct fixjob_iterargs *)arg; pg = fp->pg; mysession = fp->mysession; entering = fp->entering; hispg = proc_pgrp(p, &hissess); if (hispg != pg && hissess == mysession) { pgrp_lock(hispg); if (entering) { hispg->pg_jobc++; pgrp_unlock(hispg); } else if (--hispg->pg_jobc == 0) { pgrp_unlock(hispg); orphanpg(hispg); } else { pgrp_unlock(hispg); } } pgrp_rele(hispg); return PROC_RETURNED; } void fixjobc(proc_t p, struct pgrp *pgrp, int entering) { struct pgrp *hispgrp = PGRP_NULL; struct session *hissess = SESSION_NULL; struct session *mysession = pgrp->pg_session; proc_t parent; struct fixjob_iterargs fjarg; boolean_t proc_parent_self; /* * Check if p's parent is current proc, if yes then no need to take * a ref; calling proc_parent with current proc as parent may * deadlock if current proc is exiting. */ proc_parent_self = proc_parent_is_currentproc(p); if (proc_parent_self) { parent = current_proc(); } else { parent = proc_parent(p); } if (parent != PROC_NULL) { hispgrp = proc_pgrp(parent, &hissess); if (!proc_parent_self) { proc_rele(parent); } } /* * Check p's parent to see whether p qualifies its own process * group; if so, adjust count for p's process group. */ if (hispgrp != pgrp && hissess == mysession) { pgrp_lock(pgrp); if (entering) { pgrp->pg_jobc++; pgrp_unlock(pgrp); } else if (--pgrp->pg_jobc == 0) { pgrp_unlock(pgrp); orphanpg(pgrp); } else { pgrp_unlock(pgrp); } } pgrp_rele(hispgrp); /* * Check this process' children to see whether they qualify * their process groups; if so, adjust counts for children's * process groups. */ fjarg.pg = pgrp; fjarg.mysession = mysession; fjarg.entering = entering; proc_childrenwalk(p, fixjob_callback, &fjarg); } /* * The pidlist_* routines support the functions in this file that * walk lists of processes applying filters and callouts to the * elements of the list. * * A prior implementation used a single linear array, which can be * tricky to allocate on large systems. This implementation creates * an SLIST of modestly sized arrays of PIDS_PER_ENTRY elements. * * The array should be sized large enough to keep the overhead of * walking the list low, but small enough that blocking allocations of * pidlist_entry_t structures always succeed. */ #define PIDS_PER_ENTRY 1021 typedef struct pidlist_entry { SLIST_ENTRY(pidlist_entry) pe_link; u_int pe_nused; pid_t pe_pid[PIDS_PER_ENTRY]; } pidlist_entry_t; typedef struct { SLIST_HEAD(, pidlist_entry) pl_head; struct pidlist_entry *pl_active; u_int pl_nalloc; } pidlist_t; static __inline__ pidlist_t * pidlist_init(pidlist_t *pl) { SLIST_INIT(&pl->pl_head); pl->pl_active = NULL; pl->pl_nalloc = 0; return pl; } static u_int pidlist_alloc(pidlist_t *pl, u_int needed) { while (pl->pl_nalloc < needed) { pidlist_entry_t *pe = kalloc_type(pidlist_entry_t, Z_WAITOK | Z_ZERO | Z_NOFAIL); SLIST_INSERT_HEAD(&pl->pl_head, pe, pe_link); pl->pl_nalloc += (sizeof(pe->pe_pid) / sizeof(pe->pe_pid[0])); } return pl->pl_nalloc; } static void pidlist_free(pidlist_t *pl) { pidlist_entry_t *pe; while (NULL != (pe = SLIST_FIRST(&pl->pl_head))) { SLIST_FIRST(&pl->pl_head) = SLIST_NEXT(pe, pe_link); kfree_type(pidlist_entry_t, pe); } pl->pl_nalloc = 0; } static __inline__ void pidlist_set_active(pidlist_t *pl) { pl->pl_active = SLIST_FIRST(&pl->pl_head); assert(pl->pl_active); } static void pidlist_add_pid(pidlist_t *pl, pid_t pid) { pidlist_entry_t *pe = pl->pl_active; if (pe->pe_nused >= sizeof(pe->pe_pid) / sizeof(pe->pe_pid[0])) { if (NULL == (pe = SLIST_NEXT(pe, pe_link))) { panic("pidlist allocation exhausted"); } pl->pl_active = pe; } pe->pe_pid[pe->pe_nused++] = pid; } static __inline__ u_int pidlist_nalloc(const pidlist_t *pl) { return pl->pl_nalloc; } /* * A process group has become orphaned; if there are any stopped processes in * the group, hang-up all process in that group. */ static void orphanpg(struct pgrp *pgrp) { pidlist_t pid_list, *pl = pidlist_init(&pid_list); u_int pid_count_available = 0; proc_t p; /* allocate outside of the pgrp_lock */ for (;;) { pgrp_lock(pgrp); boolean_t should_iterate = FALSE; pid_count_available = 0; PGMEMBERS_FOREACH(pgrp, p) { pid_count_available++; if (p->p_stat == SSTOP) { should_iterate = TRUE; } } if (pid_count_available == 0 || !should_iterate) { pgrp_unlock(pgrp); goto out; /* no orphaned processes OR nothing stopped */ } if (pidlist_nalloc(pl) >= pid_count_available) { break; } pgrp_unlock(pgrp); pidlist_alloc(pl, pid_count_available); } pidlist_set_active(pl); u_int pid_count = 0; PGMEMBERS_FOREACH(pgrp, p) { pidlist_add_pid(pl, proc_pid(p)); if (++pid_count >= pid_count_available) { break; } } pgrp_unlock(pgrp); const pidlist_entry_t *pe; SLIST_FOREACH(pe, &(pl->pl_head), pe_link) { for (u_int i = 0; i < pe->pe_nused; i++) { const pid_t pid = pe->pe_pid[i]; if (0 == pid) { continue; /* skip kernproc */ } p = proc_find(pid); if (!p) { continue; } proc_transwait(p, 0); pt_setrunnable(p); psignal(p, SIGHUP); psignal(p, SIGCONT); proc_rele(p); } } out: pidlist_free(pl); } boolean_t proc_is_translated(proc_t p) { return p && ((p->p_flag & P_TRANSLATED) != 0); } int proc_is_classic(proc_t p __unused) { return 0; } bool proc_is_exotic( proc_t p) { if (p == NULL) { return false; } return task_is_exotic(proc_task(p)); } bool proc_is_alien( proc_t p) { if (p == NULL) { return false; } return task_is_alien(proc_task(p)); } bool proc_is_driver(proc_t p) { if (p == NULL) { return false; } return task_is_driver(proc_task(p)); } bool proc_is_third_party_debuggable_driver(proc_t p) { #if XNU_TARGET_OS_IOS uint64_t csflags; if (proc_csflags(p, &csflags) != 0) { return false; } if (proc_is_driver(p) && !csproc_get_platform_binary(p) && IOTaskHasEntitlement(proc_task(p), kIODriverKitEntitlementKey) && (csflags & CS_GET_TASK_ALLOW) != 0) { return true; } return false; #else /* On other platforms, fall back to existing rules for debugging */ (void)p; return false; #endif /* XNU_TARGET_OS_IOS */ } /* XXX Why does this function exist? Need to kill it off... */ proc_t current_proc_EXTERNAL(void) { return current_proc(); } int proc_is_forcing_hfs_case_sensitivity(proc_t p) { return (p->p_vfs_iopolicy & P_VFS_IOPOLICY_FORCE_HFS_CASE_SENSITIVITY) ? 1 : 0; } bool proc_ignores_content_protection(proc_t p) { return os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_IGNORE_CONTENT_PROTECTION; } bool proc_ignores_node_permissions(proc_t p) { return os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_IGNORE_NODE_PERMISSIONS; } bool proc_skip_mtime_update(proc_t p) { struct uthread *ut = NULL; /* * We only check the thread's policy if the current proc matches the given * proc. */ if (current_proc() == p) { ut = get_bsdthread_info(current_thread()); } if (ut && (os_atomic_load(&ut->uu_flag, relaxed) & UT_SKIP_MTIME_UPDATE)) { return true; } /* * If the 'UT_SKIP_MTIME_UPDATE_IGNORE' policy is set for this thread then * we override the default behavior and ignore the process's mtime update * policy. */ if (ut && (os_atomic_load(&ut->uu_flag, relaxed) & UT_SKIP_MTIME_UPDATE_IGNORE)) { return false; } if (p && (os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_SKIP_MTIME_UPDATE)) { return true; } return false; } bool proc_support_long_paths(proc_t p) { struct uthread *ut = NULL; /* * We only check the thread's policy if the current proc matches the given * proc. */ if (current_proc() == p) { ut = get_bsdthread_info(current_thread()); } if (ut != NULL && (os_atomic_load(&ut->uu_flag, relaxed) & UT_SUPPORT_LONG_PATHS)) { return true; } if (p && (os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_SUPPORT_LONG_PATHS)) { return true; } return false; } bool proc_allow_low_space_writes(proc_t p) { return os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_ALLOW_LOW_SPACE_WRITES; } bool proc_disallow_rw_for_o_evtonly(proc_t p) { return os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_DISALLOW_RW_FOR_O_EVTONLY; } bool proc_use_alternative_symlink_ea(proc_t p) { return os_atomic_load(&p->p_vfs_iopolicy, relaxed) & P_VFS_IOPOLICY_ALTLINK; } bool proc_is_rsr(proc_t p) { return os_atomic_load(&p->p_ladvflag, relaxed) & P_RSR; } #if CONFIG_COREDUMP || CONFIG_UCOREDUMP /* * proc_core_name(format, name, uid, pid) * Expand the name described in format, using name, uid, and pid. * format is a printf-like string, with four format specifiers: * %N name of process ("name") * %P process id (pid) * %U user id (uid) * %T mach_continuous_time() timestamp * For example, "%N.core" is the default; they can be disabled completely * by using "/dev/null", or all core files can be stored in "/cores/%U/%N-%P". * This is controlled by the sysctl variable kern.corefile (see above). */ __private_extern__ int proc_core_name(const char *format, const char * name, uid_t uid, pid_t pid, char *cf_name, size_t cf_name_len) { const char *appendstr; char id_buf[sizeof(OS_STRINGIFY(INT32_MAX))]; /* Buffer for pid/uid -- max 4B */ _Static_assert(sizeof(id_buf) == 11, "size mismatch"); char timestamp_buf[sizeof(OS_STRINGIFY(UINT64_MAX))]; /* Buffer for timestamp, including null terminator */ clock_sec_t secs = 0; _Static_assert(sizeof(clock_sec_t) <= sizeof(uint64_t), "size mismatch"); clock_usec_t microsecs = 0; size_t i, l, n; if (cf_name == NULL) { goto toolong; } for (i = 0, n = 0; n < cf_name_len && format[i]; i++) { switch (format[i]) { case '%': /* Format character */ i++; switch (format[i]) { case '%': appendstr = "%"; break; case 'N': /* process name */ appendstr = name; break; case 'P': /* process id */ snprintf(id_buf, sizeof(id_buf), "%u", pid); appendstr = id_buf; break; case 'U': /* user id */ snprintf(id_buf, sizeof(id_buf), "%u", uid); appendstr = id_buf; break; case 'T': /* MCT timestamp */ snprintf(timestamp_buf, sizeof(timestamp_buf), "%llu", mach_continuous_time()); appendstr = timestamp_buf; break; case 't': /* Unix timestamp */ clock_gettimeofday(&secs, µsecs); snprintf(timestamp_buf, sizeof(timestamp_buf), "%lu", secs); appendstr = timestamp_buf; break; case '\0': /* format string ended in % symbol */ goto endofstring; default: appendstr = ""; log(LOG_ERR, "Unknown format character %c in `%s'\n", format[i], format); } l = strlen(appendstr); if ((n + l) >= cf_name_len) { goto toolong; } bcopy(appendstr, cf_name + n, l); n += l; break; default: cf_name[n++] = format[i]; } } if (format[i] != '\0') { goto toolong; } return 0; toolong: log(LOG_ERR, "pid %ld (%s), uid (%u): corename is too long\n", (long)pid, name, (uint32_t)uid); return 1; endofstring: log(LOG_ERR, "pid %ld (%s), uid (%u): unexpected end of string after %% token\n", (long)pid, name, (uint32_t)uid); return 1; } #endif /* CONFIG_COREDUMP || CONFIG_UCOREDUMP */ /* Code Signing related routines */ int csops(__unused proc_t p, struct csops_args *uap, __unused int32_t *retval) { return csops_internal(uap->pid, uap->ops, uap->useraddr, uap->usersize, USER_ADDR_NULL); } int csops_audittoken(__unused proc_t p, struct csops_audittoken_args *uap, __unused int32_t *retval) { if (uap->uaudittoken == USER_ADDR_NULL) { return EINVAL; } return csops_internal(uap->pid, uap->ops, uap->useraddr, uap->usersize, uap->uaudittoken); } static int csops_copy_token(const void *start, size_t length, user_size_t usize, user_addr_t uaddr) { char fakeheader[8] = { 0 }; int error; if (usize < sizeof(fakeheader)) { return ERANGE; } /* if no blob, fill in zero header */ if (NULL == start) { start = fakeheader; length = sizeof(fakeheader); } else if (usize < length) { /* ... if input too short, copy out length of entitlement */ uint32_t length32 = htonl((uint32_t)length); memcpy(&fakeheader[4], &length32, sizeof(length32)); error = copyout(fakeheader, uaddr, sizeof(fakeheader)); if (error == 0) { return ERANGE; /* input buffer to short, ERANGE signals that */ } return error; } return copyout(start, uaddr, length); } static int csops_internal(pid_t pid, int ops, user_addr_t uaddr, user_size_t usersize, user_addr_t uaudittoken) { size_t usize = (size_t)CAST_DOWN(size_t, usersize); proc_t pt; int forself; int error; vnode_t tvp; off_t toff; csops_cdhash_t cdhash_info = {0}; audit_token_t token; unsigned int upid = 0, uidversion = 0; bool mark_invalid_allowed = false; forself = error = 0; if (pid == 0) { pid = proc_selfpid(); } if (pid == proc_selfpid()) { forself = 1; mark_invalid_allowed = true; } switch (ops) { case CS_OPS_STATUS: case CS_OPS_CDHASH: case CS_OPS_CDHASH_WITH_INFO: case CS_OPS_PIDOFFSET: case CS_OPS_ENTITLEMENTS_BLOB: case CS_OPS_DER_ENTITLEMENTS_BLOB: case CS_OPS_IDENTITY: case CS_OPS_BLOB: case CS_OPS_TEAMID: case CS_OPS_CLEAR_LV: case CS_OPS_VALIDATION_CATEGORY: break; /* not restricted to root */ default: if (forself == 0 && kauth_cred_issuser(kauth_cred_get()) != TRUE) { return EPERM; } break; } pt = proc_find(pid); if (pt == PROC_NULL) { return ESRCH; } upid = proc_getpid(pt); uidversion = proc_pidversion(pt); if (uaudittoken != USER_ADDR_NULL) { error = copyin(uaudittoken, &token, sizeof(audit_token_t)); if (error != 0) { goto out; } /* verify the audit token pid/idversion matches with proc */ if ((token.val[5] != upid) || (token.val[7] != uidversion)) { error = ESRCH; goto out; } } #if CONFIG_MACF switch (ops) { case CS_OPS_MARKINVALID: case CS_OPS_MARKHARD: case CS_OPS_MARKKILL: case CS_OPS_MARKRESTRICT: case CS_OPS_SET_STATUS: case CS_OPS_CLEARINSTALLER: case CS_OPS_CLEARPLATFORM: case CS_OPS_CLEAR_LV: if ((error = mac_proc_check_set_cs_info(current_proc(), pt, ops))) { goto out; } break; default: if ((error = mac_proc_check_get_cs_info(current_proc(), pt, ops))) { goto out; } } #endif switch (ops) { case CS_OPS_STATUS: { uint32_t retflags; proc_lock(pt); retflags = (uint32_t)proc_getcsflags(pt); if (cs_process_enforcement(pt)) { retflags |= CS_ENFORCEMENT; } if (csproc_get_platform_binary(pt)) { retflags |= CS_PLATFORM_BINARY; } if (csproc_get_platform_path(pt)) { retflags |= CS_PLATFORM_PATH; } //Don't return CS_REQUIRE_LV if we turned it on with CS_FORCED_LV but still report CS_FORCED_LV if ((proc_getcsflags(pt) & CS_FORCED_LV) == CS_FORCED_LV) { retflags &= (~CS_REQUIRE_LV); } proc_unlock(pt); if (uaddr != USER_ADDR_NULL) { error = copyout(&retflags, uaddr, sizeof(uint32_t)); } break; } case CS_OPS_MARKINVALID: if (mark_invalid_allowed == false) { error = EPERM; goto out; } proc_lock(pt); if ((proc_getcsflags(pt) & CS_VALID) == CS_VALID) { /* is currently valid */ proc_csflags_clear(pt, CS_VALID); /* set invalid */ cs_process_invalidated(pt); if ((proc_getcsflags(pt) & CS_KILL) == CS_KILL) { proc_csflags_set(pt, CS_KILLED); proc_unlock(pt); if (cs_debug) { printf("CODE SIGNING: marked invalid by pid %d: " "p=%d[%s] honoring CS_KILL, final status 0x%x\n", proc_selfpid(), proc_getpid(pt), pt->p_comm, (unsigned int)proc_getcsflags(pt)); } psignal(pt, SIGKILL); } else { proc_unlock(pt); } } else { proc_unlock(pt); } break; case CS_OPS_MARKHARD: proc_lock(pt); proc_csflags_set(pt, CS_HARD); if ((proc_getcsflags(pt) & CS_VALID) == 0) { /* @@@ allow? reject? kill? @@@ */ proc_unlock(pt); error = EINVAL; goto out; } else { proc_unlock(pt); } break; case CS_OPS_MARKKILL: proc_lock(pt); proc_csflags_set(pt, CS_KILL); if ((proc_getcsflags(pt) & CS_VALID) == 0) { proc_unlock(pt); psignal(pt, SIGKILL); } else { proc_unlock(pt); } break; case CS_OPS_PIDOFFSET: toff = pt->p_textoff; proc_rele(pt); error = copyout(&toff, uaddr, sizeof(toff)); return error; case CS_OPS_CDHASH: /* pt already holds a reference on its p_textvp */ tvp = pt->p_textvp; toff = pt->p_textoff; if (tvp == NULLVP || usize != sizeof(cdhash_info.hash)) { proc_rele(pt); return EINVAL; } error = vn_getcdhash(tvp, toff, cdhash_info.hash, &cdhash_info.type); proc_rele(pt); if (error == 0) { error = copyout(cdhash_info.hash, uaddr, sizeof(cdhash_info.hash)); } return error; case CS_OPS_CDHASH_WITH_INFO: /* pt already holds a reference on its p_textvp */ tvp = pt->p_textvp; toff = pt->p_textoff; if (tvp == NULLVP || usize != sizeof(csops_cdhash_t)) { proc_rele(pt); return EINVAL; } error = vn_getcdhash(tvp, toff, cdhash_info.hash, &cdhash_info.type); proc_rele(pt); if (error == 0) { error = copyout(&cdhash_info, uaddr, sizeof(cdhash_info)); } return error; case CS_OPS_ENTITLEMENTS_BLOB: { void *start; size_t length; struct cs_blob* blob; proc_lock(pt); if ((proc_getcsflags(pt) & (CS_VALID | CS_DEBUGGED)) == 0) { proc_unlock(pt); error = EINVAL; goto out; } blob = csproc_get_blob(pt); proc_unlock(pt); if (!blob) { error = EBADEXEC; goto out; } void* osent = csblob_os_entitlements_get(blob); if (!osent) { goto out; } CS_GenericBlob* xmlblob = NULL; if (amfi->OSEntitlements_get_xml(osent, &xmlblob)) { start = (void*)xmlblob; length = (size_t)ntohl(xmlblob->length); } else { goto out; } error = csops_copy_token(start, length, usize, uaddr); kfree_data(start, length); goto out; } case CS_OPS_DER_ENTITLEMENTS_BLOB: { const void *start; size_t length; struct cs_blob* blob; proc_lock(pt); if ((proc_getcsflags(pt) & (CS_VALID | CS_DEBUGGED)) == 0) { proc_unlock(pt); error = EINVAL; goto out; } blob = csproc_get_blob(pt); proc_unlock(pt); if (!blob) { error = EBADEXEC; goto out; } error = csblob_get_der_entitlements(blob, (const CS_GenericBlob **)&start, &length); if (error || start == NULL) { if (amfi && csblob_os_entitlements_get(blob)) { void* osent = csblob_os_entitlements_get(blob); const CS_GenericBlob* transmuted = NULL; if (amfi->OSEntitlements_get_transmuted(osent, &transmuted)) { start = transmuted; length = (size_t)ntohl(transmuted->length); } else { goto out; } } else { goto out; } } error = csops_copy_token(start, length, usize, uaddr); goto out; } case CS_OPS_VALIDATION_CATEGORY: { unsigned int validation_category = CS_VALIDATION_CATEGORY_INVALID; error = csproc_get_validation_category(pt, &validation_category); if (error) { goto out; } error = copyout(&validation_category, uaddr, sizeof(validation_category)); break; } case CS_OPS_MARKRESTRICT: proc_lock(pt); proc_csflags_set(pt, CS_RESTRICT); proc_unlock(pt); break; case CS_OPS_SET_STATUS: { uint32_t flags; if (usize < sizeof(flags)) { error = ERANGE; break; } error = copyin(uaddr, &flags, sizeof(flags)); if (error) { break; } /* only allow setting a subset of all code sign flags */ flags &= CS_HARD | CS_EXEC_SET_HARD | CS_KILL | CS_EXEC_SET_KILL | CS_RESTRICT | CS_REQUIRE_LV | CS_ENFORCEMENT | CS_EXEC_SET_ENFORCEMENT; proc_lock(pt); if (proc_getcsflags(pt) & CS_VALID) { if ((flags & CS_ENFORCEMENT) && !(proc_getcsflags(pt) & CS_ENFORCEMENT)) { vm_map_cs_enforcement_set(get_task_map(proc_task(pt)), TRUE); } proc_csflags_set(pt, flags); } else { error = EINVAL; } proc_unlock(pt); break; } case CS_OPS_CLEAR_LV: { /* * This option is used to remove library validation from * a running process. This is used in plugin architectures * when a program needs to load untrusted libraries. This * allows the process to maintain library validation as * long as possible, then drop it only when required. * Once a process has loaded the untrusted library, * relying on library validation in the future will * not be effective. An alternative is to re-exec * your application without library validation, or * fork an untrusted child. */ #if !defined(XNU_TARGET_OS_OSX) // We only support dropping library validation on macOS error = ENOTSUP; #else /* * if we have the flag set, and the caller wants * to remove it, and they're entitled to, then * we remove it from the csflags * * NOTE: We are fine to poke into the task because * we get a ref to pt when we do the proc_find * at the beginning of this function. * * We also only allow altering ourselves. */ if (forself == 1 && IOTaskHasEntitlement(proc_task(pt), CLEAR_LV_ENTITLEMENT)) { proc_lock(pt); if (!(proc_getcsflags(pt) & CS_INSTALLER) && (pt->p_subsystem_root_path == NULL)) { proc_csflags_clear(pt, CS_REQUIRE_LV | CS_FORCED_LV); error = 0; } else { error = EPERM; } proc_unlock(pt); } else { error = EPERM; } #endif break; } case CS_OPS_BLOB: { void *start; size_t length; proc_lock(pt); if ((proc_getcsflags(pt) & (CS_VALID | CS_DEBUGGED)) == 0) { proc_unlock(pt); error = EINVAL; break; } proc_unlock(pt); // Don't need to lock here as not accessing CSFLAGS error = cs_blob_get(pt, &start, &length); if (error) { goto out; } error = csops_copy_token(start, length, usize, uaddr); goto out; } case CS_OPS_IDENTITY: case CS_OPS_TEAMID: { const char *identity; uint8_t fakeheader[8]; uint32_t idlen; size_t length; /* * Make identity have a blob header to make it * easier on userland to guess the identity * length. */ if (usize < sizeof(fakeheader)) { error = ERANGE; break; } memset(fakeheader, 0, sizeof(fakeheader)); proc_lock(pt); if ((proc_getcsflags(pt) & (CS_VALID | CS_DEBUGGED)) == 0) { proc_unlock(pt); error = EINVAL; break; } identity = ops == CS_OPS_TEAMID ? csproc_get_teamid(pt) : cs_identity_get(pt); proc_unlock(pt); if (identity == NULL) { error = ENOENT; goto out; } length = strlen(identity) + 1; /* include NUL */ idlen = htonl((uint32_t)(length + sizeof(fakeheader))); memcpy(&fakeheader[4], &idlen, sizeof(idlen)); error = copyout(fakeheader, uaddr, sizeof(fakeheader)); if (error) { goto out; } if (usize < sizeof(fakeheader) + length) { error = ERANGE; } else if (usize > sizeof(fakeheader)) { error = copyout(identity, uaddr + sizeof(fakeheader), length); } goto out; } case CS_OPS_CLEARINSTALLER: proc_lock(pt); proc_csflags_clear(pt, CS_INSTALLER | CS_DATAVAULT_CONTROLLER | CS_EXEC_INHERIT_SIP); proc_unlock(pt); break; case CS_OPS_CLEARPLATFORM: #if DEVELOPMENT || DEBUG if (cs_process_global_enforcement()) { error = ENOTSUP; break; } #if CONFIG_CSR if (csr_check(CSR_ALLOW_APPLE_INTERNAL) != 0) { error = ENOTSUP; break; } #endif /* CONFIG_CSR */ proc_lock(pt); proc_csflags_clear(pt, CS_PLATFORM_BINARY | CS_PLATFORM_PATH); csproc_clear_platform_binary(pt); proc_unlock(pt); break; #else /* DEVELOPMENT || DEBUG */ error = ENOTSUP; break; #endif /* !DEVELOPMENT || DEBUG */ default: error = EINVAL; break; } out: proc_rele(pt); return error; } void proc_iterate( unsigned int flags, proc_iterate_fn_t callout, void *arg, proc_iterate_fn_t filterfn, void *filterarg) { pidlist_t pid_list, *pl = pidlist_init(&pid_list); u_int pid_count_available = 0; assert(callout != NULL); /* allocate outside of the proc_list_lock */ for (;;) { proc_list_lock(); pid_count_available = nprocs + 1; /* kernel_task not counted in nprocs */ assert(pid_count_available > 0); if (pidlist_nalloc(pl) >= pid_count_available) { break; } proc_list_unlock(); pidlist_alloc(pl, pid_count_available); } pidlist_set_active(pl); /* filter pids into the pid_list */ u_int pid_count = 0; if (flags & PROC_ALLPROCLIST) { proc_t p; ALLPROC_FOREACH(p) { /* ignore processes that are being forked */ if (p->p_stat == SIDL || proc_is_shadow(p)) { continue; } if ((filterfn != NULL) && (filterfn(p, filterarg) == 0)) { continue; } pidlist_add_pid(pl, proc_pid(p)); if (++pid_count >= pid_count_available) { break; } } } if ((pid_count < pid_count_available) && (flags & PROC_ZOMBPROCLIST)) { proc_t p; ZOMBPROC_FOREACH(p) { if (proc_is_shadow(p)) { continue; } if ((filterfn != NULL) && (filterfn(p, filterarg) == 0)) { continue; } pidlist_add_pid(pl, proc_pid(p)); if (++pid_count >= pid_count_available) { break; } } } proc_list_unlock(); /* call callout on processes in the pid_list */ const pidlist_entry_t *pe; SLIST_FOREACH(pe, &(pl->pl_head), pe_link) { for (u_int i = 0; i < pe->pe_nused; i++) { const pid_t pid = pe->pe_pid[i]; proc_t p = proc_find(pid); if (p) { if ((flags & PROC_NOWAITTRANS) == 0) { proc_transwait(p, 0); } const int callout_ret = callout(p, arg); switch (callout_ret) { case PROC_RETURNED_DONE: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED_DONE: goto out; case PROC_RETURNED: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED: break; default: panic("%s: callout =%d for pid %d", __func__, callout_ret, pid); break; } } else if (flags & PROC_ZOMBPROCLIST) { p = proc_find_zombref(pid); if (!p) { continue; } const int callout_ret = callout(p, arg); switch (callout_ret) { case PROC_RETURNED_DONE: proc_drop_zombref(p); OS_FALLTHROUGH; case PROC_CLAIMED_DONE: goto out; case PROC_RETURNED: proc_drop_zombref(p); OS_FALLTHROUGH; case PROC_CLAIMED: break; default: panic("%s: callout =%d for zombie %d", __func__, callout_ret, pid); break; } } } } out: pidlist_free(pl); } void proc_rebootscan( proc_iterate_fn_t callout, void *arg, proc_iterate_fn_t filterfn, void *filterarg) { proc_t p; assert(callout != NULL); proc_shutdown_exitcount = 0; restart_foreach: proc_list_lock(); ALLPROC_FOREACH(p) { if ((filterfn != NULL) && filterfn(p, filterarg) == 0) { continue; } p = proc_ref(p, true); if (!p) { proc_list_unlock(); goto restart_foreach; } proc_list_unlock(); proc_transwait(p, 0); (void)callout(p, arg); proc_rele(p); goto restart_foreach; } proc_list_unlock(); } void proc_childrenwalk( proc_t parent, proc_iterate_fn_t callout, void *arg) { pidlist_t pid_list, *pl = pidlist_init(&pid_list); u_int pid_count_available = 0; assert(parent != NULL); assert(callout != NULL); for (;;) { proc_list_lock(); pid_count_available = parent->p_childrencnt; if (pid_count_available == 0) { proc_list_unlock(); goto out; } if (pidlist_nalloc(pl) >= pid_count_available) { break; } proc_list_unlock(); pidlist_alloc(pl, pid_count_available); } pidlist_set_active(pl); u_int pid_count = 0; proc_t p; PCHILDREN_FOREACH(parent, p) { if (p->p_stat == SIDL || proc_is_shadow(p)) { continue; } pidlist_add_pid(pl, proc_pid(p)); if (++pid_count >= pid_count_available) { break; } } proc_list_unlock(); const pidlist_entry_t *pe; SLIST_FOREACH(pe, &(pl->pl_head), pe_link) { for (u_int i = 0; i < pe->pe_nused; i++) { const pid_t pid = pe->pe_pid[i]; p = proc_find(pid); if (!p) { continue; } const int callout_ret = callout(p, arg); switch (callout_ret) { case PROC_RETURNED_DONE: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED_DONE: goto out; case PROC_RETURNED: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED: break; default: panic("%s: callout =%d for pid %d", __func__, callout_ret, pid); break; } } } out: pidlist_free(pl); } void pgrp_iterate( struct pgrp *pgrp, proc_iterate_fn_t callout, void * arg, bool (^filterfn)(proc_t)) { pidlist_t pid_list, *pl = pidlist_init(&pid_list); u_int pid_count_available = 0; proc_t p; assert(pgrp != NULL); assert(callout != NULL); for (;;) { pgrp_lock(pgrp); /* * each member has one ref + some transient holders, * this is a good enough approximation */ pid_count_available = os_ref_get_count_mask(&pgrp->pg_refcount, PGRP_REF_BITS); if (pidlist_nalloc(pl) >= pid_count_available) { break; } pgrp_unlock(pgrp); pidlist_alloc(pl, pid_count_available); } pidlist_set_active(pl); const pid_t pgid = pgrp->pg_id; u_int pid_count = 0; PGMEMBERS_FOREACH(pgrp, p) { if ((filterfn != NULL) && (filterfn(p) == 0)) { continue; } pidlist_add_pid(pl, proc_pid(p)); if (++pid_count >= pid_count_available) { break; } } pgrp_unlock(pgrp); const pidlist_entry_t *pe; SLIST_FOREACH(pe, &(pl->pl_head), pe_link) { for (u_int i = 0; i < pe->pe_nused; i++) { const pid_t pid = pe->pe_pid[i]; if (0 == pid) { continue; /* skip kernproc */ } p = proc_find(pid); if (!p) { continue; } if (p->p_pgrpid != pgid) { proc_rele(p); continue; } const int callout_ret = callout(p, arg); switch (callout_ret) { case PROC_RETURNED: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED: break; case PROC_RETURNED_DONE: proc_rele(p); OS_FALLTHROUGH; case PROC_CLAIMED_DONE: goto out; default: panic("%s: callout =%d for pid %d", __func__, callout_ret, pid); } } } out: pidlist_free(pl); } /* consumes the newpg ref */ static void pgrp_replace(struct proc *p, struct pgrp *newpg) { struct pgrp *oldpg; proc_list_lock(); oldpg = smr_serialized_load(&p->p_pgrp); pgrp_del_member(oldpg, p); pgrp_add_member(newpg, PROC_NULL, p); proc_list_unlock(); pgrp_rele(oldpg); } struct pgrp * pgrp_alloc(pid_t pgid, pggrp_ref_bits_t bits) { struct pgrp *pgrp = zalloc_flags(pgrp_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL); os_ref_init_mask(&pgrp->pg_refcount, PGRP_REF_BITS, &p_refgrp, bits); os_ref_init_raw(&pgrp->pg_hashref, &p_refgrp); LIST_INIT(&pgrp->pg_members); lck_mtx_init(&pgrp->pg_mlock, &proc_mlock_grp, &proc_lck_attr); pgrp->pg_id = pgid; return pgrp; } void pgrp_lock(struct pgrp * pgrp) { lck_mtx_lock(&pgrp->pg_mlock); } void pgrp_unlock(struct pgrp * pgrp) { lck_mtx_unlock(&pgrp->pg_mlock); } struct session * session_find_locked(pid_t sessid) { struct session *sess; LIST_FOREACH(sess, SESSHASH(sessid), s_hash) { if (sess->s_sid == sessid) { break; } } return sess; } void session_replace_leader(struct proc *old_proc, struct proc *new_proc) { assert(old_proc == current_proc()); /* If old_proc is session leader, change the leader to new proc */ struct pgrp *pgrp = smr_serialized_load(&old_proc->p_pgrp); struct session *sessp = pgrp->pg_session; struct tty *ttyp = TTY_NULL; if (sessp == SESSION_NULL || !SESS_LEADER(old_proc, sessp)) { return; } session_lock(sessp); if (sessp->s_ttyp && sessp->s_ttyp->t_session == sessp) { ttyp = sessp->s_ttyp; ttyhold(ttyp); } /* Do the dance to take tty lock and session lock */ if (ttyp) { session_unlock(sessp); tty_lock(ttyp); session_lock(sessp); } sessp->s_leader = new_proc; session_unlock(sessp); if (ttyp) { tty_unlock(ttyp); ttyfree(ttyp); } } void session_lock(struct session * sess) { lck_mtx_lock(&sess->s_mlock); } void session_unlock(struct session * sess) { lck_mtx_unlock(&sess->s_mlock); } struct pgrp * proc_pgrp(proc_t p, struct session **sessp) { struct pgrp *pgrp = PGRP_NULL; bool success = false; if (__probable(p != PROC_NULL)) { smr_proc_task_enter(); pgrp = smr_entered_load(&p->p_pgrp); success = pgrp == PGRP_NULL || pg_ref_try(pgrp); smr_proc_task_leave(); if (__improbable(!success)) { /* * We caught the process in the middle of pgrp_replace(), * go the slow, never failing way. */ proc_list_lock(); pgrp = pg_ref(smr_serialized_load(&p->p_pgrp)); proc_list_unlock(); } } if (sessp) { *sessp = pgrp ? pgrp->pg_session : SESSION_NULL; } return pgrp; } struct pgrp * tty_pgrp_locked(struct tty *tp) { struct pgrp *pg = PGRP_NULL; /* either the tty_lock() or the proc_list_lock() must be held */ if (tp->t_pgrp) { pg = pg_ref(tp->t_pgrp); } return pg; } int proc_transstart(proc_t p, int locked, int non_blocking) { if (locked == 0) { proc_lock(p); } while ((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) { if (((p->p_lflag & P_LTRANSCOMMIT) == P_LTRANSCOMMIT) || non_blocking) { if (locked == 0) { proc_unlock(p); } return EDEADLK; } p->p_lflag |= P_LTRANSWAIT; msleep(&p->p_lflag, &p->p_mlock, 0, "proc_signstart", NULL); } p->p_lflag |= P_LINTRANSIT; p->p_transholder = current_thread(); if (locked == 0) { proc_unlock(p); } return 0; } void proc_transcommit(proc_t p, int locked) { if (locked == 0) { proc_lock(p); } assert((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT); assert(p->p_transholder == current_thread()); p->p_lflag |= P_LTRANSCOMMIT; if ((p->p_lflag & P_LTRANSWAIT) == P_LTRANSWAIT) { p->p_lflag &= ~P_LTRANSWAIT; wakeup(&p->p_lflag); } if (locked == 0) { proc_unlock(p); } } void proc_transend(proc_t p, int locked) { if (locked == 0) { proc_lock(p); } p->p_lflag &= ~(P_LINTRANSIT | P_LTRANSCOMMIT); p->p_transholder = NULL; if ((p->p_lflag & P_LTRANSWAIT) == P_LTRANSWAIT) { p->p_lflag &= ~P_LTRANSWAIT; wakeup(&p->p_lflag); } if (locked == 0) { proc_unlock(p); } } int proc_transwait(proc_t p, int locked) { if (locked == 0) { proc_lock(p); } while ((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) { if ((p->p_lflag & P_LTRANSCOMMIT) == P_LTRANSCOMMIT && current_proc() == p) { if (locked == 0) { proc_unlock(p); } return EDEADLK; } p->p_lflag |= P_LTRANSWAIT; msleep(&p->p_lflag, &p->p_mlock, 0, "proc_signstart", NULL); } if (locked == 0) { proc_unlock(p); } return 0; } void proc_klist_lock(void) { lck_mtx_lock(&proc_klist_mlock); } void proc_klist_unlock(void) { lck_mtx_unlock(&proc_klist_mlock); } void proc_knote(struct proc * p, long hint) { proc_klist_lock(); KNOTE(&p->p_klist, hint); proc_klist_unlock(); } void proc_transfer_knotes(struct proc *old_proc, struct proc *new_proc) { struct knote *kn = NULL; proc_klist_lock(); while ((kn = SLIST_FIRST(&old_proc->p_klist))) { KNOTE_DETACH(&old_proc->p_klist, kn); if (kn->kn_filtid == (uint8_t)~EVFILT_PROC) { kn->kn_proc = new_proc; KNOTE_ATTACH(&new_proc->p_klist, kn); } else { assert(kn->kn_filtid == (uint8_t)~EVFILT_SIGNAL); kn->kn_proc = NULL; } } proc_klist_unlock(); } void proc_knote_drain(struct proc *p) { struct knote *kn = NULL; /* * Clear the proc's klist to avoid references after the proc is reaped. */ proc_klist_lock(); while ((kn = SLIST_FIRST(&p->p_klist))) { kn->kn_proc = PROC_NULL; KNOTE_DETACH(&p->p_klist, kn); } proc_klist_unlock(); } void proc_setregister(proc_t p) { proc_lock(p); p->p_lflag |= P_LREGISTER; proc_unlock(p); } void proc_resetregister(proc_t p) { proc_lock(p); p->p_lflag &= ~P_LREGISTER; proc_unlock(p); } bool proc_get_pthread_jit_allowlist(proc_t p, bool *late_out) { bool ret = false; proc_lock(p); ret = (p->p_lflag & P_LPTHREADJITALLOWLIST); *late_out = (p->p_lflag & P_LPTHREADJITFREEZELATE); proc_unlock(p); return ret; } void proc_set_pthread_jit_allowlist(proc_t p, bool late) { proc_lock(p); p->p_lflag |= P_LPTHREADJITALLOWLIST; if (late) { p->p_lflag |= P_LPTHREADJITFREEZELATE; } proc_unlock(p); } pid_t proc_pgrpid(proc_t p) { return p->p_pgrpid; } pid_t proc_sessionid(proc_t p) { return p->p_sessionid; } pid_t proc_selfpgrpid() { return current_proc()->p_pgrpid; } /* return control and action states */ int proc_getpcontrol(int pid, int * pcontrolp) { proc_t p; p = proc_find(pid); if (p == PROC_NULL) { return ESRCH; } if (pcontrolp != NULL) { *pcontrolp = p->p_pcaction; } proc_rele(p); return 0; } static int proc_dopcontrol(proc_t p, memorystatus_kill_cause_t cause) { int pcontrol; os_reason_t kill_reason; proc_lock(p); pcontrol = PROC_CONTROL_STATE(p); if (PROC_ACTION_STATE(p) == 0) { switch (pcontrol) { case P_PCTHROTTLE: PROC_SETACTION_STATE(p); proc_unlock(p); memorystatus_log("memorystatus: throttling %s [%d] due to swap exhaustion\n", proc_best_name(p), proc_getpid(p)); break; case P_PCSUSP: PROC_SETACTION_STATE(p); proc_unlock(p); memorystatus_log("memorystatus: suspending %s [%d] due to swap exhaustion\n", proc_best_name(p), proc_getpid(p)); task_suspend(proc_task(p)); break; case P_PCKILL: PROC_SETACTION_STATE(p); proc_unlock(p); memorystatus_log("memorystatus: killing %s [%d] due to swap exhaustion\n", proc_best_name(p), proc_getpid(p)); kill_reason = os_reason_create(OS_REASON_JETSAM, cause); psignal_with_reason(p, SIGKILL, kill_reason); break; default: memorystatus_log("memorystatus: skipping %s [%d] without pcontrol\n", proc_best_name(p), proc_getpid(p)); proc_unlock(p); } } else { proc_unlock(p); } return PROC_RETURNED; } /* * Resume a throttled or suspended process. This is an internal interface that's only * used by the user level code that presents the GUI when we run out of swap space and * hence is restricted to processes with superuser privileges. */ int proc_resetpcontrol(int pid) { proc_t p; int pcontrol; int error; proc_t self = current_proc(); /* if the process has been validated to handle resource control or root is valid one */ if (((self->p_lflag & P_LVMRSRCOWNER) == 0) && (error = suser(kauth_cred_get(), 0))) { return error; } p = proc_find(pid); if (p == PROC_NULL) { return ESRCH; } proc_lock(p); pcontrol = PROC_CONTROL_STATE(p); if (PROC_ACTION_STATE(p) != 0) { switch (pcontrol) { case P_PCTHROTTLE: PROC_RESETACTION_STATE(p); proc_unlock(p); memorystatus_log("memorystatus: unthrottling %s [%d]\n", proc_best_name(p), proc_getpid(p)); break; case P_PCSUSP: PROC_RESETACTION_STATE(p); proc_unlock(p); memorystatus_log("memorystatus: resuming %s [%d]\n", proc_best_name(p), proc_getpid(p)); task_resume(proc_task(p)); break; case P_PCKILL: /* Huh? */ PROC_SETACTION_STATE(p); proc_unlock(p); memorystatus_log_error("memorystatus: attempt to unkill pid %s [%d] ignored\n", proc_best_name(p), proc_getpid(p)); break; default: proc_unlock(p); } } else { proc_unlock(p); } proc_rele(p); return 0; } struct no_paging_space { uint64_t pcs_max_size; uint64_t pcs_uniqueid; int pcs_pid; int pcs_proc_count; uint64_t pcs_total_size; uint64_t npcs_max_size; uint64_t npcs_uniqueid; int npcs_pid; int npcs_proc_count; uint64_t npcs_total_size; int apcs_proc_count; uint64_t apcs_total_size; }; static int proc_pcontrol_filter(proc_t p, void *arg) { struct no_paging_space *nps; uint64_t compressed; nps = (struct no_paging_space *)arg; compressed = get_task_compressed(proc_task(p)); if (PROC_CONTROL_STATE(p)) { if (PROC_ACTION_STATE(p) == 0) { if (compressed > nps->pcs_max_size) { nps->pcs_pid = proc_getpid(p); nps->pcs_uniqueid = proc_uniqueid(p); nps->pcs_max_size = compressed; } nps->pcs_total_size += compressed; nps->pcs_proc_count++; } else { nps->apcs_total_size += compressed; nps->apcs_proc_count++; } } else { if (compressed > nps->npcs_max_size) { nps->npcs_pid = proc_getpid(p); nps->npcs_uniqueid = proc_uniqueid(p); nps->npcs_max_size = compressed; } nps->npcs_total_size += compressed; nps->npcs_proc_count++; } return 0; } static int proc_pcontrol_null(__unused proc_t p, __unused void *arg) { return PROC_RETURNED; } /* * Deal with the low on compressor pool space condition... this function * gets called when we are approaching the limits of the compressor pool or * we are unable to create a new swap file. * Since this eventually creates a memory deadlock situtation, we need to take action to free up * memory resources (both compressed and uncompressed) in order to prevent the system from hanging completely. * There are 2 categories of processes to deal with. Those that have an action * associated with them by the task itself and those that do not. Actionable * tasks can have one of three categories specified: ones that * can be killed immediately, ones that should be suspended, and ones that should * be throttled. Processes that do not have an action associated with them are normally * ignored unless they are utilizing such a large percentage of the compressor pool (currently 50%) * that only by killing them can we hope to put the system back into a usable state. */ #define MB_SIZE (1024 * 1024ULL) extern int32_t max_kill_priority; bool no_paging_space_action(memorystatus_kill_cause_t cause) { proc_t p; struct no_paging_space nps; os_reason_t kill_reason; memorystatus_log("memorystatus: triggering no paging space action\n"); /* * Examine all processes and find the biggest (biggest is based on the number of pages this * task has in the compressor pool) that has been marked to have some action * taken when swap space runs out... we also find the biggest that hasn't been marked for * action. * * If the biggest non-actionable task is over the "dangerously big" threashold (currently 50% of * the total number of pages held by the compressor, we go ahead and kill it since no other task * can have any real effect on the situation. Otherwise, we go after the actionable process. */ bzero(&nps, sizeof(nps)); proc_iterate(PROC_ALLPROCLIST, proc_pcontrol_null, (void *)NULL, proc_pcontrol_filter, (void *)&nps); memorystatus_log_debug("memorystatus: npcs_proc_count = %d, npcs_total_size = %qd, npcs_max_size = %qd\n", nps.npcs_proc_count, nps.npcs_total_size, nps.npcs_max_size); memorystatus_log_debug("memorystatus: pcs_proc_count = %d, pcs_total_size = %qd, pcs_max_size = %qd\n", nps.pcs_proc_count, nps.pcs_total_size, nps.pcs_max_size); memorystatus_log_debug("memorystatus: apcs_proc_count = %d, apcs_total_size = %qd\n", nps.apcs_proc_count, nps.apcs_total_size); if (nps.npcs_max_size > (vm_compressor_pages_compressed() * PAGE_SIZE_64 * 50ull) / 100ull) { /* * for now we'll knock out any task that has more then 50% of the pages * held by the compressor */ if ((p = proc_find(nps.npcs_pid)) != PROC_NULL) { if (nps.npcs_uniqueid == proc_uniqueid(p)) { /* * verify this is still the same process * in case the proc exited and the pid got reused while * we were finishing the proc_iterate and getting to this point */ memorystatus_log("memorystatus: killing largest compressed process %s [%d] " "%llu MB\n", proc_best_name(p), proc_getpid(p), (nps.npcs_max_size / MB_SIZE)); kill_reason = os_reason_create(OS_REASON_JETSAM, cause); psignal_with_reason(p, SIGKILL, kill_reason); proc_rele(p); return false; } proc_rele(p); } } if (nps.pcs_max_size > 0) { memorystatus_log("memorystatus: attempting pcontrol on " "[%d]\n", nps.pcs_pid); if ((p = proc_find(nps.pcs_pid)) != PROC_NULL) { if (nps.pcs_uniqueid == proc_uniqueid(p)) { /* * verify this is still the same process * in case the proc exited and the pid got reused while * we were finishing the proc_iterate and getting to this point */ memorystatus_log("memorystatus: doing " "pcontrol on %s [%d]\n", proc_best_name(p), proc_getpid(p)); proc_dopcontrol(p, cause); proc_rele(p); return true; } else { memorystatus_log("memorystatus: cannot " "find process for [%d] -- may have exited\n", nps.pcs_pid); } proc_rele(p); } } memorystatus_log("memorystatus: unable to find any eligible processes to take action on\n"); return false; } int proc_trace_log(__unused proc_t p, struct proc_trace_log_args *uap, __unused int *retval) { int ret = 0; proc_t target_proc = PROC_NULL; pid_t target_pid = uap->pid; uint64_t target_uniqueid = uap->uniqueid; task_t target_task = NULL; if (priv_check_cred(kauth_cred_get(), PRIV_PROC_TRACE_INSPECT, 0)) { ret = EPERM; goto out; } target_proc = proc_find(target_pid); if (target_proc != PROC_NULL) { if (target_uniqueid != proc_uniqueid(target_proc)) { ret = ENOENT; goto out; } target_task = proc_task(target_proc); if (task_send_trace_memory(target_task, target_pid, target_uniqueid)) { ret = EINVAL; goto out; } } else { ret = ENOENT; } out: if (target_proc != PROC_NULL) { proc_rele(target_proc); } return ret; } #if VM_SCAN_FOR_SHADOW_CHAIN int proc_shadow_max(void); int proc_shadow_max(void) { int retval, max; proc_t p; task_t task; vm_map_t map; max = 0; proc_list_lock(); for (p = allproc.lh_first; (p != 0); p = p->p_list.le_next) { if (p->p_stat == SIDL) { continue; } task = proc_task(p); if (task == NULL) { continue; } map = get_task_map(task); if (map == NULL) { continue; } retval = vm_map_shadow_max(map); if (retval > max) { max = retval; } } proc_list_unlock(); return max; } #endif /* VM_SCAN_FOR_SHADOW_CHAIN */ void proc_set_responsible_pid(proc_t target_proc, pid_t responsible_pid); void proc_set_responsible_pid(proc_t target_proc, pid_t responsible_pid) { if (target_proc != NULL) { target_proc->p_responsible_pid = responsible_pid; // Also save the responsible UUID if (responsible_pid >= 0) { proc_t responsible_proc = proc_find(responsible_pid); if (responsible_proc != PROC_NULL) { proc_getexecutableuuid(responsible_proc, target_proc->p_responsible_uuid, sizeof(target_proc->p_responsible_uuid)); proc_rele(responsible_proc); } } } return; } int proc_chrooted(proc_t p) { int retval = 0; if (p) { proc_fdlock(p); retval = (p->p_fd.fd_rdir != NULL) ? 1 : 0; proc_fdunlock(p); } return retval; } boolean_t proc_send_synchronous_EXC_RESOURCE(proc_t p) { if (p == PROC_NULL) { return FALSE; } /* Send sync EXC_RESOURCE if the process is traced */ if (ISSET(p->p_lflag, P_LTRACED)) { return TRUE; } return FALSE; } #if CONFIG_MACF size_t proc_get_syscall_filter_mask_size(int which) { switch (which) { case SYSCALL_MASK_UNIX: return nsysent; case SYSCALL_MASK_MACH: return mach_trap_count; case SYSCALL_MASK_KOBJ: return mach_kobj_count; default: return 0; } } unsigned char * proc_get_syscall_filter_mask(proc_t p, int which) { switch (which) { case SYSCALL_MASK_UNIX: return proc_syscall_filter_mask(p); case SYSCALL_MASK_MACH: return mac_task_get_mach_filter_mask(proc_task(p)); case SYSCALL_MASK_KOBJ: return mac_task_get_kobj_filter_mask(proc_task(p)); default: return NULL; } } int proc_set_syscall_filter_mask(proc_t p, int which, unsigned char *maskptr, size_t masklen) { #if DEVELOPMENT || DEBUG if (syscallfilter_disable) { printf("proc_set_syscall_filter_mask: attempt to set policy for pid %d, but disabled by boot-arg\n", proc_pid(p)); return 0; } #endif // DEVELOPMENT || DEBUG switch (which) { case SYSCALL_MASK_UNIX: if (maskptr != NULL && masklen != nsysent) { return EINVAL; } proc_syscall_filter_mask_set(p, maskptr); break; case SYSCALL_MASK_MACH: if (maskptr != NULL && masklen != (size_t)mach_trap_count) { return EINVAL; } mac_task_set_mach_filter_mask(proc_task(p), maskptr); break; case SYSCALL_MASK_KOBJ: if (maskptr != NULL && masklen != (size_t)mach_kobj_count) { return EINVAL; } mac_task_set_kobj_filter_mask(proc_task(p), maskptr); break; default: return EINVAL; } return 0; } int proc_set_syscall_filter_callbacks(syscall_filter_cbs_t cbs) { if (cbs->version != SYSCALL_FILTER_CALLBACK_VERSION) { return EINVAL; } /* XXX register unix filter callback instead of using MACF hook. */ if (cbs->mach_filter_cbfunc || cbs->kobj_filter_cbfunc) { if (mac_task_register_filter_callbacks(cbs->mach_filter_cbfunc, cbs->kobj_filter_cbfunc) != 0) { return EPERM; } } return 0; } int proc_set_syscall_filter_index(int which, int num, int index) { switch (which) { case SYSCALL_MASK_KOBJ: if (ipc_kobject_set_kobjidx(num, index) != 0) { return ENOENT; } break; default: return EINVAL; } return 0; } #endif /* CONFIG_MACF */ int proc_set_filter_message_flag(proc_t p, boolean_t flag) { if (p == PROC_NULL) { return EINVAL; } task_set_filter_msg_flag(proc_task(p), flag); return 0; } int proc_get_filter_message_flag(proc_t p, boolean_t *flag) { if (p == PROC_NULL || flag == NULL) { return EINVAL; } *flag = task_get_filter_msg_flag(proc_task(p)); return 0; } bool proc_is_traced(proc_t p) { bool ret = FALSE; assert(p != PROC_NULL); proc_lock(p); if (p->p_lflag & P_LTRACED) { ret = TRUE; } proc_unlock(p); return ret; } #if CONFIG_PROC_RESOURCE_LIMITS int proc_set_filedesc_limits(proc_t p, int soft_limit, int hard_limit) { struct filedesc *fdp = &p->p_fd; int retval = 0; proc_fdlock(p); if (hard_limit > 0) { if (soft_limit >= hard_limit) { soft_limit = 0; } } fdp->fd_nfiles_soft_limit = soft_limit; fdp->fd_nfiles_hard_limit = hard_limit; /* Make sure that current fd_nfiles hasn't already exceeded these limits */ fd_check_limit_exceeded(fdp); proc_fdunlock(p); return retval; } int proc_set_kqworkloop_limits(proc_t p, int soft_limit, int hard_limit) { struct filedesc *fdp = &p->p_fd; lck_mtx_lock_spin_always(&fdp->fd_kqhashlock); fdp->kqwl_dyn_soft_limit = soft_limit; fdp->kqwl_dyn_hard_limit = hard_limit; /* Make sure existing limits aren't exceeded already */ kqworkloop_check_limit_exceeded(fdp); lck_mtx_unlock(&fdp->fd_kqhashlock); return 0; } static int proc_evaluate_fd_limits_ast(proc_t p, struct filedesc *fdp, int *soft_limit, int *hard_limit) { int fd_current_size, fd_soft_limit, fd_hard_limit; proc_fdlock(p); fd_current_size = fdp->fd_nfiles_open; fd_hard_limit = fdp->fd_nfiles_hard_limit; fd_soft_limit = fdp->fd_nfiles_soft_limit; /* * If a thread is going to take action on a specific limit exceeding, it also * clears it out to a SENTINEL so that future threads don't reevaluate the * limit as having exceeded again */ if (fd_hard_limit > 0 && fd_current_size >= fd_hard_limit) { /* Clear our soft limit when we are sending hard limit notification */ fd_soft_limit = 0; fdp->fd_nfiles_hard_limit = FD_LIMIT_SENTINEL; } else if (fd_soft_limit > 0 && fd_current_size >= fd_soft_limit) { /* Clear out hard limit when we are sending soft limit notification */ fd_hard_limit = 0; fdp->fd_nfiles_soft_limit = FD_LIMIT_SENTINEL; } else { /* Neither limits were exceeded */ fd_soft_limit = fd_hard_limit = 0; } proc_fdunlock(p); *soft_limit = fd_soft_limit; *hard_limit = fd_hard_limit; return fd_current_size; } static int proc_evaluate_kqwl_limits_ast(struct filedesc *fdp, int *soft_limit, int *hard_limit) { lck_mtx_lock_spin_always(&fdp->fd_kqhashlock); int kqwl_current_size = fdp->num_kqwls; int kqwl_soft_limit = fdp->kqwl_dyn_soft_limit; int kqwl_hard_limit = fdp->kqwl_dyn_hard_limit; /* * If a thread is going to take action on a specific limit exceeding, it also * clears it out to a SENTINEL so that future threads don't reevaluate the * limit as having exceeded again */ if (kqwl_hard_limit > 0 && kqwl_current_size >= kqwl_hard_limit) { /* Clear our soft limit when we are sending hard limit notification */ kqwl_soft_limit = 0; fdp->kqwl_dyn_hard_limit = KQWL_LIMIT_SENTINEL; } else if (kqwl_soft_limit > 0 && kqwl_current_size >= kqwl_soft_limit) { /* Clear out hard limit when we are sending soft limit notification */ kqwl_hard_limit = 0; fdp->kqwl_dyn_soft_limit = KQWL_LIMIT_SENTINEL; } else { /* Neither limits were exceeded */ kqwl_soft_limit = kqwl_hard_limit = 0; } lck_mtx_unlock(&fdp->fd_kqhashlock); *soft_limit = kqwl_soft_limit; *hard_limit = kqwl_hard_limit; return kqwl_current_size; } #endif /* CONFIG_PROC_RESOURCE_LIMITS */ void proc_filedesc_ast(__unused task_t task) { #if CONFIG_PROC_RESOURCE_LIMITS assert(task == current_task()); proc_t p = get_bsdtask_info(task); struct filedesc *fdp = &p->p_fd; /* * At this point, we can possibly race with other threads which set the AST * due to triggering the soft/hard limits for fd or kqworkloops. * * The first thread to reach this logic will always evaluate hard limit for fd * or kqworkloops even if it was the one which triggered the soft limit for * them. * * If a thread takes action on a specific limit, it will clear the limit value * in the fdp with a SENTINEL to indicate to other racing threads that they no * longer need to evaluate it. */ int soft_limit, hard_limit; int fd_current_size = proc_evaluate_fd_limits_ast(p, fdp, &soft_limit, &hard_limit); if (hard_limit || soft_limit) { return task_filedesc_ast(task, fd_current_size, soft_limit, hard_limit); } int kqwl_current_size = proc_evaluate_kqwl_limits_ast(fdp, &soft_limit, &hard_limit); if (hard_limit || soft_limit) { return task_kqworkloop_ast(task, kqwl_current_size, soft_limit, hard_limit); } #endif /* CONFIG_PROC_RESOURCE_LIMITS */ } proc_ro_t proc_ro_alloc(proc_t p, proc_ro_data_t p_data, task_t t, task_ro_data_t t_data) { proc_ro_t pr; struct proc_ro pr_local = {}; pr = (proc_ro_t)zalloc_ro(ZONE_ID_PROC_RO, Z_WAITOK | Z_NOFAIL | Z_ZERO); if (p != PROC_NULL) { pr_local.pr_proc = p; pr_local.proc_data = *p_data; } if (t != TASK_NULL) { pr_local.pr_task = t; pr_local.task_data = *t_data; } if ((p != PROC_NULL) || (t != TASK_NULL)) { zalloc_ro_update_elem(ZONE_ID_PROC_RO, pr, &pr_local); } return pr; } proc_ro_t proc_ro_ref_task(proc_ro_t pr, task_t t, task_ro_data_t t_data) { struct proc_ro pr_local; if (pr->pr_task != TASK_NULL) { panic("%s: proc_ro already has an owning task", __func__); } pr_local = *pr; pr_local.pr_task = t; pr_local.task_data = *t_data; zalloc_ro_update_elem(ZONE_ID_PROC_RO, pr, &pr_local); return pr; } void proc_ro_erase_task(proc_ro_t pr) { zalloc_ro_update_field_atomic(ZONE_ID_PROC_RO, pr, pr_task, ZRO_ATOMIC_XCHG_LONG, TASK_NULL); } __abortlike static void panic_proc_ro_proc_backref_mismatch(proc_t p, proc_ro_t ro) { panic("proc_ro->proc backref mismatch: p=%p, ro=%p, " "ro->pr_proc(ro)=%p", p, ro, ro->pr_proc); } proc_ro_t proc_get_ro(proc_t p) { proc_ro_t ro = p->p_proc_ro; zone_require_ro(ZONE_ID_PROC_RO, sizeof(struct proc_ro), ro); if (__improbable(ro->pr_proc != p)) { panic_proc_ro_proc_backref_mismatch(p, ro); } return ro; } #ifdef __BUILDING_XNU_LIB_UNITTEST__ /* this is here since unittest Makefile can't build BSD sources yet */ void mock_init_proc(proc_t p, void* (*calloc_call)(size_t, size_t)); void mock_init_proc(proc_t p, void* (*calloc_call)(size_t, size_t)) { proc_ro_t ro = calloc_call(1, sizeof(struct proc_ro)); ro->pr_proc = p; p->p_proc_ro = ro; } #endif /* __BUILDING_XNU_LIB_UNITTEST__ */ task_t proc_ro_task(proc_ro_t pr) { return pr->pr_task; } /* * pid_for_task * * Find the BSD process ID for the Mach task associated with the given Mach port * name * * Parameters: args User argument descriptor (see below) * * Indirect parameters: args->t Mach port name * args->pid Process ID (returned value; see below) * * Returns: KERL_SUCCESS Success * KERN_FAILURE Not success * * Implicit returns: args->pid Process ID * */ kern_return_t pid_for_task( struct pid_for_task_args *args) { mach_port_name_t t = args->t; user_addr_t pid_addr = args->pid; proc_t p; task_t t1; int pid = -1; kern_return_t err = KERN_SUCCESS; AUDIT_MACH_SYSCALL_ENTER(AUE_PIDFORTASK); AUDIT_ARG(mach_port1, t); t1 = port_name_to_task_name(t); if (t1 == TASK_NULL) { err = KERN_FAILURE; goto pftout; } else { p = get_bsdtask_info(t1); if (p) { pid = proc_pid(p); err = KERN_SUCCESS; } else if (task_is_a_corpse(t1)) { pid = task_pid(t1); err = KERN_SUCCESS; } else { err = KERN_FAILURE; } } task_deallocate(t1); pftout: AUDIT_ARG(pid, pid); (void) copyout((char *) &pid, pid_addr, sizeof(int)); AUDIT_MACH_SYSCALL_EXIT(err); return err; } /* * * tfp_policy = KERN_TFP_POLICY_DENY; Deny Mode: None allowed except for self * tfp_policy = KERN_TFP_POLICY_DEFAULT; default mode: all posix checks and upcall via task port for authentication * */ static int tfp_policy = KERN_TFP_POLICY_DEFAULT; static int sysctl_settfp_policy(__unused struct sysctl_oid *oidp, void *arg1, __unused int arg2, struct sysctl_req *req) { int error = 0; int new_value; error = SYSCTL_OUT(req, arg1, sizeof(int)); if (error || req->newptr == USER_ADDR_NULL) { return error; } if (!kauth_cred_issuser(kauth_cred_get())) { return EPERM; } if ((error = SYSCTL_IN(req, &new_value, sizeof(int)))) { goto out; } if ((new_value == KERN_TFP_POLICY_DENY) || (new_value == KERN_TFP_POLICY_DEFAULT)) { tfp_policy = new_value; } else { error = EINVAL; } out: return error; } SYSCTL_NODE(_kern, KERN_TFP, tfp, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "tfp"); SYSCTL_PROC(_kern_tfp, KERN_TFP_POLICY, policy, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, &tfp_policy, sizeof(uint32_t), &sysctl_settfp_policy, "I", "policy"); /* * Routine: task_for_pid_posix_check * Purpose: * Verify that the current process should be allowed to * get the target process's task port. This is only * permitted if: * - The current process is root * OR all of the following are true: * - The target process's real, effective, and saved uids * are the same as the current proc's euid, * - The target process's group set is a subset of the * calling process's group set, and * - The target process hasn't switched credentials. * * Returns: TRUE: permitted * FALSE: denied */ static int task_for_pid_posix_check(proc_t target) { kauth_cred_t targetcred, mycred; bool checkcredentials; uid_t myuid; int allowed; /* No task_for_pid on bad targets */ if (target->p_stat == SZOMB) { return FALSE; } mycred = kauth_cred_get(); myuid = kauth_cred_getuid(mycred); /* If we're running as root, the check passes */ if (kauth_cred_issuser(mycred)) { return TRUE; } /* We're allowed to get our own task port */ if (target == current_proc()) { return TRUE; } /* * Under DENY, only root can get another proc's task port, * so no more checks are needed. */ if (tfp_policy == KERN_TFP_POLICY_DENY) { return FALSE; } targetcred = kauth_cred_proc_ref(target); allowed = TRUE; checkcredentials = !proc_is_third_party_debuggable_driver(target); if (checkcredentials) { /* Do target's ruid, euid, and saved uid match my euid? */ if ((kauth_cred_getuid(targetcred) != myuid) || (kauth_cred_getruid(targetcred) != myuid) || (kauth_cred_getsvuid(targetcred) != myuid)) { allowed = FALSE; goto out; } /* Are target's groups a subset of my groups? */ if (kauth_cred_gid_subset(targetcred, mycred, &allowed) || allowed == 0) { allowed = FALSE; goto out; } } /* Has target switched credentials? */ if (target->p_flag & P_SUGID) { allowed = FALSE; goto out; } out: kauth_cred_unref(&targetcred); return allowed; } /* * __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__ * * Description: Waits for the user space daemon to respond to the request * we made. Function declared non inline to be visible in * stackshots and spindumps as well as debugging. */ static __attribute__((noinline)) int __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__( mach_port_t task_access_port, int32_t calling_pid, uint32_t calling_gid, int32_t target_pid, mach_task_flavor_t flavor) { return check_task_access_with_flavor(task_access_port, calling_pid, calling_gid, target_pid, flavor); } /* * Routine: task_for_pid * Purpose: * Get the task port for another "process", named by its * process ID on the same host as "target_task". * * Only permitted to privileged processes, or processes * with the same user ID. * * Note: if pid == 0, an error is return no matter who is calling. * * XXX This should be a BSD system call, not a Mach trap!!! */ kern_return_t task_for_pid( struct task_for_pid_args *args) { mach_port_name_t target_tport = args->target_tport; int pid = args->pid; user_addr_t task_addr = args->t; proc_t p = PROC_NULL; task_t t1 = TASK_NULL; task_t task = TASK_NULL; mach_port_name_t tret = MACH_PORT_NULL; ipc_port_t tfpport = MACH_PORT_NULL; void * sright = NULL; int error = 0; boolean_t is_current_proc = FALSE; struct proc_ident pident = {0}; AUDIT_MACH_SYSCALL_ENTER(AUE_TASKFORPID); AUDIT_ARG(pid, pid); AUDIT_ARG(mach_port1, target_tport); /* Always check if pid == 0 */ if (pid == 0) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE); return KERN_FAILURE; } t1 = port_name_to_task(target_tport); if (t1 == TASK_NULL) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE); return KERN_FAILURE; } p = proc_find(pid); if (p == PROC_NULL) { error = KERN_FAILURE; goto tfpout; } pident = proc_ident_with_policy(p, IDENT_VALIDATION_PROC_EXACT); is_current_proc = (p == current_proc()); #if CONFIG_AUDIT AUDIT_ARG(process, p); #endif if (!(task_for_pid_posix_check(p))) { error = KERN_FAILURE; goto tfpout; } if (proc_task(p) == TASK_NULL) { error = KERN_SUCCESS; goto tfpout; } /* * Grab a task reference and drop the proc reference as the proc ref * shouldn't be held accross upcalls. */ task = proc_task(p); task_reference(task); proc_rele(p); p = PROC_NULL; /* IPC is not active on the task until after `exec_resettextvp` has been called. * We don't want to call into MAC hooks until we know that this has occured, otherwise * AMFI and others will read uninitialized fields from the csproc */ if (!task_is_ipc_active(task)) { error = KERN_FAILURE; goto tfpout; } #if CONFIG_MACF error = mac_proc_check_get_task(kauth_cred_get(), &pident, TASK_FLAVOR_CONTROL); if (error) { error = KERN_FAILURE; goto tfpout; } #endif /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && !is_current_proc && (task_get_task_access_port(task, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = KERN_PROTECTION_FAILURE; goto tfpout; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_CONTROL); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = KERN_ABORTED; } else { error = KERN_FAILURE; } goto tfpout; } } /* Grant task port access */ extmod_statistics_incr_task_for_pid(task); /* this reference will be consumed during conversion */ task_reference(task); sright = (void *)convert_task_to_port(task); /* extra task ref consumed */ /* * Check if the task has been corpsified. We must do so after conversion * since we don't hold locks and may have grabbed a corpse control port * above which will prevent no-senders notification delivery. */ if (task_is_a_corpse(task)) { ipc_port_release_send(sright); error = KERN_FAILURE; goto tfpout; } tret = ipc_port_copyout_send( sright, get_task_ipcspace(current_task())); error = KERN_SUCCESS; tfpout: task_deallocate(t1); AUDIT_ARG(mach_port2, tret); (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (task != TASK_NULL) { task_deallocate(task); } if (p != PROC_NULL) { proc_rele(p); } AUDIT_MACH_SYSCALL_EXIT(error); return error; } /* * Routine: task_name_for_pid * Purpose: * Get the task name port for another "process", named by its * process ID on the same host as "target_task". * * Only permitted to privileged processes, or processes * with the same user ID. * * XXX This should be a BSD system call, not a Mach trap!!! */ kern_return_t task_name_for_pid( struct task_name_for_pid_args *args) { mach_port_name_t target_tport = args->target_tport; int pid = args->pid; user_addr_t task_addr = args->t; proc_t p = PROC_NULL; task_t t1 = TASK_NULL; mach_port_name_t tret = MACH_PORT_NULL; void * sright; int error = 0, refheld = 0; kauth_cred_t target_cred; AUDIT_MACH_SYSCALL_ENTER(AUE_TASKNAMEFORPID); AUDIT_ARG(pid, pid); AUDIT_ARG(mach_port1, target_tport); t1 = port_name_to_task(target_tport); if (t1 == TASK_NULL) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE); return KERN_FAILURE; } p = proc_find(pid); if (p != PROC_NULL) { AUDIT_ARG(process, p); target_cred = kauth_cred_proc_ref(p); refheld = 1; if ((p->p_stat != SZOMB) && ((current_proc() == p) || kauth_cred_issuser(kauth_cred_get()) || ((kauth_cred_getuid(target_cred) == kauth_cred_getuid(kauth_cred_get())) && ((kauth_cred_getruid(target_cred) == kauth_getruid()))) || IOCurrentTaskHasEntitlement("com.apple.system-task-ports.name.safe") )) { if (proc_task(p) != TASK_NULL) { struct proc_ident pident = proc_ident_with_policy(p, IDENT_VALIDATION_PROC_EXACT); task_t task = proc_task(p); task_reference(task); proc_rele(p); p = PROC_NULL; #if CONFIG_MACF error = mac_proc_check_get_task(kauth_cred_get(), &pident, TASK_FLAVOR_NAME); if (error) { task_deallocate(task); goto noperm; } #endif sright = (void *)convert_task_name_to_port(task); task = NULL; tret = ipc_port_copyout_send(sright, get_task_ipcspace(current_task())); } else { tret = MACH_PORT_NULL; } AUDIT_ARG(mach_port2, tret); (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); task_deallocate(t1); error = KERN_SUCCESS; goto tnfpout; } } #if CONFIG_MACF noperm: #endif task_deallocate(t1); tret = MACH_PORT_NULL; (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); error = KERN_FAILURE; tnfpout: if (refheld != 0) { kauth_cred_unref(&target_cred); } if (p != PROC_NULL) { proc_rele(p); } AUDIT_MACH_SYSCALL_EXIT(error); return error; } /* * Routine: task_inspect_for_pid * Purpose: * Get the task inspect port for another "process", named by its * process ID on the same host as "target_task". */ int task_inspect_for_pid(struct proc *p __unused, struct task_inspect_for_pid_args *args, int *ret) { mach_port_name_t target_tport = args->target_tport; int pid = args->pid; user_addr_t task_addr = args->t; proc_t proc = PROC_NULL; task_t t1 = TASK_NULL; task_inspect_t task_insp = TASK_INSPECT_NULL; mach_port_name_t tret = MACH_PORT_NULL; ipc_port_t tfpport = MACH_PORT_NULL; int error = 0; void *sright = NULL; boolean_t is_current_proc = FALSE; struct proc_ident pident = {0}; /* Disallow inspect port for kernel_task */ if (pid == 0) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); return EPERM; } t1 = port_name_to_task(target_tport); if (t1 == TASK_NULL) { (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); return EINVAL; } proc = proc_find(pid); if (proc == PROC_NULL) { error = ESRCH; goto tifpout; } pident = proc_ident_with_policy(proc, IDENT_VALIDATION_PROC_EXACT); is_current_proc = (proc == current_proc()); if (!(task_for_pid_posix_check(proc))) { error = EPERM; goto tifpout; } task_insp = proc_task(proc); if (task_insp == TASK_INSPECT_NULL) { goto tifpout; } /* * Grab a task reference and drop the proc reference before making any upcalls. */ task_reference(task_insp); proc_rele(proc); proc = PROC_NULL; #if CONFIG_MACF error = mac_proc_check_get_task(kauth_cred_get(), &pident, TASK_FLAVOR_INSPECT); if (error) { error = EPERM; goto tifpout; } #endif /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && !is_current_proc && (task_get_task_access_port(task_insp, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = EACCES; goto tifpout; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_INSPECT); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = EINTR; } else { error = EPERM; } goto tifpout; } } /* Check if the task has been corpsified */ if (task_is_a_corpse(task_insp)) { error = EACCES; goto tifpout; } /* could be IP_NULL, consumes a ref */ sright = (void*) convert_task_inspect_to_port(task_insp); task_insp = TASK_INSPECT_NULL; tret = ipc_port_copyout_send(sright, get_task_ipcspace(current_task())); tifpout: task_deallocate(t1); (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); if (proc != PROC_NULL) { proc_rele(proc); } if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (task_insp != TASK_INSPECT_NULL) { task_deallocate(task_insp); } *ret = error; return error; } /* * Routine: task_read_for_pid * Purpose: * Get the task read port for another "process", named by its * process ID on the same host as "target_task". */ int task_read_for_pid(struct proc *p __unused, struct task_read_for_pid_args *args, int *ret) { mach_port_name_t target_tport = args->target_tport; int pid = args->pid; user_addr_t task_addr = args->t; proc_t proc = PROC_NULL; task_t t1 = TASK_NULL; task_read_t task_read = TASK_READ_NULL; mach_port_name_t tret = MACH_PORT_NULL; ipc_port_t tfpport = MACH_PORT_NULL; int error = 0; void *sright = NULL; boolean_t is_current_proc = FALSE; struct proc_ident pident = {0}; /* Disallow read port for kernel_task */ if (pid == 0) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); return EPERM; } t1 = port_name_to_task(target_tport); if (t1 == TASK_NULL) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); return EINVAL; } proc = proc_find(pid); if (proc == PROC_NULL) { error = ESRCH; goto trfpout; } pident = proc_ident_with_policy(proc, IDENT_VALIDATION_PROC_EXACT); is_current_proc = (proc == current_proc()); if (!(task_for_pid_posix_check(proc))) { error = EPERM; goto trfpout; } task_read = proc_task(proc); if (task_read == TASK_INSPECT_NULL) { goto trfpout; } /* * Grab a task reference and drop the proc reference before making any upcalls. */ task_reference(task_read); proc_rele(proc); proc = PROC_NULL; #if CONFIG_MACF error = mac_proc_check_get_task(kauth_cred_get(), &pident, TASK_FLAVOR_READ); if (error) { error = EPERM; goto trfpout; } #endif /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && !is_current_proc && (task_get_task_access_port(task_read, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = EACCES; goto trfpout; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_READ); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = EINTR; } else { error = EPERM; } goto trfpout; } } /* Check if the task has been corpsified */ if (task_is_a_corpse(task_read)) { error = EACCES; goto trfpout; } /* could be IP_NULL, consumes a ref */ sright = (void*) convert_task_read_to_port(task_read); task_read = TASK_READ_NULL; tret = ipc_port_copyout_send(sright, get_task_ipcspace(current_task())); trfpout: task_deallocate(t1); (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); if (proc != PROC_NULL) { proc_rele(proc); } if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (task_read != TASK_READ_NULL) { task_deallocate(task_read); } *ret = error; return error; } kern_return_t pid_suspend(struct proc *p __unused, struct pid_suspend_args *args, int *ret) { task_t target = NULL; proc_t targetproc = PROC_NULL; int pid = args->pid; int error = 0; mach_port_t tfpport = MACH_PORT_NULL; if (pid == 0) { error = EPERM; goto out; } targetproc = proc_find(pid); if (targetproc == PROC_NULL) { error = ESRCH; goto out; } if (!task_for_pid_posix_check(targetproc) && !IOCurrentTaskHasEntitlement(PROCESS_RESUME_SUSPEND_ENTITLEMENT)) { error = EPERM; goto out; } #if CONFIG_MACF error = mac_proc_check_suspend_resume(targetproc, MAC_PROC_CHECK_SUSPEND); if (error) { error = EPERM; goto out; } #endif target = proc_task(targetproc); #if XNU_TARGET_OS_OSX if (target != TASK_NULL) { /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && targetproc != current_proc() && (task_get_task_access_port(target, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = EACCES; goto out; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_CONTROL); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = EINTR; } else { error = EPERM; } goto out; } } } #endif /* XNU_TARGET_OS_OSX */ task_reference(target); error = task_pidsuspend(target); if (error) { if (error == KERN_INVALID_ARGUMENT) { error = EINVAL; } else { error = EPERM; } } #if CONFIG_MEMORYSTATUS else { memorystatus_on_suspend(targetproc); } #endif task_deallocate(target); out: if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (targetproc != PROC_NULL) { proc_rele(targetproc); } *ret = error; return error; } kern_return_t debug_control_port_for_pid(struct debug_control_port_for_pid_args *args) { mach_port_name_t target_tport = args->target_tport; int pid = args->pid; user_addr_t task_addr = args->t; proc_t p = PROC_NULL; task_t t1 = TASK_NULL; task_t task = TASK_NULL; mach_port_name_t tret = MACH_PORT_NULL; ipc_port_t tfpport = MACH_PORT_NULL; ipc_port_t sright = NULL; int error = 0; boolean_t is_current_proc = FALSE; struct proc_ident pident = {0}; AUDIT_MACH_SYSCALL_ENTER(AUE_DBGPORTFORPID); AUDIT_ARG(pid, pid); AUDIT_ARG(mach_port1, target_tport); /* Always check if pid == 0 */ if (pid == 0) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE); return KERN_FAILURE; } t1 = port_name_to_task(target_tport); if (t1 == TASK_NULL) { (void) copyout((char *)&tret, task_addr, sizeof(mach_port_name_t)); AUDIT_MACH_SYSCALL_EXIT(KERN_FAILURE); return KERN_FAILURE; } p = proc_find(pid); if (p == PROC_NULL) { error = KERN_FAILURE; goto tfpout; } pident = proc_ident_with_policy(p, IDENT_VALIDATION_PROC_EXACT); is_current_proc = (p == current_proc()); #if CONFIG_AUDIT AUDIT_ARG(process, p); #endif if (!(task_for_pid_posix_check(p))) { error = KERN_FAILURE; goto tfpout; } if (proc_task(p) == TASK_NULL) { error = KERN_SUCCESS; goto tfpout; } /* * Grab a task reference and drop the proc reference before making any upcalls. */ task = proc_task(p); task_reference(task); proc_rele(p); p = PROC_NULL; if (!IOCurrentTaskHasEntitlement(DEBUG_PORT_ENTITLEMENT)) { #if CONFIG_MACF error = mac_proc_check_get_task(kauth_cred_get(), &pident, TASK_FLAVOR_CONTROL); if (error) { error = KERN_FAILURE; goto tfpout; } #endif /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && !is_current_proc && (task_get_task_access_port(task, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = KERN_PROTECTION_FAILURE; goto tfpout; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_CONTROL); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = KERN_ABORTED; } else { error = KERN_FAILURE; } goto tfpout; } } } /* Check if the task has been corpsified */ if (task_is_a_corpse(task)) { error = KERN_FAILURE; goto tfpout; } error = task_get_debug_control_port(task, &sright); if (error != KERN_SUCCESS) { goto tfpout; } tret = ipc_port_copyout_send( sright, get_task_ipcspace(current_task())); error = KERN_SUCCESS; tfpout: task_deallocate(t1); AUDIT_ARG(mach_port2, tret); (void) copyout((char *) &tret, task_addr, sizeof(mach_port_name_t)); if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (task != TASK_NULL) { task_deallocate(task); } if (p != PROC_NULL) { proc_rele(p); } AUDIT_MACH_SYSCALL_EXIT(error); return error; } kern_return_t pid_resume(struct proc *p __unused, struct pid_resume_args *args, int *ret) { task_t target = NULL; proc_t targetproc = PROC_NULL; int pid = args->pid; int error = 0; mach_port_t tfpport = MACH_PORT_NULL; if (pid == 0) { error = EPERM; goto out; } targetproc = proc_find(pid); if (targetproc == PROC_NULL) { error = ESRCH; goto out; } if (!task_for_pid_posix_check(targetproc) && !IOCurrentTaskHasEntitlement(PROCESS_RESUME_SUSPEND_ENTITLEMENT)) { error = EPERM; goto out; } #if CONFIG_MACF error = mac_proc_check_suspend_resume(targetproc, MAC_PROC_CHECK_RESUME); if (error) { error = EPERM; goto out; } #endif target = proc_task(targetproc); #if XNU_TARGET_OS_OSX if (target != TASK_NULL) { /* If we aren't root and target's task access port is set... */ if (!kauth_cred_issuser(kauth_cred_get()) && targetproc != current_proc() && (task_get_task_access_port(target, &tfpport) == 0) && (tfpport != IPC_PORT_NULL)) { if (tfpport == IPC_PORT_DEAD) { error = EACCES; goto out; } /* Call up to the task access server */ error = __KERNEL_WAITING_ON_TASKGATED_CHECK_ACCESS_UPCALL__(tfpport, proc_selfpid(), kauth_getgid(), pid, TASK_FLAVOR_CONTROL); if (error != MACH_MSG_SUCCESS) { if (error == MACH_RCV_INTERRUPTED) { error = EINTR; } else { error = EPERM; } goto out; } } } #endif /* XNU_TARGET_OS_OSX */ #if !XNU_TARGET_OS_OSX #if SOCKETS resume_proc_sockets(targetproc); #endif /* SOCKETS */ #endif /* !XNU_TARGET_OS_OSX */ task_reference(target); #if CONFIG_MEMORYSTATUS memorystatus_on_resume(targetproc); #endif error = task_pidresume(target); if (error) { if (error == KERN_INVALID_ARGUMENT) { error = EINVAL; } else { if (error == KERN_MEMORY_ERROR) { psignal(targetproc, SIGKILL); error = EIO; } else { error = EPERM; } } } task_deallocate(target); out: if (tfpport != IPC_PORT_NULL) { ipc_port_release_send(tfpport); } if (targetproc != PROC_NULL) { proc_rele(targetproc); } *ret = error; return error; } #if !XNU_TARGET_OS_OSX /* * Freeze the specified process (provided in args->pid), or find and freeze a PID. * When a process is specified, this call is blocking, otherwise we wake up the * freezer thread and do not block on a process being frozen. */ int pid_hibernate(struct proc *p __unused, struct pid_hibernate_args *args, int *ret) { int error = 0; proc_t targetproc = PROC_NULL; int pid = args->pid; /* * TODO: Create a different interface for compressor sweeps, * gated by an entitlement: rdar://116490432 */ if (pid == -2) { error = mach_to_bsd_errno(vm_pageout_anonymous_pages()); } #ifndef CONFIG_FREEZE if (pid != -2) { os_log(OS_LOG_DEFAULT, "%s: pid %d not supported when freezer is disabled.", __func__, pid); error = ENOTSUP; } #else /* * If a pid has been provided, we obtain the process handle and call task_for_pid_posix_check(). */ if (pid >= 0) { targetproc = proc_find(pid); if (targetproc == PROC_NULL) { error = ESRCH; goto out; } if (!task_for_pid_posix_check(targetproc)) { error = EPERM; goto out; } } #if CONFIG_MACF //Note that targetproc may be null error = mac_proc_check_suspend_resume(targetproc, MAC_PROC_CHECK_HIBERNATE); if (error) { error = EPERM; goto out; } #endif if (pid == -1) { memorystatus_on_inactivity(targetproc); } else if (pid >= 0) { error = memorystatus_freeze_process_sync(targetproc); } /* We already handled the pid == -2 case */ out: #endif /* CONFIG_FREEZE */ if (targetproc != PROC_NULL) { proc_rele(targetproc); } *ret = error; return error; } #endif /* !XNU_TARGET_OS_OSX */ #if SOCKETS #if SKYWALK /* * Since we make multiple passes across the fileproc array, record the * first MAX_CHANNELS channel handles found. MAX_CHANNELS should be * large enough to accomodate most, if not all cases. If we find more, * we'll go to the slow path during second pass. */ #define MAX_CHANNELS 8 /* should be more than enough */ #endif /* SKYWALK */ static int networking_defunct_callout(proc_t p, void *arg) { struct pid_shutdown_sockets_args *args = arg; int pid = args->pid; int level = args->level; struct fileproc *fp; #if SKYWALK int i; int channel_count = 0; struct kern_channel *channel_array[MAX_CHANNELS]; bzero(&channel_array, sizeof(channel_array)); sk_protect_t protect = sk_async_transmit_protect(); #endif /* SKYWALK */ proc_fdlock(p); fdt_foreach(fp, p) { struct fileglob *fg = fp->fp_glob; switch (FILEGLOB_DTYPE(fg)) { case DTYPE_SOCKET: { struct socket *so = (struct socket *)fg_get_data(fg); if (proc_getpid(p) == pid || so->last_pid == pid || ((so->so_flags & SOF_DELEGATED) && so->e_pid == pid)) { /* Call networking stack with socket and level */ (void)socket_defunct(p, so, level); } break; } #if NECP case DTYPE_NETPOLICY: /* first pass: defunct necp and get stats for ntstat */ if (proc_getpid(p) == pid) { necp_fd_defunct(p, (struct necp_fd_data *)fg_get_data(fg)); } break; #endif /* NECP */ #if SKYWALK case DTYPE_CHANNEL: /* first pass: get channels and total count */ if (proc_getpid(p) == pid) { if (channel_count < MAX_CHANNELS) { channel_array[channel_count] = (struct kern_channel *)fg_get_data(fg); } ++channel_count; } break; #endif /* SKYWALK */ default: break; } } #if SKYWALK /* * Second pass: defunct channels/flows (after NECP). Handle * the common case of up to MAX_CHANNELS count with fast path, * and traverse the fileproc array again only if we exceed it. */ if (channel_count != 0 && channel_count <= MAX_CHANNELS) { ASSERT(proc_getpid(p) == pid); for (i = 0; i < channel_count; i++) { ASSERT(channel_array[i] != NULL); kern_channel_defunct(p, channel_array[i]); } } else if (channel_count != 0) { ASSERT(proc_getpid(p) == pid); fdt_foreach(fp, p) { struct fileglob *fg = fp->fp_glob; if (FILEGLOB_DTYPE(fg) == DTYPE_CHANNEL) { kern_channel_defunct(p, (struct kern_channel *)fg_get_data(fg)); } } } sk_async_transmit_unprotect(protect); #endif /* SKYWALK */ proc_fdunlock(p); return PROC_RETURNED; } int pid_shutdown_sockets(struct proc *p __unused, struct pid_shutdown_sockets_args *args, int *ret) { int error = 0; proc_t targetproc = PROC_NULL; int pid = args->pid; int level = args->level; if (level != SHUTDOWN_SOCKET_LEVEL_DISCONNECT_SVC && level != SHUTDOWN_SOCKET_LEVEL_DISCONNECT_ALL) { error = EINVAL; goto out; } targetproc = proc_find(pid); if (targetproc == PROC_NULL) { error = ESRCH; goto out; } if (!task_for_pid_posix_check(targetproc) && !IOCurrentTaskHasEntitlement(PROCESS_RESUME_SUSPEND_ENTITLEMENT)) { error = EPERM; goto out; } #if CONFIG_MACF error = mac_proc_check_suspend_resume(targetproc, MAC_PROC_CHECK_SHUTDOWN_SOCKETS); if (error) { error = EPERM; goto out; } #endif proc_iterate(PROC_ALLPROCLIST | PROC_NOWAITTRANS, networking_defunct_callout, args, NULL, NULL); out: if (targetproc != PROC_NULL) { proc_rele(targetproc); } *ret = error; return error; } #endif /* SOCKETS */ #if DEVELOPMENT || DEBUG /* * PT: Sadly this needs to be in bsd/ as SYSCTL_ macros aren't easily usable from * osfmk/. Ideally this sysctl would live in corpse_info.c */ extern uint32_t total_corpses_allowed; SYSCTL_UINT(_kern, OID_AUTO, total_corpses_allowed, CTLFLAG_RW | CTLFLAG_LOCKED, &total_corpses_allowed, DEFAULT_TOTAL_CORPSES_ALLOWED, "Maximum in-flight corpse count"); #endif /* DEVELOPMENT || DEBUG */