Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/linux/init/binfmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Module Name:
#define LX_INIT_BINFMT_NAME "WSLInterop"
#define BINFMT_MISC_MOUNT_TARGET "/proc/sys/fs/binfmt_misc"
#define BINFMT_MISC_REGISTER_FILE BINFMT_MISC_MOUNT_TARGET "/register"

//
// N.B. The 'P' flag preserves Argv[0]. The 'F' flag (fix-binary) opens the interpreter at
// registration time, making it available across mount namespaces and chroot environments.
// WSL1 (lxcore) does not support the 'F' flag, so it must only be used in WSL2 (VM) paths.
//

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


int CreateNtProcess(int Argc, char* Argv[]);
19 changes: 12 additions & 7 deletions src/linux/init/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,33 +374,38 @@ ExecStart=/bin/mount -o bind,ro,X-mount.mkdir -t none /mnt/wslg/.X11-unix /tmp/.

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);

Comment thread
yeelam-gordon marked this conversation as resolved.
Comment on lines 375 to 396

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.
// Install the override for systemd-binfmt.service.
{
auto overrideFolder = std::format("{}/systemd-binfmt.service.d", installPath);
THROW_LAST_ERROR_IF(UtilMkdirPath(overrideFolder.c_str(), 0755) < 0);
THROW_LAST_ERROR_IF(WriteToFile(std::format("{}/override.conf", overrideFolder).c_str(), systemdBinfmtContent.c_str()) < 0);
THROW_LAST_ERROR_IF(WriteToFile(std::format("{}/override.conf", overrideFolder).c_str(), serviceOverrideContent.c_str()) < 0);
}

// Install the override for binfmt-support.service.
{
auto overrideFolder = std::format("{}/binfmt-support.service.d", installPath);
THROW_LAST_ERROR_IF(UtilMkdirPath(overrideFolder.c_str(), 0755) < 0);
THROW_LAST_ERROR_IF(WriteToFile(std::format("{}/override.conf", overrideFolder).c_str(), systemdBinfmtContent.c_str()) < 0);
THROW_LAST_ERROR_IF(WriteToFile(std::format("{}/override.conf", overrideFolder).c_str(), serviceOverrideContent.c_str()) < 0);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/linux/init/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Module Name:
#include "SocketChannel.h"

#define BSDTAR_PATH "/usr/bin/bsdtar"
#define BINFMT_REGISTER_STRING ":" LX_INIT_BINFMT_NAME ":M::MZ::" LX_INIT_PATH ":FP\n"
#define BINFMT_REGISTER_STRING BINFMT_INTEROP_REGISTRATION_STRING_VM(LX_INIT_BINFMT_NAME) "\n"
#define BINFMT_PATH PROCFS_PATH "/sys/fs/binfmt_misc"
#define CHRONY_CONF_PATH ETC_PATH "/chrony.conf"
#define CHRONYD_PATH "/sbin/chronyd"
Expand Down
17 changes: 17 additions & 0 deletions test/windows/UnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,28 +365,45 @@ class UnitTests
VERIFY_ARE_EQUAL(cmdOutput, L"ok\r\n");
};

auto validateGeneratedBinfmtFiles = []() {
auto [binfmtConfig, _] = LxsstuLaunchWslAndCaptureOutput(L"cat /run/binfmt.d/WSLInterop.conf");
VERIFY_ARE_EQUAL(binfmtConfig, L":WSLInterop:M::MZ::/init:FP\n");

auto [serviceOverride, __] =
LxsstuLaunchWslAndCaptureOutput(L"cat /run/systemd/generator/systemd-binfmt.service.d/override.conf");
VERIFY_IS_TRUE(serviceOverride.find(L"ExecStop=") != std::wstring::npos);
VERIFY_IS_TRUE(serviceOverride.find(L"ExecStartPost=") != std::wstring::npos);
};

validateGeneratedBinfmtFiles();
validateBinfmt();

// Validate that this still works after restarting the distribution.
TerminateDistribution();
validateGeneratedBinfmtFiles();
validateBinfmt();

// Validate that stopping or restarting systemd-binfmt doesn't break interop.
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"systemctl stop systemd-binfmt.service"), 0u);
validateBinfmt();

VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"systemctl restart systemd-binfmt.service"), 0u);
validateGeneratedBinfmtFiles();
validateBinfmt();

// Validate that the unit is regenerated after a daemon-reload.
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"systemctl daemon-reload && systemctl restart systemd-binfmt.service"), 0u);
validateGeneratedBinfmtFiles();
validateBinfmt();
}

{
// Enable systemd (restarts distro).
auto cleanupSystemd = EnableSystemd("protectBinfmt=false");

VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e /run/binfmt.d/WSLInterop.conf"), 1u);
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"test -e /run/systemd/generator/systemd-binfmt.service.d/override.conf"), 1u);

// Validate that WSL's binfmt interpreter is overridden
auto [output, _] = LxsstuLaunchWslAndCaptureOutput(L"cmd.exe /c echo ok");
VERIFY_IS_TRUE(wsl::shared::string::IsEqual(output, L"/mnt/c/Windows/system32/cmd.exe cmd.exe /c echo ok\n", true));
Expand Down