Coverage Report

Created: 2024-02-13 07:03

/src/fuzz/fuzz_ruby_gems.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright 2022 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
#include "init_ruby_load_paths.h"
13
#include <ruby/ruby.h>
14
#include <unistd.h>
15
16
0
#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
17
18
// The maximum number of arguments of any of the target functions.
19
// Increase as needed.
20
#define MAX_NARGS 2
21
22
enum RubyDataType { RDT_CString };
23
24
struct TargetFunction {
25
  const char *module_;
26
  const char *cls_;
27
  const char *name_;
28
  VALUE module_obj_;
29
  VALUE cls_obj_;
30
  ID method_id_;
31
  int nargs_;
32
  const enum RubyDataType *argTypes_;
33
};
34
35
struct TargetCall {
36
  struct TargetFunction *fcn_;
37
  VALUE *args_;
38
};
39
40
0
VALUE eval(const char *cmd) {
41
0
  int state = 0;
42
0
  VALUE result = rb_eval_string_protect(cmd, &state);
43
0
  if (state != 0) {
44
0
    rb_set_errinfo(Qnil);
45
0
  }
46
47
0
  return result;
48
0
}
49
50
0
static VALUE call_protected_helper(VALUE rdata) {
51
0
  struct TargetCall *call = (struct TargetCall *)(rdata);
52
0
  struct TargetFunction *fcn = call->fcn_;
53
0
  return rb_funcall2(fcn->cls_obj_, fcn->method_id_, fcn->nargs_, call->args_);
54
0
};
55
56
0
VALUE call_protected(struct TargetCall *call) {
57
0
  int state = 0;
58
0
  VALUE result = rb_protect(call_protected_helper, (VALUE)(call), &state);
59
0
  if (state != 0) {
60
0
    rb_set_errinfo(Qnil);
61
0
  }
62
0
  return result;
63
0
}
64
65
0
VALUE require(const char *module) {
66
0
  int state = 0;
67
0
  VALUE result =
68
0
      rb_protect(RUBY_METHOD_FUNC(rb_require), (VALUE)module, &state);
69
0
  if (state != 0) {
70
0
    rb_set_errinfo(Qnil);
71
0
  }
72
0
  return result;
73
0
}
74
75
struct ByteStream {
76
  const uint8_t *data_;
77
  size_t size_;
78
  size_t pos_;
79
};
80
81
0
void ByteStream_init(struct ByteStream *bs, const uint8_t *data, size_t size) {
82
0
  bs->data_ = data;
83
0
  bs->size_ = size;
84
0
  bs->pos_ = 0;
85
0
}
86
87
// Copy bytes from the ByteStream into `data`. Returns 0 on success, -1 on
88
// error. Error only occurs if there aren't enough bytes remaining in the
89
// ByteStream.
90
0
int ByteStream_get_bytes(struct ByteStream *bs, uint8_t *data, size_t size) {
91
0
  if (size > bs->size_ - bs->pos_) {
92
0
    return -1;
93
0
  }
94
0
  memcpy(data, bs->data_ + bs->pos_, size);
95
0
  bs->pos_ += size;
96
0
  return 0;
97
0
}
98
99
// Initialize x with bytes from the ByteStream. Returns -1 on error.
100
0
int BytesStream_get_uint32_t(struct ByteStream *bs, uint32_t *x) {
101
0
  return ByteStream_get_bytes(bs, (uint8_t *)x, sizeof(*x));
102
0
}
103
104
// Initialize x with bytes from the ByteStream. Returns -1 on error.
105
0
int BytesStream_get_uint64_t(struct ByteStream *bs, uint64_t *x) {
106
0
  return ByteStream_get_bytes(bs, (uint8_t *)x, sizeof(*x));
107
0
}
108
109
0
VALUE generate_CString(struct ByteStream *bs) {
110
0
  uint64_t size = 0;
111
0
  if (BytesStream_get_uint64_t(bs, &size) < 0) {
112
0
    return 0;
113
0
  }
114
0
  if (size > (unsigned long)LONG_MAX) {
115
0
    return 0;
116
0
  }
117
0
  if (size > bs->size_ - bs->pos_) {
118
    //    return 0;
119
0
    size = bs->size_ - bs->pos_;
120
0
  }
121
0
  char *data = malloc(size);
122
0
  if (!data) {
123
0
    return 0;
124
0
  }
125
0
  VALUE result = 0;
126
0
  if (ByteStream_get_bytes(bs, (uint8_t *)data, size) < 0) {
127
0
    goto out;
128
0
  }
129
0
  result = rb_str_new(data, (long)size);
130
131
0
out:
132
0
  free(data);
133
0
  return result;
134
0
}
135
136
0
VALUE generate_value(struct ByteStream *bs, const enum RubyDataType t) {
137
0
  switch (t) {
138
0
  case RDT_CString:
139
0
    return generate_CString(bs);
140
0
  default:
141
0
    return 0;
142
0
  }
143
0
}
144
145
0
int run_fuzz_function(struct ByteStream *bs, struct TargetFunction *fcn) {
146
0
  if (fcn->nargs_ < 0) {
147
0
    return -1;
148
0
  }
149
150
0
  VALUE args[MAX_NARGS] = {};
151
0
  int result = -1;
152
0
  int i;
153
0
  assert(fcn->nargs_ <= MAX_NARGS);
154
0
  for (i = 0; i < fcn->nargs_; i++) {
155
0
    VALUE v = generate_value(bs, fcn->argTypes_[i]);
156
0
    if (!v) {
157
0
      goto out;
158
0
    }
159
0
    args[i] = v;
160
0
  }
161
162
0
  struct TargetCall call;
163
0
  call.fcn_ = fcn;
164
0
  call.args_ = args;
165
166
0
  if (call_protected(&call)) {
167
0
    result = 0;
168
0
  }
169
170
0
out:
171
0
  return result;
172
0
}
173
174
void init_TargetFunction(struct TargetFunction *target, const char *module,
175
                         const char *cls, const char *name, const int nargs,
176
0
                         const enum RubyDataType *argTypes) {
177
0
  target->module_ = module;
178
0
  target->module_obj_ = require(module);
179
0
  rb_global_variable(&target->module_obj_);
180
0
  target->cls_ = cls;
181
0
  target->cls_obj_ = rb_path2class(cls);
182
0
  rb_global_variable(&target->cls_obj_);
183
0
  target->name_ = name;
184
0
  target->method_id_ = rb_intern(name);
185
0
  target->nargs_ = nargs;
186
0
  target->argTypes_ = argTypes;
187
0
}
188
189
0
static void init_date_strptime(struct TargetFunction *target) {
190
0
  static const enum RubyDataType argTypes[2] = {RDT_CString, RDT_CString};
191
0
  init_TargetFunction(target, "date", "Date", "strptime", ARRAYSIZE(argTypes),
192
0
                      argTypes);
193
0
}
194
195
0
static void init_date_httpdate(struct TargetFunction *target) {
196
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
197
0
  init_TargetFunction(target, "date", "Date", "httpdate", ARRAYSIZE(argTypes),
198
0
                      argTypes);
199
0
}
200
201
0
static void init_date_parse(struct TargetFunction *target) {
202
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
203
0
  init_TargetFunction(target, "date", "Date", "parse", ARRAYSIZE(argTypes),
204
0
                      argTypes);
205
0
}
206
207
0
static void init_regexp_new(struct TargetFunction *target) {
208
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
209
0
  init_TargetFunction(target, "regexp", "Regexp", "new",
210
0
                      ARRAYSIZE(argTypes), argTypes);
211
0
}
212
213
0
static void init_json_parse(struct TargetFunction *target) {
214
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
215
0
  init_TargetFunction(target, "json", "JSON", "parse", ARRAYSIZE(argTypes),
216
0
                      argTypes);
217
0
}
218
219
0
static void init_psych_parse(struct TargetFunction *target) {
220
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
221
0
  init_TargetFunction(target, "psych", "Psych", "parse", ARRAYSIZE(argTypes),
222
0
                      argTypes);
223
0
}
224
225
0
static void init_openssl_read(struct TargetFunction *target) {
226
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
227
0
  init_TargetFunction(target, "openssl", "OpenSSL::PKey", "read",
228
0
                      ARRAYSIZE(argTypes), argTypes);
229
0
}
230
231
0
static void init_openssl_read_smime(struct TargetFunction *target) {
232
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
233
0
  init_TargetFunction(target, "openssl", "OpenSSL::PKCS7", "read_smime",
234
0
                      ARRAYSIZE(argTypes), argTypes);
235
0
}
236
237
0
static void init_openssl_sprintf(struct TargetFunction *target) {
238
0
  static const enum RubyDataType argTypes[2] = {RDT_CString, RDT_CString};
239
0
  init_TargetFunction(target, "openssl", "Kernel", "sprintf",
240
0
                      ARRAYSIZE(argTypes), argTypes);
241
0
}
242
243
0
static void init_CGI_unescapeHTML(struct TargetFunction *target) {
244
0
  static const enum RubyDataType argTypes[1] = {RDT_CString};
245
0
  init_TargetFunction(target, "cgi", "CGI", "unescapeHTML", ARRAYSIZE(argTypes),
246
0
                      argTypes);
247
0
}
248
249
typedef void (*init_TargetFunction_ptr)(struct TargetFunction *target);
250
251
static init_TargetFunction_ptr init_functions[] = {
252
    init_date_parse,         init_date_strptime,   init_date_httpdate,
253
    init_json_parse,         init_psych_parse,     init_openssl_read,
254
    init_openssl_read_smime, init_openssl_sprintf, init_CGI_unescapeHTML,
255
    init_regexp_new };
