Skip to content

util.inspect color OOM crash #64011

Description

@markussieber

Version

26.3.1

Platform

N/A

Subsystem

node:util

What steps will reproduce the bug?

Running the following code in the Node.js REPL:

const util = require('util');
const err = new Error('boom');
err.stack = 'Error: boom\n    at /app/node_modules/foo.js:1:1';
util.inspect(err, { colors: true })

Produces a crash with the following output:

OOM Crash
<--- Last few GCs --->

[92116:0x7b9480c000]    20938 ms: Scavenge (interleaved) 4037.5 (4055.0) -> 4037.2 (4100.0) MB, pooled: 0 MB, 19.29 / 0.00 ms  (average mu = 0.223, current mu = 0.168) allocation failure;
[92116:0x7b9480c000]    23141 ms: Mark-Compact (reduce) 4037.2 (4100.0) -> 4037.2 (4039.7) MB, pooled: 0 MB, 2033.17 / 0.00 ms  (+ 0.1 ms in 1 steps since start of marking, biggest step 0.1 ms, walltime since start of marking 2036 ms) (average mu = 0.168,
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----

 1: 0x10263a0fc node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/path/to/node]
 2: 0x1028964b8 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/path/to/node]
 3: 0x102af58a4 v8::internal::Heap::stack() [/path/to/node]
 4: 0x102af8c78 v8::internal::Heap::HasLowYoungGenerationAllocationRate() [/path/to/node]
 5: 0x102b09e6c v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_1::operator()() const [/path/to/node]
 6: 0x102b09740 void heap::base::Stack::SetMarkerAndCallbackImpl<v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_1>(heap::base::Stack*, void*, void const*) [/path/to/node]
 7: 0x1034be538 PushAllRegistersAndIterateStack [/path/to/node]
 8: 0x102af35a8 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/path/to/node]
 9: 0x102a6af6c v8::internal::StackGuard::HandleInterrupts(v8::internal::StackGuard::InterruptLevel) [/path/to/node]
10: 0x102f59250 v8::internal::Runtime_HandleNoHeapWritesInterrupts(int, unsigned long*, v8::internal::Isolate*) [/path/to/node]
11: 0x1035ae234 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit [/path/to/node]
12: 0x1264cbbf4
13: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
14: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
15: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
16: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
17: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
18: 0x10350e8cc Builtins_JSEntryTrampoline [/path/to/node]
19: 0x10350e570 Builtins_JSEntry [/path/to/node]
20: 0x102a39a94 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [/path/to/node]
21: 0x102a3a058 v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) [/path/to/node]
22: 0x10289c8b8 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) [/path/to/node]
23: 0x102622c24 node::contextify::ContextifyScript::EvalMachine(v8::Local<v8::Context>, node::Environment*, long long, bool, bool, bool, v8::MicrotaskQueue*, v8::FunctionCallbackInfo<v8::Value> const&) [/path/to/node]
24: 0x1026225c8 node::contextify::ContextifyScript::RunInContext(v8::FunctionCallbackInfo<v8::Value> const&) [/path/to/node]
25: 0x103512818 Builtins_CallApiCallbackGeneric [/path/to/node]
26: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
27: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
28: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
29: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
30: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
31: 0x1264d20cc
32: 0x1264e6290
33: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
34: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
35: 0x1264dd7f4
36: 0x1264e44d4
37: 0x1264ded5c
38: 0x1264d1ef0
39: 0x1264e6290
40: 0x1264e1fd0
41: 0x103551e88 Builtins_GeneratorPrototypeNext [/path/to/node]
42: 0x1264e3624
43: 0x1264d1ef0
44: 0x1264e6290
45: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
46: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
47: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
48: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
49: 0x103510bec Builtins_InterpreterEntryTrampoline [/path/to/node]
50: 0x10350e8cc Builtins_JSEntryTrampoline [/path/to/node]
51: 0x10350e570 Builtins_JSEntry [/path/to/node]
52: 0x102a39a94 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [/path/to/node]
53: 0x102a393f8 v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>, v8::base::Vector<v8::internal::DirectHandle<v8::internal::Object> const>) [/path/to/node]
54: 0x1028ade78 v8::Function::Call(v8::Isolate*, v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) [/path/to/node]
55: 0x10251b38c node::InternalMakeCallback(node::Environment*, v8::Local<v8::Object>, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context, v8::Local<v8::Value>) [/path/to/node]
56: 0x102533dd0 node::AsyncWrap::MakeCallback(v8::Local<v8::Function>, int, v8::Local<v8::Value>*) [/path/to/node]
57: 0x102791e70 node::StreamBase::CallJSOnreadMethod(long, v8::Local<v8::ArrayBuffer>, unsigned long, node::StreamBase::StreamBaseJSChecks) [/path/to/node]
58: 0x102793604 node::EmitToJSStreamListener::OnStreamRead(long, uv_buf_t const&) [/path/to/node]
59: 0x102798ed4 node::LibuvStreamWrap::OnUvRead(long, uv_buf_t const*) [/path/to/node]
60: 0x102799604 node::LibuvStreamWrap::ReadStart()::$_1::__invoke(uv_stream_s*, long, uv_buf_t const*) [/path/to/node]
61: 0x1034fa760 uv__stream_io [/path/to/node]
62: 0x103501f94 uv__io_poll [/path/to/node]
63: 0x1034ee1c8 uv_run [/path/to/node]
64: 0x10251c6a8 node::SpinEventLoopInternal(node::Environment*) [/path/to/node]
65: 0x10269300c node::NodeMainInstance::Run() [/path/to/node]
66: 0x1025e6d14 node::Start(int, char**) [/path/to/node]
67: 0x185f30914 start [/usr/lib/dyld]
[1]    92116 abort      node

