Skip to content

Fix connect_read_pipe/connect_write_pipe double-close of the underlying fd - #764

Open
jaideeppyne wants to merge 1 commit into
MagicStack:masterfrom
jaideeppyne:fix-pipe-double-close-763
Open

jaideeppyne wants to merge 1 commit into
MagicStack:masterfrom
jaideeppyne:fix-pipe-double-close-763

Conversation

@jaideeppyne

Copy link
Copy Markdown

Problem

connect_read_pipe and connect_write_pipe passed the Python file object's fd to uv_pipe_open. libuv takes ownership and closes that fd in uv_close. The attached file object later called close() on the same number. If the kernel had reused the fd, this closed an unrelated descriptor.

This is the pipe equivalent of the socket double-close fixed in d5195d7. The EBADF on non-socket close() is not always benign.

The same race exists if the transport is GC'd instead of explicitly closed.

Fix

os.dup() the fd before uv_pipe_open so libuv and the Python file object each own a distinct descriptor. uv_close() closes the dup; fileobj.close() (or GC) closes the original. Either order is safe.

Tests

tests/test_pipes.py adds a probe (from the issue) that opens a fresh os.pipe() inside the file object's close() and fails if those fds are stolen. Covered for both read and write pipes.

Fixes #763

connect_read_pipe and connect_write_pipe passed the Python file
object's fd to libuv, which takes ownership and closes it. Closing
the file object later (or GC) then closed the same fd again, which
can steal a recycled descriptor.

Dup the fd before uv_pipe_open so libuv and the file object each
own a distinct descriptor.
@fantix

fantix commented Oct 1, 2026

Copy link
Copy Markdown
Member

Thanks for the PR! But I'm not sure dup() is the right way here.

@1st1 previously explained that duplication can make uvloop exhaust FD limits sooner than standard asyncio. 318e593 subsequently removed duplication, including from these two APIs.

I'd like to preserve your regression tests and resolve the ownership conflict without reintroducing duplication. That means we shall either patch libuv to avoid closing the FD conditionally, or replicate uv_pipe_t in uvloop with polling. Neither sounds like a good approach for maintenance, though. @1st1 wdyt?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

connect_read_pipe double-closes the underlying fd, possibly closing an unrelated fd

2 participants