fix(init): prevent binfmt_misc handler loss on distro termination#14443
fix(init): prevent binfmt_misc handler loss on distro termination#14443yeelam-gordon wants to merge 11 commits into
Conversation
Fixes the issue where terminating a WSL2 distro with systemd clears binfmt_misc handlers across all running distros. Changes: - Add mount unit override to prevent binfmt_misc unmount during shutdown - Fix FP vs P flag inconsistency in WSLInterop registration - Use declarative /run/binfmt.d/ approach for handler persistence Fixes microsoft#13885 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes a WSL2/systemd shutdown interaction where terminating one distro could clear binfmt_misc handlers (including WSLInterop) for other running distros, breaking Windows interop.
Changes:
- Generate a declarative
systemd-binfmtconfig in/run/binfmt.d/WSLInterop.conffor persistentWSLInteropregistration. - Add systemd generator drop-in overrides to prevent
systemd-binfmtstop actions and keepbinfmt_miscmounted during shutdown. - Align the
WSLInteropbinfmt registration string to consistently use:FP.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/windows/UnitTests.cpp | Adds assertions that the generated /run/binfmt.d config and systemd generator overrides exist (and stay consistent across restarts/reloads), and that protectBinfmt=false disables generation. |
| src/linux/init/main.cpp | Switches BINFMT_REGISTER_STRING to use the shared registration macro (now consistent with :FP). |
| src/linux/init/init.cpp | Implements generation of /run/binfmt.d/WSLInterop.conf and generator drop-ins for systemd-binfmt and the binfmt_misc mount unit when protectBinfmt is enabled. |
| src/linux/init/binfmt.h | Updates the shared binfmt registration macro to use :FP (fixing prior inconsistency). |
You can also share your feedback on Copilot code review. Take the survey.
Introduce BINFMT_INTEROP_REGISTRATION_STRING_VM with ':FP' flags for WSL2-only paths (mini_init, systemd generator), while keeping the original BINFMT_INTEROP_REGISTRATION_STRING with ':P' for WSL1 (lxcore) which does not support the 'F' (fix-binary) flag. - binfmt.h: Add _VM variant macro with ':FP', restore base macro to ':P' - main.cpp: Use _VM macro for mini_init registration (WSL2 only) - init.cpp: Use _VM macro for /run/binfmt.d/ config (systemd, WSL2 only) - config.cpp: Unchanged, continues using ':P' for WSL1 registration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Prevents binfmt_misc handlers (notably WSLInterop) from being cleared across other running WSL2 distros when one systemd-enabled distro is terminated, by changing how WSL registers/preserves the handler and how systemd shutdown interacts with the binfmt_misc mount.
Changes:
- Switch WSL interop registration in VM paths to use an explicit
:FPflag combination and centralize the VM registration string. - Generate
/run/binfmt.d/WSLInterop.confand systemd generator drop-ins to avoid handler removal during shutdown and keepbinfmt_miscmounted late into shutdown. - Extend Windows unit tests to validate the generated config/drop-in files and their persistence across restarts/daemon-reload.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/linux/init/binfmt.h |
Introduces VM-specific binfmt registration string (:FP) and documents flag semantics/constraints. |
src/linux/init/main.cpp |
Updates VM-side registration to use the new VM registration string macro. |
src/linux/init/init.cpp |
Generates /run/binfmt.d config and new systemd generator drop-ins to prevent shutdown unmount/handler removal when protectBinfmt=true. |
test/windows/UnitTests.cpp |
Adds assertions validating the generated binfmt config and generator drop-ins across restarts/service reloads and the protectBinfmt=false path. |
The pure declarative approach via /run/binfmt.d/ does not handle conflicting binfmt configs. When a distro installs its own binfmt handler with the same name (WSLInterop), systemd-binfmt processes files alphabetically, potentially letting the conflicting handler win. Add ExecStartPost to the systemd-binfmt service override that forcefully unregisters any conflicting handler and re-registers WSL's after systemd-binfmt's normal ExecStart completes. This combines declarative config for proper re-registration with imperative override for guaranteed priority. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
When protectBinfmt or interopEnabled is turned off, remove the /run/binfmt.d/WSLInterop.conf file from any previous run to ensure config changes take effect without requiring a full VM restart. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@benhillis , please help to retake a look if you have any concern. Thanks in advanced. |
OneBlue
left a comment
There was a problem hiding this comment.
Added a couple comments.
Also would it be possible to add a new test case that reproduces the binfmt issue that this change solves, so we can validate the fix and protect if from future regression ?
| // | ||
|
|
||
| #define BINFMT_INTEROP_REGISTRATION_STRING(Name) ":" Name ":M::MZ::" LX_INIT_PATH ":P" | ||
| #define BINFMT_INTEROP_REGISTRATION_STRING_VM(Name) ":" Name ":M::MZ::" LX_INIT_PATH ":FP" |
There was a problem hiding this comment.
@benhillis: This sounds like something that we want. Any concerns with this flag ?
- Remove proc-sys-fs-binfmt_misc.mount.d override: as @OneBlue noted, unmounting binfmt_misc only affects the current mount namespace, not other distros. The cross-distro protection comes from ExecStop= (which prevents kernel-global handler unregistration), not from keeping the mount alive. - Remove unlink(/run/binfmt.d/WSLInterop.conf): /run is tmpfs, so cleanup is unnecessary files are cleared on VM restart. - Update tests to match: remove mount override assertions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| if (interopEnabled && protectBinfmt) | ||
| { | ||
| // N.B. ExecStop is required to prevent distributions from removing the WSL binfmt entry on shutdown. | ||
| auto systemdBinfmtContent = std::format( | ||
| R"(# Note: This file is generated by WSL to prevent binfmt.d from overriding WSL's binfmt interpreter. | ||
| constexpr auto* binfmtConfigDirectory = "/run/binfmt.d"; | ||
| constexpr auto* binfmtConfigPath = "/run/binfmt.d/WSLInterop.conf"; | ||
| const auto binfmtConfigContent = std::format("{}\n", BINFMT_INTEROP_REGISTRATION_STRING_VM(LX_INIT_BINFMT_NAME)); | ||
| const auto serviceOverrideContent = std::format( | ||
| R"(# Note: This file is generated by WSL to prevent distributions from removing the WSL binfmt entry on shutdown. | ||
| # To disable this unit, add the following to /etc/wsl.conf: | ||
| # [boot] | ||
| # protectBinfmt=false | ||
|
|
||
| [Service] | ||
| ExecStop= | ||
| ExecStart=/bin/sh -c '(echo -1 > {}/{}) ; (echo "{}" > {})' )", | ||
| ExecStartPost=/bin/sh -c '(echo -1 > {}/{} 2>/dev/null || true) ; echo "{}" > {}' | ||
| )", | ||
| BINFMT_MISC_MOUNT_TARGET, | ||
| LX_INIT_BINFMT_NAME, | ||
| BINFMT_INTEROP_REGISTRATION_STRING(LX_INIT_BINFMT_NAME), | ||
| BINFMT_INTEROP_REGISTRATION_STRING_VM(LX_INIT_BINFMT_NAME), | ||
| BINFMT_MISC_REGISTER_FILE); | ||
| THROW_LAST_ERROR_IF(UtilMkdirPath(binfmtConfigDirectory, 0755) < 0); | ||
| THROW_LAST_ERROR_IF(WriteToFile(binfmtConfigPath, binfmtConfigContent.c_str()) < 0); | ||
|
|
There was a problem hiding this comment.
GenerateSystemdUnits() writes /run/binfmt.d/WSLInterop.conf when interopEnabled && protectBinfmt, but there is no corresponding cleanup path when either setting is later turned off (e.g., user sets protectBinfmt=false and runs systemctl daemon-reload). Because /run/binfmt.d is outside the generator output tree, a stale WSLInterop.conf can keep overriding distro binfmt config for the remainder of the VM lifetime. Add an else branch that removes /run/binfmt.d/WSLInterop.conf (ignoring ENOENT) when the feature is disabled so config changes take effect without a full VM restart.
|
@yeelam-gordon FYI — I rebased your branch onto current master (it was 221 commits behind) and added some hardening to your test, then opened #40617 to land it. Your 5 fix commits are unchanged (full credit preserved). Test changes:
Happy to close #40617 if you'd prefer to update this PR yourself — let me know which you prefer and I'll defer. Context for the new test: I also tried an MS_PRIVATE mount-propagation fix in #40612 / #40614 before realizing binfmt_misc is keyed by user-ns, not mount-ns. CI failures on those PRs confirmed your approach is correct. |
|
Same fix merged in #40621 |
Summary
Problem: Terminating a WSL2 distro with systemd causes
cmd.exeand other Windows executables to stop working in all other running distros (#13885).Root cause: When systemd shuts down, it unmounts
/proc/sys/fs/binfmt_miscand unregisters all binfmt handlers including WSL'sWSLInterophandler that enables running Windows executables from Linux. Since binfmt_misc is shared across all distros in the same VM, this breaks interop everywhere.Fix: Three layered defenses to protect the WSL binfmt handler:
proc-sys-fs-binfmt_misc.mountto prevent systemd from unmounting it during shutdownExecStartPosttosystemd-binfmt.servicethat re-registers WSL's handler after the service runs, ensuring priority over conflicting distro configs/run/binfmt.d/WSLInterop.confso the handler is automatically restored onsystemd-binfmtrestartAlso splits the binfmt registration macro into WSL1 (
:P) and WSL2 (:FP) variants, since WSL1 does not support theF(fix-binary) flag.PR Checklist
Test Coverage
Automated tests:
SystemdBinfmtIsRestoredsystemctl restart/stop systemd-binfmt,daemon-reload, andprotectBinfmt=falsecleanupBinfmtSurvivesDistroTerminationInteropManual validation (WSL2, systemd enabled):
systemctl restart systemd-binfmtdaemon-reload+ restartflags: PF,interpreter /initWSL1 compatibility: The
:P/:FPmacro split is validated byUnitTests::Interopwhich runs on both WSL1 and WSL2 CI pipelines (WSL1 passed on build #285326).Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com