15
15
#include " fuzzing_sync.h"
16
16
#include " shared/libfuzzer.h"
17
17
#include " utils.h"
18
+ #include < csetjmp>
18
19
#include < csignal>
19
20
#include < cstdlib>
21
+ #include < iostream>
20
22
#include < optional>
21
23
22
24
namespace {
25
+ const std::string SEGFAULT_ERROR_MESSAGE =
26
+ " Segmentation fault found in fuzz target" ;
27
+
23
28
// Information about a JS fuzz target.
24
29
struct FuzzTargetInfo {
25
30
Napi::Env env;
@@ -36,10 +41,24 @@ std::optional<FuzzTargetInfo> gFuzzTarget;
36
41
// This is only necessary in the sync fuzzing case, as async can be handled
37
42
// much nicer directly in JavaScript.
38
43
volatile std::sig_atomic_t gSignalStatus ;
44
+ std::jmp_buf errorBuffer;
39
45
} // namespace
40
46
41
47
void sigintHandler (int signum) { gSignalStatus = signum; }
42
48
49
+ // This handles signals that indicate an unrecoverable error (currently only
50
+ // segfaults). Our handling of segfaults is odd because it avoids using our
51
+ // Javascript method to print and instead prints a message within C++ and exits
52
+ // almost immediately. This is because Node seems to really not like being
53
+ // called back into after `longjmp` jumps outside the scope Node thinks it
54
+ // should be in and so things in JS-land get pretty broken. However, catching it
55
+ // here, printing an ok error message, and letting libfuzzer make the crash file
56
+ // is good enough
57
+ void ErrorSignalHandler (int signum) {
58
+ gSignalStatus = signum;
59
+ std::longjmp (errorBuffer, signum);
60
+ }
61
+
43
62
// The libFuzzer callback when fuzzing synchronously
44
63
int FuzzCallbackSync (const uint8_t *Data, size_t Size) {
45
64
// Create a new active scope so that handles for the buffer objects created in
@@ -62,15 +81,24 @@ int FuzzCallbackSync(const uint8_t *Data, size_t Size) {
62
81
// nice for efficiency if we could use a pointer instead of copying.
63
82
//
64
83
auto data = Napi::Buffer<uint8_t >::Copy (gFuzzTarget ->env , Data, Size);
65
- auto result = gFuzzTarget ->target .Call ({data});
66
-
67
- if (result.IsPromise ()) {
68
- AsyncReturnsHandler ();
69
- } else {
70
- SyncReturnsHandler ();
84
+ if (setjmp (errorBuffer) == 0 ) {
85
+ auto result = gFuzzTarget ->target .Call ({data});
86
+ if (result.IsPromise ()) {
87
+ AsyncReturnsHandler ();
88
+ } else {
89
+ SyncReturnsHandler ();
90
+ }
71
91
}
72
92
73
93
if (gSignalStatus != 0 ) {
94
+ // if we caught a segfault, print the error message and die, letting
95
+ // libfuzzer print the crash file. See the comment on `ErrorSignalHandler`
96
+ // for why
97
+ if (gSignalStatus == SIGSEGV) {
98
+ std::cerr << SEGFAULT_ERROR_MESSAGE << std::endl;
99
+ exit (EXIT_FAILURE);
100
+ }
101
+
74
102
// Non-zero exit codes will produce crash files.
75
103
auto exitCode = Napi::Number::New (gFuzzTarget ->env , 0 );
76
104
@@ -111,7 +139,7 @@ void StartFuzzing(const Napi::CallbackInfo &info) {
111
139
info[2 ].As <Napi::Function>()};
112
140
113
141
signal (SIGINT, sigintHandler);
114
- signal (SIGSEGV, sigintHandler );
142
+ signal (SIGSEGV, ErrorSignalHandler );
115
143
116
144
StartLibFuzzer (fuzzer_args, FuzzCallbackSync);
117
145
// Explicitly reset the global function pointer because the JS
0 commit comments