256
257
// setenv("RUBYLIB", ...) so that dynamic loading of ruby gems
258
// (which are in an unusual location due to the OSS-Fuzz infrastructure)
259
// will work.
260
0
static int setenv_rubylib() {
261
0
  char rubylib[0x1000];
262
0
  char exepath[PATH_MAX];
263
264
  // Deduce the location of the OUT directory from the executable path.
265
0
  ssize_t n = readlink("/proc/self/exe", exepath, sizeof(exepath));
266
267
  // Find the parent directory.
268
0
  do {
269
0
    if (n <= 0) {
270
0
      return -1;
271
0
    }
272
0
  } while(exepath[--n] != '/');
273
0
  exepath[n] = '\0';
274
0
  fprintf(stderr, "exepath = %s\n", exepath);
275
276
  // init_ruby_load_paths() is automatically generated by build.sh
277
0
  const int r = init_ruby_load_paths(rubylib, sizeof(rubylib), exepath);
278
0
  if (r < 0 || (size_t)r >= sizeof(rubylib)) {
279
0
    return -1;
280
0
  }
281
0
  fprintf(stderr, "RUBYLIB = %s\n", rubylib);
282
0
  if (setenv("RUBYLIB", rubylib, 1) < 0) {
283
0
    return -1;
284
0
  }
285
0
  return 0;
286
0
}
287
288
// Bogus code to add a bunch of extra calls to __ubsan functions so that we
289
// get over the minimum threshold (169 calls) required by bad_build_check:
290
// https://github.com/google/oss-fuzz/blob/5af82b8e388a3455690c40d3d99c92d4a213a602/infra/base-images/base-runner/bad_build_check#L44-L48
291
0
static void workaround_UBSAN_CALLS_THRESHOLD_FOR_UBSAN_BUILD(size_t x) {
292
0
  static size_t n = 0;
293
0
  switch (x) {
294
0
  case 0:
295
0
    n++;
296
0
  case 1:
297
0
    n++;
298
0
  case 2:
299
0
    n++;
300
0
  case 3:
301
0
    n++;
302
0
  case 4:
303
0
    n++;
304
0
  case 5:
305
0
    n++;
306
0
  case 6:
307
0
    n++;
308
0
  case 7:
309
0
    n++;
310
0
  case 8:
311
0
    n++;
312
0
  case 9:
313
0
    n++;
314
0
  case 10:
315
0
    n++;
316
0
  case 11:
317
0
    n++;
318
0
  case 12:
319
0
    n++;
320
0
  case 13:
321
0
    n++;
322
0
  case 14:
323
0
    n++;
324
0
  case 15:
325
0
    n++;
326
0
  case 16:
327
0
    n++;
328
0
  case 17:
329
0
    n++;
330
0
  case 18:
331
0
    n++;
332
0
  case 19:
333
0
    n++;
334
0
  case 20:
335
0
    n++;
336
0
  case 21:
337
0
    n++;
338
0
  case 22:
339
0
    n++;
340
0
  case 23:
341
0
    n++;
342
0
  case 24:
343
0
    n++;
344
0
  case 25:
345
0
    n++;
346
0
  case 26:
347
0
    n++;
348
0
  case 27:
349
0
    n++;
350
0
  case 28:
351
0
    n++;
352
0
  case 29:
353
0
    n++;
354
0
  case 30:
355
0
    n++;
356
0
  case 31:
357
0
    n++;
358
0
  case 32:
359
0
    n++;
360
0
  case 33:
361
0
    n++;
362
0
  case 34:
363
0
    n++;
364
0
  case 35:
365
0
    n++;
366
0
  case 36:
367
0
    n++;
368
0
  case 37:
369
0
    n++;
370
0
  case 38:
371
0
    n++;
372
0
  case 39:
373
0
    n++;
374
0
  default:
375
0
    n++;
376
0
  }
377
0
}
378
379
0
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
380
0
  struct ByteStream bs = {};
