Skip to content

Stack overflow crash (STATUS_STACK_BUFFER_OVERRUN 0xC0000409 / SIGSEGV) in std::regex_match on long paths when using a glob ignore option #250

@Hona

Description

@Hona

Summary

Using a glob pattern in the ignore option causes the entire host process to crash (no catchable error) when a filesystem change event has a long relative path. On Windows this surfaces as STATUS_STACK_BUFFER_OVERRUN (exit code 3221226505 / 0xC0000409); the same root cause produces a SIGSEGV on Linux.

The crash happens inside std::regex_match (src/Glob.cc), which is run against every event's relative path. std::regex is implemented recursively (notably in MSVC's STL), so a long enough path overflows the watcher thread's stack.

This is not the compile-time invalid-regex crash fixed in #195 — the regex compiles fine; the crash is at match time, after subscribe succeeds.

Minimal reproduction

const watcher = require("@parcel/watcher");
const fs = require("fs");
const path = require("path");
const os = require("os");

(async () => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pw-"));
  await watcher.subscribe(root, () => {}, {
    ignore: ["**/*.log"], // any glob -> compiled to std::regex in the native addon
  });
  console.log("subscribed");

  // Create a deeply nested tree so a change event's relative path is ~300+ chars.
  let cur = root;
  for (let i = 0; i < 5; i++) {
    cur = path.join(cur, "a".repeat(200));
    fs.mkdirSync(cur);
  }
  console.log("created deep tree");

  setTimeout(() => { console.log("survived (no crash)"); process.exit(0); }, 3000);
})();

Result

subscribed

…then the process dies immediately with exit code 3221226505 (0xC0000409, STATUS_STACK_BUFFER_OVERRUN). process.on('uncaughtException') / try/catch never fire — the crash is in native code on the watcher thread, so it cannot be caught from JS.

Threshold

The crash depends on the relative path length, not depth specifically. With ignore: ["**/*.log"]:

Relative path length Result
~200 chars survives
~300 chars crash (0xC0000409)
~500+ chars crash

Control: with ignore: [] (no globs), the same 12,000-char-deep tree is processed fine — confirming the crash is in std::regex_match, not the path handling itself.

This is an ordinary path length in real projects (e.g. deep node_modules, or git-worktree paths like …/worktree/<40-char-hash>/<branch>/…). It is much worse with non-ASCII paths: the path passed to std::regex is UTF-8, so each CJK character is 3 bytes — ~85–100 CJK chars is enough to cross the threshold.

Root cause

src/Glob.cc:

bool Glob::isIgnored(std::string relative_path) const {
  return std::regex_match(relative_path, mRegex); // recursive -> stack overflow on long input
}

isIgnored is called per event from Subscription::processEvent (src/windows/WindowsBackend.cc) on the watcher thread. std::regex_match recurses proportionally to input length, overflowing the thread stack.

Affected versions

  • Reproduced on 2.5.1 and on latest 2.5.6 (Glob.cc is unchanged across 2.5.1 → 2.5.6).
  • Verified on Windows 11 x64, Node.js v24. Given std::regex's recursive implementation, this is expected to affect Linux/macOS as well (cf. the Linux SIGSEGV backtrace in Segmentation fault using wildcard in ignore option on Node.js 22.7.0 #184, which also points at std::__detail::_Compiler/regex internals).

This appears related to #244 (the same std::regex + picomatch-generated patterns), and distinct from #184 / #194 (compile-time, fixed by #195).

Suggested fix

Replace std::regex in Glob.cc with a non-recursive matcher — e.g. RE2, or run the already-available picomatch/makeRe matching outside the native regex engine. At minimum, the match should not recurse on input length.

Environment

  • @parcel/watcher 2.5.6 (also 2.5.1)
  • Windows 11 x64, Node.js v24.14.1
  • backend: windows (default on Win32)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions