Class: Ruzzy::FuzzedDataProvider
- Inherits:
-
Object
- Object
- Ruzzy::FuzzedDataProvider
- Defined in:
- lib/ruzzy/fuzzed_data_provider.rb
Overview
Splits raw fuzzer bytes into typed Ruby values.
FuzzedDataProvider wraps a binary string (typically from libFuzzer via Ruzzy.fuzz) and provides methods to consume typed values from it. This enables fuzz targets that test APIs accepting typed arguments rather than raw byte strings.
Following libFuzzer’s FuzzedDataProvider.h design, strings and raw bytes are consumed from the front of the buffer, while integers are consumed from the end. This bidirectional consumption lets the fuzzer modify structural decisions (integers controlling lengths, indices, variant selection) independently from content (string payloads, raw bytes), improving mutation quality.
Instance Method Summary collapse
-
#consume_bool ⇒ Object
Consume a boolean from the end of the buffer.
-
#consume_bytes(count) ⇒ Object
Consume up to
countraw bytes from the front of the buffer. -
#consume_float ⇒ Object
Consume a Float spanning the full double range.
-
#consume_float_in_range(min, max) ⇒ Object
Consume a Float in [min, max] from the end of the buffer.
-
#consume_int(count) ⇒ Object
Consume a signed integer from the end of the buffer.
-
#consume_int_in_range(min, max) ⇒ Object
Consume an integer in [min, max] from the end of the buffer.
-
#consume_probability ⇒ Object
Consume a Float in [0.0, 1.0] from the end of the buffer.
-
#consume_random_length_string(max_length = remaining_bytes) ⇒ Object
Consume a variable-length string from the front of the buffer.
-
#consume_remaining_as_string ⇒ Object
Consume all remaining bytes as a String.
-
#consume_remaining_bytes ⇒ Object
Consume all remaining bytes.
-
#consume_uint(count) ⇒ Object
Consume an unsigned integer from the end of the buffer.
-
#initialize(data) ⇒ FuzzedDataProvider
constructor
A new instance of FuzzedDataProvider.
-
#pick_value_in_list(list) ⇒ Object
Return a random element from
list, consuming bytes from the end. -
#remaining_bytes ⇒ Object
Returns the number of unconsumed bytes remaining.
Constructor Details
#initialize(data) ⇒ FuzzedDataProvider
Returns a new instance of FuzzedDataProvider.
29 30 31 32 33 34 35 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 29 def initialize(data) @data = data # Front cursor for strings/bytes (advances forward) @front = 0 # Back cursor for integers (advances backward) @back = @data.bytesize end |
Instance Method Details
#consume_bool ⇒ Object
Consume a boolean from the end of the buffer. Returns false when no data remains.
154 155 156 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 154 def consume_bool (consume_uint(1) & 1) == 1 end |
#consume_bytes(count) ⇒ Object
Consume up to count raw bytes from the front of the buffer. Returns a binary-encoded String.
46 47 48 49 50 51 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 46 def consume_bytes(count) count = clamp_count(count) result = @data.byteslice(@front, count) @front += count result.force_encoding(Encoding::BINARY) end |
#consume_float ⇒ Object
Consume a Float spanning the full double range. Matches libFuzzer’s ConsumeFloatingPoint.
190 191 192 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 190 def consume_float consume_float_in_range(-Float::MAX, Float::MAX) end |
#consume_float_in_range(min, max) ⇒ Object
Consume a Float in [min, max] from the end of the buffer. Returns min when no data remains. Raises ArgumentError if min > max.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 170 def consume_float_in_range(min, max) raise ArgumentError, 'min must be <= max' if min > max return min if min == max range = max - min if range.infinite? # Overflow: split the range and recurse mid = min / 2.0 + max / 2.0 if consume_bool consume_float_in_range(mid, max) else consume_float_in_range(min, mid) end else min + range * consume_probability end end |
#consume_int(count) ⇒ Object
Consume a signed integer from the end of the buffer. Reads count bytes and interprets as two’s complement. Returns 0 when no data remains.
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 111 def consume_int(count) unsigned = consume_uint(count) return 0 if count.zero? bits = count * 8 max_unsigned = 1 << bits half = max_unsigned >> 1 unsigned >= half ? unsigned - max_unsigned : unsigned end |
#consume_int_in_range(min, max) ⇒ Object
Consume an integer in [min, max] from the end of the buffer. Returns min when no data remains. Raises ArgumentError if min > max.
Matches libFuzzer’s ConsumeIntegralInRange: consumes only as many bytes from the end as needed to cover the range.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 128 def consume_int_in_range(min, max) raise ArgumentError, "min (#{min}) must be <= max (#{max})" if min > max range = max - min return min if range.zero? # Consume bytes from the end, one at a time, until we've covered the range. # This matches libFuzzer: only consume bytes while (range >> offset) > 0. result = 0 offset = 0 while offset < 64 && (range >> offset).positive? && remaining_bytes.positive? @back -= 1 result = (result << 8) | @data.getbyte(@back) offset += 8 end if range == (1 << offset) - 1 # range+1 is a power of 2, modulo is identity min + result else min + (result % (range + 1)) end end |
#consume_probability ⇒ Object
Consume a Float in [0.0, 1.0] from the end of the buffer. Returns 0.0 when no data remains.
162 163 164 165 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 162 def consume_probability raw = consume_uint(8) raw.to_f / 18_446_744_073_709_551_615.0 # 2^64 - 1 end |
#consume_random_length_string(max_length = remaining_bytes) ⇒ Object
Consume a variable-length string from the front of the buffer. The string terminates when a backslash followed by a non-backslash byte is encountered, or when max_length characters are consumed. This encoding lets the fuzzer easily control string length through single-byte mutations.
Matches libFuzzer’s ConsumeRandomLengthString.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 60 def consume_random_length_string(max_length = remaining_bytes) result = +'' max_length.times do break if remaining_bytes.zero? byte = consume_front_byte char = byte.chr(Encoding::BINARY) if char == '\\' && remaining_bytes.positive? next_byte = consume_front_byte next_char = next_byte.chr(Encoding::BINARY) break if next_char != '\\' result << '\\' else result << char end end result end |
#consume_remaining_as_string ⇒ Object
Consume all remaining bytes as a String.
87 88 89 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 87 def consume_remaining_as_string consume_remaining_bytes end |
#consume_remaining_bytes ⇒ Object
Consume all remaining bytes. Returns a binary-encoded String.
82 83 84 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 82 def consume_remaining_bytes consume_bytes(remaining_bytes) end |
#consume_uint(count) ⇒ Object
Consume an unsigned integer from the end of the buffer. Reads up to count bytes in little-endian order from the back. Returns 0 when no data remains.
96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 96 def consume_uint(count) return 0 if count <= 0 || remaining_bytes.zero? actual = [count, remaining_bytes].min result = 0 actual.times do |i| @back -= 1 result |= @data.getbyte(@back) << (i * 8) end result end |
#pick_value_in_list(list) ⇒ Object
Return a random element from list, consuming bytes from the end. Raises ArgumentError if the list is empty.
198 199 200 201 202 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 198 def pick_value_in_list(list) raise ArgumentError, 'list must not be empty' if list.empty? list[consume_int_in_range(0, list.length - 1)] end |
#remaining_bytes ⇒ Object
Returns the number of unconsumed bytes remaining.
38 39 40 |
# File 'lib/ruzzy/fuzzed_data_provider.rb', line 38 def remaining_bytes @back - @front end |