Coverage Report

Created: 2025-10-31 09:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/test/fuzzers/fuzz_strings.cc
Line
Count
Source
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/*
16
 * A fuzzer focused on C string -> Javascript String using N-API.
17
 * Extended to cover UTF-16, external strings, optimized property keys,
18
 * length-query getter paths, napi_coerce_to_string, and exception draining.
19
 */
20
21
#include <cstdint>
22
#include <cstdlib>
23
#include <cstring>
24
#include <string>
25
26
#include "js_native_api.h"
27
#include "js_native_api_v8.h"
28
#include "node.h"
29
#include "node_internals.h"
30
#include "node_api_internals.h"
31
#include "env-inl.h"
32
#include "util-inl.h"
33
#include "v8.h"
34
35
#include "fuzz_common.h"  // IsolateScope + RunInEnvironment
36
37
// --- Helpers ---------------------------------------------------------------
38
39
// Drain (and ignore) any pending exception on the env to avoid fatal V8 scopes
40
538k
static inline void DrainLastException(napi_env env) {
41
538k
  if (!env) return;
42
538k
  bool pending = false;
43
538k
  if (napi_is_exception_pending(env, &pending) == napi_ok && pending) {
44
21.1k
    napi_value exc = nullptr;
45
21.1k
    (void)napi_get_and_clear_last_exception(env, &exc);
46
21.1k
  }
47
538k
}
48
49
// Optional: same deleter you had (used for external strings)
50
26.9k
static void free_string(node_api_nogc_env /*env*/, void* data, void* /*hint*/) {
51
26.9k
  std::free(data);
52
26.9k
}
53
54
// Static used only to receive env from addon init; reset every run.
55
static napi_env g_addon_env = nullptr;
56
57
// Non-capturing addon init to receive napi_env
58
13.4k
static napi_value CaptureEnvInit(napi_env env, napi_value exports) {
59
13.4k
  g_addon_env = env;
60
13.4k
  return exports;
61
13.4k
}
62
63
22.0k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
64
22.0k
  const char* bytes = reinterpret_cast<const char*>(data);
65
22.0k
  std::string s(bytes, size);
66
67
22.0k
  fuzz::IsolateScope iso;
68
22.0k
  if (!iso.ok()) return 0;
69
70
  // Fresh Context + Environment for this input
