// 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 #include #include #include #include #include #include #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(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 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 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; }