@samitouri / QOSAMI-WSL / commits / 55d45784

fix: port 0 tracking not working for threads (#41051)

In the port 0 tracking logic. The seccomp filter provides the tid instead of the pid. But the pidfd_open call by default requires the first parameter to be a pid. This causes bind calls in threads not tracked by the tracker. Which further causes the local host forwarding to not work for consomme. This PR applies the PIDFD_THREAD flag introduced in kernel 6.9 to the pidfd_open call. So, it works with non group leader threads as well. Please note: PIDFD_THREAD is defined in place as musl does not provide <sys/pidfd.h> and including <linux/pidfd.h> introduces conflict with musl's header files. The test is not using a perl script since the perl in the test distro is lacking threading support.

Feng Wang committed Aug 5, 2026 at 10:55 UTC 55d457846d5e5e5d3b82f7c2305480a2aa3893e7
9 files changed +191 -8
src/linux/init/GnsPortTracker.cpp
+14 -1
@@ -15,6 +15,12 @@
15 #include "lxinitshared.h"
16 #include "seccomp_defs.h"
17
18 +// TODO: Include <sys/pidfd.h> and remove this once musl provides it.
19 +// <linux/pidfd.h> cannot be used because it conflicts with musl's <fcntl.h>.
20 +#ifndef PIDFD_THREAD
21 +#define PIDFD_THREAD O_EXCL
22 +#endif
23 +
24 constexpr size_t c_bind_timeout_seconds = 60;
25 constexpr auto c_sock_diag_refresh_delay = std::chrono::milliseconds(500);
26 constexpr auto c_sock_diag_poll_timeout = std::chrono::milliseconds(10);
@@ -623,7 +629,14 @@ wil::unique_fd GnsPortTracker::DuplicateSocketFd(pid_t Pid, int SocketFd)
629 // Duplicate the socket fd from the target process into our address space.
630 // We cannot use open("/proc/pid/fd/N") for sockets because the symlink target
631 // (socket:[inode]) is not a valid filesystem path. Use pidfd_getfd() instead.
626 - wil::unique_fd pidFd(static_cast<int>(syscall(SYS_pidfd_open, Pid, 0u)));
632 + // PIDFD_THREAD requires kernel >= 6.9. Fallback to process only if not supported.
633 + int pidFdResult = static_cast<int>(syscall(SYS_pidfd_open, Pid, PIDFD_THREAD));
634 + if (pidFdResult < 0 && errno == EINVAL)
635 + {
636 + pidFdResult = static_cast<int>(syscall(SYS_pidfd_open, Pid, 0u));
637 + }
638 +
639 + wil::unique_fd pidFd(pidFdResult);
640 if (!pidFd)
641 {
642 GNS_LOG_INFO("Port-0 bind: pidfd_open failed for pid {} (errno {})", Pid, errno);
test/linux/unit_tests/Makefile
+1
@@ -51,6 +51,7 @@ UNIT_TEST_OBJECTS=\
51 sched.o \
52 sem.o \
53 shm.o \
54 + socket.o \
55 socket_nonblock.o \
56 splice.o \
57 sysfs.o \
test/linux/unit_tests/socket.c
+102 -3
@@ -19,6 +19,7 @@ Abstract:
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/un.h>
22 +#include <pthread.h>
23 #include <netinet/in.h>
24 #include <netdb.h>
25 #include "lxtcommon.h"
@@ -71,6 +72,10 @@ int SocketServerDgram(PLXT_ARGS Args);
72
73 int SocketServerUnix(PLXT_ARGS Args);
74
75 +int SocketServerPortZeroFromThread(PLXT_ARGS Args);
76 +
77 +void* SocketServerPortZeroFromThreadWorker(void* Context);
78 +
79 //
80 // Global constants.
81 //
@@ -99,7 +104,8 @@ static const LXT_VARIATION g_LxtServerVariations[] = {
104 {"Socket Server - AF_UNIX", SocketServerUnix},
105 {"Socket Server - accept multiple Ipv6", SocketServerAcceptMultipleIpv6},
106 {"Socket Server - accept (MSG_WAITALL)", SocketServerAcceptWithFlags},
102 - {"Socket Server - SOCK_DGRAM", SocketServerDgram}};
107 + {"Socket Server - SOCK_DGRAM", SocketServerDgram},
108 + {"Socket Server - port zero bind from thread", SocketServerPortZeroFromThread}};
109
110 //
111 // Function definitions.
@@ -118,7 +124,7 @@ long long GetTickCount(void)
124 return Now.tv_sec * 1000 + Now.tv_nsec / 1000000;
125 }
126
121 -int main(int Argc, char* Argv[])
127 +int SocketTestEntry(int Argc, char* Argv[])
128
129 /*++
130 --*/
@@ -1109,4 +1115,97 @@ int SocketServerUnix(PLXT_ARGS Args)
1115 {
1116
1117 return SocketServerAccept(1, AF_UNIX, SOCK_SEQPACKET, 0);
1112 -}
\ No newline at end of file
1118 +}
1119 +
1120 +int SocketServerPortZeroFromThread(PLXT_ARGS Args)
1121 +{
1122 + int Error;
1123 + int Result = LXT_RESULT_FAILURE;
1124 + int ThreadError = 0;
1125 + pthread_t Thread;
1126 +
1127 + Error = pthread_create(&Thread, NULL, SocketServerPortZeroFromThreadWorker, &ThreadError);
1128 + if (Error != 0)
1129 + {
1130 + LxtLogError("pthread_create - %s", strerror(Error));
1131 + goto ErrorExit;
1132 + }
1133 +
1134 + Error = pthread_join(Thread, NULL);
1135 + if (Error != 0)
1136 + {
1137 + LxtLogError("pthread_join - %s", strerror(Error));
1138 + goto ErrorExit;
1139 + }
1140 +
1141 + if (ThreadError != 0)
1142 + {
1143 + LxtLogError("Threaded port-zero server failed - %s", strerror(ThreadError));
1144 + goto ErrorExit;
1145 + }
1146 +
1147 + Result = LXT_RESULT_SUCCESS;
1148 +
1149 +ErrorExit:
1150 + return Result;
1151 +}
1152 +
1153 +void* SocketServerPortZeroFromThreadWorker(void* Context)
1154 +{
1155 + int* ThreadError = Context;
1156 + int AcceptedSocket = -1;
1157 + struct sockaddr_in ServerAddress = {0};
1158 + socklen_t ServerAddressLength = sizeof(ServerAddress);
1159 + int ServerSocket = -1;
1160 + int Error = 0;
1161 +
1162 + ServerSocket = socket(AF_INET, SOCK_STREAM, 0);
1163 + if (ServerSocket < 0)
1164 + {
1165 + Error = errno;
1166 + goto ErrorExit;
1167 + }
1168 +
1169 + ServerAddress.sin_family = AF_INET;
1170 + ServerAddress.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1171 + if (bind(ServerSocket, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0)
1172 + {
1173 + Error = errno;
1174 + goto ErrorExit;
1175 + }
1176 +
1177 + if (getsockname(ServerSocket, (struct sockaddr*)&ServerAddress, &ServerAddressLength) < 0)
1178 + {
1179 + Error = errno;
1180 + goto ErrorExit;
1181 + }
1182 +
1183 + if (listen(ServerSocket, 1) < 0)
1184 + {
1185 + Error = errno;
1186 + goto ErrorExit;
1187 + }
1188 +
1189 + printf("PORT_ZERO_THREAD_LISTENER_PORT=%u\n", ntohs(ServerAddress.sin_port));
1190 + fflush(stdout);
1191 +
1192 + AcceptedSocket = accept(ServerSocket, NULL, NULL);
1193 + if (AcceptedSocket < 0)
1194 + {
1195 + Error = errno;
1196 + }
1197 +
1198 +ErrorExit:
1199 + *ThreadError = Error;
1200 + if (AcceptedSocket >= 0)
1201 + {
1202 + close(AcceptedSocket);
1203 + }
1204 +
1205 + if (ServerSocket >= 0)
1206 + {
1207 + close(ServerSocket);
1208 + }
1209 +
1210 + return NULL;
1211 +}
test/linux/unit_tests/unittests.c
+1
@@ -55,6 +55,7 @@ static const LXT_TEST LxtTests[] = {
55 #endif
56 {"sem", false, SemTestEntry},
57 {"shm", false, ShmTestEntry},
58 + {"socket", false, SocketTestEntry},
59 {"socket_nonblock", false, SocketNonblockTestEntry},
60 {"splice", false, SpliceTestEntry},
61 {"sysfs", false, SysfsTestEntry},
test/linux/unit_tests/unittests.h
+3
@@ -56,6 +56,7 @@ Abstract:
56 #define SELECT_TESTNAME "select"
57 #define SEM_TESTNAME "sem"
58 #define SHM_TESTNAME "shm"
59 +#define SOCKET_TESTNAME "socket"
60 #define SOCKET_NONBLOCK_TESTNAME "socket_nonblock"
61 #define SPLICE_TESTNAME "splice"
62 #define SYSFS_TESTNAME "sysfs"
@@ -154,6 +155,8 @@ int SemTestEntry(int Argc, char* Argv[]);
155
156 int ShmTestEntry(int Argc, char* Argv[]);
157
158 +int SocketTestEntry(int Argc, char* Argv[]);
159 +
160 int SocketNonblockTestEntry(int Argc, char* Argv[]);
161
162 int SpliceTestEntry(int Argc, char* Argv[]);
test/windows/Common.h
+1
@@ -37,6 +37,7 @@ using namespace std::chrono_literals;
37 #define LXSS_DISTRO_NAME_TEST_L WIDEN(LXSS_DISTRO_NAME_TEST)
38
39 #define LXSST_REMOVE_DISTRO_CONF_COMMAND_LINE L"-u root -e rm /etc/wsl.conf"
40 +#define LXSST_TESTS_INSTALL_COMMAND_LINE L"/bin/bash -c 'cd /data/test; ./build_tests.sh'"
41
42 //
43 // Test method declaration macros that tag tests with TAEF metadata for version-based selection.
test/windows/DrvFsTests.cpp
-2
@@ -43,8 +43,6 @@ Abstract:
43
44 #define LXSST_DRVFS_METADATA_TEST_MODE (5)
45
46 -#define LXSST_TESTS_INSTALL_COMMAND_LINE L"/bin/bash -c 'cd /data/test; ./build_tests.sh'"
47 -
46 #define LXSST_METADATA_EA_NAME_LENGTH (RTL_NUMBER_OF(LX_FILE_METADATA_UID_EA_NAME) - 1)
47
48 #define LX_DRVFS_DISABLE_NONE (0)
test/windows/NetworkTests.cpp
+69
@@ -2294,6 +2294,65 @@ class NetworkTests
2294 std::chrono::minutes(2)));
2295 }
2296
2297 + static void VerifyPortZeroBindFromThreadIsTracked()
2298 + {
2299 + auto [stdOutRead, stdOutWrite] = CreateSubprocessPipe(false, true);
2300 + // LXT uses one bit per variation; the threaded port-zero server is the sixth server variation.
2301 + constexpr unsigned long long c_portZeroThreadVariationMask = 1ull << 5;
2302 + const auto commandLine = std::format(L"/data/test/wsl_unit_tests socket -s -v {}", c_portZeroThreadVariationMask);
2303 + auto cmd = LxssGenerateWslCommandLine(commandLine.data());
2304 + unique_kill_process serverProcess(LxsstuStartProcess(cmd.data(), nullptr, stdOutWrite.get()));
2305 + stdOutWrite.reset();
2306 +
2307 + constexpr std::string_view portMarker = "PORT_ZERO_THREAD_LISTENER_PORT=";
2308 + std::string output(512, '\0');
2309 + DWORD writeOffset = 0;
2310 + uint16_t assignedPort = 0;
2311 + while (assignedPort == 0)
2312 + {
2313 + if (writeOffset == output.size())
2314 + {
2315 + output.resize(output.size() * 2);
2316 + }
2317 +
2318 + DWORD bytesRead = 0;
2319 + VERIFY_IS_TRUE(ReadFile(
2320 + stdOutRead.get(), output.data() + writeOffset, static_cast<DWORD>(output.size() - writeOffset), &bytesRead, nullptr));
2321 + VERIFY_ARE_NOT_EQUAL(bytesRead, 0u);
2322 + writeOffset += bytesRead;
2323 +
2324 + const std::string_view outputView(output.data(), writeOffset);
2325 + const auto markerPosition = outputView.find(portMarker);
2326 + if (markerPosition == std::string_view::npos)
2327 + {
2328 + continue;
2329 + }
2330 +
2331 + const auto portBegin = markerPosition + portMarker.size();
2332 + const auto portEnd = outputView.find_first_not_of("0123456789", portBegin);
2333 + if (portEnd == std::string_view::npos)
2334 + {
2335 + continue;
2336 + }
2337 +
2338 + assignedPort = static_cast<uint16_t>(std::stoi(std::string(outputView.substr(portBegin, portEnd - portBegin))));
2339 + }
2340 +
2341 + LogInfo("Threaded guest listener assigned port %u", assignedPort);
2342 + const auto connectToGuest = [&]() {
2343 + wil::unique_socket clientSocket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
2344 + THROW_LAST_ERROR_IF(!clientSocket);
2345 +
2346 + SOCKADDR_IN address{};
2347 + address.sin_family = AF_INET;
2348 + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2349 + address.sin_port = htons(assignedPort);
2350 + THROW_LAST_ERROR_IF(connect(clientSocket.get(), reinterpret_cast<SOCKADDR*>(&address), sizeof(address)) == SOCKET_ERROR);
2351 + };
2352 +
2353 + VERIFY_NO_THROW(wsl::shared::retry::RetryWithTimeout<void>(connectToGuest, std::chrono::seconds(1), std::chrono::seconds(30)));
2354 + }
2355 +
2356 static void VerifyPortZeroRebindSucceeds()
2357 {
2358 // Verify that bind(0) -> close -> immediate rebind on the same port succeeds.
@@ -3880,6 +3939,9 @@ class MirroredTests
3939 {
3940 VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
3941
3942 + // Build the Linux unit tests used by the port tracking tests.
3943 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), (DWORD)0);
3944 +
3945 if (LxsstuVmMode())
3946 {
3947 m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Mirrored}));
@@ -4295,6 +4357,8 @@ class MirroredTests
4357 // this range, so even after the guest releases the port the host still cannot bind
4358 // it — the range-level reservation remains, making release unverifiable.
4359 NetworkTests::VerifyPortZeroBindIsTracked(false);
4360 +
4361 + NetworkTests::VerifyPortZeroBindFromThreadIsTracked();
4362 }
4363
4364 WSL2_TEST_METHOD(ListenWithoutBindIsTracked)
@@ -5181,6 +5245,9 @@ class ConsommeTests
5245 {
5246 VERIFY_ARE_EQUAL(LxsstuInitialize(false), TRUE);
5247
5248 + // Build the Linux unit tests used by the port tracking tests.
5249 + VERIFY_ARE_EQUAL(LxsstuLaunchWsl(LXSST_TESTS_INSTALL_COMMAND_LINE), (DWORD)0);
5250 +
5251 if (LxsstuVmMode())
5252 {
5253 m_config.emplace(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Consomme}));
@@ -5344,6 +5411,8 @@ class ConsommeTests
5411 m_config->Update(LxssGenerateTestConfig({.networkingMode = wsl::core::NetworkingMode::Consomme}));
5412
5413 NetworkTests::VerifyPortZeroBindIsTracked();
5414 +
5415 + NetworkTests::VerifyPortZeroBindFromThreadIsTracked();
5416 }
5417
5418 WSL2_TEST_METHOD(ListenWithoutBindIsTracked)
test/windows/UnitTests.cpp
-2
@@ -45,8 +45,6 @@ Abstract:
45 #define LXSST_FSTAB_SETUP_COMMAND_LINE L"/bin/bash -c 'echo C:\\\\ /mnt/c drvfs metadata 0 0 >> /etc/fstab'"
46 #define LXSST_FSTAB_CLEANUP_COMMAND_LINE L"/bin/bash -c \"cp /etc/fstab.bak /etc/fstab\""
47
48 -#define LXSST_TESTS_INSTALL_COMMAND_LINE L"/bin/bash -c 'cd /data/test; ./build_tests.sh'"
49 -
48 #define LXSST_IMPORT_DISTRO_TEST_DIR L"C:\\importtest\\"
49
50 #define LXSST_UID_ROOT 0