Kernel: Fix race between accept() and connect().

We had a bug where an accepted socket would appear to be EOF/disconnected
on the accepting side until the connecting side had called attach_fd().

Fix this by adding a new SocketRole::Connecting state.
This commit is contained in:
Andreas Kling 2019-03-20 02:33:51 +01:00
parent 31d3616027
commit 4aa0ab4e08
Notes: sideshowbarker 2024-07-19 14:59:59 +09:00
5 changed files with 14 additions and 4 deletions

View File

@ -76,8 +76,11 @@ void FileDescriptor::set_socket_role(SocketRole role)
return;
ASSERT(m_socket);
auto old_socket_role = m_socket_role;
m_socket_role = role;
m_socket->attach_fd(role);
if (old_socket_role != SocketRole::None)
m_socket->detach_fd(old_socket_role);
}
Retained<FileDescriptor> FileDescriptor::clone()

View File

@ -106,6 +106,8 @@ void LocalSocket::attach_fd(SocketRole role)
++m_accepted_fds_open;
} else if (role == SocketRole::Connected) {
++m_connected_fds_open;
} else if (role == SocketRole::Connecting) {
++m_connecting_fds_open;
}
}
@ -117,6 +119,9 @@ void LocalSocket::detach_fd(SocketRole role)
} else if (role == SocketRole::Connected) {
ASSERT(m_connected_fds_open);
--m_connected_fds_open;
} else if (role == SocketRole::Connecting) {
ASSERT(m_connecting_fds_open);
--m_connecting_fds_open;
}
}
@ -125,7 +130,7 @@ bool LocalSocket::can_read(SocketRole role) const
if (role == SocketRole::Listener)
return can_accept();
if (role == SocketRole::Accepted)
return !m_connected_fds_open || !m_for_server.is_empty();
return (!m_connected_fds_open && !m_connecting_fds_open) || !m_for_server.is_empty();
if (role == SocketRole::Connected)
return !m_accepted_fds_open || !m_for_client.is_empty();
ASSERT_NOT_REACHED();
@ -148,7 +153,7 @@ ssize_t LocalSocket::write(SocketRole role, const byte* data, ssize_t size)
return m_for_client.write(data, size);
}
if (role == SocketRole::Connected) {
if (!m_connected_fds_open)
if (!m_connected_fds_open && !m_connecting_fds_open)
return -EPIPE;
return m_for_server.write(data, size);
}
@ -158,7 +163,7 @@ ssize_t LocalSocket::write(SocketRole role, const byte* data, ssize_t size)
bool LocalSocket::can_write(SocketRole role) const
{
if (role == SocketRole::Accepted)
return !m_connected_fds_open || m_for_client.bytes_in_write_buffer() < 4096;
return (!m_connected_fds_open && !m_connecting_fds_open) || m_for_client.bytes_in_write_buffer() < 4096;
if (role == SocketRole::Connected)
return !m_accepted_fds_open || m_for_server.bytes_in_write_buffer() < 4096;
ASSERT_NOT_REACHED();

View File

@ -32,6 +32,7 @@ private:
bool m_bound { false };
int m_accepted_fds_open { 0 };
int m_connected_fds_open { 0 };
int m_connecting_fds_open { 0 };
sockaddr_un m_address;
DoubleBuffer m_for_client;

View File

@ -2503,6 +2503,7 @@ int Process::sys$connect(int sockfd, const sockaddr* address, socklen_t address_
if (!descriptor->is_socket())
return -ENOTSOCK;
auto& socket = *descriptor->socket();
descriptor->set_socket_role(SocketRole::Connecting);
auto result = socket.connect(address, address_size);
if (result.is_error())
return result;

View File

@ -8,7 +8,7 @@
#include <Kernel/UnixTypes.h>
#include <Kernel/KResult.h>
enum class SocketRole { None, Listener, Accepted, Connected };
enum class SocketRole { None, Listener, Accepted, Connected, Connecting };
class Socket : public Retainable<Socket> {
public: