Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 16 Jun 2026 22:23:15 +0000
From:      bugzilla-noreply@freebsd.org
To:        bugs@FreeBSD.org
Subject:   [Bug 295991] lib/libc posix_spawnp(): PATH-search child runs on an undersized rfork_thread stack and underflows into the caller's heap (heavily-linked processes; PHP proc_open)
Message-ID:  <bug-295991-227-7MZkVyBDe7@https.bugs.freebsd.org/bugzilla/>
In-Reply-To: <bug-295991-227@https.bugs.freebsd.org/bugzilla/>

index | next in thread | previous in thread | raw e-mail

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295991

Bryan Drewery <bdrewery@FreeBSD.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |bdrewery@FreeBSD.org

--- Comment #2 from Bryan Drewery <bdrewery@FreeBSD.org> ---
The report is an unreadable AI mess but the point is that there is some random
corruption that shows up with php on exit when loaded with many extensions.
Raising the stack size in posix_spawn avoids the problem.

This shows up easily with www/nextcloud port running `occ status` in a loop.
The proc_open repro isn't reliable as it depends on some random mechanism and
how many extensions are loaded.

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=289425 and
https://github.com/php/php-src/issues/21995 have more details.
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=289425#c22 shows some ways it
manifests at shutdown.

The revert of the xsl shutdown function was a hack that attempted to avoid the
root cause but does not.

Based on the initial report this AI-generated code avoids the problem by using
a 1MB stack size.

```
/* cc -O2 -fPIC -shared -Wall -o /usr/local/libexec/spawnstack.so spawnstack.c
*/
#include <sys/param.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>

typedef pid_t (*rfork_thread_t)(int, void *, int (*)(void *), void *);
#if __FreeBSD_version >= 1600000
typedef pid_t (*pdrfork_thread_t)(int *, int, int, void *,
    int (*)(void *), void *);
#endif

static rfork_thread_t sys_rfork_thread;
#if __FreeBSD_version >= 1600000
static pdrfork_thread_t sys_pdrfork_thread;
#endif
static size_t guard;
static size_t depth;

__attribute__((constructor)) static void
spawnstack_init(void)
{
        const char *s;
        char *end;
        long ps;
        unsigned long kb;

        sys_rfork_thread = (rfork_thread_t)dlsym(RTLD_NEXT, "rfork_thread");
#if __FreeBSD_version >= 1600000
        sys_pdrfork_thread = (pdrfork_thread_t)dlsym(RTLD_NEXT,
"pdrfork_thread");
#endif

        ps = sysconf(_SC_PAGESIZE);
        guard = ps > 0 ? (size_t)ps : 4096;

        kb = 1024;
        s = getenv("LIBC_SPAWN_STACK_KB");
        if (s != NULL && *s != '\0') {
                unsigned long v = strtoul(s, &end, 10);
                if (*end == '\0' && v >= 64)
                        kb = v;
        }
        depth = (kb * 1024 + guard - 1) & ~(guard - 1);
}

static void *
spawnstack_alloc(size_t *len)
{
        char *p;

        *len = guard + depth;
        p = mmap(NULL, *len, PROT_READ | PROT_WRITE,
            MAP_ANON | MAP_PRIVATE, -1, 0);
        if (p == MAP_FAILED)
                return (NULL);
        (void)mprotect(p, guard, PROT_NONE);
        return (p + *len);
}

pid_t
rfork_thread(int flags, void *stack, int (*func)(void *), void *arg)
{
        void *top;
        size_t len;
        pid_t pid;

        if ((flags & RFSPAWN) == 0 || stack == NULL)
                return (sys_rfork_thread(flags, stack, func, arg));

        top = spawnstack_alloc(&len);
        if (top == NULL)
                return (sys_rfork_thread(flags, stack, func, arg));

        pid = sys_rfork_thread(flags, top, func, arg);
        (void)munmap((char *)top - len, len);
        return (pid);
}

#if __FreeBSD_version >= 1600000
pid_t
pdrfork_thread(int *fdp, int pdflags, int rfflags, void *stack,
    int (*func)(void *), void *arg)
{
        void *top;
        size_t len;
        pid_t pid;

        if ((rfflags & RFSPAWN) == 0 || stack == NULL)
                return (sys_pdrfork_thread(fdp, pdflags, rfflags, stack,
                    func, arg));

        top = spawnstack_alloc(&len);
        if (top == NULL)
                return (sys_pdrfork_thread(fdp, pdflags, rfflags, stack,
                    func, arg));

        pid = sys_pdrfork_thread(fdp, pdflags, rfflags, top, func, arg);
        (void)munmap((char *)top - len, len);
        return (pid);
}
#endif
```

/usr/local/bin/occ needs LD_PRELOAD added in.
```
#!/bin/sh

args=
for arg in "$@" ; do
    if [ "${arg#* }" != "${arg}" ] ; then
       args="${args} '${arg}'"
    else
       args="${args} ${arg}"
    fi
done

(
cd /home/nextcloud/public_html/nextcloud
su -m www -c \
    "/usr/bin/env LD_PRELOAD=/usr/local/libexec/spawnstack.so
/usr/local/bin/php --define apc.enable_cli=1
/home/nextcloud/public_html/nextcloud/occ ${args}"
)
```

-- 
You are receiving this mail because:
You are the assignee for the bug.

home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-295991-227-7MZkVyBDe7>