Diagnose Guile subprocess crash on FreeBSD

This commit is contained in:
2026-04-01 08:05:32 +02:00
parent 8327fc5fc1
commit 27916cbb78
4 changed files with 401 additions and 3 deletions

View File

@@ -0,0 +1,134 @@
#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int native_spawn_closefrom_test(void) {
posix_spawn_file_actions_t actions;
pid_t pid;
int status;
char *argv[] = { "/usr/bin/true", NULL };
if (posix_spawn_file_actions_init(&actions) != 0)
return 1;
if (posix_spawn_file_actions_adddup2(&actions, STDIN_FILENO, STDIN_FILENO) != 0)
return 2;
if (posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, STDOUT_FILENO) != 0)
return 3;
if (posix_spawn_file_actions_adddup2(&actions, STDERR_FILENO, STDERR_FILENO) != 0)
return 4;
if (posix_spawn_file_actions_addclosefrom_np(&actions, 3) != 0)
return 5;
if (posix_spawn(&pid, "/usr/bin/true", &actions, NULL, argv, environ) != 0)
return 6;
if (waitpid(pid, &status, 0) < 0)
return 7;
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
return 8;
return 0;
}
static int adddup2_invalid_fd_accepted(void) {
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0)
return -1;
return posix_spawn_file_actions_adddup2(&actions, 10000000, 2) == 0;
}
static int addopen_invalid_fd_accepted(void) {
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0)
return -1;
return posix_spawn_file_actions_addopen(&actions, 10000000, "foo", 0, O_RDONLY) == 0;
}
static int write_script(char *path, size_t size) {
int fd;
const char script[] = ":\n";
if (snprintf(path, size, "/tmp/fruix-posix-spawn-script-XXXXXX") >= (int)size)
return -1;
fd = mkstemp(path);
if (fd < 0)
return -1;
if (write(fd, script, sizeof(script) - 1) != (ssize_t)(sizeof(script) - 1)) {
close(fd);
unlink(path);
return -1;
}
if (fchmod(fd, 0700) != 0) {
close(fd);
unlink(path);
return -1;
}
if (close(fd) != 0) {
unlink(path);
return -1;
}
return 0;
}
static int secure_exec_result(int use_path) {
char script[128];
pid_t child;
int err;
int status = 0;
char *argv[] = { script, NULL };
char *env[] = { "PATH=/tmp:/usr/bin:/bin", NULL };
if (write_script(script, sizeof(script)) != 0)
return -1;
err = use_path
? posix_spawnp(&child, script, NULL, NULL, argv, env)
: posix_spawn(&child, script, NULL, NULL, argv, env);
if (err == ENOEXEC) {
unlink(script);
return 0;
}
if (err != 0) {
unlink(script);
return 1;
}
while (waitpid(child, &status, 0) != child)
;
unlink(script);
if (!WIFEXITED(status))
return 2;
if (WEXITSTATUS(status) != 127)
return 3;
return 0;
}
int main(void) {
int native_ok = native_spawn_closefrom_test();
int dup2_broken = adddup2_invalid_fd_accepted();
int addopen_broken = addopen_invalid_fd_accepted();
int spawn_secure = secure_exec_result(0);
int spawnp_secure = secure_exec_result(1);
int issue_profile_match =
native_ok == 0 &&
dup2_broken == 1 &&
addopen_broken == 1 &&
spawn_secure == 0 &&
spawnp_secure == 3;
printf("native-spawn-closefrom=%s\n", native_ok == 0 ? "ok" : "fail");
printf("adddup2-invalid-fd-accepted=%s\n", dup2_broken == 1 ? "yes" : (dup2_broken == 0 ? "no" : "error"));
printf("addopen-invalid-fd-accepted=%s\n", addopen_broken == 1 ? "yes" : (addopen_broken == 0 ? "no" : "error"));
printf("posix_spawn-secure-exec-result=%d\n", spawn_secure);
printf("posix_spawnp-secure-exec-result=%d\n", spawnp_secure);
printf("issue-profile-match=%s\n", issue_profile_match ? "yes" : "no");
return issue_profile_match ? 0 : 1;
}

View File

@@ -0,0 +1,42 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd)
workdir=$(mktemp -d /tmp/fruix-guile-subprocess.XXXXXX)
trap 'rm -rf "$workdir"' EXIT INT TERM
if command -v guile3 >/dev/null 2>&1; then
guile_bin=$(command -v guile3)
elif command -v guile-3.0 >/dev/null 2>&1; then
guile_bin=$(command -v guile-3.0)
else
echo "Unable to find guile3 or guile-3.0 in PATH" >&2
exit 1
fi
ulimit -c 0 || true
cc -Wall -Wextra -O2 "$repo_root/tests/guile/posix-spawn-freebsd-diagnostics.c" \
-o "$workdir/posix-spawn-freebsd-diagnostics"
printf '== Native posix_spawn diagnostics ==\n'
"$workdir/posix-spawn-freebsd-diagnostics"
run_guile_case() {
name=$1
code=$2
set +e
"$guile_bin" -c "$code" >/dev/null 2>&1
rc=$?
set -e
printf '%s exit=%s\n' "$name" "$rc"
[ "$rc" -eq 139 ]
}
printf '== Guile subprocess crash repro ==\n'
run_guile_case system-star '(system* "/usr/bin/true")'
run_guile_case spawn '(spawn "/usr/bin/true" (list "/usr/bin/true"))'
run_guile_case open-pipe-star '(use-modules (ice-9 popen)) (open-pipe* OPEN_READ "/usr/bin/true")'
printf 'known FreeBSD Guile subprocess crash profile reproduced\n'