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
Create a binary that do the following:
Configure posix_spawnattr_t that set the process UIDs to some other user ID (I'll use 501 in this example).
Uses posix_spawn() to spawn a child process
Run eslogger with the event types setuid, fork, exec
Execute the binary as root process using sudo or from root owned shell
Terminate the launched eslogger
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
One that runs the descirbed posix_spawn flow
One that produces the exact same sequence of events by doing different operation and reaching a different process state:
Parent calls fork()
Parent process calls setuid(501)
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;
}
posix_spawn.json
fork_exec.json