Skip to content

socket is not asynchronous exception safe #166

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

Closed
lpeterse opened this issue May 21, 2015 · 2 comments
Closed

socket is not asynchronous exception safe #166

lpeterse opened this issue May 21, 2015 · 2 comments

Comments

@lpeterse
Copy link

From what I learned about all the subtleties of Haskell's threading and exceptions in the last days I think that the following snippet taken from Network.Socket is unsafe.

socket family stype protocol = do
    c_stype <- packSocketTypeOrThrow "socket" stype       --   (1)
    fd <- throwSocketErrorIfMinus1Retry "socket" $
                c_socket (packFamily family) c_stype protocol
    setNonBlockIfNeeded fd
    socket_status <- newMVar NotConnected                  -- (2)
    withSocketsDo $ return ()
    let sock = MkSocket fd family stype protocol socket_status
    ...
    return sock                                           --   (3)

An asynchronous exception may kick in at any safe point (unless it is masked). I would assume that at least (2) is a safe point. If an exception occurs in between (1) and (3) we are in a state where a file descriptor resource has been acquired but has no handle anymore and cannot be released. We're irrevocably leaking a file descriptor here.

@lpeterse
Copy link
Author

I come to the conclusion that protecting the operation against asynchronous exceptions (i.e. by masking them) is the responsibility of the caller at least as long as the operation does not contain interruptible calls (maybe this should be added to the documentation).

The problem persists with synchronous exceptions. setSocketOption may throw an exception:

setSocketOption (MkSocket s _ _ _ _) so v = do
   (level, opt) <- packSocketOption' "setSocketOption" so
   with (fromIntegral v) $ \ptr_v -> do
   throwSocketErrorIfMinus1_ "setSocketOption" $
       c_setsockopt s level opt ptr_v
          (fromIntegral (sizeOf (undefined :: CInt)))
   return ()

socket calls it without a catch (in the part with the ... between (2) and (3) above):

when (family == AF_INET6) $ setSocketOption sock IPv6Only 0

Also, setNonBlockingIfNeeded which calls System.Posix.Internals.setNonBlockingFD throws exceptions which don't get caught before the socket descriptor goes out of scope.

setNonBlockingFD fd set = do
  flags <- throwErrnoIfMinus1Retry "setNonBlockingFD"
                 (c_fcntl_read fd const_f_getfl)
  let flags' | set       = flags .|. o_NONBLOCK
             | otherwise = flags .&. complement o_NONBLOCK
  when (flags /= flags') $ do
    -- An error when setting O_NONBLOCK isn't fatal: on some systems
    -- there are certain file handles on which this will fail (eg. /dev/null
    -- on FreeBSD) so we throw away the return code from fcntl_write.
    _ <- c_fcntl_write fd const_f_setfl (fromIntegral flags')
    return ()

kazu-yamamoto added a commit to kazu-yamamoto/network that referenced this issue Jun 26, 2018
kazu-yamamoto added a commit to kazu-yamamoto/network that referenced this issue Jun 26, 2018
kazu-yamamoto added a commit to kazu-yamamoto/network that referenced this issue Jul 9, 2018
@kazu-yamamoto
Copy link
Collaborator

Closing this issue thanks to #336.

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 a pull request may close this issue.

2 participants