Activity

  1. added
    confirmed-bugIssues and PRs for confirmed bugs.
    utilIssues and PRs related to the built-in util module.
    on Jun 19, 2026
  2. ther12k commented on Jun 23, 2026

    @ther12k

    Root cause analysis

    Bug: markNodeModules() in lib/internal/util/inspect.js infinite-loops when util.inspect(err, { colors: true }) is called on an Error with a malformed stack containing node_modules but no trailing path separator.

    Mechanism:

    At line ~1878, StringPrototypeIndexOf(line, separator, moduleStart) returns -1 when the path separator (/ or \\) isn't found after the node_modules/ segment. This propagates moduleEnd = -1:

    1. line[moduleStart] is unlikely to be @, so line 1881 runs StringPrototypeIndexOf(line, separator, -2) which searches from position 0
    2. The separator IS found at an earlier position (same node_modules/ segment's leading separator), returning a valid-but-wrong position
    3. The interpreter assumes it found a real module name, calls ctx.stylize(), and finalizes with lastPos = moduleEnd = -1 / searchFrom = moduleEnd = -1
    4. Next while (true) iteration searches from position -1 → finds same node_modules → infinite loop
    5. Each iteration calls ctx.stylize() adding ANSI escape codes → unbounded memory growth → OOM

    Fix (single guard clause): Check moduleEnd === -1 after both StringPrototypeIndexOf calls. If the separator isn't found after node_modules/, skip the segment:

        if (moduleEnd === -1) {
          searchFrom = moduleStart;
          continue;
        }

    Untested test (I can't build Node in my environment to verify — appreciate if someone can run):

    // util.inspect with colors should not OOM on malformed stack
    {
      const err = new Error('boom');
      err.stack = 'Error: boom\n    at /app/node_modules/foo.js:1:1';
      const result = util.inspect(err, { colors: true });
      assert.strictEqual(typeof result, 'string');
      assert(result.length > 0);
      assert(result.includes('boom'));
    }

    Filed in the spirit of documenting the finding — I'll leave verification and PR to the Node team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.utilIssues and PRs related to the built-in util module.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions