From c7af3742050a5d201d50c85428314fb476fb8a72 Mon Sep 17 00:00:00 2001 From: wlkr73 <86650480+wlkr73@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:07:11 +0000 Subject: [PATCH 1/8] gh-160561: Close the duplicated descriptor when fromfd() is rejected --- Lib/socket.py | 8 ++++++- Lib/test/test_socket.py | 21 +++++++++++++++++++ ...-10-24-07-58-00.gh-issue-160561.b3Kq1z.rst | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-24-07-58-00.gh-issue-160561.b3Kq1z.rst diff --git a/Lib/socket.py b/Lib/socket.py index 21a852155abb3b3..da3589756b2046a 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -572,7 +572,13 @@ def fromfd(fd, family, type, proto=0): descriptor. The remaining arguments are the same as for socket(). """ nfd = dup(fd) - return socket(family, type, proto, nfd) + try: + return socket(family, type, proto, nfd) + except BaseException: + # socket() rejected the arguments; the dup()'d descriptor would + # otherwise leak, since nothing holds a reference to it. + close(nfd) + raise if hasattr(_socket.socket, "sendmsg"): def send_fds(sock, buffers, fds, flags=0, address=None): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index e4b3d848923f8c9..b906742ad12635d 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3195,6 +3195,27 @@ def testFromFd(self): def _testFromFd(self): self.serv_conn.send(MSG) + def testFromFdInvalidArgsDoesNotLeak(self): + # fromfd() dup()s the descriptor before socket() validates its + # arguments; a rejected call must not leave the duplicate open. + fddir = '/proc/self/fd' + if not os.path.isdir(fddir): + self.skipTest('test needs /proc/self/fd') + fd = self.cli_conn.fileno() + base = dict(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0) + # family, type and proto are all converted to a C int, so each of them + # can fail after the dup() has already happened. + for name in base: + kwargs = dict(base, **{name: 1 << 100}) + with self.subTest(name=name): + before = len(os.listdir(fddir)) + with self.assertRaises(OverflowError): + socket.fromfd(fd, **kwargs) + self.assertEqual(len(os.listdir(fddir)), before) + + def _testFromFdInvalidArgsDoesNotLeak(self): + pass + def testDup(self): # Testing dup() sock = self.cli_conn.dup() diff --git a/Misc/NEWS.d/next/Library/2026-10-24-07-58-00.gh-issue-160561.b3Kq1z.rst b/Misc/NEWS.d/next/Library/2026-10-24-07-58-00.gh-issue-160561.b3Kq1z.rst new file mode 100644 index 000000000000000..c49cd6a83e90bf4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-24-07-58-00.gh-issue-160561.b3Kq1z.rst @@ -0,0 +1 @@ +Fix a descriptor leak in :func:`socket.fromfd` when the socket constructor rejects its arguments. The duplicated descriptor was never closed. From b545c72af95f7e8eae085ab31996b06231ddb67c Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:31:50 +0000 Subject: [PATCH 2/8] gh-160561: Address review comments on the fromfd() leak fix Remove the code comment and use a bare except, as requested. Rename the test to testFromFdError, flatten it and use os_helper.fd_count() so the descriptor count works on all platforms. --- Lib/socket.py | 4 +--- Lib/test/test_socket.py | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py index da3589756b2046a..df99cc04ca85546 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -574,9 +574,7 @@ def fromfd(fd, family, type, proto=0): nfd = dup(fd) try: return socket(family, type, proto, nfd) - except BaseException: - # socket() rejected the arguments; the dup()'d descriptor would - # otherwise leak, since nothing holds a reference to it. + except: close(nfd) raise diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index b906742ad12635d..dffe6c42fc6175b 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3195,25 +3195,22 @@ def testFromFd(self): def _testFromFd(self): self.serv_conn.send(MSG) - def testFromFdInvalidArgsDoesNotLeak(self): - # fromfd() dup()s the descriptor before socket() validates its - # arguments; a rejected call must not leave the duplicate open. - fddir = '/proc/self/fd' - if not os.path.isdir(fddir): - self.skipTest('test needs /proc/self/fd') + def testFromFdError(self): + # Make sure that the fromfd() duplicated socket is closed on error. + # In debug mode, Python test suite checks for file descriptor leaks. fd = self.cli_conn.fileno() - base = dict(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0) - # family, type and proto are all converted to a C int, so each of them - # can fail after the dup() has already happened. - for name in base: - kwargs = dict(base, **{name: 1 << 100}) - with self.subTest(name=name): - before = len(os.listdir(fddir)) - with self.assertRaises(OverflowError): - socket.fromfd(fd, **kwargs) - self.assertEqual(len(os.listdir(fddir)), before) - - def _testFromFdInvalidArgsDoesNotLeak(self): + fd_count_before = os_helper.fd_count() + + with self.assertRaises(TypeError): + socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) + with self.assertRaises(TypeError): + socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") + with self.assertRaises(OverflowError): + socket.fromfd(fd, socket.AF_UNIX, 1 << 100) + + self.assertEqual(os_helper.fd_count(), fd_count_before) + + def _testFromFdError(self): pass def testDup(self): From 5643f6faf54b2bf5da372fc6d6118f2305d7b402 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:01:45 +0000 Subject: [PATCH 3/8] gh-160561: Use AF_INET so the test runs on Windows --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index dffe6c42fc6175b..b102437b4343044 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3204,9 +3204,9 @@ def testFromFdError(self): with self.assertRaises(TypeError): socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) with self.assertRaises(TypeError): - socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") + socket.fromfd(fd, socket.AF_INET, "not-a-valid-type") with self.assertRaises(OverflowError): - socket.fromfd(fd, socket.AF_UNIX, 1 << 100) + socket.fromfd(fd, socket.AF_INET, 1 << 100) self.assertEqual(os_helper.fd_count(), fd_count_before) From f7e4d09d4a4ff43531df97cbd2f88cbf16ec5b46 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:07:01 +0000 Subject: [PATCH 4/8] gh-160561: Use AF_UNIX in the fromfd() error test, matching the review suggestion --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index b102437b4343044..dffe6c42fc6175b 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3204,9 +3204,9 @@ def testFromFdError(self): with self.assertRaises(TypeError): socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) with self.assertRaises(TypeError): - socket.fromfd(fd, socket.AF_INET, "not-a-valid-type") + socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") with self.assertRaises(OverflowError): - socket.fromfd(fd, socket.AF_INET, 1 << 100) + socket.fromfd(fd, socket.AF_UNIX, 1 << 100) self.assertEqual(os_helper.fd_count(), fd_count_before) From 2cbb4c04fcf3a97c5b2d18ddde9ef7c7119aa1c1 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:31:34 +0000 Subject: [PATCH 5/8] gh-160561: Keep AF_INET in the fromfd() error test socket.AF_UNIX is not defined in the Windows builds, so the test raised AttributeError before it reached either assertion. AF_INET is the family testFromFd already uses. --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index dffe6c42fc6175b..b102437b4343044 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3204,9 +3204,9 @@ def testFromFdError(self): with self.assertRaises(TypeError): socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) with self.assertRaises(TypeError): - socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") + socket.fromfd(fd, socket.AF_INET, "not-a-valid-type") with self.assertRaises(OverflowError): - socket.fromfd(fd, socket.AF_UNIX, 1 << 100) + socket.fromfd(fd, socket.AF_INET, 1 << 100) self.assertEqual(os_helper.fd_count(), fd_count_before) From 3cf12d1b47c2118eb6cf576a6c2ff6a3b6b85ec8 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:01:28 +0000 Subject: [PATCH 6/8] gh-160561: Remove the outdated comment from testFromFdError --- Lib/test/test_socket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index b102437b4343044..51b666bbaba8271 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3197,7 +3197,6 @@ def _testFromFd(self): def testFromFdError(self): # Make sure that the fromfd() duplicated socket is closed on error. - # In debug mode, Python test suite checks for file descriptor leaks. fd = self.cli_conn.fileno() fd_count_before = os_helper.fd_count() From 86b749581a5384a3cd8d154804b42f7ff8cfcb42 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:01:34 +0000 Subject: [PATCH 7/8] gh-160561: Match the reviewed test exactly: no comment, AF_UNIX --- Lib/test/test_socket.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 51b666bbaba8271..91c6df7cf80c5cd 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3196,16 +3196,15 @@ def _testFromFd(self): self.serv_conn.send(MSG) def testFromFdError(self): - # Make sure that the fromfd() duplicated socket is closed on error. fd = self.cli_conn.fileno() fd_count_before = os_helper.fd_count() with self.assertRaises(TypeError): socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) with self.assertRaises(TypeError): - socket.fromfd(fd, socket.AF_INET, "not-a-valid-type") + socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") with self.assertRaises(OverflowError): - socket.fromfd(fd, socket.AF_INET, 1 << 100) + socket.fromfd(fd, socket.AF_UNIX, 1 << 100) self.assertEqual(os_helper.fd_count(), fd_count_before) From 79193050373e20cb29b1db0b078986742eb94ce4 Mon Sep 17 00:00:00 2001 From: lumenyr <86650480+lumenyr@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:31:37 +0000 Subject: [PATCH 8/8] gh-160561: Use AF_INET in testFromFdError so it runs on Windows --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 91c6df7cf80c5cd..58526be49ee9813 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -3202,9 +3202,9 @@ def testFromFdError(self): with self.assertRaises(TypeError): socket.fromfd(fd, "not-a-valid-family", socket.SOCK_STREAM) with self.assertRaises(TypeError): - socket.fromfd(fd, socket.AF_UNIX, "not-a-valid-type") + socket.fromfd(fd, socket.AF_INET, "not-a-valid-type") with self.assertRaises(OverflowError): - socket.fromfd(fd, socket.AF_UNIX, 1 << 100) + socket.fromfd(fd, socket.AF_INET, 1 << 100) self.assertEqual(os_helper.fd_count(), fd_count_before)