381
382
0
  ByteStream_init(&bs, data, size);
383
384
  // Static array of target functions. These only need to be initialized once.
385
0
  static struct TargetFunction target_functions[ARRAYSIZE(init_functions)] = {};
386
387
  // Initialize the Ruby interpreter.
388
0
  static bool ruby_initialized = false;
389
0
  if (!ruby_initialized) {
390
0
    ruby_initialized = true;
391
392
0
    if (setenv_rubylib() < 0) {
393
0
      abort();
394
0
    }
395
396
0
    ruby_init();
397
0
    ruby_init_loadpath();
398
399
    // Initialize the fuzzing functions.
400
0
    for (size_t i = 0; i < ARRAYSIZE(init_functions); i++) {
401
0
      init_functions[i](&target_functions[i]);
402
0
    }
403
404
0
    uint32_t x = 0;
405
0
    BytesStream_get_uint32_t(&bs, &x);
406
0
    workaround_UBSAN_CALLS_THRESHOLD_FOR_UBSAN_BUILD(x);
407
408
    // Reset the byte stream
409
0
    ByteStream_init(&bs, data, size);
410
0
  }
411
412
  // Choose a function from `target_functions`.
413
0
  uint32_t i = 0;
414
0
  if (BytesStream_get_uint32_t(&bs, &i) < 0) {
415
0
    goto out;
416
0
  }
417
0
  struct TargetFunction *fcn =
418
0
      &target_functions[i % ARRAYSIZE(target_functions)];
419
0
  run_fuzz_function(&bs, fcn);
420
421
0
out:
422
0
  return 0;
423
0
}