Skip to content

fix: repair four OOM error-path bugs found by malloc-fault injection - #52

Merged
gburd merged 1 commit into
masterfrom
fix/oom-error-paths
Jul 27, 2026
Merged

fix: repair four OOM error-path bugs found by malloc-fault injection#52
gburd merged 1 commit into
masterfrom
fix/oom-error-paths

Conversation

@gburd

@gburd gburd commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

The malloc-failure injection sweep (test/faultinject, #49) — fail the Nth allocation across a workload, assert every failure point returns cleanly — found 4 genuine bugs, all on OOM error/cleanup paths. All fixed here; the sweep now PASSES (0 crash/hang/dirty over 506 failure points) under both plain debug and clang ASan.

# Site Bug Trigger (fail-at-K)
1 __db_env_destroy (env_method.c) NULL-deref: teardown runs when dbenv->env calloc failed K=2
2 __lock_getlocker_int (lock_id.c) >> typo (no >>=) → NULL sh_locker walked; nomem check too late K=212,214,220,353,359
3 __db_pgin/pgout (db_conv.c) derefs empty/NULL pgcookie after open-time alloc failure K=375,376
4 __txn_end (txn.c) heap-UAF: detail freed before __lock_freelocker reads td->si_ref K≈213-216 (ASan)

Notes

  • 5.3.28 tag problem (same than 5.3.21) #2 is a real latent logic bug independent of OOM: if ((nlockers >> 1) == 0) break; was meant to be nlockers >>= 1 — the retry loop never actually shrank the request. Fixed the halving and reordered the __lock_nomem check before the insert loop.
  • Berkeley DB JE (Java Edition) is missing #4 is a lifetime-ordering bug in the SSI hardening path: __lock_freelocker_int reads td->si_ref (the SIREAD-marker refcount) via the locker, but __txn_end freed td first. Fix: free the locker while td is still alive (the locker is in its own LOCK_LOCKERS domain, so ordering is preserved). Verified the full SSI suite incl. multi-process ssi009 still passes.

Testing

  • malloc-injection sweep: PASS (0/506 crash/hang/dirty), plain debug and clang ASan
  • ssi001006, ssi009 (multi-process concurrent writers), txn001/002, lock001, test001 btree — pass
  • Clean build (--enable-debug --enable-diagnostic)

Found by / depends on #49 (malloc-fault injection harness).

…injection

The new malloc-failure injection sweep (test/faultinject, PR #49) fails the Nth
allocation across a workload and asserts every failure point returns cleanly.
It found four genuine bugs, all on OOM error/cleanup paths; fixed here:

1. __db_env_destroy (src/env/env_method.c): on the early failure path in
   __env_create (the ENV calloc itself failed), dbenv->env is NULL, but the
   subsystem destructors and the ENV memset/free all dereference it -> NULL
   deref. Guard the whole ENV teardown on dbenv->env != NULL.

2. __lock_getlocker_int (src/lock/lock_id.c): the region locker-array grow loop
   used `if ((nlockers >> 1) == 0) break;` -- a no-op test with no assignment, so
   on allocation failure it either spun on the same size or broke with sh_locker
   still NULL while nlockers stayed >= 1; the following insert loop then walked a
   NULL sh_locker. Restore the intended halving (nlockers >>= 1) and move the
   __lock_nomem check ahead of the insert loop.

3. __db_pgin / __db_pgout (src/db/db_conv.c): a page swap dereferences the
   DB_PGINFO in cookie->data immediately. If a file was registered for page-in
   but its cookie was never set (an allocation failed during open),
   __memp_get_pgcookie hands back an empty DBT, so cookie->data is "" (or NULL)
   -> out-of-bounds / NULL read. Reject a missing/short cookie with EINVAL.

4. __txn_end (src/txn/txn.c): the transaction detail (td) was freed under
   TXN_SYSTEM_LOCK, then __lock_freelocker was called after the unlock --
   __lock_freelocker_int reads td->si_ref (via the locker's td_off) to honor the
   SSI marker-lifetime protocol, dereferencing the freed td (heap-use-after-free,
   ASan). Free the locker BEFORE the detail, while td is still alive; the locker
   lives in its own LOCK_LOCKERS domain so ordering is unchanged.

Verified: the malloc-injection sweep now PASSES (0 crash/hang/dirty over 506
failure points) under both plain debug and clang ASan; ssi001-006/009 (incl.
multi-process concurrent writers) + txn001/002 + lock001 + test001 pass.
@github-actions

Copy link
Copy Markdown

Coccinelle convention checks

No new violations. ✅

Resolved since baseline (2) -- update dist/cocci/baseline.txt to lock these in.
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/crypto/mersenne/mt19937db.c|return (ret);
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/mp/mp_register.c|return (ret);

@github-actions

Copy link
Copy Markdown

ABI diff vs v5.3.30 (libabigail — authoritative)

Functions changes summary: 0 Removed, 0 Changed, 1 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

1 Added function:

  [A] 'function atomic_value_t __os_atomic_read_relaxed(const db_atomic_t*)'    {__os_atomic_read_relaxed}

Removed exported symbols (nm -D, _NNNN version suffix normalized)

None.


Advisory: libabigail/nm is the authoritative binary-ABI check; Coccinelle is complementary source-level early warning. See dist/cocci/README.md.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant