Skip to content

fix(init): prevent binfmt_misc handler loss on distro termination#14443

Closed
yeelam-gordon wants to merge 11 commits into
microsoft:masterfrom
yeelam-gordon:workitem/13885-fix-binfmt-config-cleared-on-termination
Closed

fix(init): prevent binfmt_misc handler loss on distro termination#14443
yeelam-gordon wants to merge 11 commits into
microsoft:masterfrom
yeelam-gordon:workitem/13885-fix-binfmt-config-cleared-on-termination

Conversation

@yeelam-gordon

@yeelam-gordon yeelam-gordon commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Problem: Terminating a WSL2 distro with systemd causes cmd.exe and other Windows executables to stop working in all other running distros (#13885).

Root cause: When systemd shuts down, it unmounts /proc/sys/fs/binfmt_misc and unregisters all binfmt handlers including WSL's WSLInterop handler 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:

  1. Keep binfmt_misc mounted Override proc-sys-fs-binfmt_misc.mount to prevent systemd from unmounting it during shutdown
  2. Re-register after restart Add ExecStartPost to systemd-binfmt.service that re-registers WSL's handler after the service runs, ensuring priority over conflicting distro configs
  3. Declarative config Write /run/binfmt.d/WSLInterop.conf so the handler is automatically restored on systemd-binfmt restart

Also splits the binfmt registration macro into WSL1 (:P) and WSL2 (:FP) variants, since WSL1 does not support the F (fix-binary) flag.

PR Checklist

Test Coverage

Automated tests:

Test What it validates
SystemdBinfmtIsRestored Generated config files, handler survives distro restart, systemctl restart/stop systemd-binfmt, daemon-reload, and protectBinfmt=false cleanup
BinfmtSurvivesDistroTermination Core bug scenario: two distros running, terminate one, verify interop still works in the other
Interop End-to-end binfmt registration on both WSL1 and WSL2 CI pipelines

Manual validation (WSL2, systemd enabled):

Scenario Result
Interop works before and after distro restart PASS
Interop survives systemctl restart systemd-binfmt PASS
Interop survives daemon-reload + restart PASS
Generated files have correct content (config, service override, mount override) PASS
Handler registered with flags: PF, interpreter /init PASS

WSL1 compatibility: The :P/:FP macro split is validated by UnitTests::Interop which runs on both WSL1 and WSL2 CI pipelines (WSL1 passed on build #285326).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

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>
Copilot AI review requested due to automatic review settings March 16, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-binfmt config in /run/binfmt.d/WSLInterop.conf for persistent WSLInterop registration.
  • Add systemd generator drop-in overrides to prevent systemd-binfmt stop actions and keep binfmt_misc mounted during shutdown.
  • Align the WSLInterop binfmt 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.

Comment thread src/linux/init/binfmt.h Outdated
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>
@yeelam-gordon

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :FP flag combination and centralize the VM registration string.
  • Generate /run/binfmt.d/WSLInterop.conf and systemd generator drop-ins to avoid handler removal during shutdown and keep binfmt_misc mounted 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.

Comment thread test/windows/UnitTests.cpp Outdated
Comment thread src/linux/init/init.cpp
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>
@yeelam-gordon

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI review requested due to automatic review settings March 24, 2026 01:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/linux/init/init.cpp
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>
@yeelam-gordon

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@yeelam-gordon

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI review requested due to automatic review settings March 31, 2026 13:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/linux/init/init.cpp Outdated
@yeelam-gordon yeelam-gordon marked this pull request as ready for review April 2, 2026 13:03
@yeelam-gordon yeelam-gordon requested a review from a team as a code owner April 2, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@yeelam-gordon

Copy link
Copy Markdown
Contributor Author

@benhillis , please help to retake a look if you have any concern. Thanks in advanced.

@OneBlue OneBlue left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ?

Comment thread src/linux/init/binfmt.h
//

#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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benhillis: This sounds like something that we want. Any concerns with this flag ?

Comment thread src/linux/init/init.cpp Outdated
Comment thread src/linux/init/init.cpp Outdated
Comment thread src/linux/init/init.cpp Outdated
- 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>
Copilot AI review requested due to automatic review settings April 24, 2026 11:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/linux/init/init.cpp
Comment on lines 366 to 387
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);

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@benhillis

Copy link
Copy Markdown
Member

@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:

  • WSL2_TEST_METHOD (TAEF can skip on WSL1)
  • Uses EnableSystemd() helper for the primary test distro
  • Adds ^flags:.*F check on /proc/sys/fs/binfmt_misc/WSLInterop to catch a regression from :FP back to :P

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.

@chemwolf6922

Copy link
Copy Markdown
Contributor

Same fix merged in #40621

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

distro-mgmt problems related to import, export, conversion interop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

binfmt config cleared on all distro when distro with systemd is terminated

5 participants