Skip to content

Unhandled promise rejections: reported only at exit, no event, late .catch suppresses #6077

Description

@proggeramlug

Severity: P1 (wrong lifecycle + latent UAF)
Found by: audit fable-audit-perry-2.md, late-catch confirmed by probe.

What

Unhandled promise rejections are reported only at process exit, there is no process.on('unhandledRejection')/'rejectionHandled'), and a .catch attached in any later macrotask silently suppresses what Node would treat as a fatal unhandled rejection.

const p = Promise.reject(new Error("boom"));
setTimeout(() => p.catch(() => console.log("late")), 10);
// node (default): crashes before the timer, nonzero exit
// perry: prints "late", exit 0

Why

Rejections with no reaction are tracked in static UNHANDLED_REJECTIONS: RefCell<Vec<usize>> (crates/perry-runtime/src/promise/then.rs:49-50) and only inspected by js_promise_report_unhandled_rejections(), which codegen emits after the event loop exits (crates/perry-codegen/src/codegen/entry.rs:914-918, logic at then.rs:119-194). Three problems:

  1. Timing — Node fires per-rejection at end-of-turn; Perry defers to process end, so a later handler suppresses it.
  2. No process emitter path at all (zero unhandledRejection hits in runtime+stdlib) — log-and-continue programs get exit(1) instead.
  3. UNHANDLED_REJECTIONS entries are not GC roots (no scanner references them) and the exit-time report derefs (*pr).reason — a swept/recycled promise makes that a stale read (UB; arena pages stay mapped so misreport is likelier than a hard fault).

Fix

Check the tracked set at the end of each run_microtasks drain (non-reentrant, timers-allowed mode) instead of at exit; root the reasons (or pin the promise while tracked); add a process emitter hook before the exit(1) fallback so handlers can observe/suppress.

Confidence: high on mechanism; UAF exploitability medium.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions