diff --git a/Lib/socket.py b/Lib/socket.py index 21a852155abb3b..da3589756b2046 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 e4b3d848923f8c..b906742ad12635 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 00000000000000..c49cd6a83e90bf --- /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.