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

Unix domain #306

Merged
merged 13 commits into from
Feb 16, 2018
Merged

Unix domain #306

merged 13 commits into from
Feb 16, 2018

Conversation

kazu-yamamoto
Copy link
Collaborator

@kazu-yamamoto kazu-yamamoto commented Feb 7, 2018

Cc: @vdukhovni @JonCoens

Fix: accept should return SockAddrUnix "" for unix domain sockets.

  • 4e257c5 is a test case created by @JonCoens. It shows that the bug exists.
  • adc9f8b is a fix for the bug.
  • My concern is #const sizeof(struct sockaddr_un). If we add 1 to this size, the tests fail.

@vdukhovni
Copy link

Two comments:

  • The accept() system call generally returns "" for sun_path, but not always. That's only true for anonymous clients that don't bind their own AF_UNIX socket name first. However, in the test in question, the client will be anonymous, so in this test sun_path should indeed come out empty.
  • As for adding 1 I was likely doing it too globally, what we should ideally do is add 1 only when allocating a socket buffer for the output and zeroing it. After that, and everywhere else we should use the natural size.

The idea of adding 1 is to ensure that there's always room for the terminating NUL (that we provide) even if the bound socket path has the maximum possible length.

@@ -820,7 +820,9 @@ withSocketAddress addr f = do
allocaBytes sz $ \p -> pokeSocketAddress p addr >> f (castPtr p) sz

