ruzzy 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/cruzzy/cruzzy.c +136 -0
- data/ext/cruzzy/extconf.rb +115 -0
- data/ext/dummy/dummy.c +40 -0
- data/ext/dummy/extconf.rb +9 -0
- data/lib/ruzzy.rb +31 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d1625761614ef3e6e00d5b36a446dbbba3e880e75269b81e5e44f88b46945f4
|
4
|
+
data.tar.gz: eb5733016d8caf73b5230a1277e561781649101ebc614707f91a30b558056afc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e46ea2f68f183a5f3c87fd2ce1aeb1e36e2cbd6cbcb699aaa52e0bdee9d485959118ef3dedae073a78079d0afc94838b01ca8ac3a7f0babd59603930f7964b90
|
7
|
+
data.tar.gz: 04bb8723b7dd1ea06abdbc3b52fa6a9cc05e9c333f8f263aaaad6fec6b3b49c01f8eb3fb062c044486aa1325bd41754e60e9829688799358d49fc865df7f38df
|
data/ext/cruzzy/cruzzy.c
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
#include <dlfcn.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <stdint.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <signal.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <unistd.h>
|
8
|
+
|
9
|
+
#include <ruby.h>
|
10
|
+
|
11
|
+
// 128 arguments should be enough for anybody
|
12
|
+
#define MAX_ARGS_SIZE 128
|
13
|
+
|
14
|
+
int LLVMFuzzerRunDriver(
|
15
|
+
int *argc,
|
16
|
+
char ***argv,
|
17
|
+
int (*cb)(const uint8_t *data, size_t size)
|
18
|
+
);
|
19
|
+
|
20
|
+
VALUE PROC_HOLDER = Qnil;
|
21
|
+
|
22
|
+
static VALUE c_libfuzzer_is_loaded(VALUE self)
|
23
|
+
{
|
24
|
+
void *self_lib = dlopen(NULL, RTLD_LAZY);
|
25
|
+
|
26
|
+
if (!self_lib) {
|
27
|
+
return Qfalse;
|
28
|
+
}
|
29
|
+
|
30
|
+
void *sym = dlsym(self_lib, "LLVMFuzzerRunDriver");
|
31
|
+
|
32
|
+
dlclose(self_lib);
|
33
|
+
|
34
|
+
return sym ? Qtrue : Qfalse;
|
35
|
+
}
|
36
|
+
|
37
|
+
int ATEXIT_RETCODE = 0;
|
38
|
+
|
39
|
+
static void ruzzy_exit() {
|
40
|
+
_exit(ATEXIT_RETCODE);
|
41
|
+
}
|
42
|
+
|
43
|
+
static void graceful_exit(int code) {
|
44
|
+
// Disable libFuzzer's atexit
|
45
|
+
ATEXIT_RETCODE = code;
|
46
|
+
atexit(ruzzy_exit);
|
47
|
+
exit(code);
|
48
|
+
}
|
49
|
+
|
50
|
+
static void sigint_handler(int signal) {
|
51
|
+
fprintf(
|
52
|
+
stderr,
|
53
|
+
"Signal %d (%s) received. Exiting...\n",
|
54
|
+
signal,
|
55
|
+
strsignal(signal)
|
56
|
+
);
|
57
|
+
graceful_exit(signal);
|
58
|
+
}
|
59
|
+
|
60
|
+
static int proc_caller(const uint8_t *data, size_t size)
|
61
|
+
{
|
62
|
+
VALUE arg = rb_str_new((char *)data, size);
|
63
|
+
VALUE rb_args = rb_ary_new3(1, arg);
|
64
|
+
VALUE result = rb_proc_call(PROC_HOLDER, rb_args);
|
65
|
+
|
66
|
+
// By default, Ruby procs and lambdas will return nil if an explicit return
|
67
|
+
// is not specified. Rather than forcing callers to specify a return, let's
|
68
|
+
// handle the nil case for them and continue adding the input to the corpus.
|
69
|
+
if (NIL_P(result)) {
|
70
|
+
// https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs
|
71
|
+
return 0;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (!FIXNUM_P(result)) {
|
75
|
+
rb_raise(
|
76
|
+
rb_eTypeError,
|
77
|
+
"fuzz target function did not return an integer or nil"
|
78
|
+
);
|
79
|
+
}
|
80
|
+
|
81
|
+
return NUM2INT(result);
|
82
|
+
}
|
83
|
+
|
84
|
+
static VALUE c_fuzz(VALUE self, VALUE test_one_input, VALUE args)
|
85
|
+
{
|
86
|
+
char *argv[MAX_ARGS_SIZE];
|
87
|
+
int args_len = RARRAY_LEN(args);
|
88
|
+
|
89
|
+
// Assume caller always passes in at least the program name as args[0]
|
90
|
+
if (args_len <= 0) {
|
91
|
+
rb_raise(
|
92
|
+
rb_eRuntimeError,
|
93
|
+
"zero arguments passed, we assume at least the program name is present"
|
94
|
+
);
|
95
|
+
}
|
96
|
+
|
97
|
+
// Account for NULL byte at the end
|
98
|
+
if ((args_len + 1) >= MAX_ARGS_SIZE) {
|
99
|
+
rb_raise(
|
100
|
+
rb_eRuntimeError,
|
101
|
+
"cannot specify %d or more arguments",
|
102
|
+
MAX_ARGS_SIZE
|
103
|
+
);
|
104
|
+
}
|
105
|
+
|
106
|
+
if (!rb_obj_is_proc(test_one_input)) {
|
107
|
+
rb_raise(rb_eRuntimeError, "expected a proc or lambda");
|
108
|
+
}
|
109
|
+
|
110
|
+
PROC_HOLDER = test_one_input;
|
111
|
+
|
112
|
+
for (int i = 0; i < args_len; i++) {
|
113
|
+
VALUE arg = RARRAY_PTR(args)[i];
|
114
|
+
argv[i] = StringValuePtr(arg);
|
115
|
+
}
|
116
|
+
argv[args_len] = NULL;
|
117
|
+
|
118
|
+
char **args_ptr = &argv[0];
|
119
|
+
|
120
|
+
// https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library
|
121
|
+
int result = LLVMFuzzerRunDriver(&args_len, &args_ptr, proc_caller);
|
122
|
+
|
123
|
+
return INT2NUM(result);
|
124
|
+
}
|
125
|
+
|
126
|
+
void Init_cruzzy()
|
127
|
+
{
|
128
|
+
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
|
129
|
+
fprintf(stderr, "Could not set SIGINT signal handler\n");
|
130
|
+
exit(1);
|
131
|
+
}
|
132
|
+
|
133
|
+
VALUE ruzzy = rb_const_get(rb_cObject, rb_intern("Ruzzy"));
|
134
|
+
rb_define_module_function(ruzzy, "c_fuzz", &c_fuzz, 2);
|
135
|
+
rb_define_module_function(ruzzy, "c_libfuzzer_is_loaded", &c_libfuzzer_is_loaded, 0);
|
136
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
require 'open3'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'rbconfig'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
LOGGER = Logger.new(STDERR)
|
10
|
+
LOGGER.level = ENV.key?('RUZZY_DEBUG') ? Logger::DEBUG : Logger::INFO
|
11
|
+
|
12
|
+
# These ENV variables really shouldn't be used because we don't support
|
13
|
+
# compilers other than clang, like gcc, etc. Instead prefer to properly include
|
14
|
+
# clang in your PATH. But they're here if you really need them. Also note that
|
15
|
+
# *technically* Ruby does not support C extensions compiled with a different
|
16
|
+
# compiler than Ruby itself was compiled with. So we're on somewhat shaky
|
17
|
+
# ground here. For more information see:
|
18
|
+
# https://github.com/rubygems/rubygems/issues/1508
|
19
|
+
CC = ENV.fetch('CC', 'clang')
|
20
|
+
CXX = ENV.fetch('CXX', 'clang++')
|
21
|
+
AR = ENV.fetch('AR', 'ar')
|
22
|
+
FUZZER_NO_MAIN_LIB_ENV = 'FUZZER_NO_MAIN_LIB'
|
23
|
+
|
24
|
+
LOGGER.debug("Ruby CC: #{RbConfig::CONFIG['CC']}")
|
25
|
+
LOGGER.debug("Ruby CXX: #{RbConfig::CONFIG['CXX']}")
|
26
|
+
LOGGER.debug("Ruby AR: #{RbConfig::CONFIG['AR']}")
|
27
|
+
|
28
|
+
find_executable(CC)
|
29
|
+
find_executable(CXX)
|
30
|
+
|
31
|
+
def get_clang_file_name(file_name)
|
32
|
+
stdout, status = Open3.capture2(CC, '--print-file-name', file_name)
|
33
|
+
success = status.success?
|
34
|
+
exists = success ? File.exist?(stdout.strip) : false
|
35
|
+
LOGGER.debug("Search for #{file_name} using #{CC}: success=#{success} exists=#{exists}")
|
36
|
+
success && exists ? stdout.strip : false
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge_asan_libfuzzer_lib(asan_lib, fuzzer_no_main_lib)
|
40
|
+
merged_output = 'asan_with_fuzzer.so'
|
41
|
+
|
42
|
+
# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#why-this-is-necessary
|
43
|
+
Tempfile.create do |file|
|
44
|
+
file.write(File.open(asan_lib).read)
|
45
|
+
|
46
|
+
LOGGER.debug("Creating ASAN archive at #{file.path}")
|
47
|
+
_, status = Open3.capture2(
|
48
|
+
AR,
|
49
|
+
'd',
|
50
|
+
file.path,
|
51
|
+
'asan_preinit.cc.o',
|
52
|
+
'asan_preinit.cpp.o'
|
53
|
+
)
|
54
|
+
unless status.success?
|
55
|
+
LOGGER.error("The #{AR} archive command failed.")
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
LOGGER.debug("Merging ASAN at #{file.path} and libFuzzer at #{fuzzer_no_main_lib} to #{merged_output}")
|
60
|
+
_, status = Open3.capture2(
|
61
|
+
CXX,
|
62
|
+
'-Wl,--whole-archive',
|
63
|
+
fuzzer_no_main_lib,
|
64
|
+
file.path,
|
65
|
+
'-Wl,--no-whole-archive',
|
66
|
+
'-lpthread',
|
67
|
+
'-ldl',
|
68
|
+
'-shared',
|
69
|
+
'-o',
|
70
|
+
merged_output
|
71
|
+
)
|
72
|
+
unless status.success?
|
73
|
+
LOGGER.error("The #{CXX} shared object merging command failed.")
|
74
|
+
exit(1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
fuzzer_no_main_libs = [
|
80
|
+
'libclang_rt.fuzzer_no_main.a',
|
81
|
+
'libclang_rt.fuzzer_no_main-aarch64.a',
|
82
|
+
'libclang_rt.fuzzer_no_main-x86_64.a'
|
83
|
+
]
|
84
|
+
fuzzer_no_main_lib = fuzzer_no_main_libs.map { |lib| get_clang_file_name(lib) }.find(&:itself)
|
85
|
+
|
86
|
+
unless fuzzer_no_main_lib
|
87
|
+
LOGGER.warn("Could not find fuzzer_no_main using #{CC}.")
|
88
|
+
fuzzer_no_main_lib = ENV.fetch(FUZZER_NO_MAIN_LIB_ENV, nil)
|
89
|
+
if fuzzer_no_main_lib.nil?
|
90
|
+
LOGGER.error("Could not find fuzzer_no_main in #{FUZZER_NO_MAIN_LIB_ENV}.")
|
91
|
+
LOGGER.error("Please include #{CC} in your path or specify #{FUZZER_NO_MAIN_LIB_ENV} ENV variable.")
|
92
|
+
exit(1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
asan_libs = [
|
97
|
+
'libclang_rt.asan.a',
|
98
|
+
'libclang_rt.asan-aarch64.a',
|
99
|
+
'libclang_rt.asan-x86_64.a'
|
100
|
+
]
|
101
|
+
asan_lib = asan_libs.map { |lib| get_clang_file_name(lib) }.find(&:itself)
|
102
|
+
|
103
|
+
unless asan_lib
|
104
|
+
LOGGER.error("Could not find asan using #{CC}.")
|
105
|
+
exit(1)
|
106
|
+
end
|
107
|
+
|
108
|
+
merge_asan_libfuzzer_lib(asan_lib, fuzzer_no_main_lib)
|
109
|
+
|
110
|
+
# The LOCAL_LIBS variable allows linking arbitrary libraries into Ruby C
|
111
|
+
# extensions. It is supported by the Ruby mkmf library and C extension Makefile.
|
112
|
+
# For more information, see https://github.com/ruby/ruby/blob/master/lib/mkmf.rb.
|
113
|
+
$LOCAL_LIBS = fuzzer_no_main_lib
|
114
|
+
|
115
|
+
create_makefile('cruzzy/cruzzy')
|
data/ext/dummy/dummy.c
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#include <stdlib.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
|
6
|
+
// https://llvm.org/docs/LibFuzzer.html#toy-example
|
7
|
+
static int _c_dummy_test_one_input(const uint8_t *data, size_t size)
|
8
|
+
{
|
9
|
+
char test[] = {'a', 'b', 'c'};
|
10
|
+
|
11
|
+
if (size > 0 && data[0] == 'H') {
|
12
|
+
if (size > 1 && data[1] == 'I') {
|
13
|
+
// This code exists specifically to test the driver and ensure
|
14
|
+
// libFuzzer is functioning as expected, so we can safely ignore
|
15
|
+
// the warning.
|
16
|
+
#pragma clang diagnostic push
|
17
|
+
#pragma clang diagnostic ignored "-Warray-bounds"
|
18
|
+
test[1024] = 'd';
|
19
|
+
#pragma clang diagnostic pop
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
return 0;
|
24
|
+
}
|
25
|
+
|
26
|
+
static VALUE c_dummy_test_one_input(VALUE self, VALUE data)
|
27
|
+
{
|
28
|
+
int result = _c_dummy_test_one_input(
|
29
|
+
(uint8_t *)RSTRING_PTR(data),
|
30
|
+
RSTRING_LEN(data)
|
31
|
+
);
|
32
|
+
|
33
|
+
return INT2NUM(result);
|
34
|
+
}
|
35
|
+
|
36
|
+
void Init_dummy()
|
37
|
+
{
|
38
|
+
VALUE ruzzy = rb_const_get(rb_cObject, rb_intern("Ruzzy"));
|
39
|
+
rb_define_module_function(ruzzy, "c_dummy_test_one_input", &c_dummy_test_one_input, 1);
|
40
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
# https://github.com/google/sanitizers/wiki/AddressSanitizerFlags
|
6
|
+
$CFLAGS = '-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g'
|
7
|
+
$CXXFLAGS = '-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g'
|
8
|
+
|
9
|
+
create_makefile('dummy/dummy')
|
data/lib/ruzzy.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
# A Ruby C extension fuzzer
|
6
|
+
module Ruzzy
|
7
|
+
require 'cruzzy/cruzzy'
|
8
|
+
|
9
|
+
DEFAULT_ARGS = [$PROGRAM_NAME] + ARGV
|
10
|
+
|
11
|
+
def fuzz(test_one_input, args = DEFAULT_ARGS)
|
12
|
+
c_fuzz(test_one_input, args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def ext_path
|
16
|
+
(Pathname.new(__FILE__).parent.parent + 'ext' + 'cruzzy').to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def dummy_test_one_input(data)
|
20
|
+
# This 'require' depends on LD_PRELOAD, so it's placed inside the function
|
21
|
+
# scope. This allows us to run ext_path for LD_PRELOAD and not have a
|
22
|
+
# circular dependency.
|
23
|
+
require 'dummy/dummy'
|
24
|
+
|
25
|
+
c_dummy_test_one_input(data)
|
26
|
+
end
|
27
|
+
|
28
|
+
module_function :fuzz
|
29
|
+
module_function :ext_path
|
30
|
+
module_function :dummy_test_one_input
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruzzy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Trail of Bits
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake-compiler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.60'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.60'
|
55
|
+
description:
|
56
|
+
email: support@trailofbits.com
|
57
|
+
executables: []
|
58
|
+
extensions:
|
59
|
+
- ext/cruzzy/extconf.rb
|
60
|
+
- ext/dummy/extconf.rb
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ext/cruzzy/cruzzy.c
|
64
|
+
- ext/cruzzy/extconf.rb
|
65
|
+
- ext/dummy/dummy.c
|
66
|
+
- ext/dummy/extconf.rb
|
67
|
+
- lib/ruzzy.rb
|
68
|
+
homepage: https://rubygems.org/gems/ruzzy
|
69
|
+
licenses:
|
70
|
+
- AGPL-3.0-only
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 3.1.0
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubygems_version: 3.0.3.1
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: A Ruby C extension fuzzer
|
91
|
+
test_files: []
|