[Bug 297426] linuxulator: linprocfs /proc/self/exe host path breaks execve under Linux ABI

[email protected]
Newsgroups gmane.os.freebsd.bugs
Message-ID <[email protected]/bugzilla/>
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297426

--- Comment #8 from Devin Teske <[email protected]> ---
(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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.