withNewSocketAddress :: SocketAddress sa => (Ptr sa -> Int -> IO a) -> IO a
withNewSocketAddress f = allocaBytes 128 $ \ptr -> f ptr 128
withNewSocketAddress f = allocaBytes 128 $ \ptr -> do
zeroMemory ptr 128

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant 128 should probably be computed from max(sizeof(sockaddr_storage), sizeof(sockaddr_un)) or similar (some systems don't have a sockaddr_storage, and sockaddr_in and sockaddr_in6 might need to be used instead). And it is here that we might want to add and zero one more byte.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, memory is cheap. So, let's just use 128 which is the size of struct sockaddr_strage. +1 is not necessary for 128.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so the idea is that 128 is large enough for sockaddr_un on all platforms, and naturally also for AF_INET and AF_INET6` socket addresses. Perhaps a comment to that effect and a named constant at the top of the file? I am a bit averse to unexplained magic numbers in code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

poker = case path of ('\0':_) -> pokeArray; _ -> pokeArray0 0
poker ((#ptr struct sockaddr_un, sun_path) p) pathC
-- the buffer is already filled with nulls.
pokeArray ((#ptr struct sockaddr_un, sun_path) p) pathC

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might not be safe when path is unexpectedly long. We now have sizeOfSockAddr for unix-domain sockets as a constant sizeof(sockaddr_un), and it seems possible that trying to use a longer path will corrupt memory?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this with a short program that calls allocaBytes 128 and then pokeArray p with an array of > 128 C chars. The compiler and run-time did not object, and dutifully dumped core when the array was large enough. So here, we need to fail if SockAddrUnix path contains a path that is too long (which is a bit shorter than 128 bytes, since we have to account for the sun_family structure member. It may be unwise to assume that it is the standard 2 bytes, so we could either compute the overhead explicitly, or just cap the size at 120 (again as a named constant, properly commented at the top of the file).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, throw an error.


serverSetup = do
sock <- socket AF_UNIX Stream defaultProtocol
bind sock (SockAddrUnix unixAddr)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing unlink before bind

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more careful test might check whether the directory entry of that name (if it exists) is a socket (not a regular file or directory) and only unlink in that case, but "/tmp/network-socket" should be safe enough. We should probably also unlink when the test is done.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlink is good. But is it available on Windows?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be the same as the existing cleanup after the test.

E.tryJust (guard . isDoesNotExistError) $ removeFile unixAddr

Note also that Windows has no unix-domain sockets, so both the test and the unlink can be skipped on Windows.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done just in case.

killClientSock sock = do
shutdown sock ShutdownBoth
close sock
E.tryJust (guard . isDoesNotExistError) $ removeFile unixAddr

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, there's the unlink after the test...

@@ -910,6 +914,9 @@ withSockAddr addr f = do
let sz = sizeOfSockAddr addr
allocaBytes sz $ \p -> pokeSockAddr p addr >> f (castPtr p) sz

unixPathMax :: Int
unixPathMax = 108

Copy link

@vdukhovni vdukhovni Feb 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that on NetBSD, FreeBSD and Darwin the size of sun_path in sockaddr_un is 104 bytes. However, allocating larger sockaddr buffers (than sizeof(sockaddr_un)) allows use of longer (NUL-terminated) pathnames. So we can in fact bind and connect to sockets with names that are 108 (or more) bytes in length on these systems, provided the sockaddr buffer is long enough. The only issue is that not all clients will be able to interoperate with sockets whose names that exceed the default sizeof(sun_path) of sockaddr_un, but that's the user's problem if he/she chooses to create such sockets.

If there are still Linux systems that fail to NUL-terminate returned socket addresses, we should however promise to the OS one less byte than we allocated and zeroed:

withNewSocketAddress f = allocaBytes sockaddrStorageLen $ \ptr -> do
     zeroMemory ptr $ fromIntegral sockaddrStorageLen
     f ptr (sockaddrStorageLen-1)

This is likely needed everywhere, since maximal length pathnames might otherwise not get a NUL terminator even on BSD and Darwin.

That said, note that this pull request uses sockaddrStorageLen = 128 only to allocate storage for socket addresses we get back from the OS in withNewSocketAddress but still leaves

sizeOfSockAddr SockAddrUnix{}  = #const sizeof(struct sockaddr_un)

as the size used in withSocketAddress to allocate socket addresses for passing to the OS. Therefore, on BSD and Darwin pathnames of 108 bytes will overflow the allocated buffer when binding paths of 108 bytes.

Since we need to support initialization of the same size sockets as we're willing to receive (and in particular path of up 108 bytes on BSD and the like, where this requires socket paths longer than fit in the stock sockaddr_un) we need to also allocate space for sockaddrStorageLen bytes when initializing AF_UNIX sockaddrs with a user provided path.
Since we'll refuse to actually write more than 108 bytes of path in pokeSockAddr, the result will always be NUL-terminated.

sizeOfSockAddr SockAddrUnix{}  = #const sizeof(struct sockaddr_un)

to

sizeOfSockAddr SockAddrUnix{path}  = sockaddrStorageLen

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this investigation. I agree with your approach. So, I pushed the fix.

@vdukhovni
Copy link

One more thing: in peekSockAddr we should be using peekCAString not peekCString, socket path names are octet strings, not locale-specific filenames. Especially since pokeSockAddr is not doing locale conversions, peekSockAddr should also avoid these.

They should probably have been represented as ByteStrings, not Strings. If the API in 3.0 is different enough to allow that change, that might also make sense.

@kazu-yamamoto
Copy link
Collaborator Author

I'm busy now. I will come back to this issue on 15th Feb.

@kazu-yamamoto
Copy link
Collaborator Author

  • The accept() system call generally returns "" for sun_path, but not always. That's only true for anonymous clients that don't bind their own AF_UNIX socket name first. However, in the test in question, the client will be anonymous, so in this test sun_path should indeed come out empty.

Good point. But in this case, clients are anonymous. So, "" should be OK.

@kazu-yamamoto
Copy link
Collaborator Author

One more thing: in peekSockAddr we should be using peekCAString not peekCString, socket path names are octet strings, not locale-specific filenames. Especially since pokeSockAddr is not doing locale conversions, peekSockAddr should also avoid these.

Done.

They should probably have been represented as ByteStrings, not Strings. If the API in 3.0 is different enough to allow that change, that might also make sense.

I would like to discuss this kind of changes in a separate issue. If you wish, please record this issue.

@kazu-yamamoto
Copy link
Collaborator Author

I'm waiting for results from CI.

@kazu-yamamoto
Copy link
Collaborator Author

c3e2e2e does not work for Linux.

@vdukhovni
Copy link

Can you be more explicit about what breaks?

@vdukhovni
Copy link

See pull-request 1 against your unix-domain branch.

@kazu-yamamoto
Copy link
Collaborator Author

@vdukhovni OK. Thank you for your thorough review. I think we can merge this now.

@JonCoens I will merge this instead of #305 and try to backport this to 2.6. Thank you for reporting the bug.

@kazu-yamamoto
Copy link
Collaborator Author

@vdukhovni Oh, you are assigned as a reviewer. Please approve this!

Copy link

@vdukhovni vdukhovni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separately there should probably be documentation that path in SockAddrUnix path, though defined as String, must hold only characters from 1-255, and is not subject to any encoding to/from String. Whether this warrants a type change to ByteString is less clear. That could do more harm than good.

kazu-yamamoto added a commit to kazu-yamamoto/network that referenced this pull request Feb 16, 2018
@kazu-yamamoto kazu-yamamoto merged commit b80dbeb into haskell:master Feb 16, 2018
@kazu-yamamoto
Copy link
Collaborator Author

Thanks. Merged.

I will take care of doc stuff.

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.

3 participants