kisaten 0.0.1
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/LICENSE +21 -0
- data/Rakefile +42 -0
- data/ext/kisaten/extconf.rb +9 -0
- data/ext/kisaten/kisaten.c +569 -0
- data/lib/kisaten/version.rb +3 -0
- data/lib/kisaten.rb +3 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cbf4ace7b5e9dfefd4dfce14ac14a30bb75daf00
|
4
|
+
data.tar.gz: 919678346cc61d12689caa0d260ead40e1ef5e23
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ff1473e9692f589a559eba8866403b761d624871e07bfce66213fb6a9c8bd99035712400d99c302b203750993593a9a239e66effcb4bc73767f6636ee3ac929
|
7
|
+
data.tar.gz: 511db7193de267c0a5f73ccff415f1526587f49050f40c233fd5d8e884fa2a5a5f2d5ed806b91a65940842f4669f1e454b2702687b5d5a85bec7ed877603e18f
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Ariel Zelivansky
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake/extensiontask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rubygems/package_task'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
8
|
+
require 'kisaten/version'
|
9
|
+
|
10
|
+
Rake::ExtensionTask.new 'kisaten' do |ext|
|
11
|
+
|
12
|
+
# This causes the shared object to be placed in lib/kisaten/kisaten.so
|
13
|
+
# TODO: Test this vs multiple different ruby versions
|
14
|
+
ext.lib_dir = 'lib/kisaten'
|
15
|
+
end
|
16
|
+
|
17
|
+
spec = Gem::Specification.new 'kisaten' do |spec|
|
18
|
+
spec.summary = 'Ruby instrumentation for afl fuzzing'
|
19
|
+
spec.version = Kisaten::VERSION
|
20
|
+
spec.authors = ["Ariel Zelivansky"]
|
21
|
+
|
22
|
+
spec.license = 'MIT'
|
23
|
+
|
24
|
+
# This tells RubyGems to build an extension upon install
|
25
|
+
spec.extensions = %w[ext/kisaten/extconf.rb]
|
26
|
+
|
27
|
+
spec.files = Dir["Rakefile", "{ext,lib}/**/*.{rb,c}", "LICENSE", "README"]
|
28
|
+
spec.required_ruby_version = ">= 2.0.0"
|
29
|
+
spec.add_development_dependency "rake-compiler"
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::TestTask.new(:test) do |t|
|
34
|
+
t.libs << "test"
|
35
|
+
t.warning = true
|
36
|
+
t.options = "--verbose"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Build the gem in pkg/kisaten.*.gem
|
40
|
+
Gem::PackageTask.new spec do end
|
41
|
+
|
42
|
+
task :default => :test
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
# This environment variable should be used when compiling the extension for testing
|
4
|
+
# Will set if the env is not defined (or set to 0)
|
5
|
+
if "0" != ENV.fetch("TEST_KISATEN") { "0" }
|
6
|
+
$defs.push("-DTEST_KISATEN_FNV") unless $defs.include? "-TEST_KISATEN_FNV"
|
7
|
+
end
|
8
|
+
|
9
|
+
create_makefile 'kisaten/kisaten'
|
@@ -0,0 +1,569 @@
|
|
1
|
+
#ifndef _KISATEN_INCLUDE
|
2
|
+
#define _KISATEN_INCLUDE
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <ruby/debug.h>
|
6
|
+
/* TODO: Low: Check if all of these will work with Windows one day */
|
7
|
+
#include <stdint.h>
|
8
|
+
#include <errno.h>
|
9
|
+
#include <signal.h>
|
10
|
+
#include <sys/wait.h>
|
11
|
+
#include <sys/types.h>
|
12
|
+
#include <sys/shm.h>
|
13
|
+
|
14
|
+
#endif
|
15
|
+
|
16
|
+
/* Ruby 2.4 */
|
17
|
+
#ifndef RB_INTEGER_TYPE_P
|
18
|
+
#define RB_INTEGER_TYPE_P(obj) (RB_FIXNUM_P(obj) || RB_TYPE_P(obj, T_BIGNUM))
|
19
|
+
#endif
|
20
|
+
|
21
|
+
/* General TODO:
|
22
|
+
* Replace all rb_eRuntimeError with a Kisaten error type
|
23
|
+
* Consider treating AFL_INST_RATIO one day
|
24
|
+
*/
|
25
|
+
|
26
|
+
/* Constants that must be in sync with afl-fuzz (afl/config.h) */
|
27
|
+
#define AFL_SHM_ENV_VAR "__AFL_SHM_ID"
|
28
|
+
#define AFL_FORKSRV_FD 198
|
29
|
+
#define AFL_MAP_SIZE_POW2 16
|
30
|
+
#define AFL_MAP_SIZE (1 << AFL_MAP_SIZE_POW2)
|
31
|
+
|
32
|
+
/* Implementation globals */
|
33
|
+
unsigned int prev_location = 0;
|
34
|
+
uint8_t *afl_area_ptr = NULL;
|
35
|
+
|
36
|
+
uint8_t use_forkserver = 0;
|
37
|
+
uint8_t kisaten_init_done = 0;
|
38
|
+
uint8_t afl_persistent_mode = 0;
|
39
|
+
int crash_exception_id = 0;
|
40
|
+
|
41
|
+
VALUE crash_exception_types = Qnil;
|
42
|
+
VALUE crash_exception_ignore = Qnil;
|
43
|
+
VALUE tp_scope_event = Qnil;
|
44
|
+
VALUE tp_raise_event = Qnil;
|
45
|
+
|
46
|
+
static void kisaten_register_globals()
|
47
|
+
{
|
48
|
+
/* When using global C variables that contain a Ruby value, the code must manually inform the GC of these variable.
|
49
|
+
Otherwise they get reaped */
|
50
|
+
/* rb_gc_register_address is equivalent to rb_global_variable */
|
51
|
+
rb_gc_register_address(&crash_exception_types);
|
52
|
+
rb_gc_register_address(&crash_exception_ignore);
|
53
|
+
rb_gc_register_address(&tp_scope_event);
|
54
|
+
rb_gc_register_address(&tp_raise_event);
|
55
|
+
}
|
56
|
+
|
57
|
+
static void kisaten_unregister_globals()
|
58
|
+
{
|
59
|
+
/* TODO: Figure out if cleanup should be called by the module. Don't want these vars to leak. */
|
60
|
+
rb_gc_unregister_address(&crash_exception_types);
|
61
|
+
rb_gc_unregister_address(&crash_exception_ignore);
|
62
|
+
rb_gc_unregister_address(&tp_scope_event);
|
63
|
+
rb_gc_unregister_address(&tp_raise_event);
|
64
|
+
}
|
65
|
+
|
66
|
+
static inline void kisaten_map_shm()
|
67
|
+
{
|
68
|
+
char *shm_id_str = NULL;
|
69
|
+
int shm_id = 0;
|
70
|
+
|
71
|
+
if (NULL != afl_area_ptr)
|
72
|
+
{
|
73
|
+
rb_raise(rb_eRuntimeError, "Kisaten error: kisaten_map_shm was called but afl_area_ptr is not NULL");
|
74
|
+
}
|
75
|
+
|
76
|
+
shm_id_str = getenv(AFL_SHM_ENV_VAR);
|
77
|
+
|
78
|
+
if (!shm_id_str)
|
79
|
+
{
|
80
|
+
/* rb_warning only prints if $VERBOSE is true. */
|
81
|
+
rb_warning("Kisaten failed to get AFL shared memory environment variable");
|
82
|
+
}
|
83
|
+
else
|
84
|
+
{
|
85
|
+
errno = 0;
|
86
|
+
shm_id = strtol(shm_id_str, NULL, 10); /* Safer atoi */
|
87
|
+
if (0 != errno)
|
88
|
+
{
|
89
|
+
rb_raise(rb_eRuntimeError, "Kisaten failed to read valid integer from AFL_SHM_ENV_VAR!");
|
90
|
+
}
|
91
|
+
|
92
|
+
afl_area_ptr = shmat(shm_id, NULL, 0);
|
93
|
+
if ((void *) -1 == afl_area_ptr)
|
94
|
+
{
|
95
|
+
/* TODO: Check errno */
|
96
|
+
rb_raise(rb_eRuntimeError, "Kisaten failed to attach AFL shared memory to address space!");
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
/* Inspired by python-afl - using FNV hash algorithm to generate the current_line
|
102
|
+
This is a 32-bit Fowler–Noll–Vo hash function based on the jwilk's implementation for python-afl */
|
103
|
+
static inline uint32_t kisaten_location_fnv_hash(const VALUE path, const VALUE lineno)
|
104
|
+
{
|
105
|
+
long _len; /* In ruby/ruby.h RString->len is type long */
|
106
|
+
unsigned char *_path_ptr;
|
107
|
+
int _lineno; /* In ruby/vm_trace.c lineno is int */
|
108
|
+
|
109
|
+
uint32_t h = 0x811C9DC5; /* Seed(?) for 32-bit */
|
110
|
+
|
111
|
+
/* Type enforcement to prevent segfault */
|
112
|
+
if (!RB_INTEGER_TYPE_P(lineno))
|
113
|
+
{
|
114
|
+
rb_raise(rb_eRuntimeError, "Kisaten internal error: lineno is not an integer.");
|
115
|
+
}
|
116
|
+
|
117
|
+
if (T_STRING != TYPE(path))
|
118
|
+
{
|
119
|
+
rb_raise(rb_eRuntimeError, "Kisaten internal error: path is not a string.");
|
120
|
+
}
|
121
|
+
|
122
|
+
_len = RSTRING_LEN(path);
|
123
|
+
_path_ptr = RSTRING_PTR(path);
|
124
|
+
_lineno = FIX2INT(lineno);
|
125
|
+
|
126
|
+
/* TODO: Someone should verify this C is optimized */
|
127
|
+
while (_len > 0)
|
128
|
+
{
|
129
|
+
h ^= _path_ptr[0];
|
130
|
+
h *= 0x01000193;
|
131
|
+
_len--;
|
132
|
+
_path_ptr++;
|
133
|
+
}
|
134
|
+
|
135
|
+
while (_lineno > 0)
|
136
|
+
{
|
137
|
+
h ^= (unsigned char) _lineno;
|
138
|
+
h *= 0x01000193;
|
139
|
+
_lineno >>= 8;
|
140
|
+
}
|
141
|
+
|
142
|
+
return h;
|
143
|
+
}
|
144
|
+
|
145
|
+
#ifdef TEST_KISATEN_FNV
|
146
|
+
static VALUE rb_fnv_kisaten(VALUE self, VALUE path, VALUE lineno)
|
147
|
+
{
|
148
|
+
|
149
|
+
uint32_t result = kisaten_location_fnv_hash(path, lineno);
|
150
|
+
return INT2FIX(result);
|
151
|
+
}
|
152
|
+
#endif
|
153
|
+
|
154
|
+
static void kisaten_scope_event(VALUE self, void *data)
|
155
|
+
{
|
156
|
+
/* Personal: Refer to byebug.c for full debugger example also using debug_context_t */
|
157
|
+
rb_trace_arg_t *trace_arg;
|
158
|
+
VALUE path, lineno = Qnil;
|
159
|
+
unsigned int _cur_location = 0;
|
160
|
+
|
161
|
+
/* TODO: LOW: Check if event is in valid thread */
|
162
|
+
|
163
|
+
trace_arg = rb_tracearg_from_tracepoint(self);
|
164
|
+
|
165
|
+
/* Maybe: are rb_tracearg_method_id or rb_tracearg_binding helpful for instrumentation? */
|
166
|
+
path = rb_tracearg_path(trace_arg); /* String path */
|
167
|
+
lineno = rb_tracearg_lineno(trace_arg); /* Integer line */
|
168
|
+
|
169
|
+
if (NULL != afl_area_ptr)
|
170
|
+
{
|
171
|
+
/* Refer to afl's technical_details.txt, 1 (Coverage measurements) for info on the injection */
|
172
|
+
_cur_location = kisaten_location_fnv_hash(path, lineno) % AFL_MAP_SIZE;
|
173
|
+
afl_area_ptr[_cur_location ^ prev_location]++;
|
174
|
+
prev_location = _cur_location >> 1;
|
175
|
+
}
|
176
|
+
|
177
|
+
/* TODO: Does it not get triggered by this module's C calls? */
|
178
|
+
}
|
179
|
+
|
180
|
+
static void kisaten_raise_event(VALUE self, void *data)
|
181
|
+
{
|
182
|
+
rb_trace_arg_t *trace_arg;
|
183
|
+
VALUE raised_exception, _cur_exception_class = Qnil;
|
184
|
+
VALUE _exception_class_name = Qnil;
|
185
|
+
uint8_t _exception_blacklisted = 0;
|
186
|
+
int i = 0;
|
187
|
+
|
188
|
+
trace_arg = rb_tracearg_from_tracepoint(self);
|
189
|
+
raised_exception = rb_tracearg_raised_exception(trace_arg);
|
190
|
+
|
191
|
+
if (T_ARRAY != TYPE(crash_exception_types))
|
192
|
+
{
|
193
|
+
rb_warn("Kisaten :raise event called but crash_exception_types is not an Array");
|
194
|
+
}
|
195
|
+
else if (T_ARRAY != TYPE(crash_exception_ignore) && !NIL_P(crash_exception_ignore))
|
196
|
+
{
|
197
|
+
rb_warn("Kisaten :raise event called but crash_exception_ignore is of bad type");
|
198
|
+
}
|
199
|
+
else
|
200
|
+
{
|
201
|
+
/* First verify exception class is not blacklisted.
|
202
|
+
This will also verify that it's not a subclass of blacklisted class */
|
203
|
+
if (!NIL_P(crash_exception_ignore))
|
204
|
+
{
|
205
|
+
for (i = 0; i < RARRAY_LENINT(crash_exception_ignore); i++)
|
206
|
+
{
|
207
|
+
_cur_exception_class = rb_ary_entry(crash_exception_ignore, i);
|
208
|
+
if (rb_obj_is_kind_of(raised_exception, _cur_exception_class))
|
209
|
+
{
|
210
|
+
_exception_blacklisted = 1;
|
211
|
+
break;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
if (!_exception_blacklisted)
|
217
|
+
{
|
218
|
+
/* Match raised exception class/subclass with given crash exceptions */
|
219
|
+
for (i = 0; i < RARRAY_LENINT(crash_exception_types); i++)
|
220
|
+
{
|
221
|
+
_cur_exception_class = rb_ary_entry(crash_exception_types, i);
|
222
|
+
if (rb_obj_is_kind_of(raised_exception, _cur_exception_class))
|
223
|
+
{
|
224
|
+
/* Before crashing, inform the host with warning. This can make it easier to set up fuzzers */
|
225
|
+
/* It should be possible to also get the message with rb_obj_as_string, see exc_inspect code. */
|
226
|
+
_exception_class_name = rb_str_dup(rb_class_name(CLASS_OF(raised_exception)));
|
227
|
+
rb_warning("Kisaten crashing execution because exception was raised: %s", StringValuePtr(_exception_class_name)); /* Assume class name can't include null char */
|
228
|
+
|
229
|
+
/* TODO: After raising the signal, Kisaten still catches the SignalException that was raised by kill.
|
230
|
+
This is currently okay because the handler still works afterwards */
|
231
|
+
/* Crash execution with given signal */
|
232
|
+
if (0 != kill(getpid(), crash_exception_id))
|
233
|
+
{
|
234
|
+
rb_raise(rb_eRuntimeError, "Kisaten catched exception but failed to crash execution with given signal");
|
235
|
+
}
|
236
|
+
break;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
static void kisaten_trace_begin()
|
244
|
+
{
|
245
|
+
/* TODO: Consider allowing instrumenation by other events other than :line
|
246
|
+
Specifically c_call would be interesting and then :call,:call,:b_call, etc. */
|
247
|
+
tp_scope_event = rb_tracepoint_new(Qnil, RUBY_EVENT_LINE, kisaten_scope_event, NULL);
|
248
|
+
rb_tracepoint_enable(tp_scope_event);
|
249
|
+
|
250
|
+
/* If requested, catch raised exceptions and cause a crash (so afl can catch) */
|
251
|
+
/* TODO: The current implementation relies on :raise tracepoint hooks.
|
252
|
+
This means it is called on every exception before it is rescued.
|
253
|
+
|
254
|
+
Possibly implement something like byebug's post-mortem, then catching only unhandled exceptions.
|
255
|
+
Or other ways to detect only unhandled exceptions (i.e. no rescue). */
|
256
|
+
|
257
|
+
if (T_ARRAY == TYPE(crash_exception_types))
|
258
|
+
{
|
259
|
+
tp_raise_event = rb_tracepoint_new(Qnil, RUBY_EVENT_RAISE, kisaten_raise_event, NULL);
|
260
|
+
rb_tracepoint_enable(tp_raise_event);
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
static inline void kisaten_trace_stop()
|
265
|
+
{
|
266
|
+
rb_tracepoint_disable(tp_scope_event);
|
267
|
+
}
|
268
|
+
|
269
|
+
static void kisaten_init()
|
270
|
+
{
|
271
|
+
static uint8_t _tmp[4] = {0};
|
272
|
+
|
273
|
+
ssize_t _cnt = 0;
|
274
|
+
int _rc = -1;
|
275
|
+
uint32_t _child_killed = 0;
|
276
|
+
uint8_t _child_stopped = 0;
|
277
|
+
pid_t child_pid = -1;
|
278
|
+
int child_status = 0;
|
279
|
+
|
280
|
+
struct sigaction _old_sigchld, _dfl_sigchld;
|
281
|
+
|
282
|
+
/* Reset sigaction structs regardless of their use */
|
283
|
+
sigemptyset(&_old_sigchld.sa_mask);
|
284
|
+
_old_sigchld.sa_flags = 0;
|
285
|
+
_old_sigchld.sa_sigaction = NULL;
|
286
|
+
_old_sigchld.sa_handler = SIG_DFL;
|
287
|
+
|
288
|
+
sigemptyset(&_dfl_sigchld.sa_mask);
|
289
|
+
_dfl_sigchld.sa_flags = 0;
|
290
|
+
_dfl_sigchld.sa_sigaction = NULL;
|
291
|
+
_dfl_sigchld.sa_handler = SIG_DFL;
|
292
|
+
|
293
|
+
/* Initialization logic begin */
|
294
|
+
|
295
|
+
if (kisaten_init_done)
|
296
|
+
{
|
297
|
+
rb_raise(rb_eRuntimeError, "Kisaten init already done");
|
298
|
+
}
|
299
|
+
|
300
|
+
use_forkserver = 1;
|
301
|
+
|
302
|
+
/* Start the forkserver: "Phone home".
|
303
|
+
If the pipe doesn't exist then assume we are not running in forkserver mode.
|
304
|
+
Otherwise failure is considered a bug */
|
305
|
+
_cnt = write(AFL_FORKSRV_FD + 1, _tmp, 4);
|
306
|
+
if (4 != _cnt)
|
307
|
+
{
|
308
|
+
if (_cnt < 0 && EBADF == errno)
|
309
|
+
{
|
310
|
+
/* TODO: Consider a way to allow disabling this warning message.
|
311
|
+
It may end up as a bottleneck in some cases where forkserver is not needed */
|
312
|
+
rb_warning("Kisaten is running without forkserver");
|
313
|
+
use_forkserver = 0;
|
314
|
+
}
|
315
|
+
else
|
316
|
+
{
|
317
|
+
rb_raise(rb_eRuntimeError, "Kisaten forkserver initialization failure");
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
kisaten_init_done = 1;
|
322
|
+
|
323
|
+
if (use_forkserver)
|
324
|
+
{
|
325
|
+
/* From my understanding (and sampling) it appears MRI does not set a handler for SIGCHLD here.
|
326
|
+
The user code can change the handler though. The code resets it (like in python-afl)
|
327
|
+
|
328
|
+
TODO: Are there any unexpectable implications of temporarily resetting SIGCHLD handler in MRI?
|
329
|
+
*/
|
330
|
+
_rc = sigaction(SIGCHLD, &_dfl_sigchld, &_old_sigchld);
|
331
|
+
if (0 != _rc)
|
332
|
+
{
|
333
|
+
/* TODO: Check errno */
|
334
|
+
rb_raise(rb_eRuntimeError, "Kisaten failure setting (DFL) SIGCHLD");
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
while (use_forkserver)
|
339
|
+
{
|
340
|
+
_cnt = 0;
|
341
|
+
_cnt = read(AFL_FORKSRV_FD, &_child_killed, 4);
|
342
|
+
if (4 != _cnt)
|
343
|
+
{
|
344
|
+
rb_raise(rb_eRuntimeError, "Kisaten forkserver failure to read from parent pipe");
|
345
|
+
}
|
346
|
+
|
347
|
+
/* This is for when the child has been stopped and afl killed it with SIGKILL */
|
348
|
+
if (_child_stopped && _child_killed)
|
349
|
+
{
|
350
|
+
_child_stopped = 0;
|
351
|
+
if (0 > waitpid(child_pid, &child_status, 0))
|
352
|
+
{
|
353
|
+
rb_raise(rb_eRuntimeError, "Kisaten critical failure on (killed) waitpid");
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
357
|
+
if (!_child_stopped)
|
358
|
+
{
|
359
|
+
/* Child is woke, clone our process */
|
360
|
+
child_pid = fork();
|
361
|
+
if (0 > child_pid)
|
362
|
+
{
|
363
|
+
/* TODO: Handle this without runtime error which I'm not sure how will behave */
|
364
|
+
rb_raise(rb_eRuntimeError, "Kisaten failure to fork");
|
365
|
+
}
|
366
|
+
if (!child_pid)
|
367
|
+
{
|
368
|
+
/* Continue to instrumentation for the child */
|
369
|
+
break;
|
370
|
+
}
|
371
|
+
}
|
372
|
+
else
|
373
|
+
{
|
374
|
+
/* Persistent mode: if child is alive but stopped give it a SIGCONT to resume */
|
375
|
+
if (0 != kill(child_pid, SIGCONT))
|
376
|
+
{
|
377
|
+
rb_raise(rb_eRuntimeError, "Kisaten persistent mode signal failure");
|
378
|
+
}
|
379
|
+
_child_stopped = 0;
|
380
|
+
}
|
381
|
+
|
382
|
+
/* In parent process write PID to the pipe and wait for the child */
|
383
|
+
/* Todo: Low: extremely unlikely scenario of sizeof(t_pid)!=4? */
|
384
|
+
_cnt = write(AFL_FORKSRV_FD + 1, &child_pid, 4);
|
385
|
+
if (4 != _cnt)
|
386
|
+
{
|
387
|
+
rb_raise(rb_eRuntimeError, "Kisaten failure writing (child_pid) to parent pipe");
|
388
|
+
}
|
389
|
+
|
390
|
+
if (0 > waitpid(child_pid, &child_status, afl_persistent_mode ? WUNTRACED : 0))
|
391
|
+
{
|
392
|
+
rb_raise(rb_eRuntimeError, "Kisaten critical failure on waitpid");
|
393
|
+
}
|
394
|
+
|
395
|
+
/* Persistent mode: the child stops itself with SIGSTOP after running, wake it up without forking again */
|
396
|
+
if (WIFSTOPPED(child_status))
|
397
|
+
{
|
398
|
+
_child_stopped = 1;
|
399
|
+
}
|
400
|
+
|
401
|
+
/* Send status back to pipe
|
402
|
+
TODO: Low: Make sure sizeof(int) is always good */
|
403
|
+
_cnt = write(AFL_FORKSRV_FD + 1, &child_status, 4);
|
404
|
+
if (4 != _cnt)
|
405
|
+
{
|
406
|
+
rb_raise(rb_eRuntimeError, "Kisaten failure writing (status) to parent pipe");
|
407
|
+
}
|
408
|
+
|
409
|
+
}
|
410
|
+
|
411
|
+
/* Instrumentation logic continue (in child if forkserver) */
|
412
|
+
if (use_forkserver)
|
413
|
+
{
|
414
|
+
/* Return the original SIGCHLD */
|
415
|
+
_rc = sigaction(SIGCHLD, &_old_sigchld, NULL);
|
416
|
+
if (0 != _rc)
|
417
|
+
{
|
418
|
+
/* TODO: Check errno */
|
419
|
+
rb_raise(rb_eRuntimeError, "Kisaten failure returning SIGCHLD");
|
420
|
+
}
|
421
|
+
|
422
|
+
/* Clean descriptors for child */
|
423
|
+
if (0 > close(AFL_FORKSRV_FD) || 0 > close(AFL_FORKSRV_FD + 1))
|
424
|
+
{
|
425
|
+
/* I am not sure of the implications of failing here so only warn */
|
426
|
+
rb_warn("Kisaten warning: failed to clean FORKSRV_FDs in child");
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
/* Get AFL shared memory before starting instrumentation */
|
431
|
+
kisaten_map_shm();
|
432
|
+
|
433
|
+
kisaten_trace_begin();
|
434
|
+
}
|
435
|
+
|
436
|
+
static VALUE rb_init_kisaten(VALUE self)
|
437
|
+
{
|
438
|
+
afl_persistent_mode = 0;
|
439
|
+
kisaten_init();
|
440
|
+
return Qnil;
|
441
|
+
}
|
442
|
+
|
443
|
+
static VALUE rb_loop_kisaten(VALUE self, VALUE max_count)
|
444
|
+
{
|
445
|
+
static int _saved_max_cnt = 0;
|
446
|
+
static uint8_t _first_pass = 1;
|
447
|
+
static uint32_t _run_cnt = 0;
|
448
|
+
|
449
|
+
/* Note: LLVM mode and python-afl use an enviroment variable to enforce persistent mode.
|
450
|
+
Currently not implementating unless it has a good reason */
|
451
|
+
|
452
|
+
if (_first_pass)
|
453
|
+
{
|
454
|
+
/* Check if given max_count is valid */
|
455
|
+
if (!RB_INTEGER_TYPE_P(max_count) && !NIL_P(max_count))
|
456
|
+
{
|
457
|
+
rb_raise(rb_eRuntimeError, "Kisaten loop max_count must be an integer or nil (for infinite)");
|
458
|
+
}
|
459
|
+
|
460
|
+
if (NIL_P(max_count))
|
461
|
+
{
|
462
|
+
/* nil max_count means infinite loop */
|
463
|
+
_saved_max_cnt = -1;
|
464
|
+
}
|
465
|
+
else
|
466
|
+
{
|
467
|
+
_saved_max_cnt = NUM2INT(max_count);
|
468
|
+
if (0 >= _saved_max_cnt)
|
469
|
+
{
|
470
|
+
/* Return without doing anything, next run will be like new */
|
471
|
+
return Qfalse;
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
/* Start the fork server with persistent mode */
|
476
|
+
afl_persistent_mode = 1;
|
477
|
+
prev_location = 0;
|
478
|
+
|
479
|
+
kisaten_init();
|
480
|
+
|
481
|
+
_first_pass = 0;
|
482
|
+
_run_cnt++;
|
483
|
+
|
484
|
+
return Qtrue;
|
485
|
+
}
|
486
|
+
|
487
|
+
if (_run_cnt < _saved_max_cnt)
|
488
|
+
{
|
489
|
+
if (0 != kill(getpid(), SIGSTOP))
|
490
|
+
{
|
491
|
+
/* TODO: Low: should this be an error? Maybe just return Qfalse and log? */
|
492
|
+
rb_raise(rb_eRuntimeError, "Kisaten failed to raise SIGSTOP");
|
493
|
+
}
|
494
|
+
|
495
|
+
_run_cnt++;
|
496
|
+
return Qtrue;
|
497
|
+
}
|
498
|
+
else
|
499
|
+
{
|
500
|
+
/* Loop has ended, disable instrumentation for the rest of the Ruby code */
|
501
|
+
kisaten_trace_stop();
|
502
|
+
return Qfalse;
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
/* This function will be used to set exceptions that should crash execution */
|
507
|
+
static VALUE rb_crash_at_kisaten(VALUE self, VALUE arr_exceptions, VALUE arr_ignore, VALUE int_crash_id)
|
508
|
+
{
|
509
|
+
if (kisaten_init_done)
|
510
|
+
{
|
511
|
+
/* TODO: Consider allowing calling crash_at after init if there is a need for it.
|
512
|
+
Basically just need to set or remove the raise TP if changed */
|
513
|
+
rb_raise(rb_eRuntimeError, "Kisaten init already done, crash_at currently unsupported");
|
514
|
+
}
|
515
|
+
|
516
|
+
/* Allow "reset" by setting nil,nil,nil */
|
517
|
+
if (NIL_P(arr_ignore) && NIL_P(arr_exceptions) && NIL_P(int_crash_id))
|
518
|
+
{
|
519
|
+
crash_exception_types = Qnil;
|
520
|
+
crash_exception_ignore = Qnil;
|
521
|
+
crash_exception_id = 0;
|
522
|
+
return Qtrue;
|
523
|
+
}
|
524
|
+
|
525
|
+
/* Check types
|
526
|
+
Accept only arrays for exception and ignore list
|
527
|
+
Accept only integer for crash id. Can be found with Signal, i.e Signal.list["USR1"] */
|
528
|
+
if (T_ARRAY != TYPE(arr_exceptions))
|
529
|
+
{
|
530
|
+
rb_raise(rb_eRuntimeError, "Kisaten.crash_at needs an Array for crash exceptions list");
|
531
|
+
}
|
532
|
+
if (T_ARRAY != TYPE(arr_ignore) && !NIL_P(arr_ignore))
|
533
|
+
{
|
534
|
+
rb_raise(rb_eRuntimeError, "Kisaten.crash_at needs an Array for ignore exceptions list");
|
535
|
+
}
|
536
|
+
if (!RB_INTEGER_TYPE_P(int_crash_id))
|
537
|
+
{
|
538
|
+
rb_raise(rb_eRuntimeError, "Kisaten.crash_at crash exception signal ID must be an integer");
|
539
|
+
}
|
540
|
+
|
541
|
+
crash_exception_types = rb_ary_dup(arr_exceptions);
|
542
|
+
/* Secretly allow nil for ignore list */
|
543
|
+
if (NIL_P(arr_ignore))
|
544
|
+
{
|
545
|
+
crash_exception_ignore = Qnil;
|
546
|
+
}
|
547
|
+
else
|
548
|
+
{
|
549
|
+
crash_exception_ignore = rb_ary_dup(arr_ignore);
|
550
|
+
}
|
551
|
+
crash_exception_id = NUM2INT(int_crash_id);
|
552
|
+
|
553
|
+
return Qtrue;
|
554
|
+
}
|
555
|
+
|
556
|
+
void Init_kisaten()
|
557
|
+
{
|
558
|
+
VALUE rb_mKisaten = Qnil;
|
559
|
+
|
560
|
+
rb_mKisaten = rb_define_module("Kisaten");
|
561
|
+
rb_define_singleton_method(rb_mKisaten, "init", rb_init_kisaten, 0);
|
562
|
+
rb_define_singleton_method(rb_mKisaten, "loop", rb_loop_kisaten, 1);
|
563
|
+
rb_define_singleton_method(rb_mKisaten, "crash_at", rb_crash_at_kisaten, 3);
|
564
|
+
#ifdef TEST_KISATEN_FNV
|
565
|
+
rb_define_singleton_method(rb_mKisaten, "_fnv", rb_fnv_kisaten, 2);
|
566
|
+
#endif
|
567
|
+
|
568
|
+
kisaten_register_globals();
|
569
|
+
}
|
data/lib/kisaten.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kisaten
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ariel Zelivansky
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
executables: []
|
30
|
+
extensions:
|
31
|
+
- ext/kisaten/extconf.rb
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- Rakefile
|
36
|
+
- ext/kisaten/extconf.rb
|
37
|
+
- ext/kisaten/kisaten.c
|
38
|
+
- lib/kisaten.rb
|
39
|
+
- lib/kisaten/version.rb
|
40
|
+
homepage:
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 2.0.0
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 2.5.2.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Ruby instrumentation for afl fuzzing
|
64
|
+
test_files: []
|