/src/hothd_payload_fuzzer.cc
Line | Count | Source |
1 | | // Copyright 2026 Google LLC |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | //////////////////////////////////////////////////////////////////////////////// |
16 | | |
17 | | #include <cstdint> |
18 | | #include <cstddef> |
19 | | #include <vector> |
20 | | #include <string> |
21 | | #include <cstring> |
22 | | #include <algorithm> |
23 | | #include <span> |
24 | | #include <memory> |
25 | | #include <chrono> |
26 | | |
27 | | #include <fuzzer/FuzzedDataProvider.h> |
28 | | |
29 | | #include "payload_update.hpp" |
30 | | #include "sys.hpp" |
31 | | #include "host_command.hpp" |
32 | | #include "google3/host_commands.h" |
33 | | |
34 | | // Mock the Sys interface to read from fuzzer data in memory |
35 | | class FuzzerSys : public google::hoth::internal::Sys { |
36 | | public: |
37 | 1.01k | FuzzerSys(const uint8_t* data, size_t size) : data_(data), size_(size), offset_(0) {} |
38 | | |
39 | 5.64k | int open(const char* pathname, int flags) const override { |
40 | 5.64k | return 42; // dummy fd |
41 | 5.64k | } |
42 | | |
43 | 5.64k | int close(int fd) const override { |
44 | 5.64k | return 0; |
45 | 5.64k | } |
46 | | |
47 | 9.48k | off_t lseek(int fd, off_t offset, int whence) const override { |
48 | 9.48k | if (whence == SEEK_SET) { |
49 | 6.67k | if (offset < 0 || static_cast<size_t>(offset) > size_) return -1; |
50 | 6.14k | offset_ = offset; |
51 | 6.14k | } else if (whence == SEEK_END) { |
52 | 2.81k | offset_ = size_; |
53 | 2.81k | } else if (whence == SEEK_CUR) { |
54 | 0 | if (static_cast<ssize_t>(offset_) + offset < 0 || static_cast<size_t>(offset_ + offset) > size_) return -1; |
55 | 0 | offset_ += offset; |
56 | 0 | } |
57 | 8.95k | return offset_; |
58 | 9.48k | } |
59 | | |
60 | 30.3k | ssize_t read(int fd, void* buf, size_t count) const override { |
61 | 30.3k | if (offset_ >= size_) return 0; |
62 | 28.9k | size_t to_read = std::min(count, size_ - offset_); |
63 | 28.9k | std::memcpy(buf, data_ + offset_, to_read); |
64 | 28.9k | offset_ += to_read; |
65 | 28.9k | return to_read; |
66 | 30.3k | } |
67 | | |
68 | 0 | ssize_t write(int fd, const void* buf, size_t count) const override { |
69 | 0 | return count; // dummy write |
70 | 0 | } |
71 | | |
72 | 0 | int ioctl(int fd, uint64_t request, void* mtd) const override { |
73 | 0 | return 0; |
74 | 0 | } |
75 | | |
76 | | private: |
77 | | const uint8_t* data_; |
78 | | size_t size_; |
79 | | mutable size_t offset_; |
80 | | }; |
81 | | |
82 | | // Mock the HostCommand interface to avoid real Hoth communication |
83 | | class FuzzerHostCommand : public google::hoth::internal::HostCommand { |
84 | | public: |
85 | 0 | std::vector<uint8_t> sendCommand(const std::vector<uint8_t>& command) override { |
86 | 0 | return {}; |
87 | 0 | } |
88 | 0 | std::vector<uint8_t> sendCommand(const std::vector<uint8_t>& command, std::chrono::milliseconds timeout) override { |
89 | 0 | return {}; |
90 | 0 | } |
91 | 24.9M | std::vector<uint8_t> sendCommand(uint16_t command, uint8_t commandVersion, const void* request, size_t requestSize) override { |
92 | | // Return a dummy response with EC_RES_SUCCESS |
93 | 24.9M | std::vector<uint8_t> rsp(sizeof(google::hoth::internal::RspHeader), 0); |
94 | 24.9M | auto* hdr = reinterpret_cast<google::hoth::internal::RspHeader*>(rsp.data()); |
95 | 24.9M | hdr->result = google::hoth::internal::EC_RES_SUCCESS; |
96 | 24.9M | return rsp; |
97 | 24.9M | } |
98 | 0 | std::vector<uint8_t> sendCommand(uint16_t command, uint8_t commandVersion, const void* request, size_t requestSize, std::chrono::milliseconds timeout) override { |
99 | 0 | return sendCommand(command, commandVersion, request, requestSize); |
100 | 0 | } |
101 | 0 | uint64_t sendCommandAsync(const std::vector<uint8_t>& command) override { |
102 | 0 | return 0; |
103 | 0 | } |
104 | 0 | std::vector<uint8_t> getResponse(uint64_t callToken) override { |
105 | 0 | return {}; |
106 | 0 | } |
107 | 0 | bool communicationFailure() const override { |
108 | 0 | return false; |
109 | 0 | } |
110 | 0 | int collectHothLogsAsync(bool cleanupPromiseAfterExecution) override { |
111 | 0 | return 0; |
112 | 0 | } |
113 | 0 | void collectUartLogsAsync() override {} |
114 | 0 | void stopUartLogs() override {} |
115 | | }; |
116 | | |
117 | 1.01k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
118 | 1.01k | if (size < 2) return 0; |
119 | | |
120 | 1.01k | FuzzedDataProvider provider(data, size); |
121 | | |
122 | | // Allocate up to 80% of fuzzer data to represent the firmware file contents |
123 | 1.01k | size_t file_data_len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes() * 8 / 10); |
124 | 1.01k | std::vector<uint8_t> file_data = provider.ConsumeBytes<uint8_t>(file_data_len); |
125 | | |
126 | 1.01k | FuzzerSys fuzzer_sys(file_data.data(), file_data.size()); |
127 | 1.01k | FuzzerHostCommand fuzzer_host_cmd; |
128 | 1.01k | google::hoth::internal::PayloadUpdateImpl payload_update(&fuzzer_host_cmd, &fuzzer_sys); |
129 | | |
130 | 1.01k | size_t action_count = 0; |
131 | 11.6k | while (provider.remaining_bytes() > 0 && action_count < 20) { |
132 | 10.6k | action_count++; |
133 | 10.6k | uint8_t action = provider.ConsumeIntegralInRange<uint8_t>(0, 8); |
134 | 10.6k | try { |
135 | 10.6k | switch (action) { |
136 | 2.14k | case 0: { |
137 | 2.14k | payload_update.initiate(); |
138 | 2.14k | break; |
139 | 0 | } |
140 | 723 | case 1: { |
141 | 723 | uint32_t offset = provider.ConsumeIntegral<uint32_t>(); |
142 | 723 | uint32_t size = provider.ConsumeIntegral<uint32_t>(); |
143 | 723 | payload_update.erase(offset, size); |
144 | 723 | break; |
145 | 0 | } |
146 | 2.81k | case 2: { |
147 | 2.81k | payload_update.eraseAndSendStaticWP("/tmp/fuzz_file"); |
148 | 2.81k | break; |
149 | 0 | } |
150 | 1.62k | case 3: { |
151 | 1.62k | uint32_t offset = provider.ConsumeIntegral<uint32_t>(); |
152 | 1.62k | uint32_t read_len = provider.ConsumeIntegralInRange<uint32_t>(0, 1024); |
153 | 1.62k | std::vector<uint8_t> read_buf(read_len); |
154 | 1.62k | payload_update.read(offset, read_buf); |
155 | 1.62k | break; |
156 | 0 | } |
157 | 525 | case 4: { |
158 | 525 | payload_update.verify(); |
159 | 525 | break; |
160 | 0 | } |
161 | 733 | case 5: { |
162 | 733 | payload_update.getStatus(); |
163 | 733 | break; |
164 | 0 | } |
165 | 506 | case 6: { |
166 | 506 | auto side = provider.PickValueInArray({google::hoth::internal::Side::A, google::hoth::internal::Side::B}); |
167 | 506 | auto persistence = provider.PickValueInArray({google::hoth::internal::Persistence::kNonPersistent, google::hoth::internal::Persistence::kPersistent}); |
168 | 506 | payload_update.activate(side, persistence); |
169 | 506 | break; |
170 | 0 | } |
171 | 900 | case 7: { |
172 | 900 | payload_update.send("/tmp/fuzz_file"); |
173 | 900 | break; |
174 | 0 | } |
175 | 707 | case 8: { |
176 | 707 | auto option = provider.PickValueInArray({ |
177 | 707 | payload_update_confirm_option::Enable, |
178 | 707 | payload_update_confirm_option::Enable_with_timeout, |
179 | 707 | payload_update_confirm_option::Disable, |
180 | 707 | payload_update_confirm_option::Confirm, |
181 | 707 | payload_update_confirm_option::Get_timeout_values |
182 | 707 | }); |
183 | 707 | uint32_t timeout = provider.ConsumeIntegral<uint32_t>(); |
184 | 707 | uint64_t cookie = provider.ConsumeIntegral<uint64_t>(); |
185 | 707 | payload_update.confirm(option, timeout, cookie); |
186 | 707 | break; |
187 | 0 | } |
188 | 10.6k | } |
189 | 10.6k | } catch (...) { |
190 | | // Catch expected exceptions (e.g. from mock filesystem or D-Bus response failures) |
191 | 5.30k | } |
192 | 10.6k | } |
193 | | |
194 | 1.01k | return 0; |
195 | 1.01k | } |