Skip to content

Accept may leak file descriptor when thrown an asynchronous exception #568

@andrewthad

Description

@andrewthad

This library's implementation of accept can leak file descriptors if the thread calling accept receives an asynchronous (throwTo) exception during the execution of accept. The current version, with CPP removed to show only the codepath for Linux, looks like this:

accept :: SocketAddress sa => Socket -> IO (Socket, sa)
accept listing_sock = withNewSocketAddress $ \new_sa sz ->
    withFdSocket listing_sock $ \listing_fd -> do
        new_sock <- callAccept listing_fd new_sa sz >>= mkSocket
        new_addr <- peekSocketAddress new_sa
        return (new_sock, new_addr)
  where
     callAccept fd sa sz = with (fromIntegral sz) $ \ ptr_len -> do
       throwSocketErrorWaitRead listing_sock "Network.Socket.accept"
                        (c_accept4 fd sa ptr_len (sockNonBlock .|. sockCloexec))

After callAccept but before mkSocket, an asynchronous exception could be received. One simple solution is to mask exceptions:

new_sock <- mask_ (callAccept listing_fd new_sa sz >>= mkSocket)

Now, even if an exception is delivered, the finalizer associated with the socket should close it. Note: I am assuming that mkSocket is not interruptible, and if this assumption is wrong, none of this will work. It is defined as

mkSocket :: CInt -> IO Socket
mkSocket fd = do
    ref <- newIORef fd
    let s = Socket ref fd
    void $ mkWeakIORef ref $ close s
    return s

And the documentation in Control.Exception makes it clear that newIORef is not interruptible. I believe that mkWeakIORef is also not interruptible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions