mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-08 17:49:04 -10:00
The test previously hanged when a tcp socket was alive during test execution. With this patch, we only consider sockets that were created by the test itself. This allows the test to pass when, for example, SSHServer is also running in the background. Also, this patch sets a maximum number of tries after which the test will fail. In other words, we now report a failure instead of hanging if a socket is leaked. Interestingly enough, the required number of tries is higher on faster systems. On my x86_64 machine, in x86_64 QEMU it takes ~800 tries while on RISC-V QEMU it only takes ~10.
186 lines
5.0 KiB
C++
186 lines
5.0 KiB
C++
/*
|
|
* Copyright (c) 2023, Idan Horowitz <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/JsonArray.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibTest/TestCase.h>
|
|
#include <netinet/in.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <sys/socket.h>
|
|
|
|
static constexpr u16 port = 1337;
|
|
|
|
static void* server_handler(void* accept_semaphore)
|
|
{
|
|
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
EXPECT(server_fd >= 0);
|
|
|
|
sockaddr_in sin {};
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(port);
|
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
int rc = bind(server_fd, (sockaddr*)(&sin), sizeof(sin));
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = listen(server_fd, 1);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = sem_post(reinterpret_cast<sem_t*>(accept_semaphore));
|
|
VERIFY(rc == 0);
|
|
|
|
int client_fd = accept(server_fd, nullptr, nullptr);
|
|
EXPECT(client_fd >= 0);
|
|
|
|
u8 data;
|
|
int nread = recv(client_fd, &data, sizeof(data), 0);
|
|
EXPECT_EQ(nread, 1);
|
|
EXPECT_EQ(data, 'A');
|
|
|
|
rc = close(client_fd);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = close(server_fd);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
pthread_exit(nullptr);
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
static pthread_t start_tcp_server()
|
|
{
|
|
pthread_t thread;
|
|
sem_t accept_semaphore;
|
|
|
|
int rc = sem_init(&accept_semaphore, 0, 0);
|
|
VERIFY(rc == 0);
|
|
rc = pthread_create(&thread, nullptr, server_handler, &accept_semaphore);
|
|
VERIFY(rc == 0);
|
|
rc = sem_wait(&accept_semaphore);
|
|
VERIFY(rc == 0);
|
|
rc = sem_destroy(&accept_semaphore);
|
|
VERIFY(rc == 0);
|
|
return thread;
|
|
}
|
|
|
|
TEST_CASE(tcp_sendto)
|
|
{
|
|
pthread_t server = start_tcp_server();
|
|
|
|
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
EXPECT(client_fd >= 0);
|
|
|
|
sockaddr_in sin {};
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(port);
|
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
int rc = connect(client_fd, (sockaddr*)(&sin), sizeof(sin));
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
u8 data = 'A';
|
|
sockaddr_in dst {};
|
|
dst.sin_family = AF_INET;
|
|
dst.sin_port = htons(port + 1); // Different port, should be ignored
|
|
dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
int nwritten = sendto(client_fd, &data, sizeof(data), 0, (sockaddr*)(&dst), sizeof(dst));
|
|
EXPECT_EQ(nwritten, 1);
|
|
|
|
rc = close(client_fd);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = pthread_join(server, nullptr);
|
|
EXPECT_EQ(rc, 0);
|
|
}
|
|
|
|
TEST_CASE(tcp_bind_connect)
|
|
{
|
|
pthread_t server = start_tcp_server();
|
|
|
|
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
EXPECT(client_fd >= 0);
|
|
|
|
sockaddr_in sin {};
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons(port - 1);
|
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
int rc = bind(client_fd, (sockaddr*)(&sin), sizeof(sin));
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
sockaddr_in dst {};
|
|
dst.sin_family = AF_INET;
|
|
dst.sin_port = htons(port);
|
|
dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
rc = connect(client_fd, (sockaddr*)(&dst), sizeof(dst));
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
u8 data = 'A';
|
|
int nwritten = send(client_fd, &data, sizeof(data), 0);
|
|
EXPECT_EQ(nwritten, 1);
|
|
|
|
rc = close(client_fd);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = pthread_join(server, nullptr);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
// Hacky check to make sure there are no registered TCP sockets, if the sockets were closed properly, there should
|
|
// be none left, but if the early-bind caused a desync in sockets_by_tuple a UAF'd socket will be left in there.
|
|
// NOTE: We have to give it multiple tries since the TimedWait stage during socket close means the socket might not
|
|
// close immediately after our close(2) call.
|
|
static constexpr u32 max_tries = 2000;
|
|
for (u32 i = 0; i < max_tries; ++i) {
|
|
auto file = MUST(Core::File::open("/sys/kernel/net/tcp"sv, Core::File::OpenMode::Read));
|
|
auto file_contents = MUST(file->read_until_eof());
|
|
auto json = MUST(JsonValue::from_string(file_contents));
|
|
EXPECT(json.is_array());
|
|
u32 leaked_sockets = 0;
|
|
for (auto const& entry : json.as_array().values()) {
|
|
auto const& object = entry.as_object();
|
|
if (object.get_i64("origin_pid"sv) == getpid())
|
|
leaked_sockets++;
|
|
}
|
|
|
|
if (leaked_sockets == 0)
|
|
break;
|
|
|
|
if (i == max_tries - 1)
|
|
EXPECT_EQ(leaked_sockets, 0u);
|
|
else
|
|
sched_yield();
|
|
}
|
|
}
|
|
|
|
TEST_CASE(socket_connect_after_bind)
|
|
{
|
|
unlink("/tmp/tmp-client.test");
|
|
unlink("/tmp/tmp.test");
|
|
|
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
EXPECT(fd >= 0);
|
|
|
|
struct sockaddr_un addr {
|
|
.sun_family = AF_UNIX,
|
|
.sun_path = "/tmp/tmp-client.test",
|
|
};
|
|
|
|
int bound = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
|
|
EXPECT_NE(bound, -1);
|
|
|
|
struct sockaddr_un server_sockaddr {
|
|
.sun_family = AF_UNIX,
|
|
.sun_path = "/tmp/tmp.test",
|
|
};
|
|
int connected = connect(fd, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr));
|
|
EXPECT_EQ(connected, -1);
|
|
|
|
int closed = close(fd);
|
|
EXPECT_EQ(closed, 0);
|
|
|
|
unlink("/tmp/tmp-client.test");
|
|
unlink("/tmp/tmp.test");
|
|
}
|