[Bug 299836] [linuxulator][arm64] Linux brand does not set LINUX_BI_FUTEX_REQUEUE, musl containers deadlock on FUTEX_REQUEUE

From: <bugzilla-noreply_at_freebsd.org>
Date: Fri, 23 Oct 2026 09:01:26 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=299836

            Bug ID: 299836
           Summary: [linuxulator][arm64] Linux brand does not set
                    LINUX_BI_FUTEX_REQUEUE, musl containers deadlock on
                    FUTEX_REQUEUE
           Product: Base System
           Version: 15.1-RELEASE
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Many People
          Priority: ---
         Component: kern
          Assignee: bugs@FreeBSD.org
          Reporter: amutu@amutu.com

linux_futex() only allows FUTEX_REQUEUE when the process' ELF brandinfo sets
the
LINUX_BI_FUTEX_REQUEUE flag; otherwise the operation is logged as unsupported
and EINVAL is
returned:
        case LINUX_FUTEX_REQUEUE:
                ...
                /*
                 * The above is true, however musl libc does make use of the
                 * futex requeue operation, allow operation for brands which
                 * set LINUX_BI_FUTEX_REQUEUE bit of Brandinfo flags.
                 */
                bi = td->td_proc->p_elf_brandinfo;
                if (bi == NULL || ((bi->flags & LINUX_BI_FUTEX_REQUEUE)) == 0)
                        return (EINVAL);
The flag is set by the amd64 and i386 brands — amd64 even carries a dedicated
linux_muslbrand
entry with interp_path = "/lib/ld-musl-x86_64.so.1". The arm64 brand does not
set it, and arm64 has
no musl brand at all:
/* sys/arm64/linux/linux_sysvec.c */
static const Elf64_Brandinfo linux_glibc2brand = {
        .brand          = ELFOSABI_LINUX,
        .machine        = EM_AARCH64,
        .compat_3_brand = "Linux",
        .interp_path    = "/lib64/ld-linux-x86-64.so.2",
        .sysvec         = &elf_linux_sysvec,
        .interp_newpath = NULL,
        .brand_note     = &linux64_brandnote,
        .flags          = BI_CAN_EXEC_DYN | BI_BRAND_NOTE
};

const Elf64_Brandinfo *linux_brandlist[] = {
        &linux_glibc2brand,
        NULL
};
musl uses FUTEX_REQUEUE in its condition-variable path. With EINVAL returned,
waiters are never
requeued/woken, the wakeup is lost, and the process deadlocks: every thread
ends up sleeping in
linux_sys_futex and no further syscalls are issued.
In practice this is the second blocker for musl-based Linux containers on
arm64. After the
linux_mremap() fix (PR #1) is applied, the CPU spin is gone but node -e still
never completes;
dmesg shows:
linux: jid 15 pid 4476 (DelayedTaskSche): unsupported FUTEX_REQUEUE

【Steps to reproduce】

1. Apply the linux_mremap() fix (PR 299835:
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=299835), otherwise the
process spins before reaching this bug.
2. Run any Alpine/musl Linux container that starts Node.js:
sudo podman run --rm --os=linux ghcr.io/gethomepage/homepage:latest node -e
"console.log(1)"
Observed: the process hangs with 0% CPU, all threads in umtxq_sleep, and no
mremap/futex
traffic afterwards; dmesg contains unsupported FUTEX_REQUEUE.
$ sudo procstat -kk <pid>
  mi_switch ... _sleep ... umtxq_sleep ... linux_sys_futex ...   (all threads)
$ sudo dtrace -n 'fbt::linux_*:entry /pid==<pid>/ { @[probefunc]=count(); }
tick-2s {exit(0);}'
  (no output — zero syscalls in two seconds)

【Fix】

Allow FUTEX_REQUEUE on arm64 by setting the brand flag. A faithful port of the
amd64 layout (a
separate linux_muslbrand with /lib/ld-musl-aarch64.so.1) is also possible and
probably desirable,
but the flag alone is sufficient and has no effect on glibc binaries, which do
not issue this
operation.
--- a/sys/arm64/linux/linux_sysvec.c
+++ b/sys/arm64/linux/linux_sysvec.c
@@ -601,7 +601,8 @@ static const Elf64_Brandinfo linux_glibc2brand = {
        .sysvec         = &elf_linux_sysvec,
        .interp_newpath = NULL,
        .brand_note     = &linux64_brandnote,
-       .flags          = BI_CAN_EXEC_DYN | BI_BRAND_NOTE
+       .flags          = BI_CAN_EXEC_DYN | BI_BRAND_NOTE |
+                           LINUX_BI_FUTEX_REQUEUE
 };
Affected versions checked: releng/15.1 and main; the arm64 file is identical in
both.

【Test plan】

- After the patch dmesg no longer reports unsupported FUTEX_REQUEUE, and
podman run --rm --os=linux <alpine image> node -e "console.log('ok')" prints ok
and exits 0.
- End-to-end: the original workload starts and serves requests —
$ sudo podman run --rm --os=linux --network=host \
    -e HOSTNAME=0.0.0.0 -e HOMEPAGE_ALLOWED_HOSTS=gethomepage.dev \
    ghcr.io/gethomepage/homepage:latest
▲ Next.js 16.3.8
- Network:  http://0.0.0.0:3000
✓ Ready in 0ms

$ curl -H "Host: gethomepage.dev" -o /dev/null -w "%{http_code}\n"
http://127.0.0.1:3000/
200

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