255 lines
7.6 KiB
C++
255 lines
7.6 KiB
C++
|
// Copyright 2021 The ChromiumOS Authors
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
#include <cstddef>
|
||
|
#include <cstdint>
|
||
|
#include <dirent.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <fuzzer/FuzzedDataProvider.h>
|
||
|
|
||
|
#include <wayland-client.h>
|
||
|
|
||
|
#include "sommelier.h" // NOLINT(build/include_directory)
|
||
|
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
|
||
|
#include "virtualization/wayland_channel.h" // NOLINT(build/include_directory)
|
||
|
|
||
|
class FuzzChannel : public WaylandChannel {
|
||
|
private:
|
||
|
int recv_fd = -1;
|
||
|
|
||
|
public:
|
||
|
int send_fd = -1;
|
||
|
|
||
|
~FuzzChannel() {
|
||
|
if (send_fd != -1) {
|
||
|
close(send_fd);
|
||
|
}
|
||
|
if (recv_fd != -1) {
|
||
|
close(recv_fd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int32_t init() override { return 0; }
|
||
|
|
||
|
bool supports_dmabuf() override { return false; }
|
||
|
|
||
|
int32_t create_context(int& out_channel_fd) override {
|
||
|
int sv[2];
|
||
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv)) {
|
||
|
return -errno;
|
||
|
}
|
||
|
|
||
|
send_fd = sv[0];
|
||
|
recv_fd = sv[1];
|
||
|
out_channel_fd = sv[1];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int32_t create_pipe(int& out_pipe_fd) override { return -1; }
|
||
|
|
||
|
int32_t send(const struct WaylandSendReceive& send) override { return 0; }
|
||
|
|
||
|
int32_t handle_channel_event(enum WaylandChannelEvent& event_type,
|
||
|
struct WaylandSendReceive& receive,
|
||
|
int& out_read_pipe) override {
|
||
|
uint8_t* buffer = static_cast<uint8_t*>(malloc(DEFAULT_BUFFER_SIZE));
|
||
|
int bytes = recv(receive.channel_fd, buffer, DEFAULT_BUFFER_SIZE, 0);
|
||
|
if (bytes < 0) {
|
||
|
free(buffer);
|
||
|
return -errno;
|
||
|
}
|
||
|
|
||
|
receive.data = buffer;
|
||
|
receive.data_size = bytes;
|
||
|
receive.num_fds = 0;
|
||
|
|
||
|
event_type = WaylandChannelEvent::Receive;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int32_t allocate(const struct WaylandBufferCreateInfo& create_info,
|
||
|
struct WaylandBufferCreateOutput& create_output) override {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int32_t sync(int dmabuf_fd, uint64_t flags) override { return 0; }
|
||
|
int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
size_t max_send_size(void) override { return DEFAULT_BUFFER_SIZE; }
|
||
|
};
|
||
|
|
||
|
void null_logger(const char*, va_list) {}
|
||
|
|
||
|
class Environment {
|
||
|
public:
|
||
|
Environment() {
|
||
|
wl_log_set_handler_client(null_logger);
|
||
|
wl_log_set_handler_server(null_logger);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static int handle_host_to_sommelier_event(int fd, uint32_t mask, void* data) {
|
||
|
struct sl_context* ctx = (struct sl_context*)data;
|
||
|
int count = 0;
|
||
|
|
||
|
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (mask & WL_EVENT_READABLE)
|
||
|
count = wl_display_dispatch(ctx->display);
|
||
|
if (mask & WL_EVENT_WRITABLE)
|
||
|
wl_display_flush(ctx->display);
|
||
|
|
||
|
if (mask == 0) {
|
||
|
count = wl_display_dispatch_pending(ctx->display);
|
||
|
wl_display_flush(ctx->display);
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
int drain_socket(int fd, uint32_t mask, void* data) {
|
||
|
uint8_t buffer[DEFAULT_BUFFER_SIZE];
|
||
|
return recv(fd, buffer, DEFAULT_BUFFER_SIZE, 0) > 0;
|
||
|
}
|
||
|
|
||
|
// Count the number of open file descriptors to make sure we don't leak any.
|
||
|
// Aborts on error, as this should never fail.
|
||
|
int count_fds() {
|
||
|
DIR* dir = opendir("/proc/self/fd");
|
||
|
if (!dir) {
|
||
|
fprintf(stderr, "Failed to open /proc/self/fd: %m\n");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
// Ignore the "." and ".." entries, along with the fd we just opened.
|
||
|
int count = -3;
|
||
|
|
||
|
// Needed to distinguish between eof and errors.
|
||
|
errno = 0;
|
||
|
while (struct dirent* dirent = readdir(dir)) {
|
||
|
count++;
|
||
|
}
|
||
|
if (errno) {
|
||
|
fprintf(stderr, "Failed to read from /proc/self/fd: %m\n");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
if (closedir(dir)) {
|
||
|
fprintf(stderr, "Failed to close /proc/self/fd: %m\n");
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
int LLVMFuzzerTestOneInput_real(const uint8_t* data, size_t size) {
|
||
|
static Environment env;
|
||
|
FuzzedDataProvider source(data, size);
|
||
|
|
||
|
struct sl_context ctx;
|
||
|
FuzzChannel channel;
|
||
|
int host_to_sommelier, client_to_sommelier;
|
||
|
|
||
|
sl_context_init_default(&ctx);
|
||
|
|
||
|
// Create a connection from the host to sommelier. This is done via a
|
||
|
// WaylandChannel implementation that keeps a socketpair internally. One end
|
||
|
// goes to sommelier to listen on/write to, the other end is kept by us. The
|
||
|
// channel implements send with a no-op, so we don't ever have to read from
|
||
|
// our end.
|
||
|
ctx.host_display = wl_display_create();
|
||
|
struct wl_event_loop* event_loop =
|
||
|
wl_display_get_event_loop(ctx.host_display);
|
||
|
ctx.channel = &channel;
|
||
|
sl_context_init_wayland_channel(&ctx, event_loop, /*display=*/false);
|
||
|
// `display` takes ownership of `virtwl_display_fd`
|
||
|
ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd);
|
||
|
|
||
|
// Create a connection from the client to sommelier. One end is passed into
|
||
|
// libwayland for sommelier to handle, the other end is owned by the
|
||
|
// fuzzer. We set up the event loop to drain any data send by sommelier to our
|
||
|
// end, and write fuzz data to our end in the main loop.
|
||
|
int sv[2];
|
||
|
int ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv);
|
||
|
assert(!ret);
|
||
|
// wl_client takes ownership of its file descriptor
|
||
|
ctx.client = wl_client_create(ctx.host_display, sv[0]);
|
||
|
std::unique_ptr<struct wl_event_source> drain_event(wl_event_loop_add_fd(
|
||
|
event_loop, sv[1], WL_EVENT_READABLE, drain_socket, nullptr));
|
||
|
|
||
|
// Add the host-to-sommelier display to the event loop. The client wayland
|
||
|
// library doesn't have an event loop system, instead you're expected to
|
||
|
// integrate into another event loop using wl_display_get_fd if you need to.
|
||
|
std::unique_ptr<struct wl_event_source> display_event(
|
||
|
wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display),
|
||
|
WL_EVENT_READABLE | WL_EVENT_WRITABLE,
|
||
|
handle_host_to_sommelier_event, &ctx));
|
||
|
|
||
|
// Getting the registry object from the host and listening for events is the
|
||
|
// top-level task in sommelier. Everything else is driven by messages from the
|
||
|
// host or the client.
|
||
|
auto* registry = wl_display_get_registry(ctx.display);
|
||
|
wl_registry_add_listener(registry, &sl_registry_listener, &ctx);
|
||
|
|
||
|
host_to_sommelier = channel.send_fd;
|
||
|
client_to_sommelier = sv[1];
|
||
|
|
||
|
while (source.remaining_bytes() &&
|
||
|
!wl_list_empty(wl_display_get_client_list(ctx.host_display)) &&
|
||
|
!wl_display_get_error(ctx.display)) {
|
||
|
// Randomly pick a connection to write too.
|
||
|
int fd = source.ConsumeBool() ? host_to_sommelier : client_to_sommelier;
|
||
|
|
||
|
// The random length string extractor uses a terminator character to prevent
|
||
|
// the boundaries of a message from shifting when the fuzzer inserts or
|
||
|
// removed bytes. This is (probably) better then consuming a length and then
|
||
|
// consuming that many characters.
|
||
|
std::string data = source.ConsumeRandomLengthString(DEFAULT_BUFFER_SIZE);
|
||
|
|
||
|
int sent = send(fd, data.data(), data.length(), 0);
|
||
|
assert(sent == data.length());
|
||
|
|
||
|
wl_display_flush_clients(ctx.host_display);
|
||
|
wl_event_loop_dispatch(event_loop, 0);
|
||
|
}
|
||
|
|
||
|
wl_registry_destroy(registry);
|
||
|
|
||
|
close(ctx.virtwl_socket_fd);
|
||
|
close(client_to_sommelier);
|
||
|
|
||
|
ctx.wayland_channel_event_source.reset();
|
||
|
ctx.virtwl_socket_event_source.reset();
|
||
|
drain_event.reset();
|
||
|
display_event.reset();
|
||
|
|
||
|
wl_display_destroy_clients(ctx.host_display);
|
||
|
wl_display_destroy(ctx.host_display);
|
||
|
|
||
|
wl_display_disconnect(ctx.display);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||
|
int start_fds = count_fds();
|
||
|
|
||
|
int ret = LLVMFuzzerTestOneInput_real(data, size);
|
||
|
|
||
|
int end_fds = count_fds();
|
||
|
if (start_fds != end_fds) {
|
||
|
fprintf(stderr, "Leaked %d file descriptors!\n", end_fds - start_fds);
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|