[Bug 297426] linuxulator: linprocfs /proc/self/exe host path breaks execve under Linux ABI
Date: Wed, 12 Aug 2026 06:47:41 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297426
--- Comment #8 from Devin Teske <dteske@FreeBSD.org> ---
(In reply to Alex S from comment #7)
> I don't normally mount procfs at /proc, but there is no impact either
> way.
I repeated the exact Python test from your comment 3 while changing only the
native procfs mount state. On this 16.0-CURRENT system, mount state
directly changes the result.
Test environment
----------------
kern.osreldate: 1600019
linux_base-rl9: 9.8
compat.linux.emul_path: /compat/linux
unmodified source: 34718e01869b
D58780 source: bec989b72aae
The linprocfs module was rebuilt using the already-attached
linux_rebuild2.sh and installed/reloaded using linux_reload2.sh.
Exact Python reproduction
-------------------------
With no native procfs mounted:
$ mount | grep proc
linprocfs on /compat/linux/proc (linprocfs, local)
$ /compat/linux/usr/bin/python3.9 -c "
import os
print('readlink:', os.readlink('/proc/self/exe'))
try:
print('stat:', os.stat('/proc/self/exe'))
except OSError as e:
print('stat: FAIL --', e)
try:
os.execv('/proc/self/exe',
['python3', '-c', 'print(\"execv OK\")'])
except OSError as e:
print('execv: FAIL --', e)
"
readlink: /compat/linux/usr/bin/python3.9
stat: FAIL -- [Errno 2] No such file or directory: '/proc/self/exe'
execv: FAIL -- [Errno 2] No such file or directory
After mounting native procfs, with no other change:
$ sudo mount -t procfs proc /proc
$ mount | grep proc
linprocfs on /compat/linux/proc (linprocfs, local)
procfs on /proc (procfs, local)
Running the same Python command then produces:
readlink: /compat/linux/usr/bin/python3.9
stat: OK
execv OK
Thus native procfs mount state does affect the outcome: stat/execve
change from ENOENT to success. The successful case still exposes the
FreeBSD host path through readlink rather than the Linux-visible path.
C litmus
--------
The attached linux-proc-self-exe-litmus.c tests readlink, stat,
access(X_OK), and execve independently.
Install the Linux development tools and compile it as a Linux binary:
$ sudo pkg install linux-rl9-devtools
$ /compat/linux/usr/bin/gcc -O2 \
-o ~/linux-proc-self-exe-litmus \
~/linux-proc-self-exe-litmus.c
Run it first with its executable text outside the emulator root:
$ ~/linux-proc-self-exe-litmus
Create a Linux-side temporary directory under the emulator root. This
directory is not created by default:
$ sudo mkdir -p /compat/linux/tmp
$ sudo chmod 1777 /compat/linux/tmp
Copy the same binary beneath compat.linux.emul_path and run it using its
FreeBSD host path:
$ cp ~/linux-proc-self-exe-litmus /compat/linux/tmp/
$ /compat/linux/tmp/linux-proc-self-exe-litmus
The program exits nonzero on failure and prints the result of readlink,
stat, access(X_OK), and execve independently.
Complete result matrix
----------------------
The complete observed matrix is:
executable linprocfs native /proc readlink result verdict
------------ ----------- ------------- -------------------- -------
outside-emul unmodified absent host path FAIL
outside-emul unmodified mounted host path PASS
under-emul unmodified absent /compat/linux/... FAIL
under-emul unmodified mounted /compat/linux/... FAIL (*)
under-emul D58780 absent Linux-visible path PASS
under-emul D58780 mounted Linux-visible path PASS
(*) With native procfs mounted, stat/access/execve succeed through
native-namespace fallback, but readlink still returns the host path
including /compat/linux. The litmus therefore correctly reports failure:
The Linuxulator should not expose FreeBSD host emulator configuration through
/proc/self/exe. To emulate native Linux accurately, it should report the path
in the process’s own namespace, improving compatibility with Linux binaries.
Returning a host-only emulator prefix through an ABI-visible pathname is a
Linuxulator implementation leak with no analogue in native Linux and is highly
irregular for a Linux compatibility layer.
Representative output
---------------------
Unmodified linprocfs, executable outside emul_path, no native procfs:
case: outside-emul
readlink(/proc/self/exe) =
/home/dteske/linux-proc-self-exe-litmus
readlink: OK
stat(/proc/self/exe): FAIL (No such file or directory)
access(X_OK): FAIL (No such file or directory)
execve(/proc/self/exe): FAIL
VERDICT: FAIL (outside-emul)
Unmodified linprocfs, executable outside emul_path, native procfs
mounted:
case: outside-emul
readlink(/proc/self/exe) =
/home/dteske/linux-proc-self-exe-litmus
readlink: OK
stat(/proc/self/exe): OK
access(X_OK): OK
execve(/proc/self/exe): OK
VERDICT: PASS (outside-emul)
Unmodified linprocfs, executable under emul_path, no native procfs:
case: under-emul
readlink(/proc/self/exe) =
/compat/linux/tmp/linux-proc-self-exe-litmus
readlink: FAIL (host path; expected the Linux namespace path)
stat(/proc/self/exe): FAIL (No such file or directory)
access(X_OK): FAIL (No such file or directory)
execve(/proc/self/exe): FAIL
VERDICT: FAIL (under-emul)
Unmodified linprocfs, executable under emul_path, native procfs mounted:
case: under-emul
readlink(/proc/self/exe) =
/compat/linux/tmp/linux-proc-self-exe-litmus
readlink: FAIL (host path; expected the Linux namespace path)
stat(/proc/self/exe): OK
access(X_OK): OK
execve(/proc/self/exe): OK
VERDICT: FAIL (under-emul)
D58780, executable under emul_path, no native procfs:
case: under-emul
readlink(/proc/self/exe) =
/tmp/linux-proc-self-exe-litmus
readlink: OK
stat(/proc/self/exe): OK
access(X_OK): OK
execve(/proc/self/exe): OK
VERDICT: PASS (under-emul)
D58780, executable under emul_path, native procfs mounted:
case: under-emul
readlink(/proc/self/exe) =
/tmp/linux-proc-self-exe-litmus
readlink: OK
stat(/proc/self/exe): OK
access(X_OK): OK
execve(/proc/self/exe): OK
VERDICT: PASS (under-emul)
Concrete consumer
-----------------
This is not only a path-format discrepancy. www/linux-brave provides a
concrete consumer of the failing follow operation.
Brave is installed outside compat.linux.emul_path, under
/usr/local/share/brave. Chromium starts helper processes by executing
/proc/self/exe. Without procfs mounted at native /proc, that operation
fails with ENOENT:
LaunchProcess: failed to execvp:
/proc/self/exe
The GPU helper consequently exits and Brave cannot launch successfully.
D58784 makes this requirement explicit by refusing to start
www/linux-brave without native procfs and printing the required mount
instructions.
The two fixes address complementary cases:
* Linux binaries outside compat.linux.emul_path, such as
www/linux-brave, require procfs mounted at native /proc.
* Linux binaries under compat.linux.emul_path should receive a
Linux-visible /proc/self/exe target. D58780 supplies that behavior by
stripping the emulator prefix.
D58780 therefore does not attempt to replace native procfs for
outside-emul binaries. It fixes the distinct under-emul case where
linprocfs itself emits a target that is not valid in the process's Linux
namespace.
Mechanism and scope
-------------------
proc_get_binpath() returns a FreeBSD host path. For executable text under
the emulator root, unmodified linprocfs therefore reports a target such as:
/compat/linux/usr/bin/python3.9
The Linux ABI first resolves that absolute path relative to
compat.linux.emul_path, producing an initial lookup equivalent to:
/compat/linux/compat/linux/usr/bin/python3.9
Without native procfs, following /proc/self/exe consequently fails with
ENOENT. Native procfs can mask the follow operation through
native-namespace fallback, but it cannot correct the readlink result.
D58780 strips compat.linux.emul_path when the executable is beneath that
root. The symlink then reports the Linux-visible path:
/usr/bin/python3.9
That fixes readlink and makes stat/access/execve work regardless of
whether native procfs is mounted.
D58780 intentionally does not alter paths for executables outside the
emulator root. Those require procfs mounted at native /proc. Making
arbitrary FreeBSD host paths directly meaningful inside the Linux
namespace is a separate and substantially broader problem and is not
part of D58780.
I am attaching the complete six output files underlying this matrix. If
another system produces different results, please provide both
`mount | grep proc` and the complete litmus output so the differing
lookup path can be identified. Also, please provide for test env:
1. uname -K # for kernel version
2. pkg info linux_base-rl9 # for Linuxulator version
--
You are receiving this mail because:
You are the assignee for the bug.