780 lines
21 KiB
C
780 lines
21 KiB
C
/*
|
|
* vlibc — process control test (todo 20).
|
|
*
|
|
* Exercises fork/vfork/exec family/ids/session + system/popen end to
|
|
* end:
|
|
*
|
|
* 1. getpid/getppid/getuid/geteuid/getgid/getegid return live values.
|
|
* 2. fork: the child writes a marker and its pid through a pipe and
|
|
* exits 7 via the raw syscall; the parent sees 0 + the pid round
|
|
* trip and reaps a 7<<8 status via raw SYS_wait4.
|
|
* 3. fork + execve("/bin/true") exits 0.
|
|
* 4. fork + execve of a nonexistent binary: the child exits 127, the
|
|
* parent reaps 127<<8.
|
|
* 5. system("exit 3") returns 768 (WEXITSTATUS 3).
|
|
* 6. popen("echo hi","r") reads "hi\n" and pclose returns 0;
|
|
* popen("cat","w") writes through and pclose returns 0.
|
|
* 7. setuid(getuid()) etc. round trip to 0; getgroups counts and
|
|
* fills consistently.
|
|
* 8. (L2) session control in fork children: setsid/getpgrp/getsid and
|
|
* setpgid/setpgrp/getpgid; tcgetpgrp/tcsetpgrp fail with -1 on a
|
|
* pipe (ENOTTY, no errno read).
|
|
* 9. one fork+exec each for execl/execlp/execv/execvp/execle/fexecve.
|
|
* 10. PATH search: a helper script in a private directory is found via
|
|
* a custom PATH (exit 5 → 5<<8), an empty PATH entry means cwd,
|
|
* and execvp falls back to the default PATH when environ is NULL.
|
|
* 11. (L2) vfork: the child execs /bin/true, the parent reaps 0.
|
|
*
|
|
* sys/wait.h is todo 23 and does not exist: the test waits via raw
|
|
* SYS_wait4 in its own helper, and children exit via the raw syscalls.
|
|
*
|
|
* The negative paths make the LIBRARY write errno (syscall_ret), which
|
|
* under a host-linked binary targets glibc's private dtv slot at
|
|
* %fs:0+8. In the default mode each such call is bracketed with a
|
|
* save/restore of that slot (task 13 technique) — only vlibc/raw-syscall
|
|
* code runs between the write and the restore, so host state is intact
|
|
* when host code runs again. The test itself NEVER reads errno; every
|
|
* negative is asserted on the return value. The -f mode runs the
|
|
* failure scenarios and exits via raw SYS_exit_group (house pattern,
|
|
* tests/test_malloc.c).
|
|
*
|
|
* All diagnostics go through raw SYS_write (no host stdio): under
|
|
* -Iinclude the vlibc public headers shadow GCC's internal ones, so a
|
|
* host header would not compile. Only vlibc headers are included.
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "../include/stdio.h"
|
|
#include "../include/stdlib.h"
|
|
#include "../include/unistd.h"
|
|
|
|
#include "../src/internal/syscall.h"
|
|
|
|
/* Kernel-UAPI constants local to this test (fcntl.h is todo 21). */
|
|
#define T20_O_RDONLY 0x0
|
|
#define T20_O_WRONLY 0x1
|
|
#define T20_O_CREAT 0x40
|
|
#define T20_O_TRUNC 0x200
|
|
#define T20_AT_FDCWD (-100)
|
|
#define T20_EINTR 4
|
|
|
|
static int failures;
|
|
|
|
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
|
* optimize attribute keeps GCC from lowering the length loop into a
|
|
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
|
* host-linked standalone binary (house idiom, see src/string). */
|
|
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
|
say(int fd, const char *s)
|
|
{
|
|
long n = 0;
|
|
|
|
while (s[n] != '\0')
|
|
{
|
|
n++;
|
|
}
|
|
__syscall3(SYS_write, fd, (long)s, n);
|
|
}
|
|
|
|
/* Write v in decimal to fd. */
|
|
static void
|
|
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
|
{
|
|
char buf[24];
|
|
int i = (int)sizeof(buf);
|
|
|
|
buf[--i] = '\0';
|
|
do
|
|
{
|
|
buf[--i] = (char)('0' + (v % 10));
|
|
v /= 10;
|
|
} while (v != 0);
|
|
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
|
}
|
|
|
|
static void
|
|
check(int cond, const char *what)
|
|
{
|
|
if (cond)
|
|
{
|
|
say(1, "PASS: ");
|
|
say(1, what);
|
|
say(1, "\n");
|
|
}
|
|
else
|
|
{
|
|
say(2, "FAIL: ");
|
|
say(2, what);
|
|
say(2, "\n");
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Host-TCB slot-1 bracket: the library's errno write on a negative path
|
|
* lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each
|
|
* such call; only vlibc/raw-syscall code runs in between (task 13
|
|
* technique).
|
|
*/
|
|
static unsigned long
|
|
tcb_slot1(void)
|
|
{
|
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
|
}
|
|
|
|
static void
|
|
tcb_slot1_set(unsigned long value)
|
|
{
|
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = value;
|
|
}
|
|
|
|
/* Wait for pid via the raw syscall (sys/wait.h is todo 23) and return
|
|
* the raw wait status, or -1 when wait4 failed for a reason other than
|
|
* EINTR. */
|
|
static int
|
|
raw_wait(pid_t pid)
|
|
{
|
|
int status = 0;
|
|
|
|
for (;;)
|
|
{
|
|
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
|
|
|
|
if (r < 0 && -r == T20_EINTR)
|
|
{
|
|
continue;
|
|
}
|
|
if (r < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
return status;
|
|
}
|
|
}
|
|
|
|
/* Run fn(argv) in a fork child (via a public exec entry point) and
|
|
* return the child's wait status; the child exits 127 through the raw
|
|
* syscall when the exec entry point returns. */
|
|
typedef int (*t20_exec_fn)(void);
|
|
|
|
static int
|
|
exec_one(const char *label, t20_exec_fn fn)
|
|
{
|
|
pid_t pid = fork();
|
|
int status;
|
|
|
|
if (pid < 0)
|
|
{
|
|
check(0, label);
|
|
return -1;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
/* Child: exec (or raw-exit 127 on failure). */
|
|
(void)fn();
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 0, label);
|
|
return status;
|
|
}
|
|
|
|
/* The per-entry-point payloads. Each runs in the fork child. */
|
|
static char *av_true[] = {"true", 0};
|
|
static char *env_le[] = {"VLIBC_T20=le", 0};
|
|
static char *env_ve[] = {"VLIBC_T20=ve", 0};
|
|
|
|
static int
|
|
run_execve(void)
|
|
{
|
|
static char *envp_ok[] = {"VLIBC_T20=yes", 0};
|
|
|
|
return execve("/bin/true", av_true, envp_ok);
|
|
}
|
|
|
|
static int
|
|
run_fexecve(void)
|
|
{
|
|
int fd = open("/bin/true", T20_O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
return fexecve(fd, av_true, environ);
|
|
}
|
|
|
|
/* 1. Process ids. */
|
|
static void
|
|
id_scenarios(void)
|
|
{
|
|
check(getpid() > 0, "getpid returns a positive pid");
|
|
check(getppid() > 0, "getppid returns a positive pid");
|
|
check(getuid() == geteuid(), "getuid and geteuid agree");
|
|
check(getgid() == getegid(), "getgid and getegid agree");
|
|
}
|
|
|
|
/* 2. fork: marker + pid round trip through a pipe, child exits 7. */
|
|
static void
|
|
fork_scenario(void)
|
|
{
|
|
int fds[2] = {0};
|
|
unsigned char buf[5] = {0}; /* filled by SYS_read; the analyzer cannot model it */
|
|
pid_t pid;
|
|
int status;
|
|
|
|
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
|
{
|
|
check(0, "fork scenario pipe");
|
|
return;
|
|
}
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
check(0, "fork returns a pid");
|
|
check(0, "child writes the marker and its pid");
|
|
check(0, "parent reaps the child with status 7<<8");
|
|
(void)__syscall1(SYS_close, fds[0]);
|
|
(void)__syscall1(SYS_close, fds[1]);
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
int mypid = getpid();
|
|
|
|
/* Child: marker + pid, then exit 7 via the raw syscall. */
|
|
buf[0] = 'c';
|
|
buf[1] = (unsigned char)(mypid & 0xff);
|
|
buf[2] = (unsigned char)((mypid >> 8) & 0xff);
|
|
buf[3] = (unsigned char)((mypid >> 16) & 0xff);
|
|
buf[4] = (unsigned char)((mypid >> 24) & 0xff);
|
|
(void)__syscall3(SYS_write, fds[1], (long)buf, 5);
|
|
__syscall1(SYS_exit_group, 7);
|
|
}
|
|
check(pid > 0, "fork returns the child pid in the parent");
|
|
check(__syscall3(SYS_read, fds[0], (long)buf, 5) == 5, "parent reads the five marker bytes");
|
|
{
|
|
int child_pid = (int)((unsigned int)buf[1] | ((unsigned int)buf[2] << 8) |
|
|
((unsigned int)buf[3] << 16) | ((unsigned int)buf[4] << 24));
|
|
|
|
check(buf[0] == 'c' && child_pid == pid,
|
|
"the child's getpid matches the parent's fork return value");
|
|
}
|
|
(void)__syscall1(SYS_close, fds[0]);
|
|
(void)__syscall1(SYS_close, fds[1]);
|
|
status = raw_wait(pid);
|
|
check(status == 7 << 8, "the child exited 7 (status 7<<8 == 1792)");
|
|
}
|
|
|
|
/* 3+4. execve of a good and of a nonexistent binary. */
|
|
static void
|
|
exec_scenarios(void)
|
|
{
|
|
pid_t pid;
|
|
int status;
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
check(0, "execve /bin/true child exits 0");
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
(void)run_execve();
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 0, "fork + execve(\"/bin/true\") exits 0");
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
check(0, "execve of a nonexistent binary gives 127<<8");
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
static char *av_bad[] = {"vlibc-no-such-binary", 0};
|
|
|
|
(void)execve("/bin/nonexistent-vlibc-xyz", av_bad, environ);
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 127 << 8, "failed execve: the child exited 127 (status 127<<8)");
|
|
}
|
|
|
|
/* 5. system. */
|
|
// NOLINTBEGIN(bugprone-command-processor)
|
|
static void
|
|
system_scenario(void)
|
|
{
|
|
check(system("exit 3") == 768, "system(\"exit 3\") returns 768 (WEXITSTATUS 3)");
|
|
}
|
|
// NOLINTEND(bugprone-command-processor)
|
|
|
|
/* 6. popen/pclose. */
|
|
// NOLINTBEGIN(bugprone-command-processor)
|
|
static void
|
|
popen_scenario(void)
|
|
{
|
|
FILE *f;
|
|
char rbuf[8];
|
|
long n = 0;
|
|
int rc;
|
|
|
|
f = popen("echo hi", "r");
|
|
check(f != 0, "popen(\"echo hi\", \"r\") returns a stream");
|
|
if (f == 0)
|
|
{
|
|
return;
|
|
}
|
|
n = (long)fread(rbuf, 1, sizeof(rbuf), f);
|
|
check(n == 3, "fread on the popen stream returns three bytes");
|
|
n = 0;
|
|
while (n < 3 && rbuf[n] == "hi\n"[n])
|
|
{
|
|
n++;
|
|
}
|
|
check(n == 3, "the stream reads \"hi\\n\"");
|
|
rc = pclose(f);
|
|
check(rc == 0, "pclose of the \"r\" stream returns 0");
|
|
|
|
f = popen("cat >/dev/null", "w");
|
|
check(f != 0, "popen(\"cat >/dev/null\", \"w\") returns a stream");
|
|
if (f == 0)
|
|
{
|
|
return;
|
|
}
|
|
check(fwrite("bye", 1, 3, f) == 3, "three bytes are written to the \"w\" stream");
|
|
rc = pclose(f);
|
|
check(rc == 0, "pclose of the \"w\" stream returns 0");
|
|
}
|
|
// NOLINTEND(bugprone-command-processor)
|
|
|
|
/* 7. uid/gid round trip + supplementary groups. */
|
|
static void
|
|
uid_scenarios(void)
|
|
{
|
|
gid_t gids[64];
|
|
int n;
|
|
int m;
|
|
|
|
check(setuid(getuid()) == 0, "setuid(getuid()) returns 0");
|
|
check(seteuid(geteuid()) == 0, "seteuid(geteuid()) returns 0");
|
|
check(setgid(getgid()) == 0, "setgid(getgid()) returns 0");
|
|
check(setegid(getegid()) == 0, "setegid(getegid()) returns 0");
|
|
n = getgroups(0, 0);
|
|
check(n >= 0, "getgroups(0, NULL) returns a non-negative count");
|
|
if (n >= 0)
|
|
{
|
|
m = getgroups(64, gids);
|
|
check(m == n, "getgroups(64, list) returns the same count");
|
|
}
|
|
}
|
|
|
|
#if VLIBC_LEVEL_GE(2)
|
|
|
|
/* 8. Session control in fork children (never in the parent: leaving the
|
|
* parent's process group would trigger SIGTTOU on terminal writes). */
|
|
static void
|
|
session_scenarios(void)
|
|
{
|
|
int fds[2] = {0};
|
|
pid_t pid;
|
|
int status;
|
|
unsigned long saved;
|
|
|
|
/* Child A: setsid makes it a session and group leader. */
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
check(0, "setsid child");
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
pid_t sid = setsid();
|
|
|
|
if (sid != getpid())
|
|
{
|
|
__syscall1(SYS_exit_group, 1);
|
|
}
|
|
if (getpgrp() != getpid())
|
|
{
|
|
__syscall1(SYS_exit_group, 2);
|
|
}
|
|
if (getsid(0) != getpid())
|
|
{
|
|
__syscall1(SYS_exit_group, 3);
|
|
}
|
|
__syscall1(SYS_exit_group, 0);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 0, "child: setsid() == getpid(), getpgrp() == pid, getsid(0) == pid");
|
|
|
|
/* Child B: setpgid(0,0) + setpgrp() + getpgid. */
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
check(0, "setpgid child");
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
if (setpgid(0, 0) != 0)
|
|
{
|
|
__syscall1(SYS_exit_group, 1);
|
|
}
|
|
if (setpgrp() != 0)
|
|
{
|
|
__syscall1(SYS_exit_group, 2);
|
|
}
|
|
if (getpgrp() != getpid())
|
|
{
|
|
__syscall1(SYS_exit_group, 3);
|
|
}
|
|
if (getpgid(0) != getpid())
|
|
{
|
|
__syscall1(SYS_exit_group, 4);
|
|
}
|
|
__syscall1(SYS_exit_group, 0);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 0, "child: setpgid(0,0), setpgrp(), getpgrp(), getpgid(0) all agree");
|
|
|
|
/* tcgetpgrp/tcsetpgrp on a pipe fail with -1 (ENOTTY). */
|
|
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
|
{
|
|
check(0, "tcgetpgrp on a pipe");
|
|
check(0, "tcsetpgrp on a pipe");
|
|
return;
|
|
}
|
|
saved = tcb_slot1();
|
|
check(tcgetpgrp(fds[0]) == (pid_t)-1, "tcgetpgrp on a pipe returns -1");
|
|
tcb_slot1_set(saved);
|
|
saved = tcb_slot1();
|
|
check(tcsetpgrp(fds[0], 0) == -1, "tcsetpgrp on a pipe returns -1");
|
|
tcb_slot1_set(saved);
|
|
(void)__syscall1(SYS_close, fds[0]);
|
|
(void)__syscall1(SYS_close, fds[1]);
|
|
}
|
|
|
|
/* 11. vfork: the child execs /bin/true while the parent is suspended. */
|
|
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.vfork, clang-analyzer-unix.Vfork,
|
|
// bugprone-unsafe-functions)
|
|
static void
|
|
vfork_scenario(void)
|
|
{
|
|
pid_t pid = vfork();
|
|
int status;
|
|
|
|
if (pid == 0)
|
|
{
|
|
/* Child: exec (the only sanctioned follow-up) or raw-exit. */
|
|
(void)execve("/bin/true", av_true, environ);
|
|
__syscall1(SYS_exit_group, 126);
|
|
}
|
|
check(pid >= 0, "vfork returns a pid in the parent (0 in the child)");
|
|
if (pid < 0)
|
|
{
|
|
return;
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 0, "vfork child exec'd /bin/true and exited 0");
|
|
}
|
|
// NOLINTEND(clang-analyzer-security.insecureAPI.vfork, clang-analyzer-unix.Vfork,
|
|
// bugprone-unsafe-functions)
|
|
|
|
#endif /* VLIBC_LEVEL_GE(2) */
|
|
|
|
/* 9. One fork+exec per entry point. */
|
|
static int
|
|
run_execv(void)
|
|
{
|
|
return execv("/bin/true", av_true);
|
|
}
|
|
|
|
static int
|
|
run_execvp(void)
|
|
{
|
|
return execvp("true", av_true);
|
|
}
|
|
|
|
static int
|
|
run_execl(void)
|
|
{
|
|
return execl("/bin/true", "true", (char *)0);
|
|
}
|
|
|
|
static int
|
|
run_execlp(void)
|
|
{
|
|
return execlp("true", "true", (char *)0);
|
|
}
|
|
|
|
static int
|
|
run_execle(void)
|
|
{
|
|
return execle("/bin/true", "true", (char *)0, env_le);
|
|
}
|
|
|
|
static void
|
|
exec_variants(void)
|
|
{
|
|
(void)exec_one("execv(\"/bin/true\")", run_execv);
|
|
(void)exec_one("execvp(\"true\") via the default PATH", run_execvp);
|
|
(void)exec_one("execl(\"/bin/true\")", run_execl);
|
|
(void)exec_one("execlp(\"true\") via the default PATH", run_execlp);
|
|
(void)exec_one("execle(\"/bin/true\") with a custom envp", run_execle);
|
|
(void)exec_one("fexecve of an open /bin/true", run_fexecve);
|
|
}
|
|
|
|
/* 10. PATH search with a private helper script. */
|
|
static void
|
|
path_search_scenario(void)
|
|
{
|
|
const char *dir = "/tmp/vlibc-t20-bin";
|
|
const char *script_path = "/tmp/vlibc-t20-bin/vlibc-t20-helper";
|
|
const char script[] = "#!/bin/sh\nexit 5\n";
|
|
static char *path_env[] = {"PATH=/tmp/vlibc-t20-bin", 0};
|
|
static char *path_env_cwd[] = {"PATH=:/tmp/vlibc-t20-bin", 0};
|
|
char **saved_environ;
|
|
unsigned long saved;
|
|
int fd;
|
|
|
|
(void)__syscall2(SYS_mkdir, (long)dir, 0755); /* EEXIST is fine */
|
|
fd = open(script_path, T20_O_WRONLY | T20_O_CREAT | T20_O_TRUNC, 0755);
|
|
check(fd >= 0, "the helper script is created");
|
|
if (fd >= 0)
|
|
{
|
|
check(write(fd, script, sizeof(script) - 1) == (ssize_t)(sizeof(script) - 1),
|
|
"the helper script body is written");
|
|
check(close(fd) == 0, "the helper script descriptor is closed");
|
|
}
|
|
|
|
saved_environ = environ;
|
|
environ = path_env;
|
|
{
|
|
pid_t pid = fork();
|
|
int status;
|
|
|
|
if (pid < 0)
|
|
{
|
|
check(0, "execvp finds the helper via the custom PATH");
|
|
environ = saved_environ;
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
static char *av[] = {"vlibc-t20-helper", 0};
|
|
|
|
(void)execvp("vlibc-t20-helper", av);
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 5 << 8, "execvp finds the helper via PATH and it exits 5");
|
|
}
|
|
{
|
|
pid_t pid = fork();
|
|
int status;
|
|
|
|
if (pid < 0)
|
|
{
|
|
check(0, "execlp finds the helper via the custom PATH");
|
|
environ = saved_environ;
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
(void)execlp("vlibc-t20-helper", "vlibc-t20-helper", (char *)0);
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 5 << 8, "execlp finds the helper via PATH and it exits 5");
|
|
}
|
|
|
|
/* An empty PATH entry means the current directory (skipped here:
|
|
* the helper is not in cwd), and the search still reaches the
|
|
* second entry. */
|
|
environ = path_env_cwd;
|
|
{
|
|
pid_t pid = fork();
|
|
int status;
|
|
|
|
if (pid < 0)
|
|
{
|
|
check(0, "empty PATH entry is skipped");
|
|
environ = saved_environ;
|
|
return;
|
|
}
|
|
if (pid == 0)
|
|
{
|
|
static char *av[] = {"vlibc-t20-helper", 0};
|
|
|
|
(void)execvp("vlibc-t20-helper", av);
|
|
__syscall1(SYS_exit_group, 127);
|
|
}
|
|
status = raw_wait(pid);
|
|
check(status == 5 << 8, "an empty PATH entry means cwd and the search continues");
|
|
}
|
|
|
|
/* environ NULL -> the built-in default PATH finds /bin/true. */
|
|
environ = 0;
|
|
(void)exec_one("execvp(\"true\") with environ NULL (default PATH)", run_execvp);
|
|
|
|
/* Not found anywhere: -1 (errno written, bracketed). */
|
|
saved = tcb_slot1();
|
|
check(execvp("vlibc-no-such-helper-xyz", av_true) == -1,
|
|
"execvp of a missing helper returns -1");
|
|
tcb_slot1_set(saved);
|
|
|
|
environ = saved_environ;
|
|
(void)__syscall3(SYS_unlinkat, T20_AT_FDCWD, (long)script_path, 0);
|
|
(void)__syscall1(SYS_rmdir, (long)dir);
|
|
}
|
|
|
|
/*
|
|
* Failure scenarios (-f): every assertion is on the return value only,
|
|
* and the process exits through raw SYS_exit_group because the library
|
|
* writes errno on these paths (host-TCB hazard).
|
|
*/
|
|
// NOLINTBEGIN(bugprone-command-processor)
|
|
static int
|
|
failure_scenarios(void)
|
|
{
|
|
unsigned long saved;
|
|
int rc = 0;
|
|
|
|
saved = tcb_slot1();
|
|
if (execve("/bin/nonexistent-vlibc-xyz", av_true, env_ve) != -1)
|
|
{
|
|
say(2, "FAIL: execve of a nonexistent binary did not return -1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: execve of a nonexistent binary -> -1\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
|
|
if (system(0) != 1)
|
|
{
|
|
say(2, "FAIL: system(NULL) did not return 1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: system(NULL) -> 1\n");
|
|
}
|
|
|
|
saved = tcb_slot1();
|
|
if (popen("echo hi", "x") != 0)
|
|
{
|
|
say(2, "FAIL: popen with an invalid mode did not return NULL\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: popen with an invalid mode -> NULL\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
|
|
saved = tcb_slot1();
|
|
if (fexecve(-1, av_true, env_ve) != -1)
|
|
{
|
|
say(2, "FAIL: fexecve(-1) did not return -1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: fexecve(-1) -> -1\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
|
|
saved = tcb_slot1();
|
|
if (execvp("", av_true) != -1)
|
|
{
|
|
say(2, "FAIL: execvp(\"\") did not return -1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: execvp(\"\") -> -1\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
|
|
saved = tcb_slot1();
|
|
if (getgroups(-1, 0) != -1)
|
|
{
|
|
say(2, "FAIL: getgroups(-1) did not return -1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: getgroups(-1) -> -1\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
|
|
#if VLIBC_LEVEL_GE(2)
|
|
{
|
|
gid_t one = 0;
|
|
|
|
saved = tcb_slot1();
|
|
if (setgroups(1, &one) != -1)
|
|
{
|
|
say(2, "FAIL: setgroups without privilege did not return -1\n");
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
say(1, "PASS: setgroups without privilege -> -1\n");
|
|
}
|
|
tcb_slot1_set(saved);
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
// NOLINTEND(bugprone-command-processor)
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
|
{
|
|
/*
|
|
* The failure scenarios write errno inside the library; under
|
|
* the host libc that slot is glibc's private TLS state, so leave
|
|
* via the raw syscall without running host cleanup.
|
|
*/
|
|
int rc = failure_scenarios();
|
|
|
|
__syscall1(SYS_exit_group, rc);
|
|
return rc; /* not reached */
|
|
}
|
|
|
|
id_scenarios();
|
|
fork_scenario();
|
|
exec_scenarios();
|
|
system_scenario();
|
|
popen_scenario();
|
|
uid_scenarios();
|
|
exec_variants();
|
|
path_search_scenario();
|
|
#if VLIBC_LEVEL_GE(2)
|
|
session_scenarios();
|
|
vfork_scenario();
|
|
#endif
|
|
|
|
if (failures > 0)
|
|
{
|
|
say(2, "FAILED (");
|
|
say_dec(2, (unsigned long)failures);
|
|
say(2, " check(s))\n");
|
|
return 1;
|
|
}
|
|
say(1, "all process tests passed\n");
|
|
return 0;
|
|
}
|