Line | Count | Source |
1 | | /* Copyright 2025 Google LLC |
2 | | Licensed under the Apache License, Version 2.0 (the "License"); |
3 | | you may not use this file except in compliance with the License. |
4 | | You may obtain a copy of the License at |
5 | | http://www.apache.org/licenses/LICENSE-2.0 |
6 | | Unless required by applicable law or agreed to in writing, software |
7 | | distributed under the License is distributed on an "AS IS" BASIS, |
8 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
9 | | See the License for the specific language governing permissions and |
10 | | limitations under the License. |
11 | | */ |
12 | | |
13 | | #include <stdint.h> |
14 | | #include <stddef.h> |
15 | | #include <stdlib.h> |
16 | | #include <string> |
17 | | #include <fuzzer/FuzzedDataProvider.h> |
18 | | #include "ruby.h" |
19 | | |
20 | | static int ruby_initialized = 0; |
21 | | |
22 | | extern "C" VALUE ruby_verbose; |
23 | | static VALUE cInstructionSequence = Qnil; |
24 | | static ID id_load_from_binary = 0; |
25 | | |
26 | | // Wrapper for rb_protect to load ISEQ from binary |
27 | 477 | static VALUE call_iseq_load_from_binary(VALUE arg) { |
28 | 477 | VALUE str = (VALUE)arg; |
29 | | |
30 | | // Call RubyVM::InstructionSequence.load_from_binary(binary_string) |
31 | | // This exercises the complete ISEQ binary deserialization path |
32 | 477 | VALUE iseq = rb_funcall(cInstructionSequence, id_load_from_binary, 1, str); |
33 | | |
34 | 477 | if (!NIL_P(iseq)) { |
35 | | // Try to access various ISEQ methods to ensure it was properly loaded |
36 | 0 | rb_funcall(iseq, rb_intern("path"), 0); |
37 | 0 | rb_funcall(iseq, rb_intern("label"), 0); |
38 | 0 | rb_funcall(iseq, rb_intern("first_lineno"), 0); |
39 | 0 | rb_funcall(iseq, rb_intern("to_a"), 0); |
40 | | |
41 | | // Try to inspect it |
42 | 0 | rb_funcall(iseq, rb_intern("inspect"), 0); |
43 | 0 | } |
44 | | |
45 | 477 | return Qnil; |
46 | 477 | } |
47 | | |
48 | 481 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
49 | | // Initialize Ruby once on first call |
50 | 481 | if (!ruby_initialized) { |
51 | 1 | ruby_init(); |
52 | 1 | ruby_initialized = 1; |
53 | | |
54 | | // Get RubyVM::InstructionSequence class |
55 | 1 | VALUE mRubyVM = rb_const_get(rb_cObject, rb_intern("RubyVM")); |
56 | 1 | cInstructionSequence = rb_const_get(mRubyVM, rb_intern("InstructionSequence")); |
57 | | |
58 | | // Get the load_from_binary method ID |
59 | 1 | id_load_from_binary = rb_intern("load_from_binary"); |
60 | 1 | } |
61 | | |
62 | | // Limit input size to avoid excessive processing |
63 | | // ISEQ binary format can be moderately large |
64 | 481 | if (size == 0 || size > 16384) { |
65 | 4 | return 0; |
66 | 4 | } |
67 | | |
68 | | // Use FuzzedDataProvider for structured data consumption |
69 | 477 | FuzzedDataProvider fdp(data, size); |
70 | | |
71 | | // Create a Ruby string from the fuzzer input |
72 | | // This will be passed to load_from_binary |
73 | 477 | std::string binary_data = fdp.ConsumeRemainingBytesAsString(); |
74 | 477 | VALUE binary_str = rb_str_new(binary_data.data(), binary_data.size()); |
75 | | |
76 | | // Call with rb_protect to catch any exceptions/errors |
77 | 477 | int state = 0; |
78 | 477 | rb_protect(call_iseq_load_from_binary, binary_str, &state); |
79 | | |
80 | | // Clear any exception that occurred |
81 | 477 | if (state) { |
82 | 477 | rb_set_errinfo(Qnil); |
83 | 477 | } |
84 | | |
85 | | // Force GC to release memory and detect any memory issues |
86 | 477 | rb_gc_start(); |
87 | | |
88 | 477 | return 0; |
89 | 481 | } |