Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition in asyncdispatch.closeSocket on Unix #12652

Open
endragor opened this issue Nov 14, 2019 · 5 comments
Open

Race condition in asyncdispatch.closeSocket on Unix #12652

endragor opened this issue Nov 14, 2019 · 5 comments
Labels
Async Everything related to Nim's async

Comments

@endragor
Copy link
Contributor

endragor commented Nov 14, 2019

closeSocket's unix implementation first closes the socket handle, making it available for OS to allocate to the process for other purposes, then invokes callbacks, which call recv and send on the same handle. This makes it possible for recv and send to read/write data from/to another socket or file or whatever is re-bound to the same handle, if there are other threads opening files/sockets. This opens up a way for unexpected behavior and security vulnerabilities.

The code is:

Nim/lib/pure/asyncdispatch.nim

Lines 1269 to 1276 in f22d3c7

sock.SocketHandle.close()
# We need to unblock the read and write callbacks which could still be
# waiting for the socket to become readable and/or writeable.
for cb in data.readList & data.writeList:
if not cb(sock):
raise newException(
ValueError, "Expecting async operations to stop when fd has closed."
)

As a side note, if read cb throws an exception, which is possible, since it calls user code, then the subsequent callbacks in the list will be leaked, i.e. if something awaits a send on the same socket, it will await forever. I don't think that is the desired behavior here.

@mratsim mratsim added the Async Everything related to Nim's async label Nov 14, 2019
@dom96
Copy link
Contributor

dom96 commented Nov 14, 2019

As far as the primary issue is concerned I'm not sure what we can do to resolve it off the top of my head. Any suggestions?

@endragor
Copy link
Contributor Author

I can see two ways:

  1. Store the flags and futures in addition to callbacks and directly fail or complete them depending on SafeDisconn flag within closeSocket() after closing the socket.

  2. Call shutdown(socket, SHUT_RDWR), then callbacks, then close(). recv() and send() syscalls must fail after invoking shutdown(). Notice the user may have called shutdown on their own before that and I'm not sure what the behavior is if you call shutdown twice on the same socket.

@endragor
Copy link
Contributor Author

Actually, since shutdown() is only applicable to established TCP connections and not to server sockets or UDP sockets, I don't think (2) is a solution.

Araq added a commit that referenced this issue Nov 20, 2019
@dom96
Copy link
Contributor

dom96 commented Nov 21, 2019

Store the flags and futures in addition to callbacks and directly fail or complete them depending on SafeDisconn flag within closeSocket() after closing the socket.

Okay, we might be able to do that. Although it won't be easy and I'm still not 100% sure it's possible.

I think as a short-term fix we should put a lock around this code to make sure no weirdness occurs for parallel code. What do you think?

@endragor
Copy link
Contributor Author

The problem may actually occur even for single-threaded code - in case callbacks open sockets. In my fork of Nim (I have to use an older version) I fixed this by adding a bool parameter to callbacks. If it's true, the callbacks assume the socket is closed and react accordingly.

addEvent, which is an exported procedure, uses the same callback though and it has to be changed to the original signature (without the bool argument).

@Araq Araq added Severe and removed Showstopper labels Jan 14, 2020
@Araq Araq removed the Severe label Dec 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Async Everything related to Nim's async
Projects
None yet
Development

No branches or pull requests

4 participants