diff options
author | Bhasker Hariharan <bhaskerh@google.com> | 2020-11-17 10:34:03 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-11-17 10:36:29 -0800 |
commit | 05d2a26f7a86318216db2256815338c4f9cf8cf2 (patch) | |
tree | c8b084a8b57e60211bafc47abc730b2c2a6d449d /test | |
parent | fb9a649f39dc6f6a6accbf26db30a21fcc4bcf23 (diff) |
Fix possible deadlock in UDP.Write().
In UDP endpoint.Write() sendUDP is called with e.mu Rlocked. But if this happens
to send a datagram over loopback which ends up generating an ICMP response of
say ErrNoPortReachable, the handling of the response in HandleControlPacket also
acquires e.mu using RLock. This is mostly fine unless there is a competing
caller trying to acquire e.mu in exclusive mode using Lock(). This will deadlock
as a caller waiting in Lock() disallows an new RLocks() to ensure it can
actually acquire the Lock.
This is documented here https://golang.org/pkg/sync/#RWMutex.
This change releases the endpoint mutex before calling sendUDP to resolve the
possibility of the deadlock.
Reported-by: syzbot+537989797548c66e8ee3@syzkaller.appspotmail.com
Reported-by: syzbot+eb0b73b4ab486f7673ba@syzkaller.appspotmail.com
PiperOrigin-RevId: 342894148
Diffstat (limited to 'test')
-rw-r--r-- | test/syscalls/linux/udp_socket.cc | 36 |
1 files changed, 36 insertions, 0 deletions
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index d65275fd3..34255bfb8 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -374,6 +374,42 @@ TEST_P(UdpSocketTest, BindInUse) { SyscallFailsWithErrno(EADDRINUSE)); } +TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) { + ASSERT_NO_ERRNO(BindLoopback()); + + // Discover a free unused port by creating a new UDP socket, binding it + // recording the just bound port and closing it. This is not guaranteed as it + // can still race with other port UDP sockets trying to bind a port at the + // same time. + struct sockaddr_storage addr_storage = InetLoopbackAddr(); + socklen_t addrlen = sizeof(addr_storage); + struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage); + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds()); + ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds()); + EXPECT_EQ(addrlen, addrlen_); + EXPECT_NE(*Port(&addr_storage), 0); + ASSERT_THAT(close(s.release()), SyscallSucceeds()); + + // Now connect to the port that we just released. This should generate an + // ECONNREFUSED error. + ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds()); + char buf[512]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. + ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_), + SyscallSucceedsWithValue(sizeof(buf))); + + // Now verify that we got an ICMP error back of ECONNREFUSED. + int err; + socklen_t optlen = sizeof(err); + ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, ECONNREFUSED); + ASSERT_EQ(optlen, sizeof(err)); +} + TEST_P(UdpSocketTest, ReceiveAfterConnect) { ASSERT_NO_ERRNO(BindLoopback()); ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); |