diff options
author | Jianfeng Tan <henry.tjf@antfin.com> | 2019-08-19 11:15:49 +0800 |
---|---|---|
committer | Jianfeng Tan <henry.tjf@antfin.com> | 2019-08-22 15:25:38 +0000 |
commit | 96f78e24668da189f78212e8c23951a461e14a8c (patch) | |
tree | 34205ee6b4eeaf6e4f6adfad78e5c1a27bda757e | |
parent | 8d9276ed564ffef5d12426e839aeef7de5164d7d (diff) |
unix: return zero if peer is closed
Previously, recvmsg() on a unix stream socket with its peer closed will
never return, with goroutine call trace like this:
...
2 in gvisor.dev/gvisor/pkg/sentry/kernel.(*Task).block
at pkg/sentry/kernel/task_block.go:124
3 in gvisor.dev/gvisor/pkg/sentry/kernel.(*Task).BlockWithDeadline
at pkg/sentry/kernel/task_block.go:69
4 in gvisor.dev/gvisor/pkg/sentry/socket/unix.(*SocketOperations).RecvMsg
at pkg/sentry/socket/unix/unix.go:612
5 in gvisor.dev/gvisor/pkg/sentry/syscalls/linux.recvFrom
at pkg/sentry/syscalls/linux/sys_socket.go:885
6 in gvisor.dev/gvisor/pkg/sentry/syscalls/linux.RecvFrom
at pkg/sentry/syscalls/linux/sys_socket.go:910
...
The issue is caused by that ErrClosedForReceive returned by
unix/transport.queue is turned into nil in
unix.(*EndpointReader).ReadToBlocks():
err.ToError()
As a result, in unix.(*SocketOperations).RecvMsg():
n == 0 and err == nil
We shall differentiate it from another case - no data to read where
ErrWouldBlock shall be returned; and return 0 immediately.
Fixes: #734
Reported-by: chenglang.hy <chenglang.hy@antfin.com>
Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
-rw-r--r-- | pkg/sentry/socket/unix/unix.go | 3 | ||||
-rw-r--r-- | test/syscalls/linux/socket_unix_stream.cc | 26 |
2 files changed, 28 insertions, 1 deletions
diff --git a/pkg/sentry/socket/unix/unix.go b/pkg/sentry/socket/unix/unix.go index 0d0cb68df..8e4f06a22 100644 --- a/pkg/sentry/socket/unix/unix.go +++ b/pkg/sentry/socket/unix/unix.go @@ -595,7 +595,8 @@ func (s *SocketOperations) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags total += n } - if err != nil || !waitAll || isPacket || n >= dst.NumBytes() { + streamPeerClosed := s.stype == linux.SOCK_STREAM && n == 0 && err == nil + if err != nil || !waitAll || isPacket || n >= dst.NumBytes() || streamPeerClosed { if total > 0 { err = nil } diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc index 659c93945..c7e8860f0 100644 --- a/test/syscalls/linux/socket_unix_stream.cc +++ b/test/syscalls/linux/socket_unix_stream.cc @@ -44,6 +44,32 @@ TEST_P(StreamUnixSocketPairTest, ReadOneSideClosed) { SyscallSucceedsWithValue(0)); } +TEST_P(StreamUnixSocketPairTest, RecvmsgOneSideClosed) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Set timeout so that it will not wait for ever. + struct timeval tv { + .tv_sec = 0, .tv_usec = 10 + }; + EXPECT_THAT( + setsockopt(sockets->second_fd(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), + SyscallSucceeds()); + + ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds()); + + char received_data[10] = {}; + struct iovec iov; + iov.iov_base = received_data; + iov.iov_len = sizeof(received_data); + struct msghdr msg = {}; + msg.msg_flags = -1; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + ASSERT_THAT(recvmsg(sockets->second_fd(), &msg, MSG_WAITALL), + SyscallSucceedsWithValue(0)); +} + INSTANTIATE_TEST_SUITE_P( AllUnixDomainSockets, StreamUnixSocketPairTest, ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( |