|
2 | 2 | * @file protobuf_wire_fuzzer.cpp |
3 | 3 | * @brief libFuzzer harness for the internal protobuf wire-format decoder. |
4 | 4 | * |
5 | | - * Targets kcenon::monitoring::protobuf_wire's decode primitives |
6 | | - * (decode_varint / decode_tag / decode_length_delimited), which are the |
| 5 | + * Targets kcenon::monitoring::protobuf_wire's decode primitives via the |
| 6 | + * `reader` class (decode_tag / reader::read_varint / reader::read_fixed64 / |
| 7 | + * reader::read_fixed32 / reader::read_length_delimited), which are the |
7 | 8 | * monitoring_system's lowest-level untrusted-input parsing surface: they are |
8 | 9 | * used to deserialize Jaeger api_v2 and Zipkin proto3 span messages received |
9 | 10 | * over the wire by the trace exporters. Malformed or adversarial bytes must be |
10 | | - * rejected gracefully (std::nullopt) without out-of-bounds reads, overflow, or |
11 | | - * other undefined behavior. |
| 11 | + * rejected gracefully (false / std::nullopt) without out-of-bounds reads, |
| 12 | + * overflow, or other undefined behavior. |
12 | 13 | * |
13 | 14 | * The header is fully inline / header-only, so this harness needs no link |
14 | 15 | * against the monitoring_system library. |
|
28 | 29 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
29 | 30 | namespace pw = kcenon::monitoring::protobuf_wire; |
30 | 31 |
|
| 32 | + pw::reader r(data, size); |
| 33 | + |
31 | 34 | // Walk the buffer as a sequence of protobuf fields, exactly as a real |
32 | 35 | // message decoder would, exercising every decode primitive. The loop must |
33 | 36 | // terminate on any malformed field; the decoders signal that by returning |
34 | | - // std::nullopt and not advancing past the buffer end. |
35 | | - std::size_t offset = 0; |
36 | | - while (offset < size) { |
37 | | - const std::size_t before = offset; |
| 37 | + // false / std::nullopt without advancing past the buffer end. |
| 38 | + while (!r.eof()) { |
| 39 | + const std::size_t before = r.position(); |
38 | 40 |
|
39 | | - auto tag = pw::decode_tag(data, size, offset); |
40 | | - if (!tag.has_value()) { |
| 41 | + std::uint32_t field_number = 0; |
| 42 | + pw::wire_type wt{}; |
| 43 | + if (!pw::decode_tag(r, field_number, wt)) { |
41 | 44 | break; // truncated/invalid tag |
42 | 45 | } |
43 | 46 |
|
44 | | - switch (tag->second) { |
45 | | - case pw::wire_type::varint: { |
46 | | - auto v = pw::decode_varint(data, size, offset); |
47 | | - if (!v.has_value()) { |
48 | | - offset = size; // stop: truncated varint |
| 47 | + switch (wt) { |
| 48 | + case pw::wire_type::varint: |
| 49 | + if (!r.read_varint().has_value()) { |
| 50 | + return 0; // truncated varint |
49 | 51 | } |
50 | 52 | break; |
51 | | - } |
52 | | - case pw::wire_type::length_delimited: { |
53 | | - auto ld = pw::decode_length_delimited(data, size, offset); |
54 | | - if (!ld.has_value()) { |
55 | | - offset = size; // stop: bad length-delimited field |
| 53 | + case pw::wire_type::fixed64: |
| 54 | + if (!r.read_fixed64().has_value()) { |
| 55 | + return 0; // truncated fixed64 |
56 | 56 | } |
57 | 57 | break; |
58 | | - } |
59 | | - case pw::wire_type::fixed64: { |
60 | | - if (offset + 8 > size) { |
61 | | - offset = size; |
62 | | - } else { |
63 | | - offset += 8; |
| 58 | + case pw::wire_type::fixed32: |
| 59 | + if (!r.read_fixed32().has_value()) { |
| 60 | + return 0; // truncated fixed32 |
64 | 61 | } |
65 | 62 | break; |
66 | | - } |
67 | | - case pw::wire_type::fixed32: { |
68 | | - if (offset + 4 > size) { |
69 | | - offset = size; |
70 | | - } else { |
71 | | - offset += 4; |
| 63 | + case pw::wire_type::length_delimited: { |
| 64 | + const std::uint8_t* ptr = nullptr; |
| 65 | + std::size_t len = 0; |
| 66 | + if (!r.read_length_delimited(&ptr, &len)) { |
| 67 | + return 0; // bad length-delimited field |
72 | 68 | } |
73 | 69 | break; |
74 | 70 | } |
75 | 71 | default: |
76 | | - // Unknown wire type: cannot safely skip; stop. |
77 | | - offset = size; |
78 | | - break; |
| 72 | + // Unknown/unsupported wire type (e.g. deprecated groups): |
| 73 | + // cannot safely skip; stop. |
| 74 | + return 0; |
79 | 75 | } |
80 | 76 |
|
81 | 77 | // Guard against any decoder that fails to make forward progress, which |
82 | 78 | // would otherwise spin forever on a crafted input. |
83 | | - if (offset == before) { |
| 79 | + if (r.position() == before) { |
84 | 80 | break; |
85 | 81 | } |
86 | 82 | } |
|
0 commit comments