Added fastboot_fuzzer
Test: ./fastboot_fuzzer
Bug: 189053436
Change-Id: Idf9be2f86238eb2c7090402adc54bbb9c0b43582
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 2c70778..339f392 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -408,3 +408,9 @@
":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 0000000..fcd3bd6
--- /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 0000000..10b06ea
--- /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 0000000..60940fe
--- /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 <android-base/file.h>
+#include "fastboot.h"
+#include "socket.h"
+#include "socket_mock_fuzz.h"
+#include "tcp.h"
+#include "udp.h"
+#include "vendor_boot_img_utils.h"
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+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<Socket>* server,
+ unique_ptr<Socket>* client, const string& hostname);
+ unique_ptr<FuzzedDataProvider> fdp_ = nullptr;
+};
+
+void FastbootFuzzer::InvokeParseApi() {
+ boot_img_hdr_v1 hdr = {};
+ FastBootTool fastBoot;
+
+ int32_t year = fdp_->ConsumeIntegralInRange<int32_t>(kYearMin, kYearMax);
+ int32_t month = fdp_->ConsumeIntegralInRange<int32_t>(kMonthMin, kMonthMax);
+ int32_t day = fdp_->ConsumeIntegralInRange<int32_t>(kDayMin, kDayMax);
+ string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day);
+ fastBoot.ParseOsPatchLevel(&hdr, date.c_str());
+
+ int32_t major = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t minor = fdp_->ConsumeIntegralInRange<int32_t>(kVersionMin, kVersionMax);
+ int32_t patch = fdp_->ConsumeIntegralInRange<int32_t>(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<Socket>* server,
+ unique_ptr<Socket>* 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<Socket> 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<size_t>(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> transport = tcp::internal::Connect(unique_ptr<Socket>(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<char>(value >> 8), static_cast<char>(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<uint16_t>();
+ int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize
+ : fdp_->ConsumeIntegralInRange<uint16_t>(
+ 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> transport = udp::internal::Connect(unique_ptr<Socket>(udp_mock), &error);
+ bool is_transport_initialized = transport != nullptr && error.empty();
+
+ if (is_transport_initialized) {
+ uint16_t num_packets =
+ fdp_->ConsumeIntegralInRange<uint16_t>(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>();
+ uint64_t ramdisk_size =
+ fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral<uint64_t>();
+ (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<FuzzedDataProvider>(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 0000000..df96eb0
--- /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<const char*>(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<cutils_socket_buffer_t> buffers) {
+ std::string data;
+ for (const auto& buffer : buffers) {
+ data.append(reinterpret_cast<const char*>(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<Socket> SocketMockFuzz::Accept() {
+ if (events_.empty()) {
+ return nullptr;
+ }
+
+ if (events_.front().type != EventType::kAccept) {
+ return nullptr;
+ }
+
+ std::unique_ptr<Socket> 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<Socket> sock) {
+ events_.push(Event(EventType::kAccept, "", false, std::move(sock)));
+}
+
+SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _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 0000000..67bd0d6
--- /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 <memory>
+#include <queue>
+#include <string>
+
+#include <android-base/macros.h>
+
+#include "socket.h"
+
+class SocketMockFuzz : public Socket {
+ public:
+ SocketMockFuzz();
+ ~SocketMockFuzz() override;
+
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
+ ssize_t Receive(void* data, size_t length, int timeout_ms) override;
+ int Close() override;
+ virtual std::unique_ptr<Socket> 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<Socket> sock);
+
+ private:
+ enum class EventType { kSend, kReceive, kAccept };
+
+ struct Event {
+ Event(EventType _type, std::string _message, ssize_t _status,
+ std::unique_ptr<Socket> _sock);
+
+ EventType type;
+ std::string message;
+ bool status; // Return value for Send() or timeout status for Receive().
+ std::unique_ptr<Socket> sock;
+ };
+
+ std::queue<Event> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz);
+};