Endpoint Security Framework Bug: setuid Event Incorrectly Attributed to Parent Process During posix_spawn

Feedback ticket ID: FB21797397

Summary

When using posix_spawn() with posix_spawnattr_set_uid_np() to spawn a child process with a different UID, the eslogger incorrectly reports a setuid event as an event originating from the parent process instead of the child process.

Steps to Reproduce

  1. Create a binary that do the following:
    1. Configure posix_spawnattr_t that set the process UIDs to some other user ID (I'll use 501 in this example).
    2. Uses posix_spawn() to spawn a child process
  2. Run eslogger with the event types setuid, fork, exec
  3. Execute the binary as root process using sudo or from root owned shell
  4. Terminate the launched eslogger
  5. Observe the process field in the setuid event

Expected behavior

  • The eslogger will report events indicating a process launch and uid changes so the child process is set to 501. i.e.:
    • fork
    • setuid - Done by child process
    • exec

Actual behavior

The process field in the setuid event is reported as the parent process (that called posix_spawn) - indicating UID change to the parent process.

Attachments

I'm attaching source code for a small project with a 2 binaries:

I'll add the source code for the project at the end of the file + attach filtered eslogger JSONs

  1. One that runs the descirbed posix_spawn flow
  2. One that produces the exact same sequence of events by doing different operation and reaching a different process state:
    1. Parent calls fork()
    2. Parent process calls setuid(501)
    3. Child process calls exec()

Why this is problematic

Both binaries in my attachment do different operations, achieving different process state (1 is parent with UID=0 and child with UID=501 while the other is parent UID=501 and child UID=0), but report the same sequence of events.

Code

#include <cstdio>
#include <spawn.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

// environ contains the current environment variables
extern char **environ;

extern "C" {
int posix_spawnattr_set_uid_np(posix_spawnattr_t *attr, uid_t uid);
int posix_spawnattr_set_gid_np(posix_spawnattr_t *attr, gid_t gid);
}

int main() {
    pid_t pid;
    int status;
    posix_spawnattr_t attr;

    // 1. Define the executable path and arguments
    const char *path = "/bin/sleep";
    char *const argv[] = {(char *)"sleep", (char *)"1", NULL};

    // 2. Initialize spawn attributes
    if ((status = posix_spawnattr_init(&attr)) != 0) {
        fprintf(stderr, "posix_spawnattr_init: %s\n", strerror(status));
        return EXIT_FAILURE;
    }

    // 3. Set the UID for the child process (e.g., UID 501)
    // Note: Parent must be root to change to a different user
    uid_t target_uid = 501; 
    if ((status = posix_spawnattr_set_uid_np(&attr, target_uid)) != 0) {
        fprintf(stderr, "posix_spawnattr_set_uid_np: %s\n", strerror(status));
        posix_spawnattr_destroy(&attr);
        return EXIT_FAILURE;
    }

    // 4. Spawn the process
    printf("Spawning /bin/sleep 1 as UID %d...\n", target_uid);
    status = posix_spawn(&pid, path, NULL, &attr, argv, environ);

    if (status == 0) {
        printf("Successfully spawned child with PID: %d\n", pid);
        
        // Wait for the child to finish (will take 63 seconds)
        if (waitpid(pid, &status, 0) != -1) {
            printf("Child process exited with status %d\n", WEXITSTATUS(status));
        } else {
            perror("waitpid");
        }
    } else {
        fprintf(stderr, "posix_spawn: %s\n", strerror(status));
    }

    // 5. Clean up
    posix_spawnattr_destroy(&attr);

    return (status == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

// This program demonstrates fork + setuid + exec behavior for ES framework bug report
// 1. Parent forks
// 2. Parent does setuid(501) 
// 3. Child waits with sleep syscall
// 4. Child performs exec

int main() {
    printf("Parent PID: %d, UID: %d, EUID: %d\n", getpid(), getuid(), geteuid());
    
    pid_t pid = fork();
    
    if (pid < 0) {
        // Fork failed
        perror("fork");
        return EXIT_FAILURE;
    }
    
    if (pid == 0) {
        // Child process
        printf("Child PID: %d, UID: %d, EUID: %d\n", getpid(), getuid(), geteuid());
        
        // Child waits for a bit with sleep syscall
        printf("Child sleeping for 2 seconds...\n");
        sleep(2);
        
        // Child performs exec
        printf("Child executing child_exec...\n");
        
        // Get the path to child_exec (same directory as this executable)
        char *const argv[] = {(char *)"/bin/sleep", (char *)"2", NULL};
        
        // Try to exec child_exec from current directory first
        execv("/bin/sleep", argv);
        
        // If exec fails
        perror("execv");
        return EXIT_FAILURE;
    } else {
        // Parent process
        printf("Parent forked child with PID: %d\n", pid);
        
        // Parent does setuid(501)
        printf("Parent calling setuid(501)...\n");
        if (setuid(501) != 0) {
            perror("setuid");
            // Continue anyway to observe behavior
        }
        printf("Parent after setuid - UID: %d, EUID: %d\n", getuid(), geteuid());
        
        // Wait for child to finish
        int status;
        if (waitpid(pid, &status, 0) != -1) {
            if (WIFEXITED(status)) {
                printf("Child exited with status %d\n", WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("Child killed by signal %d\n", WTERMSIG(status));
            }
        } else {
            perror("waitpid");
        }
    }
    
    return EXIT_SUCCESS;
}

Answered by DTS Engineer in 874769022
Feedback ticket ID: FB21797397

Thanks for that. Honestly, I think a bug report is the best path forward for this, and I took quick look and it seems that it’s landed in the right place.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Feedback ticket ID: FB21797397

Thanks for that. Honestly, I think a bug report is the best path forward for this, and I took quick look and it seems that it’s landed in the right place.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Endpoint Security Framework Bug: setuid Event Incorrectly Attributed to Parent Process During posix_spawn
 
 
Q