-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfuzz_stream.cpp
More file actions
200 lines (165 loc) · 7.23 KB
/
Copy pathfuzz_stream.cpp
File metadata and controls
200 lines (165 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
#include "fuzz_util.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <istream>
#include <streambuf>
#include <string>
using ByteDance::pjson;
namespace {
// Chunk-limited stream adapter.
// Exposes an in-memory string through refills no larger than the requested chunk size.
class ChunkedBuffer : public std::streambuf {
public:
// Starts with an empty get area; the harness keeps chunkSize within the 32-byte buffer.
ChunkedBuffer(const std::string& input, size_t chunkSize)
: _input(input)
, _chunkSize(chunkSize)
, _position(0) {
setg(_buffer, _buffer, _buffer);
}
protected:
// Refills the get area with the next bounded chunk, or reports end of input.
int_type underflow() override {
if (_position >= _input.size())
return traits_type::eof();
const size_t count = std::min(_chunkSize, _input.size() - _position);
for (size_t i = 0; i < count; ++i)
_buffer[i] = _input[_position + i];
_position += count;
setg(_buffer, _buffer, _buffer + static_cast<std::ptrdiff_t>(count));
return traits_type::to_int_type(*gptr());
}
private:
const std::string& _input;
size_t _chunkSize;
size_t _position;
char _buffer[32];
};
// Owns the chunking stream buffer and installs it on an input-stream facade.
class ChunkedStream : public std::istream {
public:
// Attaches the fully constructed chunking buffer to the otherwise bufferless base stream.
ChunkedStream(const std::string& input, size_t chunkSize)
: std::istream(nullptr)
, _buffer(input, chunkSize) {
rdbuf(&_buffer);
}
private:
ChunkedBuffer _buffer;
};
// Order-sensitive SAX event fingerprinting.
// Records both event count and content so buffered and streamed SAX traces can be compared.
// The small methods below all fold a distinct event tag plus any payload
// into the same order-sensitive digest and always continue parsing.
struct DigestHandler : pjson::SaxHandler {
DigestHandler()
: digest(1469598103934665603ULL)
, events(0) {}
uint64_t digest;
size_t events;
void mix(uint64_t value) {
digest ^= value;
digest *= 1099511628211ULL;
}
// Include length so different adjacent strings cannot produce the same byte stream.
void mixString(const std::string& value) {
for (size_t i = 0; i < value.size(); ++i)
mix(static_cast<unsigned char>(value[i]));
mix(value.size());
}
bool mark(uint64_t tag) {
++events;
mix(tag);
return true;
}
bool onNull() override { return mark(1); }
bool onBool(bool value) override { return mark(value ? 3 : 2); }
bool onInt(int64_t value) override {
mark(4);
mix(static_cast<uint64_t>(value));
return true;
}
// Canonical serialization gives floating-point values a stable byte representation.
bool onDouble(double value) override {
mark(5);
pjson number;
number = value;
mixString(number.toString());
return true;
}
bool onString(const std::string& value) override {
mark(6);
mixString(value);
return true;
}
bool onStartArray() override { return mark(7); }
bool onEndArray() override { return mark(8); }
bool onStartObject() override { return mark(9); }
// Keys use their own tag so they cannot collide with ordinary strings.
bool onKey(const std::string& value) override {
mark(10);
mixString(value);
return true;
}
bool onEndObject() override { return mark(11); }
};
// Cross-interface parser consistency.
// Compares contiguous and chunked DOM/SAX parsing for one
// option variant.
void exerciseStreams(const uint8_t* data, size_t size, const std::string& input,
size_t chunkSize, size_t variantOffset) {
const pjson::ParseOptions options =
pjson_fuzz::parseOptionsVariant(data, size, variantOffset);
// Contiguous DOM parsing provides the baseline status and value.
pjson::ParseError bufferError;
pjson::unique_ptr buffered =
pjson::parse(input.c_str(), input.size(), bufferError, options);
pjson_fuzz::require(static_cast<bool>(buffered) == bufferError.ok);
// Chunk boundaries must not affect DOM acceptance or serialized output.
ChunkedStream domInput(input, chunkSize);
pjson::ParseError streamError;
pjson::unique_ptr streamed = pjson::parseStream(domInput, streamError, options);
pjson_fuzz::require(static_cast<bool>(streamed) == streamError.ok);
pjson_fuzz::require(static_cast<bool>(buffered) == static_cast<bool>(streamed));
if (buffered)
pjson_fuzz::require(buffered->toString() == streamed->toString());
// Capture the SAX trace from the same contiguous baseline input.
DigestHandler bufferHandler;
pjson::ParseError saxBufferError;
const bool saxBuffer =
pjson::parseSax(input.c_str(), input.size(), bufferHandler, saxBufferError, options);
pjson_fuzz::require(saxBuffer == saxBufferError.ok);
// Streamed SAX parsing must agree on status, event count, order, and payloads.
ChunkedStream saxInput(input, chunkSize);
DigestHandler streamHandler;
pjson::ParseError saxStreamError;
const bool saxStream =
pjson::parseSaxStream(saxInput, streamHandler, saxStreamError, options);
pjson_fuzz::require(saxStream == saxStreamError.ok);
pjson_fuzz::require(saxBuffer == saxStream);
pjson_fuzz::require(static_cast<bool>(buffered) == saxBuffer);
// Failure can be detected before any callbacks for a bounded in-memory
// input but only after prefix callbacks for a stream, so compare traces
// only when both parsers consumed the complete document successfully.
if (saxBuffer) {
pjson_fuzz::require(bufferHandler.events == streamHandler.events);
pjson_fuzz::require(bufferHandler.digest == streamHandler.digest);
}
}
} // namespace
// libFuzzer entry point.
// Derives a safe 1..32-byte chunk size and tests several
// duplicate-policy/resource-budget variants.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size > pjson_fuzz::kMaxInputBytes)
return 0;
const std::string input(pjson_fuzz::bytes(data, size), size);
const size_t chunkSize = size == 0 ? 1U : static_cast<size_t>(data[0] % 32U) + 1U;
exerciseStreams(data, size, input, chunkSize, 0U);
exerciseStreams(data, size, input, chunkSize, 4U);
exerciseStreams(data, size, input, chunkSize, 8U);
return 0;
}