ruzzy 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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: []