mirror of
https://github.com/darlinghq/darling-newlkm.git
synced 2024-11-30 07:20:29 +00:00
573 lines
13 KiB
C
573 lines
13 KiB
C
/*
|
|
* Darling Mach Linux Kernel Module
|
|
* Copyright (C) 2015-2020 Lubos Dolezel
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include <duct/compiler/clang/asm-inline.h>
|
|
#include <linux/types.h>
|
|
#include "task_registry.h"
|
|
#include <linux/rbtree.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/version.h>
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
|
#include <linux/sched/signal.h>
|
|
#else
|
|
#include <linux/sched.h>
|
|
#endif
|
|
#include <linux/completion.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/hashtable.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/semaphore.h>
|
|
#include <linux/list.h>
|
|
#include <linux/eventfd.h>
|
|
#include <linux/mutex.h>
|
|
#include "debug_print.h"
|
|
#include "uthreads.h"
|
|
|
|
DEFINE_SPINLOCK(my_task_lock);
|
|
DEFINE_SPINLOCK(my_thread_lock);
|
|
DEFINE_HASHTABLE(all_tasks, 6);
|
|
DEFINE_HASHTABLE(all_threads, 6);
|
|
static unsigned int task_count = 0;
|
|
|
|
extern unsigned int task_get_linux_pid(task_t t);
|
|
|
|
static struct registry_entry* darling_task_get_entry_unlocked(int pid);
|
|
|
|
#define THREAD_CANCELDISABLED 0x1
|
|
#define THREAD_CANCELED 0x2
|
|
|
|
struct registry_entry
|
|
{
|
|
struct hlist_node node;
|
|
int tid;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
task_t task;
|
|
void* keys[TASK_KEY_COUNT];
|
|
task_key_dtor dtors[TASK_KEY_COUNT];
|
|
|
|
struct semaphore sem_fork;
|
|
struct list_head proc_notification;
|
|
struct mutex mut_proc_notification;
|
|
struct completion proc_dyld_info;
|
|
unsigned long proc_dyld_info_allimg_loc, proc_dyld_info_allimg_len;
|
|
bool do_sigstop;
|
|
};
|
|
|
|
struct
|
|
{
|
|
thread_t thread;
|
|
int flags;
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
struct proc_notification
|
|
{
|
|
struct list_head list;
|
|
int registration_id;
|
|
darling_proc_notification_listener listener;
|
|
void* context;
|
|
darling_proc_event_t event_mask;
|
|
};
|
|
|
|
static struct kmem_cache* proc_notification_cache = NULL;
|
|
|
|
void darling_task_init(void)
|
|
{
|
|
proc_notification_cache = kmem_cache_create("proc_notification", sizeof(struct proc_notification), 0, 0, NULL);
|
|
}
|
|
|
|
static void darling_proc_post_notification_internal(struct registry_entry* entry, darling_proc_event_t event, unsigned long int extra)
|
|
{
|
|
struct list_head *pos, *tmp;
|
|
|
|
debug_msg("Posting notification 0x%x for task %p\n", event, entry->task);
|
|
|
|
mutex_lock(&entry->mut_proc_notification);
|
|
|
|
list_for_each_safe(pos, tmp, &entry->proc_notification)
|
|
{
|
|
struct proc_notification* dn = list_entry(pos, struct proc_notification, list);
|
|
if ((event & dn->event_mask) != 0) {
|
|
dn->listener(entry->tid, dn->context, event, extra);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&entry->mut_proc_notification);
|
|
}
|
|
|
|
_Bool darling_proc_post_notification(int pid, darling_proc_event_t event, unsigned long int extra)
|
|
{
|
|
struct registry_entry* e;
|
|
|
|
rcu_read_lock();
|
|
e = darling_task_get_entry_unlocked(pid);
|
|
rcu_read_unlock();
|
|
|
|
if (e != NULL) {
|
|
darling_proc_post_notification_internal(e, event, extra);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct registry_entry* darling_task_get_current_entry(void)
|
|
{
|
|
rcu_read_lock();
|
|
struct registry_entry* e = darling_task_get_entry_unlocked(current->tgid);
|
|
rcu_read_unlock();
|
|
return e;
|
|
}
|
|
|
|
static struct registry_entry* darling_task_get_entry_unlocked(int pid)
|
|
{
|
|
struct registry_entry* ret;
|
|
|
|
hash_for_each_possible_rcu(all_tasks, ret, node, pid)
|
|
{
|
|
if (ret->tid == pid)
|
|
return ret;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
task_t darling_task_get_current(void)
|
|
{
|
|
struct registry_entry* ret = darling_task_get_current_entry();
|
|
return (ret) ? ret->task : NULL;
|
|
}
|
|
|
|
extern void task_reference_wrapper(task_t);
|
|
task_t darling_task_get(int pid)
|
|
{
|
|
task_t task = NULL;
|
|
rcu_read_lock();
|
|
|
|
struct registry_entry* ret = darling_task_get_entry_unlocked(pid);
|
|
if (ret != NULL)
|
|
{
|
|
task_reference_wrapper(ret->task);
|
|
task = ret->task;
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
return task;
|
|
}
|
|
|
|
// Requires read_lock(&my_thread_lock)
|
|
struct registry_entry* darling_thread_get_entry(unsigned int pid)
|
|
{
|
|
struct registry_entry* ret;
|
|
|
|
hash_for_each_possible_rcu(all_threads, ret, node, pid)
|
|
{
|
|
if (ret->tid == pid)
|
|
return ret;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct registry_entry* darling_thread_get_current_entry(void)
|
|
{
|
|
struct registry_entry* e;
|
|
|
|
rcu_read_lock();
|
|
e = darling_thread_get_entry(current->pid);
|
|
rcu_read_unlock();
|
|
|
|
return e;
|
|
}
|
|
|
|
thread_t darling_thread_get_current(void)
|
|
{
|
|
struct registry_entry* e = darling_thread_get_current_entry();
|
|
if (!e)
|
|
{
|
|
debug_msg("No current thread in registry!\n");
|
|
return NULL;
|
|
}
|
|
return e->thread;
|
|
}
|
|
|
|
thread_t darling_thread_get(unsigned int pid)
|
|
{
|
|
struct registry_entry* e;
|
|
|
|
e = darling_thread_get_entry(current->pid);
|
|
|
|
if (e != NULL)
|
|
return e->thread;
|
|
return NULL;
|
|
}
|
|
|
|
void darling_task_register(task_t task)
|
|
{
|
|
struct registry_entry *entry, *this;
|
|
|
|
rcu_read_lock();
|
|
hash_for_each_possible_rcu(all_tasks, this, node, current->tgid)
|
|
{
|
|
if (this->tid == current->tgid)
|
|
{
|
|
debug_msg("Replacing task %p -> %p\n", this->task, task);
|
|
|
|
this->task = task;
|
|
|
|
// exec case
|
|
darling_proc_post_notification_internal(this, DTE_REPLACED, 0);
|
|
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
entry = (struct registry_entry*) kzalloc(sizeof(struct registry_entry), GFP_KERNEL);
|
|
entry->task = task;
|
|
entry->tid = current->tgid;
|
|
|
|
sema_init(&entry->sem_fork, 0);
|
|
INIT_LIST_HEAD(&entry->proc_notification);
|
|
mutex_init(&entry->mut_proc_notification);
|
|
init_completion(&entry->proc_dyld_info);
|
|
|
|
spin_lock(&my_task_lock);
|
|
hash_add_rcu(all_tasks, &entry->node, entry->tid);
|
|
task_count++;
|
|
spin_unlock(&my_task_lock);
|
|
}
|
|
|
|
|
|
void darling_thread_register(thread_t thread)
|
|
{
|
|
struct registry_entry *entry, *this;
|
|
|
|
rcu_read_lock();
|
|
|
|
hash_for_each_possible_rcu(all_threads, this, node, current->pid)
|
|
{
|
|
if (this->tid == current->pid)
|
|
{
|
|
if (this->thread != thread)
|
|
{
|
|
// Overwrite existing thread entry
|
|
debug_msg("Overwriting thread entry %p -> %p\n", this->thread, thread);
|
|
this->thread = thread;
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
entry = (struct registry_entry*) kzalloc(sizeof(struct registry_entry), GFP_KERNEL);
|
|
entry->thread = thread;
|
|
entry->tid = current->pid;
|
|
entry->flags = 0;
|
|
|
|
spin_lock(&my_thread_lock);
|
|
hash_add_rcu(all_threads, &entry->node, entry->tid);
|
|
spin_unlock(&my_thread_lock);
|
|
}
|
|
|
|
void darling_task_free(struct registry_entry* entry)
|
|
{
|
|
int i;
|
|
struct list_head *pos, *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &entry->proc_notification)
|
|
{
|
|
struct proc_notification* dn = list_entry(pos, struct proc_notification, list);
|
|
kmem_cache_free(proc_notification_cache, dn);
|
|
}
|
|
|
|
for (i = 0; i < TASK_KEY_COUNT; i++)
|
|
{
|
|
if (entry->keys[i] != NULL)
|
|
{
|
|
// printk(KERN_NOTICE "ptr %d = %p\n", i, entry->keys[i]);
|
|
entry->dtors[i](entry->keys[i]);
|
|
}
|
|
}
|
|
|
|
kfree(entry);
|
|
}
|
|
|
|
void darling_task_deregister(task_t t)
|
|
{
|
|
struct registry_entry *entry;
|
|
|
|
spin_lock(&my_task_lock);
|
|
|
|
hash_for_each_possible(all_tasks, entry, node, current->tgid)
|
|
{
|
|
if (entry->tid == current->tgid)
|
|
{
|
|
if (entry->task != t)
|
|
break;
|
|
|
|
hash_del_rcu(&entry->node);
|
|
task_count--;
|
|
spin_unlock(&my_task_lock);
|
|
synchronize_rcu();
|
|
|
|
darling_task_free(entry);
|
|
return;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&my_task_lock);
|
|
}
|
|
|
|
void darling_thread_deregister(thread_t t)
|
|
{
|
|
struct registry_entry *entry;
|
|
unsigned int temp;
|
|
|
|
spin_lock(&my_thread_lock);
|
|
|
|
hash_for_each(all_threads, temp, entry, node)
|
|
{
|
|
if (entry->thread == t)
|
|
{
|
|
debug_msg("deregistering thread %p\n", t);
|
|
|
|
hash_del_rcu(&entry->node);
|
|
spin_unlock(&my_thread_lock);
|
|
synchronize_rcu();
|
|
|
|
kfree(entry);
|
|
return;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&my_thread_lock);
|
|
}
|
|
|
|
bool darling_task_key_set(unsigned int key, void* value, task_key_dtor dtor)
|
|
{
|
|
struct registry_entry* entry;
|
|
entry = darling_task_get_current_entry();
|
|
|
|
if (entry->keys[key] != NULL)
|
|
return false;
|
|
|
|
if (cmpxchg(&entry->keys[key], NULL, value) != NULL)
|
|
return false;
|
|
|
|
entry->dtors[key] = dtor;
|
|
|
|
return true;
|
|
}
|
|
|
|
void* darling_task_key_get(unsigned int key)
|
|
{
|
|
struct registry_entry* entry;
|
|
entry = darling_task_get_current_entry();
|
|
return entry ? entry->keys[key] : NULL;
|
|
}
|
|
|
|
void darling_task_fork_wait_for_child(void)
|
|
{
|
|
struct registry_entry* e = darling_task_get_current_entry();
|
|
|
|
down_interruptible(&e->sem_fork);
|
|
}
|
|
|
|
void darling_task_fork_child_done(void)
|
|
{
|
|
rcu_read_lock();
|
|
struct registry_entry* entry = darling_task_get_entry_unlocked(current->real_parent->tgid);
|
|
|
|
debug_msg("task_fork_child_done - notify entry %p\n", entry);
|
|
if (entry != NULL)
|
|
up(&entry->sem_fork);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
long int darling_proc_notify_register(int pid, darling_proc_notification_listener listener, void* context, darling_proc_event_t event_mask)
|
|
{
|
|
struct proc_notification* dn;
|
|
struct registry_entry* e;
|
|
static long int reg_id_counter = 0;
|
|
long int rv = 0;
|
|
|
|
rcu_read_lock();
|
|
|
|
e = darling_task_get_entry_unlocked(pid);
|
|
|
|
if (e == NULL) {
|
|
rv = -ESRCH;
|
|
goto out;
|
|
}
|
|
|
|
dn = (struct proc_notification*)kmem_cache_alloc(proc_notification_cache, GFP_KERNEL);
|
|
dn->listener = listener;
|
|
dn->context = context;
|
|
dn->event_mask = event_mask;
|
|
|
|
mutex_lock(&e->mut_proc_notification);
|
|
rv = dn->registration_id = reg_id_counter++;
|
|
// underflow
|
|
if (reg_id_counter < 0) {
|
|
// i doubt we'll have both listener `0` and listener `(2**63)` active at the same time
|
|
reg_id_counter = 0;
|
|
}
|
|
list_add(&dn->list, &e->proc_notification);
|
|
mutex_unlock(&e->mut_proc_notification);
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return rv;
|
|
}
|
|
|
|
_Bool darling_proc_notify_deregister(int pid, long int registration_id)
|
|
{
|
|
struct registry_entry* e;
|
|
_Bool rv = false;
|
|
struct list_head* next;
|
|
|
|
rcu_read_lock();
|
|
|
|
e = darling_task_get_entry_unlocked(pid);
|
|
|
|
if (e == NULL)
|
|
{
|
|
debug_msg("darling_proc_notify_deregister failed to get task for PID %d\n", pid);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&e->mut_proc_notification);
|
|
list_for_each(next, &e->proc_notification)
|
|
{
|
|
struct proc_notification* dn = list_entry(next, struct proc_notification, list);
|
|
|
|
if (dn->registration_id == registration_id)
|
|
{
|
|
debug_msg("darling_proc_notify_deregister found proc notification entry for id %ld, deleting it...\n", registration_id);
|
|
rv = true;
|
|
list_del(&dn->list);
|
|
kmem_cache_free(proc_notification_cache, dn);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&e->mut_proc_notification);
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return rv;
|
|
}
|
|
|
|
void darling_task_set_dyld_info(unsigned long all_img_location, unsigned long all_img_length)
|
|
{
|
|
struct registry_entry* e = darling_task_get_current_entry();
|
|
|
|
e->proc_dyld_info_allimg_loc = all_img_location;
|
|
e->proc_dyld_info_allimg_len = all_img_length;
|
|
|
|
complete_all(&e->proc_dyld_info);
|
|
}
|
|
|
|
void darling_task_get_dyld_info(unsigned int pid, unsigned long long* all_img_location, unsigned long long* all_img_length)
|
|
{
|
|
struct registry_entry* e;
|
|
|
|
*all_img_location = *all_img_length = 0;
|
|
rcu_read_lock();
|
|
|
|
e = darling_task_get_entry_unlocked(pid);
|
|
if (e != NULL)
|
|
{
|
|
if (wait_for_completion_interruptible(&e->proc_dyld_info) == 0)
|
|
{
|
|
*all_img_location = e->proc_dyld_info_allimg_loc;
|
|
*all_img_length = e->proc_dyld_info_allimg_len;
|
|
}
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
// Will send SIGSTOP at next darling_task_set_dyld_info() call
|
|
void darling_task_mark_start_suspended(void)
|
|
{
|
|
struct registry_entry* e = darling_task_get_current_entry();
|
|
e->do_sigstop = true;
|
|
}
|
|
|
|
_Bool darling_task_marked_start_suspended(void)
|
|
{
|
|
struct registry_entry* e = darling_task_get_current_entry();
|
|
bool rv = e->do_sigstop;
|
|
e->do_sigstop = false;
|
|
return rv;
|
|
}
|
|
|
|
extern struct uthread* current_uthread(void);
|
|
|
|
_Bool darling_thread_canceled(void)
|
|
{
|
|
struct uthread* uth = current_uthread();
|
|
return (uth == NULL) ? false : darling_uthread_is_canceling(uth);
|
|
}
|
|
|
|
void darling_thread_cancelable(_Bool cancelable)
|
|
{
|
|
struct uthread* uth = current_uthread();
|
|
if (uth != NULL) {
|
|
darling_uthread_change_cancelable(uth, cancelable);
|
|
}
|
|
}
|
|
|
|
extern struct task_struct* thread_get_linux_task(thread_t thread);
|
|
extern struct uthread* get_bsdthread_info(thread_t thread);
|
|
|
|
void darling_thread_markcanceled(unsigned int pid)
|
|
{
|
|
struct registry_entry* e;
|
|
struct uthread* uth;
|
|
|
|
debug_msg("Marking thread %d as canceled\n", pid);
|
|
|
|
rcu_read_lock();
|
|
e = darling_thread_get_entry(pid);
|
|
uth = (e == NULL) ? NULL : get_bsdthread_info(e->thread);
|
|
|
|
if (uth != NULL && darling_uthread_mark_canceling(uth))
|
|
{
|
|
// FIXME: We should be calling wake_up_state(TASK_INTERRUPTIBLE),
|
|
// but this function is not exported.
|
|
wake_up_process(thread_get_linux_task(e->thread));
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
|