Repository navigation
mod_dav_fs: serialize lock database access with a global mutex (backport r1914438) - #814
Closed
manfred-kaiser wants to merge 1 commit into
Closed
manfred-kaiser wants to merge 1 commit into
manfred-kaiser wants to merge 1 commit into
Conversation
mod_dav_fs reads the whole per-resource lock record, modifies it in memory, and writes the whole record back, with no mutual exclusion around that fetch-modify-store cycle. When APR-util is built against a DBM backend that does no internal locking of its own (Berkeley DB and ndbm; sdbm and gdbm serialize at the APR/library level and are unaffected), concurrent LOCK requests race on the shared record. Observed on Berkeley DB, the default on Debian, Ubuntu and the RHEL 9 family: - granted locks are dropped from the stored list (a client is told it holds a lock that the server has already forgotten), - two clients can both be granted an exclusive lock on one resource, - under sustained concurrent LOCK load the Berkeley DB file can reach DB_PAGE_NOTFOUND / PANIC (DB_RUNRECOVERY), after which every LOCK returns 500 until the server is restarted. trunk fixed this in r1914438 (git 455147a, GitHub PR apache#395), with the 2.4.x-compatibility follow-up r1914457. The fix was never backported to 2.4.x. This change backports it, adapted to 2.4.x (only http_core.h had to be included for ap_state_query; no default lockdb path is added, so the ap_state_dir_relative guard from r1914457 is not needed here). It intentionally leaves out the DAVLockDBType directive that r1914438 added alongside the fix, which is a separate feature not required to close the race. RHEL/AlmaLinux already ship an equivalent vendor backport. Verified against a Berkeley-DB build: lost locks and exclusive double-grants drop to zero, the DB_PAGE_NOTFOUND/500 symptom disappears, and single-resource LOCK throughput is unchanged.
Collaborator
|
Thanks for the PR, I have this tracked already at #399 and plan to propose it before the next release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
mod_dav_fsreads a per-resource lock record from the lock database,modifies it in memory, and writes the whole record back. There is no
mutual exclusion around that fetch-modify-store cycle: no
apr_global_mutex,no advisory file lock, no compare-and-swap. When APR-util is built against a
DBM backend that does no internal locking of its own, concurrent
LOCKrequests on the same resource race on the shared record.
trunk fixed this in r1914438 (git
455147a3, GitHub PR #395: anapr_global_mutexaround the lockdb open..modify..close cycle, releasedthrough a request-pool cleanup), with the 2.4.x-compatibility follow-up
r1914457. The fix was never backported to 2.4.x and is not listed in the
branch
STATUS, so every current 2.4.x release is still affected. This PRbackports that locking.
RHEL, CentOS Stream, AlmaLinux and Rocky already ship an equivalent vendor
backport of this fix; it has simply never landed in upstream 2.4.x. This PR
brings the same change upstream.
Which backends are affected
Whether the race is reachable depends on the DBM backend APR-util was built
with. Some serialize access as a side effect; some do not.
fcntllock around every open/close (APR-util's own wrapper)flockingdbm_open()Impact (Berkeley DB)
Lock-Tokenthe server issued, but the lock is absent from a laterPROPFIND lockdiscovery.which defeats the guarantee exclusive locking exists to provide.
LOCKload the lock file corrupts(
DB_PAGE_NOTFOUND, andPANIC: DB_RUNRECOVERY), after which everyLOCKreturns
500for all users until the server is restarted.This first showed up as an intermittent failure under ordinary concurrent
test traffic, not a crafted attack. A client with write access can also
trigger it deliberately to break another client's exclusive lock.
Security relevance
This is security-relevant, not only a robustness bug. An authenticated
client with write access can:
exclusive lock on a resource another client has exclusively locked, and
write through it;
DB_RUNRECOVERY) underconcurrent
LOCKload, taking the whole lock service down with500forall users until a restart (availability).
The fix has been public in trunk since 2023 (r1914438), so this is not an
undisclosed issue; all current 2.4.x releases are nonetheless still
affected. Because both the issue and its fix are already public, this is
submitted directly as a public backport PR rather than through the private
security process. The security team may still wish to assign a CVE for the
2.4.x exposure; this PR is the backport regardless of that decision.
The change
mod_dav_fs.c: register and create the global mutex (pre_config,post_config,child_init) and store it on the per-server config.lock.c: take the mutex indav_fs_really_open_lockdb(); release it andclose the DB through an
apr_pool_cleanuprun fromdav_fs_close_lockdb().repos.h: carry the mutex ondav_fs_server_conf.Two
APLOGNO()tags (10488, 10489) are carried over from r1914438 withtheir existing numbers, as is normal for a backport. The change
intentionally omits the
DAVLockDBTypedirective that r1914438 addedalongside the fix: that is a separate feature (it threads a dbm type through
dav_dbm_open_direct()and its callers) and is not needed to close therace. The full r1914438 is of course also an option if preferred. Only
http_core.hhad to be included forap_state_query; this backport adds nodefault lockdb path, so the
ap_state_dir_relativeguard from the follow-upr1914457 is not needed here.
Verification
Built against a Berkeley-DB APR-util, compared with stock 2.4.69 on the same
backend. prefork,
StartServers 5(forces mid-run worker spawning, therealistic default) unless noted. Harness: concurrent real-HTTP
LOCKclients, then a
PROPFINDchecking that every issued token is present.Lost locks / exclusive race (integrity)
Shared locks, one fresh resource per round, 40 rounds:
Exclusive-lock mutual exclusion (PoC: several concurrent exclusive
LOCKs ona never-locked resource): stock grants 2+ winners on the same resource; with
the patch, exactly one.
Corruption / outage (availability)
One reused resource, concurrency 24, 60 rounds:
DB_PAGE_NOTFOUNDHigh concurrent LOCK load, distinct resources (8 s/run)
Stock produced ~3900 failed requests and 41
DB_PAGE_NOTFOUNDlog entriesover these runs. Throughput is the same with and without the patch: the
per-op Berkeley DB open/close dominates, not the mutex wait. The mutex does
not cost throughput, it only stops the concurrent corruption. Patched tail
latency (p99) was 85 / 170 / 661 ms at concurrency 16 / 32 / 64, every
request succeeding.
Collection-lock serialization (the one behavioral cost)
The mutex is held across a
Depth: infinitycollectionLOCKfor the wholetree walk, so one large collection operation delays other lock operations
while it runs. Six concurrent 8000-member exclusive collection
LOCKs, withten unrelated single
LOCKs issued during them:This is not a regression introduced by the patch. The sdbm backend, which is
not affected by the race, already serializes this way (APR holds a
whole-file
fcntllock for the open handle's lifetime), and with longer,less even waits than the patched Berkeley DB path. The patch brings Berkeley
DB to the same, in fact tighter, behavior. Ordinary
GET/PUTandsingle-resource locking are unaffected.
The security-vulnerability checkbox below is left unticked on purpose: the
change is security-relevant (see "Security relevance" above). It is already
public, so this is submitted as a normal public backport rather than through
the private security process.
This is a backport, so two further checklist items do not apply verbatim: the two
APLOGNO()tags (10488, 10489) are carried over from r1914438 with theirexisting numbers rather than being empty, and this 2.4.x branch records
user-visible changes in the
CHANGESfile (there is nochanges-entries/directory here), where an entry is included.
vulnerability (see
SECURITY.mdand https://www.apache.org/security/#reporting-a-vulnerability).
APLOGNO()tag;numbers are assigned by a committer at merge time
(see
docs/log-message-tags/README).changes-entries/*.txtfile is included,following the template in
README.CHANGES(not needed otherwise).