Skip to content

libdb 2026.09

Choose a tag to compare

@gburd gburd released this 12 Sep 17:23
· 153 commits to master since this release
v2026.09

Warning

Known regression in this release — queue extent verification is disabled.
The queue extent-scan DoS fix (#160) clamps the verify scan to vdp->last_pgno, which is the page count of the main .db file. For an extent queue that file holds only the meta page, so the bound collapses to 0 and __qam_vrfy_walkqueue skips the entire extent walk: db_verify reports success on a queue with a corrupted extent page. Confirmed on a 20,000-record / 770-extent queue.
Only affects verification of Queue databases that use extents (DB->set_q_extentsize). No effect on normal reads/writes, other access methods, or on-disk data. Fixed in 5.3.36 (#163 clamps the verify scan, #168 bounds the cursor read path) — upgrade.

A correctness release. It fixes every externally reported defect against 5.3.34, two crash-on-valid-API NULL dereferences found while raising test coverage, and three hardening items deferred from the security review.

Every fix ships with a regression test that runs in CI, and each test was verified to fail when its fix is reverted — a passing test proves nothing unless it can fail.

If you are running 5.3.34, upgrade: #136 could silently commit a non-serializable schedule, and #137/#138 could exhaust the mutex region.

Externally reported (#136#140, all closed)

#136 — a write skew could COMMIT under DB_TXN_SNAPSHOT. The commit-time pivot check read its two conflict flags under TXN_SYSTEM_LOCK and released it, but the TXN_RUNNING → TXN_COMMITTED transition happens far later in __txn_end. The writer-side "the reader will abort" optimization trusted that stale status and deferred to a check that had already happened, so both transactions escaped. A passing check now publishes TXN_DTL_SICHECKED inside the same critical section, and both writer sites — the lock table and the MVCC mirror in __memp_si_rwconflict — test that instead of the raw status.

Two design notes worth recording: re-checking later is not merely worse but incorrect (by then the log is flushed and read locks are released, so the window is not abortable), and testing status != TXN_RUNNING would spuriously abort writers whose edge points into an aborted transaction.

#137 / #138 — resource exhaustion. Committed-reader lockers were never reclaimed, so sequential read-only snapshot transactions exhausted the mutex region and txn_begin returned ENOMEM; and __txn_reap_si_details freed a transaction detail without releasing its MVCC mutex. A third leak, reported by nobody, surfaced while validating those: si_ref was not decremented when a transaction upgraded its own SIREAD marker to WRITE.

#139 — a hash lookup missed a key that was present. On a legacy unsorted hash page with a custom comparator, DB->get returned DB_NOTFOUND and put(DB_NOOVERWRITE) could store a duplicate. The comparator's result was dropped — and fixing only that turned the false negative into a false positive, because the stored-key DBT was built with the search key's length, so a prefix compared equal against a truncated view of the stored item. Both halves are fixed.

#140 — heap buffer overflow. DB_LOCK_SIREAD was not counted when sizing the replication commit lock list, yet still consumed a slot. Reachable in release builds. Sizing and population now share one IS_WRITELOCK predicate so they agree by construction, and the diagnostic-only DB_ASSERT became __env_panic.

The root-cause class is closed as well: all 19 lock-mode enumeration sites were audited (4 were wrong), and CI now fails if a new DB_LOCK_* mode appears without updating the inventory.

Found while raising coverage (#148, #149)

  • #148set_lk_priority()/get_lk_priority() SIGSEGV on a locker id with no live locker. __lock_getlocker(create=0) reports not found by returning 0 with a NULL locker. The check is deliberately local to those two functions, because __lock_vec_pp passes the same possibly-NULL locker into __lock_vec, which handles it on purpose.
  • #149rep_get_nsites() SIGSEGV on a repmgr-configured but unopened environment (ENV_NOT_CONFIGURED expands to nothing before open); and __cdsgroup_begin installed only 8 of DB_TXN's 12 methods, so calling get_priority, set_priority, set_commit_token or set_txn_lsnp on a CDS group transaction jumped through a NULL pointer.

Hardening

  • Recovery no longer receives a half-opened DB handle. __dbreg_do_open registers a handle before its open finishes, so a failure after that point left an entry whose __db_open never completed; __dbreg_id_to_db returned it with ret == 0 and REC_INTRO dereferenced its unattached mpool file. The ASan fault-injection sweep now passes at all 947 failure points: 0 crashes, 0 hangs, 0 dirty teardowns.
  • Queue verification no longer scans billions of pages. A wrapped queue meta page made __qam_vrfy_walkqueue scan to the page holding recno UINT32_MAX, one page at a time. Clamped to the real last page, and verified not to reject legitimate queues — including one with 639 real extent files. A separate DoS in the cursor read path was filed as #159 with a reproducer, and is fixed in 5.3.36 (#168).
  • Encryption IVs are seeded from the OS CSPRNG (getrandom, arc4random_buf, /dev/urandom, RtlGenRandom).

Testing

Three new tiers, each proven to reproduce its target defect before the fix landed:

Tier What it checks
test/isolation an anomaly checker validating concurrent schedules against some serial order
test/soak resource accounting over tens of thousands of transactions
test/lockmatrix an exhaustive lock-mode matrix under ASan

Four test/db regression runners that no CI job had ever invoked are now wired up and made portable to macOS (three genuine portability bugs fell out: .dylib vs .so, no GNU timeout, and a baked-in .dylib install name that overrides -rpath). The crash-seed gate grew a per-seed timeout, so a runaway scan now fails CI instead of hanging it.

Also

  • db 2.3.16 and db 2.4.14 added to the historical import timeline (#135).
  • The serializability claims retracted ahead of these fixes are restored, now that each is covered by a test.

Qualification

Qualified from a pristine clone: autoconf (--enable-cxx) and meson both clean; test/db 6/6; isolation, lockmatrix and soak (5 workloads, 0 unexpected) all pass; crash-seed gate 9/9; fi_sweep 947/947 clean; DST test_sim_rng + test_sim_crash_recover; TCL ssi001/002/009, txn001–003, lock001–003, recd001/002 and test001 across btree/hash/queue/recno all fail=0; docs build with man-coverage and spelling gates passing.