#include <sys/ktrace.h>
#include <mach/host_priv.h>
#include <mach/mach_types.h>
#include <mach/ktrace_background.h>
#include <sys/kauth.h>
#include <sys/priv.h>
#include <sys/proc.h>
char *proc_name_address(void *p);
#include <sys/sysctl.h>
#include <sys/vm.h>
#include <kern/locks.h>
#include <kern/assert.h>
#include <sys/kdebug.h>
#include <kperf/kperf.h>
#include <kern/host.h>
kern_return_t ktrace_background_available_notify_user(void);
lck_mtx_t *ktrace_lock;
static enum ktrace_state ktrace_state = KTRACE_STATE_OFF;
static uint64_t ktrace_owning_unique_id = 0;
static pid_t ktrace_owning_pid = 0;
static uint64_t ktrace_bg_unique_id = 0;
static pid_t ktrace_bg_pid = 0;
static char ktrace_last_owner_execname[MAXCOMLEN + 1] = { 0 };
static uint32_t ktrace_active_mask = 0;
static boolean_t should_notify_on_init = TRUE;
static void ktrace_set_owning_proc(proc_t p);
static void ktrace_release_ownership(void);
static void ktrace_promote_background(void);
boolean_t ktrace_keep_ownership_on_reset = FALSE;
static void ktrace_set_invalid_owning_pid(void);
int ktrace_root_set_owner_allowed = 0;
void
ktrace_reset(uint32_t reset_mask)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
assert(reset_mask != 0);
if (ktrace_active_mask == 0) {
if (!ktrace_keep_ownership_on_reset) {
assert(ktrace_state == KTRACE_STATE_OFF);
}
return;
}
if (!ktrace_keep_ownership_on_reset) {
ktrace_active_mask &= ~reset_mask;
}
if (reset_mask & KTRACE_KPERF) {
kperf_reset();
}
if (reset_mask & KTRACE_KDEBUG) {
kdebug_reset();
}
if (ktrace_active_mask == 0) {
if (ktrace_state == KTRACE_STATE_FG) {
ktrace_promote_background();
} else if (ktrace_state == KTRACE_STATE_BG) {
should_notify_on_init = TRUE;
ktrace_release_ownership();
ktrace_state = KTRACE_STATE_OFF;
}
}
}
static void
ktrace_promote_background(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
assert(ktrace_state != KTRACE_STATE_BG);
if (ktrace_background_available_notify_user() == KERN_FAILURE) {
should_notify_on_init = TRUE;
} else {
should_notify_on_init = FALSE;
}
ktrace_release_ownership();
ktrace_state = KTRACE_STATE_OFF;
}
bool
ktrace_background_active(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
return (ktrace_state == KTRACE_STATE_BG);
}
int
ktrace_read_check(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
if (proc_uniqueid(current_proc()) == ktrace_owning_unique_id)
{
return 0;
}
return kauth_cred_issuser(kauth_cred_get()) ? 0 : EPERM;
}
static void
ktrace_ownership_maintenance(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
if (ktrace_owning_unique_id == 0) {
return;
}
proc_t owning_proc = proc_find(ktrace_owning_pid);
if (owning_proc != NULL) {
if (proc_uniqueid(owning_proc) != ktrace_owning_unique_id) {
ktrace_release_ownership();
}
proc_rele(owning_proc);
} else {
ktrace_release_ownership();
}
}
int
ktrace_configure(uint32_t config_mask)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
assert(config_mask != 0);
proc_t p = current_proc();
if (proc_uniqueid(p) == ktrace_owning_unique_id) {
ktrace_active_mask |= config_mask;
return 0;
}
if (proc_uniqueid(p) == ktrace_bg_unique_id &&
ktrace_state == KTRACE_STATE_FG)
{
return EBUSY;
}
ktrace_ownership_maintenance();
if (ktrace_owning_unique_id == 0 || ktrace_state == KTRACE_STATE_BG) {
if (!kauth_cred_issuser(kauth_cred_get())) {
return EPERM;
}
ktrace_set_owning_proc(p);
ktrace_active_mask |= config_mask;
return 0;
}
return EBUSY;
}
void
ktrace_disable(enum ktrace_state state_to_match)
{
if (ktrace_state == state_to_match) {
kernel_debug_disable();
kperf_sampling_disable();
}
}
int
ktrace_get_owning_pid(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
ktrace_ownership_maintenance();
return ktrace_owning_pid;
}
void
ktrace_kernel_configure(uint32_t config_mask)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
if (ktrace_state != KTRACE_STATE_OFF) {
if (ktrace_active_mask & KTRACE_KPERF) {
kperf_reset();
}
if (ktrace_active_mask & KTRACE_KDEBUG) {
kdebug_reset();
}
}
ktrace_active_mask = config_mask;
ktrace_state = KTRACE_STATE_FG;
ktrace_release_ownership();
strlcpy(ktrace_last_owner_execname, "kernel_task",
sizeof(ktrace_last_owner_execname));
}
static errno_t
ktrace_init_background(void)
{
int err = 0;
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
if ((err = priv_check_cred(kauth_cred_get(), PRIV_KTRACE_BACKGROUND, 0))) {
return err;
}
if (should_notify_on_init) {
if (ktrace_state == KTRACE_STATE_OFF) {
if (ktrace_background_available_notify_user() == KERN_FAILURE) {
return EINVAL;
}
}
should_notify_on_init = FALSE;
}
proc_t p = current_proc();
ktrace_bg_unique_id = proc_uniqueid(p);
ktrace_bg_pid = proc_pid(p);
if (ktrace_state == KTRACE_STATE_BG) {
ktrace_set_owning_proc(p);
}
return 0;
}
void
ktrace_set_invalid_owning_pid(void)
{
if (ktrace_keep_ownership_on_reset) {
ktrace_keep_ownership_on_reset = FALSE;
ktrace_reset(ktrace_active_mask);
}
}
int
ktrace_set_owning_pid(int pid)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
if (pid == -1) {
ktrace_set_invalid_owning_pid();
return 0;
}
if (pid == 0) {
ktrace_set_invalid_owning_pid();
return EINVAL;
}
proc_t p = proc_find(pid);
if (!p) {
ktrace_set_invalid_owning_pid();
return ESRCH;
}
ktrace_keep_ownership_on_reset = TRUE;
ktrace_set_owning_proc(p);
proc_rele(p);
return 0;
}
static void
ktrace_set_owning_proc(proc_t p)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
assert(p);
if (ktrace_state != KTRACE_STATE_FG) {
if (proc_uniqueid(p) == ktrace_bg_unique_id) {
ktrace_state = KTRACE_STATE_BG;
} else {
if (ktrace_state == KTRACE_STATE_BG) {
if (ktrace_active_mask & KTRACE_KPERF) {
kperf_reset();
}
if (ktrace_active_mask & KTRACE_KDEBUG) {
kdebug_reset();
}
ktrace_active_mask = 0;
}
ktrace_state = KTRACE_STATE_FG;
should_notify_on_init = FALSE;
}
}
ktrace_owning_unique_id = proc_uniqueid(p);
ktrace_owning_pid = proc_pid(p);
strlcpy(ktrace_last_owner_execname, proc_name_address(p),
sizeof(ktrace_last_owner_execname));
}
static void
ktrace_release_ownership(void)
{
lck_mtx_assert(ktrace_lock, LCK_MTX_ASSERT_OWNED);
ktrace_owning_unique_id = 0;
ktrace_owning_pid = 0;
}
#define SYSCTL_INIT_BACKGROUND (1)
static int ktrace_sysctl SYSCTL_HANDLER_ARGS;
SYSCTL_NODE(, OID_AUTO, ktrace, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "ktrace");
SYSCTL_UINT(_ktrace, OID_AUTO, state, CTLFLAG_RD | CTLFLAG_LOCKED,
&ktrace_state, 0,
"");
SYSCTL_INT(_ktrace, OID_AUTO, owning_pid, CTLFLAG_RD | CTLFLAG_LOCKED,
&ktrace_owning_pid, 0,
"pid of the process that owns ktrace");
SYSCTL_INT(_ktrace, OID_AUTO, background_pid, CTLFLAG_RD | CTLFLAG_LOCKED,
&ktrace_bg_pid, 0,
"pid of the background ktrace tool");
SYSCTL_STRING(_ktrace, OID_AUTO, configured_by, CTLFLAG_RD | CTLFLAG_LOCKED,
ktrace_last_owner_execname, 0,
"execname of process that last configured ktrace");
SYSCTL_PROC(_ktrace, OID_AUTO, init_background, CTLFLAG_RW | CTLFLAG_LOCKED,
(void *)SYSCTL_INIT_BACKGROUND, sizeof(int),
ktrace_sysctl, "I", "initialize calling process as background");
static int
ktrace_sysctl SYSCTL_HANDLER_ARGS
{
#pragma unused(oidp, arg2)
int ret = 0;
uintptr_t type = (uintptr_t)arg1;
lck_mtx_lock(ktrace_lock);
if (!kauth_cred_issuser(kauth_cred_get())) {
ret = EPERM;
goto out;
}
if (type == SYSCTL_INIT_BACKGROUND) {
if (req->newptr != USER_ADDR_NULL) {
ret = ktrace_init_background();
goto out;
} else {
ret = EINVAL;
goto out;
}
} else {
ret = EINVAL;
goto out;
}
out:
lck_mtx_unlock(ktrace_lock);
return ret;
}
void
ktrace_init(void)
{
static lck_grp_attr_t *lock_grp_attr = NULL;
static lck_grp_t *lock_grp = NULL;
static boolean_t initialized = FALSE;
if (initialized) {
return;
}
lock_grp_attr = lck_grp_attr_alloc_init();
lock_grp = lck_grp_alloc_init("ktrace", lock_grp_attr);
lck_grp_attr_free(lock_grp_attr);
ktrace_lock = lck_mtx_alloc_init(lock_grp, LCK_ATTR_NULL);
assert(ktrace_lock);
initialized = TRUE;
}