mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-08 12:56:23 +03:00
863e8c30ad
Previously we would incorrectly handle the (somewhat uncommon) case of binding and then separately connecting a tcp socket to a server, as we would register the socket during the manual bind(2) in the sockets by tuple table, but our effective tuple would then change as the result of the connect updating our target peer address. This would result in the the entry not being removed from the table on destruction, which could lead to a UAF. We now make sure to update the table entry if needed during connects.
131 lines
3.6 KiB
C++
131 lines
3.6 KiB
C++
/*
|
|
* Copyright (c) 2023, Idan Horowitz <idan.horowitz@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/JsonArray.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibTest/TestCase.h>
|
|
#include <netinet/in.h>
|
|
#include <pthread.h>
|
|
#include <sys/socket.h>
|
|
|
|
static constexpr u16 port = 1337;
|
|
|
|
static void* server_handler(void*)
|
|
{
|
|
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);
|
|
|
|
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;
|
|
int rc = pthread_create(&thread, nullptr, server_handler, nullptr);
|
|
EXPECT_EQ(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 loop since the TimedWait stage during socket close means the socket might not close immediately
|
|
// after our close(2) call. This also means that on failure we will loop here forever.
|
|
while (true) {
|
|
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());
|
|
if (json.as_array().size() == 0)
|
|
return;
|
|
sched_yield();
|
|
}
|
|
}
|