Coverage Report

Created: 2026-03-31 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_pack.cpp
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
/*
14
 * Fuzzer for Ruby's Array#pack and String#unpack (pack.c)
15
 * 
16
 * Purpose: Test binary packing/unpacking with various template directives
17
 * to find bugs in template parsing, data conversion, and boundary handling.
18
 * 
19
 * Coverage:
20
 * - Template parsing: All pack directives (C, S, L, Q, c, s, l, q, A, a, Z, etc.)
21
 * - Array#pack: Convert Ruby objects to binary string
22
 * - String#unpack: Parse binary data according to template
23
 * - Round-trip: pack → unpack consistency
24
 * - Edge cases: Invalid templates, buffer overflows, encoding issues
25
 */
26
27
#include <stdint.h>
28
#include <stddef.h>
29
#include <stdlib.h>
30
#include <fuzzer/FuzzedDataProvider.h>
31
#include "ruby.h"
32
33
static int ruby_initialized = 0;
34
35
extern "C" VALUE ruby_verbose;
36
37
// Test Array#pack with fuzzer-provided template
38
1.26k
static VALUE call_array_pack(VALUE arg) {
39
1.26k
    VALUE *args = (VALUE *)arg;
40
1.26k
    VALUE ary = args[0];
41
1.26k
    VALUE template_str = args[1];
42
    
43
    // Call Array#pack with the template
44
    // This exercises the pack template parser and binary packing logic
45
1.26k
    VALUE result = rb_funcall(ary, rb_intern("pack"), 1, template_str);
46
    
47
    // Try to use the result to ensure it's valid
48
1.26k
    if (!NIL_P(result)) {
49
0
        rb_funcall(result, rb_intern("length"), 0);
50
0
        rb_funcall(result, rb_intern("encoding"), 0);
51
0
    }
52
    
53
1.26k
    return result;
54
1.26k
}
55
56
// Test String#unpack with fuzzer-provided template and data
57
630
static VALUE call_string_unpack(VALUE arg) {
58
630
    VALUE *args = (VALUE *)arg;
59
630
    VALUE str = args[0];
60
630
    VALUE template_str = args[1];
61
    
62
    // Call String#unpack with the template
63
    // This exercises the unpack template parser and binary unpacking logic
64
630
    VALUE result = rb_funcall(str, rb_intern("unpack"), 1, template_str);
65
    
66
    // Try to iterate the result to ensure it's valid
67
630
    if (!NIL_P(result) && RB_TYPE_P(result, T_ARRAY)) {
68
0
        long len = RARRAY_LEN(result);
69
0
        for (long i = 0; i < len && i < 10; i++) {
70
0
            (void)rb_ary_entry(result, i);  // Suppress unused warning
71
0
        }
72
0
    }
73
    
74
630
    return result;
75
630
}
76
77
632
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
78
    // Initialize Ruby once on first call
79
632
    if (!ruby_initialized) {
80
1
        ruby_init();
81
1
        ruby_initialized = 1;
82
        
83
        // Suppress Ruby warnings to avoid log noise
84
1
        ruby_verbose = Qfalse;
85
1
    }
86
    
87
632
    if (size < 2) {
88
2
        return 0;
89
2
    }
90
    
91
    // Use FuzzedDataProvider for structured data consumption
92
630
    FuzzedDataProvider fdp(data, size);
93
    
94
    // Split input: template and binary data
95
630
    size_t template_len = fdp.ConsumeIntegralInRange<size_t>(1, 10000);
96
630
    std::string template_data = fdp.ConsumeBytesAsString(template_len);
97
630
    size_t binary_data_len = fdp.ConsumeIntegralInRange<size_t>(1, 10000);
98
630
    std::string binary_data = fdp.ConsumeBytesAsString(binary_data_len);
99
    
100
630
    VALUE template_str = rb_str_new(template_data.data(), template_data.size());
101
630
    VALUE binary_str = rb_str_new(binary_data.data(), binary_data.size());
102
    
103
630
    int state = 0;
104
    
105
    // Test String#unpack with fuzzer data
106
630
    VALUE unpack_args[] = {binary_str, template_str};
107
630
    rb_protect(call_string_unpack, (VALUE)unpack_args, &state);
108
630
    if (state) {
109
630
        rb_set_errinfo(Qnil);
110
630
        state = 0;
111
630
    }
112
    
113
    // Test Array#pack with fuzzer template and randomized array data
114
630
    VALUE test_array = rb_ary_new();
115
    
116
    // Add various Ruby objects with random data from fuzzer
117
630
    rb_ary_push(test_array, INT2FIX(fdp.ConsumeIntegral<int16_t>()));
118
630
    rb_ary_push(test_array, INT2FIX(fdp.ConsumeIntegral<int16_t>()));
119
630
    rb_ary_push(test_array, LONG2NUM(fdp.ConsumeIntegral<int32_t>()));
120
630
    rb_ary_push(test_array, rb_float_new(fdp.ConsumeFloatingPoint<double>()));
121
    
122
630
    size_t str1_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000);
123
630
    std::string str1 = fdp.ConsumeBytesAsString(str1_len);
124
630
    rb_ary_push(test_array, rb_str_new(str1.data(), str1.size()));
125
    
126
630
    size_t str2_len = fdp.ConsumeIntegralInRange<size_t>(0, 10000);
127
630
    std::string str2 = fdp.ConsumeBytesAsString(str2_len);
128
630
    rb_ary_push(test_array, rb_str_new(str2.data(), str2.size()));
129
    
130
630
    if (fdp.remaining_bytes() >= 8) {
131
165
        rb_ary_push(test_array, UINT2NUM(fdp.ConsumeIntegral<uint32_t>()));
132
165
        rb_ary_push(test_array, UINT2NUM(fdp.ConsumeIntegral<uint32_t>()));
133
165
    }
134
    
135
630
    VALUE pack_args[] = {test_array, template_str};
136
630
    rb_protect(call_array_pack, (VALUE)pack_args, &state);
137
630
    if (state) {
138
630
        rb_set_errinfo(Qnil);
139
630
    }
140
    
141
    // Test round-trip: pack then unpack
142
630
    VALUE packed = Qnil;
143
630
    state = 0;
144
630
    packed = rb_protect(call_array_pack, (VALUE)pack_args, &state);
145
    
146
630
    if (state == 0 && !NIL_P(packed)) {
147
0
        VALUE roundtrip_args[] = {packed, template_str};
148
0
        state = 0;
149
0
        rb_protect(call_string_unpack, (VALUE)roundtrip_args, &state);
150
0
        if (state) {
151
0
            rb_set_errinfo(Qnil);
152
0
        }
153
630
    } else if (state) {
154
630
        rb_set_errinfo(Qnil);
155
630
    }
156
    
157
    // Force GC
158
630
    rb_gc_start();
159
    
160
630
    return 0;
161
632
}