10.14 and task_for_pid

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

10.14 and task_for_pid

Postby scknight » Sat Jan 05, 2019 5:13 pm

I'm trying to write some short sample code that creates a new process in a suspended fashion and then allows me to use task_for_pid to connect and modify the new process and resume it. I'm testing this all out on 10.14.2. So for I've approached this by using the ptrace system calls. Here's the current code I have.

This is a small child process to test starting in a suspended state

Code: Select all
#include <stdio.h>

int main()
{
    puts("Hello World!");

    return 0;
}


And here's my code doing a fork and execv using ptrace to get the child into a suspended state

Code: Select all
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <mach/mach.h>

static void setup_inferior(const char *path, char *const argv[])
{
    int result;
    ptrace(PT_TRACE_ME, 0, 0, 0);
    execv(path, argv);
}

static void attach_to_inferior(pid_t pid)
{
    while(1) {
        int   status;
        waitpid(pid, &status, 0);

        if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
            printf("Inferior stopped on SIGTRAP - continuing...\n");

            task_t task;
            kern_return_t kr;

            kr = task_for_pid(mach_task_self(), pid, &task);
            if (kr != KERN_SUCCESS) {
                mach_error("task_for_pid:", kr);
            }
            else {
                printf("Got task port\n");
            }

            ptrace(PT_CONTINUE, pid, (caddr_t)1, 0);
        }
        else if (WIFEXITED(status)) {
            printf("Inferior exited - debugger terminating...\n");
            exit(0);
        }
    }
}

void dbg_inferior_exec(const char *path, char *const argv[])
{
    pid_t result;

    do {
        result = fork();
        switch (result) {
        case 0:   // inferior
            setup_inferior(path, argv);
            break;
        case -1:  // error
            break;
        default:  // debugger
            attach_to_inferior(result);
            break;
        }
  } while (result == -1 && errno == EAGAIN);
}

int main()
{
    char *argv[1] = { 0 };
    dbg_inferior_exec("./hello", argv);

    return 0;
}


