From ce5c033e2b5a07c172f10dbcb5055515cd9290db Mon Sep 17 00:00:00 2001 From: Ayushi Khopkar Date: Mon, 20 Sep 2021 12:00:21 +0530 Subject: [PATCH] Added fastboot_fuzzer Test: ./fastboot_fuzzer Bug: 189053436 Change-Id: Idf9be2f86238eb2c7090402adc54bbb9c0b43582 --- fastboot/Android.bp | 6 + fastboot/fuzzer/Android.bp | 57 ++++++ fastboot/fuzzer/README.md | 51 +++++ fastboot/fuzzer/fastboot_fuzzer.cpp | 276 +++++++++++++++++++++++++++ fastboot/fuzzer/socket_mock_fuzz.cpp | 126 ++++++++++++ fastboot/fuzzer/socket_mock_fuzz.h | 73 +++++++ 6 files changed, 589 insertions(+) create mode 100644 fastboot/fuzzer/Android.bp create mode 100644 fastboot/fuzzer/README.md create mode 100644 fastboot/fuzzer/fastboot_fuzzer.cpp create mode 100644 fastboot/fuzzer/socket_mock_fuzz.cpp create mode 100644 fastboot/fuzzer/socket_mock_fuzz.h diff --git a/fastboot/Android.bp b/fastboot/Android.bp index 2c70778ae..339f392de 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -408,3 +408,9 @@ cc_test_host { ":fastboot_test_vendor_boot_v4_with_frag" ], } + +cc_library_headers { + name: "fastboot_headers", + host_supported: true, + export_include_dirs: ["."], +} diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp new file mode 100644 index 000000000..fcd3bd64e --- /dev/null +++ b/fastboot/fuzzer/Android.bp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +cc_fuzz { + name: "fastboot_fuzzer", + host_supported: true, + device_supported: false, + srcs: [ + "fastboot_fuzzer.cpp", + "socket_mock_fuzz.cpp", + ], + header_libs: [ + "bootimg_headers", + "fastboot_headers", + ], + static_libs: [ + "libext4_utils", + "libcrypto", + "libfastboot", + "libbuildversion", + "libbase", + "libziparchive", + "libsparse", + "libutils", + "liblog", + "libz", + "libdiagnose_usb", + "libbase", + "libcutils", + "libgtest", + "libgtest_main", + "libbase", + "libadb_host", + "liblp", + "liblog", + ], + fuzz_config: { + cc: [ + "android-media-fuzzing-reports@google.com", + ], + componentid: 533764, + }, +} diff --git a/fastboot/fuzzer/README.md b/fastboot/fuzzer/README.md new file mode 100644 index 000000000..10b06ea78 --- /dev/null +++ b/fastboot/fuzzer/README.md @@ -0,0 +1,51 @@ +# Fuzzer for libfastboot + +## Plugin Design Considerations +The fuzzer plugin for libfastboot is designed based on the understanding of the +source code and tries to achieve the following: + +##### Maximize code coverage +The configuration parameters are not hardcoded, but instead selected based on +incoming data. This ensures more code paths are reached by the fuzzer. + +libfastboot supports the following parameters: +1. Year (parameter name: `year`) +2. Month (parameter name: `month`) +3. Day (parameter name: `day`) +4. Version (parameter name: `version`) +5. Fs Option (parameter name: `fsOption`) + +| Parameter| Valid Values| Configured Value| +|------------- |-------------| ----- | +| `year` | `2000` to `2127` | Value obtained from FuzzedDataProvider| +| `month` | `1` to `12` | Value obtained from FuzzedDataProvider| +| `day` | `1` to `31` | Value obtained from FuzzedDataProvider| +| `version` | `0` to `127` | Value obtained from FuzzedDataProvider| +| `fsOption` | 0. `casefold` 1. `projid` 2. `compress` | Value obtained from FuzzedDataProvider| + +##### Maximize utilization of input data +The plugin feeds the entire input data to the module. +This ensures that the plugin tolerates any kind of input (empty, huge, +malformed, etc) and doesnt `exit()` on any input and thereby increasing the +chance of identifying vulnerabilities. + +## Build + +This describes steps to build fastboot_fuzzer binary. + +### Android + +#### Steps to build +Build the fuzzer +``` + $ mm -j$(nproc) fastboot_fuzzer_fuzzer +``` +#### Steps to run +To run on host +``` + $ $ANDROID_HOST_OUT/fuzz/${TARGET_ARCH}/fastboot_fuzzer/fastboot_fuzzer CORPUS_DIR +``` + +## References: + * http://llvm.org/docs/LibFuzzer.html + * https://github.com/google/oss-fuzz diff --git a/fastboot/fuzzer/fastboot_fuzzer.cpp b/fastboot/fuzzer/fastboot_fuzzer.cpp new file mode 100644 index 000000000..60940fe3d --- /dev/null +++ b/fastboot/fuzzer/fastboot_fuzzer.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "fastboot.h" +#include "socket.h" +#include "socket_mock_fuzz.h" +#include "tcp.h" +#include "udp.h" +#include "vendor_boot_img_utils.h" + +#include + +using namespace std; + +const size_t kYearMin = 2000; +const size_t kYearMax = 2127; +const size_t kMonthMin = 1; +const size_t kMonthMax = 12; +const size_t kDayMin = 1; +const size_t kDayMax = 31; +const size_t kVersionMin = 0; +const size_t kVersionMax = 127; +const size_t kMaxStringSize = 100; +const size_t kMinTimeout = 10; +const size_t kMaxTimeout = 3000; +const uint16_t kValidUdpPacketSize = 512; +const uint16_t kMinUdpPackets = 1; +const uint16_t kMaxUdpPackets = 10; + +const string kValidTcpHandshakeString = "FB01"; +const string kInvalidTcpHandshakeString = "FB00"; +const string kValidRamdiskName = "default"; +const string kVendorBootFile = "/tmp/vendorBootFile"; +const string kRamdiskFile = "/tmp/ramdiskFile"; +const char* kFsOptionsArray[] = {"casefold", "projid", "compress"}; + +class FastbootFuzzer { + public: + void Process(const uint8_t* data, size_t size); + + private: + void InvokeParseApi(); + void InvokeSocket(); + void InvokeTcp(); + void InvokeUdp(); + void InvokeVendorBootImgUtils(const uint8_t* data, size_t size); + bool MakeConnectedSockets(Socket::Protocol protocol, unique_ptr* server, + unique_ptr* client, const string& hostname); + unique_ptr fdp_ = nullptr; +}; + +void FastbootFuzzer::InvokeParseApi() { + boot_img_hdr_v1 hdr = {}; + FastBootTool fastBoot; + + int32_t year = fdp_->ConsumeIntegralInRange(kYearMin, kYearMax); + int32_t month = fdp_->ConsumeIntegralInRange(kMonthMin, kMonthMax); + int32_t day = fdp_->ConsumeIntegralInRange(kDayMin, kDayMax); + string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day); + fastBoot.ParseOsPatchLevel(&hdr, date.c_str()); + + int32_t major = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); + int32_t minor = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); + int32_t patch = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); + string version = to_string(major) + "." + to_string(minor) + "." + to_string(patch); + fastBoot.ParseOsVersion(&hdr, version.c_str()); + + fastBoot.ParseFsOption(fdp_->PickValueInArray(kFsOptionsArray)); +} + +bool FastbootFuzzer::MakeConnectedSockets(Socket::Protocol protocol, unique_ptr* server, + unique_ptr* client, + const string& hostname = "localhost") { + *server = Socket::NewServer(protocol, 0); + if (*server == nullptr) { + return false; + } + *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr); + if (*client == nullptr) { + return false; + } + if (protocol == Socket::Protocol::kTcp) { + *server = (*server)->Accept(); + if (*server == nullptr) { + return false; + } + } + return true; +} + +void FastbootFuzzer::InvokeSocket() { + unique_ptr server, client; + + for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { + if (MakeConnectedSockets(protocol, &server, &client)) { + string message = fdp_->ConsumeRandomLengthString(kMaxStringSize); + client->Send(message.c_str(), message.length()); + string received(message.length(), '\0'); + if (fdp_->ConsumeBool()) { + client->Close(); + } + if (fdp_->ConsumeBool()) { + server->Close(); + } + server->ReceiveAll(&received[0], received.length(), + /* timeout_ms */ + fdp_->ConsumeIntegralInRange(kMinTimeout, kMaxTimeout)); + server->Close(); + client->Close(); + } + } +} + +void FastbootFuzzer::InvokeTcp() { + /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */ + SocketMockFuzz* tcp_mock = new SocketMockFuzz; + tcp_mock->ExpectSend(fdp_->ConsumeBool() ? kValidTcpHandshakeString + : kInvalidTcpHandshakeString); + tcp_mock->AddReceive(fdp_->ConsumeBool() ? kValidTcpHandshakeString + : kInvalidTcpHandshakeString); + + string error; + unique_ptr transport = tcp::internal::Connect(unique_ptr(tcp_mock), &error); + + if (transport.get()) { + string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); + if (fdp_->ConsumeBool()) { + tcp_mock->ExpectSend(write_message); + } else { + tcp_mock->ExpectSendFailure(write_message); + } + string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); + if (fdp_->ConsumeBool()) { + tcp_mock->AddReceive(read_message); + } else { + tcp_mock->AddReceiveFailure(); + } + + transport->Write(write_message.data(), write_message.length()); + + string buffer(read_message.length(), '\0'); + transport->Read(&buffer[0], buffer.length()); + + transport->Close(); + } +} + +static string PacketValue(uint16_t value) { + return string{static_cast(value >> 8), static_cast(value)}; +} + +static string ErrorPacket(uint16_t sequence, const string& message = "", + char flags = udp::internal::kFlagNone) { + return string{udp::internal::kIdError, flags} + PacketValue(sequence) + message; +} + +static string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) { + return string{udp::internal::kIdInitialization, udp::internal::kFlagNone} + + PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size); +} + +static string QueryPacket(uint16_t sequence, uint16_t new_sequence) { + return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence) + + PacketValue(new_sequence); +} + +static string QueryPacket(uint16_t sequence) { + return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence); +} + +static string FastbootPacket(uint16_t sequence, const string& data = "", + char flags = udp::internal::kFlagNone) { + return string{udp::internal::kIdFastboot, flags} + PacketValue(sequence) + data; +} + +void FastbootFuzzer::InvokeUdp() { + /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */ + SocketMockFuzz* udp_mock = new SocketMockFuzz; + uint16_t starting_sequence = fdp_->ConsumeIntegral(); + int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize + : fdp_->ConsumeIntegralInRange( + 0, kValidUdpPacketSize - 1); + udp_mock->ExpectSend(QueryPacket(0)); + udp_mock->AddReceive(QueryPacket(0, starting_sequence)); + udp_mock->ExpectSend(InitPacket(starting_sequence, udp::internal::kProtocolVersion, + udp::internal::kHostMaxPacketSize)); + udp_mock->AddReceive( + InitPacket(starting_sequence, udp::internal::kProtocolVersion, device_max_packet_size)); + + string error; + unique_ptr transport = udp::internal::Connect(unique_ptr(udp_mock), &error); + bool is_transport_initialized = transport != nullptr && error.empty(); + + if (is_transport_initialized) { + uint16_t num_packets = + fdp_->ConsumeIntegralInRange(kMinUdpPackets, kMaxUdpPackets); + + for (uint16_t i = 0; i < num_packets; ++i) { + string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); + string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); + if (fdp_->ConsumeBool()) { + udp_mock->ExpectSend(FastbootPacket(i, write_message)); + } else { + udp_mock->ExpectSend(ErrorPacket(i, write_message)); + } + + if (fdp_->ConsumeBool()) { + udp_mock->AddReceive(FastbootPacket(i, read_message)); + } else { + udp_mock->AddReceive(ErrorPacket(i, read_message)); + } + transport->Write(write_message.data(), write_message.length()); + string buffer(read_message.length(), '\0'); + transport->Read(&buffer[0], buffer.length()); + } + transport->Close(); + } +} + +void FastbootFuzzer::InvokeVendorBootImgUtils(const uint8_t* data, size_t size) { + int32_t vendor_boot_fd = open(kVendorBootFile.c_str(), O_CREAT | O_RDWR, 0644); + if (vendor_boot_fd < 0) { + return; + } + int32_t ramdisk_fd = open(kRamdiskFile.c_str(), O_CREAT | O_RDWR, 0644); + if (ramdisk_fd < 0) { + return; + } + write(vendor_boot_fd, data, size); + write(ramdisk_fd, data, size); + string ramdisk_name = fdp_->ConsumeBool() ? kValidRamdiskName + : fdp_->ConsumeRandomLengthString(kMaxStringSize); + string content_vendor_boot_fd = {}; + string content_ramdisk_fd = {}; + lseek(vendor_boot_fd, 0, SEEK_SET); + lseek(ramdisk_fd, 0, SEEK_SET); + android::base::ReadFdToString(vendor_boot_fd, &content_vendor_boot_fd); + android::base::ReadFdToString(ramdisk_fd, &content_ramdisk_fd); + uint64_t vendor_boot_size = + fdp_->ConsumeBool() ? content_vendor_boot_fd.size() : fdp_->ConsumeIntegral(); + uint64_t ramdisk_size = + fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral(); + (void)replace_vendor_ramdisk(vendor_boot_fd, vendor_boot_size, ramdisk_name, ramdisk_fd, + ramdisk_size); + close(vendor_boot_fd); + close(ramdisk_fd); +} + +void FastbootFuzzer::Process(const uint8_t* data, size_t size) { + fdp_ = make_unique(data, size); + InvokeParseApi(); + InvokeSocket(); + InvokeTcp(); + InvokeUdp(); + InvokeVendorBootImgUtils(data, size); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FastbootFuzzer fastbootFuzzer; + fastbootFuzzer.Process(data, size); + return 0; +} diff --git a/fastboot/fuzzer/socket_mock_fuzz.cpp b/fastboot/fuzzer/socket_mock_fuzz.cpp new file mode 100644 index 000000000..df96eb005 --- /dev/null +++ b/fastboot/fuzzer/socket_mock_fuzz.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "socket_mock_fuzz.h" + +SocketMockFuzz::SocketMockFuzz() : Socket(INVALID_SOCKET) {} + +SocketMockFuzz::~SocketMockFuzz() {} + +bool SocketMockFuzz::Send(const void* data, size_t length) { + if (events_.empty()) { + return false; + } + + if (events_.front().type != EventType::kSend) { + return false; + } + + std::string message(reinterpret_cast(data), length); + if (events_.front().message != message) { + return false; + } + + bool return_value = events_.front().status; + events_.pop(); + return return_value; +} + +// Mock out multi-buffer send to be one large send, since that's what it should looks like from +// the user's perspective. +bool SocketMockFuzz::Send(std::vector buffers) { + std::string data; + for (const auto& buffer : buffers) { + data.append(reinterpret_cast(buffer.data), buffer.length); + } + return Send(data.data(), data.size()); +} + +ssize_t SocketMockFuzz::Receive(void* data, size_t length, int /*timeout_ms*/) { + if (events_.empty()) { + return -1; + } + + const Event& event = events_.front(); + if (event.type != EventType::kReceive) { + return -1; + } + + const std::string& message = event.message; + if (message.length() > length) { + return -1; + } + + receive_timed_out_ = event.status; + ssize_t return_value = message.length(); + + // Empty message indicates failure. + if (message.empty()) { + return_value = -1; + } else { + memcpy(data, message.data(), message.length()); + } + + events_.pop(); + return return_value; +} + +int SocketMockFuzz::Close() { + return 0; +} + +std::unique_ptr SocketMockFuzz::Accept() { + if (events_.empty()) { + return nullptr; + } + + if (events_.front().type != EventType::kAccept) { + return nullptr; + } + + std::unique_ptr sock = std::move(events_.front().sock); + events_.pop(); + return sock; +} + +void SocketMockFuzz::ExpectSend(std::string message) { + events_.push(Event(EventType::kSend, std::move(message), true, nullptr)); +} + +void SocketMockFuzz::ExpectSendFailure(std::string message) { + events_.push(Event(EventType::kSend, std::move(message), false, nullptr)); +} + +void SocketMockFuzz::AddReceive(std::string message) { + events_.push(Event(EventType::kReceive, std::move(message), false, nullptr)); +} + +void SocketMockFuzz::AddReceiveTimeout() { + events_.push(Event(EventType::kReceive, "", true, nullptr)); +} + +void SocketMockFuzz::AddReceiveFailure() { + events_.push(Event(EventType::kReceive, "", false, nullptr)); +} + +void SocketMockFuzz::AddAccept(std::unique_ptr sock) { + events_.push(Event(EventType::kAccept, "", false, std::move(sock))); +} + +SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status, + std::unique_ptr _sock) + : type(_type), message(_message), status(_status), sock(std::move(_sock)) {} diff --git a/fastboot/fuzzer/socket_mock_fuzz.h b/fastboot/fuzzer/socket_mock_fuzz.h new file mode 100644 index 000000000..67bd0d6ae --- /dev/null +++ b/fastboot/fuzzer/socket_mock_fuzz.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include "socket.h" + +class SocketMockFuzz : public Socket { + public: + SocketMockFuzz(); + ~SocketMockFuzz() override; + + bool Send(const void* data, size_t length) override; + bool Send(std::vector buffers) override; + ssize_t Receive(void* data, size_t length, int timeout_ms) override; + int Close() override; + virtual std::unique_ptr Accept(); + + // Adds an expectation for Send(). + void ExpectSend(std::string message); + + // Adds an expectation for Send() that returns false. + void ExpectSendFailure(std::string message); + + // Adds data to provide for Receive(). + void AddReceive(std::string message); + + // Adds a Receive() timeout after which ReceiveTimedOut() will return true. + void AddReceiveTimeout(); + + // Adds a Receive() failure after which ReceiveTimedOut() will return false. + void AddReceiveFailure(); + + // Adds a Socket to return from Accept(). + void AddAccept(std::unique_ptr sock); + + private: + enum class EventType { kSend, kReceive, kAccept }; + + struct Event { + Event(EventType _type, std::string _message, ssize_t _status, + std::unique_ptr _sock); + + EventType type; + std::string message; + bool status; // Return value for Send() or timeout status for Receive(). + std::unique_ptr sock; + }; + + std::queue events_; + + DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz); +};