How to pause a task's thread from within the kernel

Questions and Answers about all things *OS (macOS, iOS, tvOS, watchOS)

How to pause a task's thread from within the kernel

Postby TheDarkKnight » Tue May 17, 2016 4:19 pm

Given a pid of a process, my goal is to pause a task's thread.

This can be done successfully from a userland application, after retrieving a process's task port, by retrieving the task's thread list and calling task_suspend on a thread.

However, I now need to do the same from within a kernel extension. I have the following code:-

Code: Select all
// Minimal process structure - don't need the rest of it!
struct   proc {
    LIST_ENTRY(proc) p_list;      /* List of all processes. */
   
    pid_t      p_pid;
    void *       task;
};


void PauseMainThread(pid)
{
        proc_t proc = proc_find(pid);
        if(!proc)
        {
            IOLog("failed to find process with pid: %d\n", pid);
            return;
        }
       
        task_t targetTask = (task_t)self->task
       
        _task_reference(targetTask);     
       
        thread_act_array_t th_act_array = 0;
        mach_msg_type_number_t listCnt = 0;
       
        if(_task_threads(targetTask, &th_act_array, &listCnt) != KERN_SUCCESS)
        {
            IOLog("failed to retrieve task thread list for %d\n", pid);
        }
        else if(listCnt)
        {
            IOLog("pausing thread with pid: %d | numThreads: %d\n", pid, listCnt);
            thread_act_t targetThread = th_act_array[0];

            kern_return_t ret;
            ret = _thread_suspend(targetThread); // suspend the thread
            if(ret != KERN_SUCCESS)
                IOLog("failed to suspend thread for proc %d, error: %d\n", pid, ret);           
        }       
        proc_rele(proc);
}


Private functions have been correctly resolved for _task_reference, _task_threads and _thread_suspend.

The problem I'm seeing is that a call to _thread_suspend usually returns with error 37 - KERN_TERMINATED, which is commented in kern_return.h as

Object has been terminated and is no longer available


I can confirm that the process in question is still running at the time _task_suspend is called, so what is the problem here and why does this not pause the process's thread, as expected?
TheDarkKnight
 
Posts: 26
Joined: Wed Dec 16, 2015 10:30 am

Re: How to pause a task's thread from within the kernel

Postby morpheus » Tue May 17, 2016 6:31 pm

Two Dark Knights with questions in one day :-)

Observe the code from http://newosxbook.com/src.jl?tree=xnu&f ... e_internal

THREAD_TERMINATE is called if your thread isn't active at the time you're trying this, you get THREAD_TERMINATED.

You're racing the thread, which is why you get that.
morpheus
Site Admin
 
Posts: 530
Joined: Thu Apr 11, 2013 6:24 pm

Re: How to pause a task's thread from within the kernel

Postby TheDarkKnight » Tue May 17, 2016 9:24 pm

Thanks for the response J.

If THREAD_TERMINATE is being called and this is the first thread, wouldn't that imply that the process would fail to run, as this is not what I'm seeing?

If I delay the call to ensure that the target process is in the foreground I still get the same issue, or sometimes the kernel panics.

Any ideas how I can do this safely?
TheDarkKnight
 
Posts: 26
Joined: Wed Dec 16, 2015 10:30 am

Re: How to pause a task's thread from within the kernel

Postby morpheus » Wed May 18, 2016 5:32 am

Ok - so first, a correction. I saw the link I posted by mistake was for thread_terminate_internal. I of course meant thread_suspend,

which is : http://newosxbook.com/src.jl?tree=xnu&f ... ad_suspend

And to be more specific, the code is:

Code: Select all
   if (thread->active) {
      if (   thread->user_stop_count++ == 0      &&
            thread->suspend_count++ == 0      ) {
         install_special_handler(thread);
         if (thread != self)
            thread_wakeup_one(&thread->suspend_count);
      }
   }
   else
      result = KERN_TERMINATED;

   thread_mtx_unlock(thread);

   if (thread != self && result == KERN_SUCCESS)
      thread_wait(thread, FALSE);


and you get the KERN_TERMINATED, above, though the thread isn't necessarily terminated - it's just not active.

Now, re why you get a panic, I won't know without seeing what the panic string is. Ditto for doing it "safely".KAuth isn't designed for the functionality of stopping threads - what you're trying to achieve is a hack. In theory, though, it should be simple as waiting for thread to become active, THEN calling thread_suspend. If you wait too long, thread might be gone or mem referenced might change, which could explain panic . A panic message or - better yet - reproducing code , could help.
morpheus
Site Admin
 
Posts: 530
Joined: Thu Apr 11, 2013 6:24 pm

Re: How to pause a task's thread from within the kernel

Postby TheDarkKnight » Wed May 18, 2016 8:18 am

it should be simple as waiting for thread to become active


I've moved the pause code out of the KAuth callback and call it on a message to the kext from a user-land daemon (using IOConnectCallMethod), after the daemon has been notified of the target process execution, via the file scope.

Rather than trying to calculate the address of the active variable (flag), as it's prone to change between OS X releases, I tried calling thread_suspend repeatedly, until it succeeds: -

Code: Select all
       if(_task_threads(targetTask, &th_act_array, &listCnt) != KERN_SUCCESS)
        {
            IOLog("FileWatcher - failed to retrieve task thread list for %d\n", pid);
        }
        else if(listCnt)
        {
            IOLog("FileWatcher::processed - pausing thread %d | numThreads: %d\n", pid, listCnt);
            thread_act_t targetThread = th_act_array[0];
            kern_return_t ret = KERN_NO_ACCESS;
            do
            {
                ret = _thread_suspend(targetThread);

                if(ret != KERN_SUCCESS)
                    IOLog("failed to suspend thread for proc %d, error: %d\n", pid, ret);

            }while(ret != KERN_SUCCESS);
           
        }


The kernel is happy (no panic or immediate, adverse affects) but repeatedly prints out that it failed to suspend. Meanwhile, the target application executes and functions normally.

I then changed the code to call thread_suspend once, in the kext and return the result to the user-land daemon. Even if the daemon retries every half a second, calling to the kext to suspend the thread, the same failure occurs with error 37 (KERN_TERMINATED), so it's as though the thread is never active.

Is it possible that the target thread is subject to preemption every time I enter the kernel to suspend it, or is something else going on?
TheDarkKnight
 
Posts: 26
Joined: Wed Dec 16, 2015 10:30 am

Re: How to pause a task's thread from within the kernel

Postby TheDarkKnight » Tue Jun 14, 2016 10:42 am

Is it possible that the target thread is subject to preemption every time I enter the kernel to suspend it, or is something else going on?


Any thoughts on this?
Alternatively, is there another way to ensure that a new process's target thread can be suspended before it executes its 'main' function?
TheDarkKnight
 
Posts: 26
Joined: Wed Dec 16, 2015 10:30 am


Return to Questions and Answers

Who is online

Users browsing this forum: No registered users and 1 guest