Coverage Report

Created: 2026-07-16 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}