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