NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/60653: posix_spawn(3) causes incorrect stack base information



>Number:         60653
>Category:       kern
>Synopsis:       posix_spawn(3) causes incorrect stack base information
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Aug 26 20:10:01 +0000 2026
>Originator:     Thomas Klausner
>Release:        NetBSD 11.99.7
>Organization:
	
>Environment:
Architecture: x86_64
Machine: amd64
>Description:
Trying to track down a problem in webkit(-gtk)'s garbage collector, I had to find out
that the problem is not caused by webkit, but by posix_spawn.

webkit wants to walk its stack so it can garbage collect objects, but it regularly,
if randomly, falls into unmapped regions while doing so.

The visible problem is that after posix_spawn (but not after fork+exec), the stack
base address reported by the standard methods is incorrect. Even worse, it's not
only incorrect for the spawned process, but also for the parent process.
>How-To-Repeat:
Save the following program as posix_spawn_stack_bug.c and compile it:

cc -Wall -o posix_spawn_stack_bug posix_spawn_stack_bug.c -lpthread

Then run it:

./posix_spawn_stack_bug

#include <sys/exec.h>
#include <sys/exec_elf.h>
#include <sys/wait.h>

#include <dlfcn.h>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <spawn.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <ucontext.h>
#include <unistd.h>

extern char **environ;
extern struct ps_strings *__ps_strings;

/* how many children to fork */
#define NFORK  2
/* how many children to posix_spawn */
#define NSPAWN 2

/*
 * Kernel's p_stackbase, as handed back by getcontext(2), see
 * pthread__initmainstack() in src/lib/libpthread/pthread.c
 */
static uintptr_t
getcontext_stackbase(void)
{
	ucontext_t uc;

	if (getcontext(&uc) == -1)
		err(1, "getcontext");
	return (uintptr_t)uc.uc_stack.ss_sp;
}

/*
 *  AT_STACKBASE from the ELF auxiliary vector, see
 * pthread__initmainstack() in src/lib/libpthread/pthread.c
 */
static uintptr_t
auxv_stackbase(void)
{
	const AuxInfo *aux;

	for (aux = _dlauxinfo(); aux != NULL && aux->a_type != AT_NULL; ++aux)
		if (aux->a_type == AT_STACKBASE)
			return (uintptr_t)aux->a_v;
	return 0;
}

/* struct ps_strings is the first thing put on the stack, see the
 * comment starting at line 103 in /usr/include/sys/exec.h, so the top
 * of the stack is its address + its size.
 */
static uintptr_t
psstrings_stackbase(void)
{
	if (__ps_strings == NULL)
		return 0;
	return (uintptr_t)__ps_strings + sizeof(struct ps_strings);
}

/* Stack bottom and top as reported by libpthread. */
static void
pthread_main_stack(uintptr_t *lo, uintptr_t *hi)
{
	pthread_attr_t attr;
	void *base;
	size_t size;

	*lo = *hi = 0;
	if (pthread_getattr_np(pthread_self(), &attr) != 0)
		return;
	if (pthread_attr_getstack(&attr, &base, &size) != 0)
		return;
	pthread_attr_destroy(&attr);
	*lo = (uintptr_t)base;
	*hi = (uintptr_t)base + size;
}

/*
 * Compare stack information retrieved in different ways
 */
static int
compare_stackdata(const char *who)
{
	uintptr_t getcontext_stack = getcontext_stackbase();
	uintptr_t auxv_stack = auxv_stackbase();
	uintptr_t psstrings_stack = psstrings_stackbase();
	uintptr_t lo, hi;

	pthread_main_stack(&lo, &hi);

	printf("%s pid=%d\n", who, (int)getpid());
	printf("      real stack top (ps_strings) = %#lx\n", psstrings_stack);
	printf("      AT_STACKBASE                = %#lx%s\n", auxv_stack, auxv_stack == psstrings_stack ? "" : "   <-- WRONG");
	printf("      p_stackbase (getcontext)    = %#lx%s\n", getcontext_stack, getcontext_stack == psstrings_stack ? "" : "   <-- WRONG");
	printf("      pthread main stack          = %#lx .. %#lx%s\n", lo, hi, hi == psstrings_stack ? "" : "   <-- WRONG");
	if (auxv_stack != psstrings_stack) {
		long delta = (long)auxv_stack - (long)psstrings_stack;
		printf("      => off by %+ld bytes (%+ld kiB); the reported stack %s the mapping\n\n", delta, delta / 1024, delta > 0 ? "runs past the end of" : "does not reach");
		return 1;
	}

	printf("\n");
	return 0;
}

static int
run_child(char *self, char *how, int use_spawn)
{
	char *argv[3];
	pid_t pid;
	int status, error;

	argv[0] = self;
	argv[1] = how;
	argv[2] = NULL;

	if (use_spawn) {
		error = posix_spawn(&pid, self, NULL, NULL, argv, environ);
		if (error != 0) {
			errno = error;
			err(1, how);
		}
	} else {
		pid = fork();
		if (pid == -1)
			err(1, how);
		if (pid == 0) {
			execve(self, argv, environ);
			_exit(1);
		}
	}

	if (waitpid(pid, &status, 0) == -1)
		err(1, "waitpid");
	return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}

int
main(int argc, char **argv)
{
	uintptr_t before, after;
	int bad = 0, i;

	if (argc > 1)
		return compare_stackdata(argv[1]);

	compare_stackdata("parent");
	before = getcontext_stackbase();
	printf("\n");

	printf("--- fork + execve ---\n");
	for (i = 0; i < NFORK; i++)
		bad += run_child(argv[0], "fork+exec", 0);
	printf("\n");

	printf("--- posix_spawn ---\n");
	for (i = 0; i < NSPAWN; i++)
		bad += run_child(argv[0], "posix_spawn", 1);
	printf("\n");

	compare_stackdata("parent after spawn");
	after = getcontext_stackbase();
	if (after != before) {
		printf("\nparent p_stackbase changed after spawns: %#lx -> %#lx\n", before, after);
		bad++;
	}

	return bad != 0;
}


>Fix:




Home | Main Index | Thread Index | Old Index