What I'm trying to figure out is should this code work running as just a regular user? Right now it only works if I use sudo. When I look over the actually kernel code for task_for_pid I see roughly this logic

  • Don't allow pid 0 and the retrieval of the kernel port
  • Don't allow TASK_NULL being passed in
  • Don't allow an invalid pid
  • Perform a posix check (only allow current process of root OR self and target must have same uid, group set and hasn't switched credentials)
  • Performa a mac_proc_check_get_task check
  • If caller isn't root check if target task access port is set and if it is call out to taskgated to check if allowed

What I can't figure out is why the code above wouldn't work as a regular user. The pid being passed in isn't 0 it's the child pid. There's a valid target_tport passed in. It's a valid pid passed in. I would think the posix check passes. Because the parent and child have the same uid and gid. I don't think credentials have been switched. The only thing I can think of is mac_proc_check_get_task or taskgated blocking me. What am I missing? I tried looking through the kernel logs to see if there were any errors indicating what might be wrong but couldn't find any.

Is there a way to get the code sample above to work as a regular user?
scknight
 
Posts: 47
Joined: Thu Nov 10, 2016 1:01 pm

Re: 10.14 and task_for_pid

Postby scknight » Sat Jan 05, 2019 5:19 pm

And no sooner do I post this than do I notice that there is a relevant message in the console

From AMFI

Code: Select all
kernel   macOSTaskPolicy: (debugger) (pid: 5537) not (yet) allowed to go invalid
kernel   macOSTaskPolicy: (<unknown>) may not get the taskport of (hello) (pid: 5538): (hello) is not hardened and not restricted but (<unknown>) is not root, (hello) is not get-task-allow, (<unknown>) is not a declared debugger
kernel   warning: debugger(5537) performed out-of-band resume on pid 5538


I believe AMFI is using the macf hooks and that's what's stopping me then.

So is there any way on macOS as a regular user to start a second process and have task_for_pid access to it?
scknight
 
Posts: 47
Joined: Thu Nov 10, 2016 1:01 pm

Re: 10.14 and task_for_pid

Postby scknight » Sat Jan 05, 2019 5:27 pm

Looks like I can get it to work as a regular user if I code sign and add this entitlement

Code: Select all
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.debugger</key>
    <true/>
</dict>
</plist>


But then obviously I'm prompted for credentials to allow the debugging.

I know on linux I can basically take the same code sample and then use process_vm_readv and process_vm_writev to manipulate the process and all of it runs as a regular user. I'm just wondering if that's completely not an option on macOS or not.
scknight
 
Posts: 47
Joined: Thu Nov 10, 2016 1:01 pm

Re: 10.14 and task_for_pid

Postby morpheus » Sat Jan 05, 2019 6:17 pm

So, you basically answered yourself. The com.apple.security.cs.debugger is a new 10.14 (0x20500 code signature) entitlement Apple added specifically for MacOS developers.
(https://developer.apple.com/documentati ... guage=objc)

The credentials prompt you are seeing is coming from security agent. The process of getting the task port in MacOS is a bit different than on iOS, and involves not just amfi but /usr/libexec/taskgated. If you entitle the target process with get-task-allow then other processes can obtain its task port.

ptrace also works differently here - mostly enabling you to control process start/stop, but not like Linux's PEEK and POKE - for this you need the Mach APIs of mach_vm_read/write (similar to Linux's process_vm_readv in idea).

If you want to start a child, I'd suggest using posix_spawn with SPAWNATTR_SUSPENDED.
morpheus
Site Admin
 
Posts: 729
Joined: Thu Apr 11, 2013 6:24 pm

Re: 10.14 and task_for_pid

Postby scknight » Sat Jan 05, 2019 10:59 pm

Thanks a lot for the extra insight. I changed up the code I was using to this

Code: Select all
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>

int
main(int argc, char **argv)
{
    pid_t pid;
    int status;
    posix_spawnattr_t attr;

    status = posix_spawnattr_init(&attr);
    if (status != 0) {
        perror("can't init spawnattr");
        exit(status);
    }

    status = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
    if (status != 0) {
        perror("can't set flags");
        exit(status);
    }

    status = posix_spawn(&pid, "./hello", NULL, &attr, NULL, NULL);
    if (status != 0) {
        printf("posix_spawn: %s\n", strerror(status));
        exit(status);
    }

    printf("Child pid: %i\n", pid);
   
    // Wait for the child to actually suspend
    waitpid(pid, &status, WUNTRACED);
    if (WIFSTOPPED(status)) {
        printf("Child stopped on %d\n", WSTOPSIG(status));
           
        sleep(5);
        kill(pid, SIGCONT);
    }
    else {
        perror("child didn't suspend\n");
    }

    return 0;
}


And I think It's a lot nicer not having to use ptrace calls. I assuming sending SIGCONT is the correct way to tell the process to continue?
scknight
 
Posts: 47
Joined: Thu Nov 10, 2016 1:01 pm

Re: 10.14 and task_for_pid

Postby morpheus » Sun Jan 06, 2019 4:13 am

So, A) you're welcome B) you don't have to wait for the child to "actually suspend". It starts suspended. If you get the PID, the child is already stopped.
And the right way is to use task_resume. (or pid_resume, #434)
morpheus
Site Admin
 
Posts: 729
Joined: Thu Apr 11, 2013 6:24 pm

Re: 10.14 and task_for_pid

Postby scknight » Sun Jan 06, 2019 6:22 pm

What would be the proper way to call pid_resume? I tried just modifying the code to do this

Code: Select all
sleep(10);
status = pid_resume(pid);
if (status != 0) {
    printf("pid_resume: %s\n", strerror(status));
    exit(status);
}


And the code compiles but pid_resume returns -1. Even if running as root. I'm trying to come up with an example of creating a suspended process for macOS that doesn't require root or permissions so I don't want to use task_resume since I can't call task_for_pid without having root or the entitlements mentioned earlier.
scknight
 
Posts: 47
Joined: Thu Nov 10, 2016 1:01 pm


Return to Questions and Answers

Who is online

Users browsing this forum: No registered users and 2 guests