kisaten 0.0.1

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
+ 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
+ }
@@ -0,0 +1,3 @@
1
+ module Kisaten
2
+ VERSION = "0.0.1"
3
+ end
data/lib/kisaten.rb ADDED
@@ -0,0 +1,3 @@
1
+ # TODO: Include logic to test for RUBY_ENGINE and verify version
2
+
3
+ require 'kisaten/kisaten'
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: []