Environment
- Host: macOS on Apple Silicon (M-series)
- Guest arch: x86_64 (run via Rosetta translation)
- Trigger: Running any package management operation that invokes Debian/Ubuntu maintainer scripts (e.g.,
apt upgrade, dpkg -i)
Problem Description
When an x86_64 guest binary is executed under Rosetta translation, proc_intercept_readlink has two separate code paths that both return the Rosetta interpreter path (/Library/Apple/usr/libexec/oah/RosettaLinux/rosetta, i.e. ROSETTA_PATH) instead of the actual path of the running guest binary. This breaks any guest application that uses /proc/self/exe or /proc/self/fd/N for self-identification.
Code path 1 — readlink("/proc/self/exe")
In src/runtime/procemu.c, proc_intercept_readlink had an early-exit branch at the top of the /proc/self/exe handler:
if (!strcmp(path, "/proc/self/exe")) {
/* Under rosetta, readlink("/proc/self/exe") points at the rosetta
* translator (the binfmt_misc interpreter). Matches the behavior Linux
* exposes when binfmt_misc dispatch is active.
*/
if (proc_rosetta_active()) {
size_t len = strlen(ROSETTA_PATH);
if (len > bufsiz)
len = bufsiz;
memcpy(buf, ROSETTA_PATH, len);
return (int) len;
}
/* ... normal path follows */
}
This was written to mimic Linux binfmt_misc behavior, where readlink("/proc/self/exe") in a qemu-static context would return the interpreter (qemu) rather than the guest binary. However, on Linux with native binfmt_misc + Rosetta, /proc/self/exe is supposed to resolve to the real binary, not the interpreter. The behavior that returns the interpreter was specific to older versions of the Rosetta Linux ABI and is not what most guest applications expect.
Code path 2 — readlink("/proc/self/fd/N") when N points to /proc/self/exe
In proc_intercept_open, opening /proc/self/exe under Rosetta opens ROSETTA_PATH on the host:
if (!strcmp(path, "/proc/self/exe")) {
if (g && g->is_rosetta)
return open(ROSETTA_PATH, O_RDONLY); /* <-- returns ROSETTA_PATH fd */
/* ... */
}
This is intentional: the Rosetta VZ ioctl gate (rosetta_ioctl_target_fd) needs to recognize the fd it is handed as pointing to ROSETTA_PATH, so it accepts the ioctl. However, a side effect is that when the guest subsequently calls readlink("/proc/self/fd/<N>") on that descriptor, fcntl(host_fd, F_GETPATH, fdpath) correctly resolves the host fd to ROSETTA_PATH. The /proc/self/fd/N handler then returns that path verbatim:
/* /proc/self/fd/N -> path of host fd (via fcntl F_GETPATH on macOS) */
if (!strncmp(path, "/proc/self/fd/", 14)) {
/* ... parse N, get host_fd ... */
char fdpath[MAXPATHLEN];
if (fcntl(host_fd, F_GETPATH, fdpath) < 0) { ... }
/* fdpath is now ROSETTA_PATH -- returned as-is */
memcpy(buf, fdpath, len);
return (int) len;
}
Why this matters: GNU coreutils multi-call binary self-identification
Debian/Ubuntu ship a multi-call coreutils binary. Each applet (rm, mv, sed, install, etc.) is a hard-link to the same ELF. At startup, coreutils determines which applet to run by the following sequence:
- Open
/proc/self/exe → get fd N
- Call
readlink("/proc/self/fd/N") → expect something like /usr/bin/rm
- Match the basename against the applet table
With both bugs above, step 2 returns /Library/Apple/usr/libexec/oah/RosettaLinux/rosetta instead. coreutils fails to match rosetta against any known applet and prints:
coreutils: unknown program 'rosetta'
and exits with status 1.
Observed failure chain during apt upgrade
apt upgrade invokes dpkg, which runs maintainer scripts for every package being upgraded. The maintainer scripts call utilities such as:
dpkg-maintscript-helper (a shell wrapper) which calls rm, sed, install, dpkg-trigger
base-files.preinst which calls dpkg-query, rm, ln
All of these are hard-linked to the multi-call coreutils binary. Under the Rosetta-path bug, every one of them silently fails with coreutils: unknown program 'rosetta', meaning no package can be configured or installed. dpkg reports them all as failing with exit status 1, and apt upgrade rolls back:
23:45:37 ERROR src/core/elf.c:42: /path/to/rootfs/usr/sbin/dpkg-preconfigure: not an ELF file
(The "not an ELF file" message appears because dpkg-preconfigure is itself a Perl script; when coreutils reports the wrong name, the outer shell tries to execute the coreutils binary thinking it is a new interpreter.)
Environment
apt upgrade,dpkg -i)Problem Description
When an x86_64 guest binary is executed under Rosetta translation,
proc_intercept_readlinkhas two separate code paths that both return the Rosetta interpreter path (/Library/Apple/usr/libexec/oah/RosettaLinux/rosetta, i.e.ROSETTA_PATH) instead of the actual path of the running guest binary. This breaks any guest application that uses/proc/self/exeor/proc/self/fd/Nfor self-identification.Code path 1 —
readlink("/proc/self/exe")In
src/runtime/procemu.c,proc_intercept_readlinkhad an early-exit branch at the top of the/proc/self/exehandler:This was written to mimic Linux binfmt_misc behavior, where
readlink("/proc/self/exe")in a qemu-static context would return the interpreter (qemu) rather than the guest binary. However, on Linux with native binfmt_misc + Rosetta,/proc/self/exeis supposed to resolve to the real binary, not the interpreter. The behavior that returns the interpreter was specific to older versions of the Rosetta Linux ABI and is not what most guest applications expect.Code path 2 —
readlink("/proc/self/fd/N")when N points to/proc/self/exeIn
proc_intercept_open, opening/proc/self/exeunder Rosetta opensROSETTA_PATHon the host:This is intentional: the Rosetta VZ ioctl gate (
rosetta_ioctl_target_fd) needs to recognize the fd it is handed as pointing toROSETTA_PATH, so it accepts the ioctl. However, a side effect is that when the guest subsequently callsreadlink("/proc/self/fd/<N>")on that descriptor,fcntl(host_fd, F_GETPATH, fdpath)correctly resolves the host fd toROSETTA_PATH. The/proc/self/fd/Nhandler then returns that path verbatim:Why this matters: GNU
coreutilsmulti-call binary self-identificationDebian/Ubuntu ship a multi-call
coreutilsbinary. Each applet (rm,mv,sed,install, etc.) is a hard-link to the same ELF. At startup,coreutilsdetermines which applet to run by the following sequence:/proc/self/exe→ get fdNreadlink("/proc/self/fd/N")→ expect something like/usr/bin/rmWith both bugs above, step 2 returns
/Library/Apple/usr/libexec/oah/RosettaLinux/rosettainstead.coreutilsfails to matchrosettaagainst any known applet and prints:and exits with status 1.
Observed failure chain during
apt upgradeapt upgradeinvokesdpkg, which runs maintainer scripts for every package being upgraded. The maintainer scripts call utilities such as:dpkg-maintscript-helper(a shell wrapper) which callsrm,sed,install,dpkg-triggerbase-files.preinstwhich callsdpkg-query,rm,lnAll of these are hard-linked to the multi-call
coreutilsbinary. Under the Rosetta-path bug, every one of them silently fails withcoreutils: unknown program 'rosetta', meaning no package can be configured or installed.dpkgreports them all as failing with exit status 1, andapt upgraderolls back:(The "not an ELF file" message appears because
dpkg-preconfigureis itself a Perl script; whencoreutilsreports the wrong name, the outer shell tries to execute thecoreutilsbinary thinking it is a new interpreter.)