ruzzy 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|