71
22.0k
  fuzz::RunInEnvironment(iso.isolate(),
72
22.0k
    [&](node::Environment* /*env*/, v8::Local<v8::Context> context) {
73
13.4k
      g_addon_env = nullptr;  // reset before registering
74
75
      // Create module/exports objects and register a dummy addon to obtain napi_env.
76
13.4k
      v8::Isolate* isolate = v8::Isolate::GetCurrent();
77
13.4k
      v8::Local<v8::Object> module_obj  = v8::Object::New(isolate);
78
13.4k
      v8::Local<v8::Object> exports_obj = v8::Object::New(isolate);
79
80
13.4k
      napi_module_register_by_symbol(
81
13.4k
          exports_obj, module_obj, context, &CaptureEnvInit, NAPI_VERSION);
82
83
13.4k
      napi_env addon_env = g_addon_env;
84
13.4k
      if (addon_env == nullptr) {
85
        // Couldn’t get an env; bail out gracefully.
86
0
        return;
87
0
      }
88
89
      // ---- Original N-API string ops (augmented below) ----
90
13.4k
      size_t copied1 = 0, copied2 = 0;
91
13.4k
      bool copied3 = false;
92
13.4k
      napi_value output1{}, output2{}, output3{}, output4{}, output5{}, output6{},
93
13.4k
                 output7{}, output8{}, output9{}, output10{}, output11{},
94
13.4k
                 output12{};
95
96
      // Allocate temp buffers (respecting size)
97
13.4k
      char* buf1 = static_cast<char*>(std::malloc(size ? size : 1));
98
13.4k
      char* buf2 = static_cast<char*>(std::malloc(size ? size : 1));
99
13.4k
      if (!buf1 || !buf2) {
100
0
        std::free(buf1); std::free(buf2);
101
0
        return;
102
0
      }
103
104
      // create/get UTF-8
105
13.4k
      (void) napi_create_string_utf8(addon_env, s.data(), size, &output1);
106
13.4k
      DrainLastException(addon_env);
107
13.4k
      (void) napi_get_value_string_utf8(addon_env, output1, buf1, size, &copied1);
108
13.4k
      DrainLastException(addon_env);
109
110
      // create/get Latin-1
111
13.4k
      (void) napi_create_string_latin1(addon_env, s.data(), size, &output2);
112
13.4k
      DrainLastException(addon_env);
113
13.4k
      (void) napi_get_value_string_latin1(addon_env, output2, buf2, size, &copied2);
114
13.4k
      DrainLastException(addon_env);
115
116
      // symbol.for
117
13.4k
      (void) node_api_symbol_for(addon_env, s.data(), size, &output4);
118
13.4k
      DrainLastException(addon_env);
119
120
      // Property ops using raw fuzz bytes for the property name
121
13.4k
      (void) napi_set_named_property(addon_env, output1, s.c_str(), output2);
122
13.4k
      DrainLastException(addon_env);
123
13.4k
      (void) napi_get_named_property(addon_env, output1, s.c_str(), &output6);
124
13.4k
      DrainLastException(addon_env);
125
13.4k
      (void) napi_has_named_property(addon_env, output1, s.c_str(), &copied3);
126
13.4k
      DrainLastException(addon_env);
127
128
13.4k
      (void) napi_get_property_names(addon_env, output1, &output7);
129
13.4k
      DrainLastException(addon_env);
130
13.4k
      (void) napi_has_property(addon_env, output1, output2, &copied3);
131
13.4k
      DrainLastException(addon_env);
132
13.4k
      (void) napi_get_property(addon_env, output1, output2, &output8);
133
13.4k
      DrainLastException(addon_env);
134
13.4k
      (void) napi_delete_property(addon_env, output1, output2, &copied3);
135
13.4k
      DrainLastException(addon_env);
136
13.4k
      (void) napi_has_own_property(addon_env, output1, output2, &copied3);
137
13.4k
      DrainLastException(addon_env);
138
139
13.4k
      (void) napi_create_type_error(addon_env, output1, output2, &output9);
140
13.4k
      DrainLastException(addon_env);
141
13.4k
      (void) napi_create_range_error(addon_env, output1, output2, &output10);
142
13.4k
      DrainLastException(addon_env);
143
13.4k
      (void) node_api_create_syntax_error(addon_env, output1, output2, &output11);
144
13.4k
      DrainLastException(addon_env);
145
146
13.4k
      (void) napi_run_script(addon_env, output2, &output12);
147
13.4k
      DrainLastException(addon_env);
148
149
      // -----------------------------------------------------------------
150
      // Length-query getter paths for UTF-8 / Latin-1
151
13.4k
      size_t needed_utf8 = 0;
152
13.4k
      (void) napi_get_value_string_utf8(addon_env, output1, nullptr, 0, &needed_utf8);
153
13.4k
      DrainLastException(addon_env);
154
13.4k
      if (needed_utf8 > 0 && needed_utf8 < (1ull << 20)) { // cap alloc
155
13.3k
        char* dyn_utf8 = static_cast<char*>(std::malloc(needed_utf8 + 1));
156
13.3k
        if (dyn_utf8) {
157
13.3k
          size_t got = 0;
158
13.3k
          (void) napi_get_value_string_utf8(addon_env, output1, dyn_utf8, needed_utf8 + 1, &got);
159
13.3k
          DrainLastException(addon_env);
160
13.3k
          std::free(dyn_utf8);
161
13.3k
        }
162
13.3k
      }
163
164
13.4k
      size_t needed_latin1 = 0;
165
13.4k
      (void) napi_get_value_string_latin1(addon_env, output2, nullptr, 0, &needed_latin1);
166
13.4k
      DrainLastException(addon_env);
167
13.4k
      if (needed_latin1 > 0 && needed_latin1 < (1ull << 20)) {
168
13.4k
        char* dyn_latin1 = static_cast<char*>(std::malloc(needed_latin1 + 1));
169
13.4k
        if (dyn_latin1) {
170
13.4k
          size_t got = 0;
171
13.4k
          (void) napi_get_value_string_latin1(addon_env, output2, dyn_latin1, needed_latin1 + 1, &got);
172
13.4k
          DrainLastException(addon_env);
173
13.4k
          std::free(dyn_latin1);
174
13.4k
        }
175
13.4k
      }
176
177
      // UTF-16 create/get and length-query path
178
      // Build a UTF-16LE buffer from fuzz bytes (pair up bytes; if odd, drop last byte)
179
13.4k
      size_t u16_len = size / 2;
180
13.4k
      if (u16_len == 0) u16_len = 1; // ensure non-zero for coverage
181
13.4k
      char16_t* u16_in = static_cast<char16_t*>(std::malloc(sizeof(char16_t) * u16_len));
182
13.4k
      if (u16_in) {
183
125M
        for (size_t i = 0; i < u16_len; ++i) {
184
125M
          uint16_t lo = (2*i < size) ? static_cast<uint8_t>(data[2*i]) : 0;
185
125M
          uint16_t hi = (2*i + 1 < size) ? static_cast<uint8_t>(data[2*i + 1]) : 0;
186
125M
          u16_in[i] = static_cast<char16_t>((hi << 8) | lo);
187
125M
        }
188
13.4k
        napi_value out_u16{};
189
13.4k
        (void) napi_create_string_utf16(addon_env, u16_in, u16_len, &out_u16);
190
13.4k
        DrainLastException(addon_env);
191
192
        // Length query first
193
13.4k
        size_t needed_u16 = 0;
194
13.4k
        (void) napi_get_value_string_utf16(addon_env, out_u16, nullptr, 0, &needed_u16);
195
13.4k
        DrainLastException(addon_env);
196
197
        // Then allocate and fetch
198
13.4k
        if (needed_u16 == 0) needed_u16 = 1;
199
13.4k
        char16_t* u16_out = static_cast<char16_t*>(std::malloc(sizeof(char16_t) * (needed_u16 + 1)));
200
13.4k
        if (u16_out) {
201
13.4k
          size_t got_u16 = 0;
202
13.4k
          (void) napi_get_value_string_utf16(addon_env, out_u16, u16_out, needed_u16 + 1, &got_u16);
203
13.4k
          DrainLastException(addon_env);
204
13.4k
          std::free(u16_out);
205
13.4k
        }
206
207
        // Use the UTF-16 string in property ops too
208
13.4k
        (void) napi_set_property(addon_env, out_u16, output2 /*key string*/, out_u16);
209
13.4k
        (void) napi_has_property(addon_env, out_u16, output2, &copied3);
210
13.4k
        DrainLastException(addon_env);
211
212
13.4k
        std::free(u16_in);
213
13.4k
      }
214
215
      // Optimized property-key creators (UTF-8 / Latin-1 / UTF-16)
216
13.4k
      napi_value key_u8{}, key_l1{}, key_u16{};
217
13.4k
      (void) node_api_create_property_key_utf8(addon_env, s.data(), size, &key_u8);
218
13.4k
      (void) node_api_create_property_key_latin1(addon_env, s.data(), size, &key_l1);
219
13.4k
      DrainLastException(addon_env);
220
221
      // Build a short u16 key buffer (re-use first few code units of previous conversion)
222
13.4k
      size_t key16_len = (size / 2) ? (size / 2) : 1;
223
13.4k
      char16_t* key16_buf = static_cast<char16_t*>(std::malloc(sizeof(char16_t) * key16_len));
224
13.4k
      if (key16_buf) {
225
125M
        for (size_t i = 0; i < key16_len; ++i) {
226
125M
          uint16_t lo = (2*i < size) ? static_cast<uint8_t>(data[2*i]) : 0;
227
125M
          uint16_t hi = (2*i + 1 < size) ? static_cast<uint8_t>(data[2*i + 1]) : 0;
228
125M
          key16_buf[i] = static_cast<char16_t>((hi << 8) | lo);
229
125M
        }
230
13.4k
        (void) node_api_create_property_key_utf16(addon_env, key16_buf, key16_len, &key_u16);
231
13.4k
        DrainLastException(addon_env);
232
13.4k
      }
233
234
      // Try using these keys on a string receiver (boxed in JS)
235
13.4k
      if (key_u8)  {
236
13.4k
        napi_value tmp{};
237
13.4k
        (void) napi_set_property(addon_env, output1, key_u8, output2);
238
13.4k
        DrainLastException(addon_env);
239
13.4k
        (void) napi_get_property(addon_env, output1, key_u8, &tmp);
240
13.4k
        DrainLastException(addon_env);
241
13.4k
      }
242
13.4k
      if (key_l1)  {
243
13.4k
        napi_value tmp{};
244
13.4k
        (void) napi_set_property(addon_env, output1, key_l1, output1);
245
13.4k
        DrainLastException(addon_env);
246
13.4k
        (void) napi_get_property(addon_env, output1, key_l1, &tmp);
247
13.4k
        DrainLastException(addon_env);
248
13.4k
      }
249
13.4k
      if (key_u16) {
250
13.4k
        napi_value tmp{};
251
13.4k
        (void) napi_set_property(addon_env, output1, key_u16, output4);
252
13.4k
        DrainLastException(addon_env);
253
13.4k
        (void) napi_get_property(addon_env, output1, key_u16, &tmp);
254
13.4k
        DrainLastException(addon_env);
255
13.4k
      }
256
257
13.4k
      std::free(key16_buf);
258
259
      // External strings (Latin-1 and UTF-16) with finalizers.
260
      // For external-string APIs: if 'copied' comes back true, free immediately
261
      // (engine made a copy and will NOT call the finalizer). If false, the GC
262
      // will call 'free_string' later, so don't free here.
263
264
      // External Latin-1
265
13.4k
      {
266
13.4k
        bool copied = false;
267
13.4k
        size_t ext_len = size ? size : 1;
268
13.4k
        char* ext_l1 = static_cast<char*>(std::malloc(ext_len));
269
13.4k
        if (ext_l1) {
270
13.4k
          if (size) {
271
13.4k
            std::memcpy(ext_l1, s.data(), size);
272
13.4k
          } else {
273
0
            ext_l1[0] = '\0';  // deterministic content when input is empty
274
0
          }
275
276
13.4k
          napi_value ext_l1_val{};
277
13.4k
          (void) node_api_create_external_string_latin1(
278
13.4k
              addon_env, ext_l1, ext_len, free_string, nullptr, &ext_l1_val, &copied);
279
13.4k
          DrainLastException(addon_env);
280
281
          // Touch it a bit
282
13.4k
          (void) napi_coerce_to_string(addon_env, ext_l1_val, &output3);
283
13.4k
          (void) napi_has_property(addon_env, ext_l1_val, key_u8 ? key_u8 : output2, &copied3);
284
13.4k
          DrainLastException(addon_env);
285
286
13.4k
          if (copied) {
287
            // Engine copied data; finalizer won't run. Free now.
288
0
            std::free(ext_l1);
289
0
          }
290
13.4k
        }
291
13.4k
      }
292
293
      // External UTF-16
294
13.4k
      {
295
13.4k
        bool copied_ext16 = false;
296
13.4k
        size_t ext16_len = (size / 2) ? (size / 2) : 1;
297
13.4k
        char16_t* ext_u16 = static_cast<char16_t*>(std::malloc(sizeof(char16_t) * ext16_len));
298
13.4k
        if (ext_u16) {
299
125M
          for (size_t i = 0; i < ext16_len; ++i) {
300
125M
            uint16_t lo = (2*i < size) ? static_cast<uint8_t>(data[2*i]) : 0;
301
125M
            uint16_t hi = (2*i + 1 < size) ? static_cast<uint8_t>(data[2*i + 1]) : 0;
302
125M
            ext_u16[i] = static_cast<char16_t>((hi << 8) | lo);
303
125M
          }
304
305
13.4k
          napi_value ext_u16_val{};
306
13.4k
          (void) node_api_create_external_string_utf16(
307
13.4k
              addon_env, ext_u16, ext16_len, free_string, nullptr, &ext_u16_val, &copied_ext16);
308
13.4k
          DrainLastException(addon_env);
309
310
          // Exercise getter path on it
311
13.4k
          size_t need16 = 0;
312
13.4k
          (void) napi_get_value_string_utf16(addon_env, ext_u16_val, nullptr, 0, &need16);
313
13.4k
          DrainLastException(addon_env);
314
13.4k
          if (need16 == 0) need16 = 1;
315
13.4k
          char16_t* tmp16 = static_cast<char16_t*>(std::malloc(sizeof(char16_t) * (need16 + 1)));
316
13.4k
          if (tmp16) {
317
13.4k
            size_t got16 = 0;
318
13.4k
            (void) napi_get_value_string_utf16(addon_env, ext_u16_val, tmp16, need16 + 1, &got16);
319
13.4k
            DrainLastException(addon_env);
320
13.4k
            std::free(tmp16);
321
13.4k
          }
322
323
13.4k
          if (copied_ext16) {
324
0
            std::free(ext_u16);
325
0
          }
326
13.4k
        }
327
13.4k
      }
328
329
13.4k
      {
330
13.4k
        napi_value coerced{};
331
13.4k
        (void) napi_coerce_to_string(addon_env, output4 /* Symbol.for(...) */, &coerced);
332
13.4k
        (void) napi_coerce_to_string(addon_env, output7 /* property names array */, &coerced);
333
13.4k
        (void) napi_coerce_to_string(addon_env, output9 /* TypeError object */, &coerced);
334
13.4k
        (void) napi_coerce_to_string(addon_env, output12 /* result of run_script */, &coerced);
335
13.4k
        DrainLastException(addon_env);
336
13.4k
      }
337
338
      // Clean up original temp buffers
339
13.4k
      std::free(buf1);
340
13.4k
      std::free(buf2);
341
342
      // Final safeguard: ensure no exception is left pending before we return.
343
13.4k
      DrainLastException(addon_env);
344
13.4k
      g_addon_env = nullptr;  // avoid leakage across inputs
345
13.4k
    },
346
22.0k
    /*opts=*/fuzz::EnvRunOptions{
347
22.0k
        node::EnvironmentFlags::kDefaultFlags,
348
22.0k
        /*print_js_to_stdout=*/false,
349
22.0k
        /*max_pumps=*/4  // give microtasks/callbacks a chance, still bounded
350
22.0k
    });
351
352
22.0k
  return 0;
353
22.0k
}