https://bz.apache.org/bugzilla/show_bug.cgi?id=70051
Bug ID: 70051
Summary: httpd 2.4.67 trunk - NULL pointer dereference in DAV
leads to DOS
Product: Apache httpd-2
Version: 2.4.67
Hardware: PC
OS: Linux
Status: NEW
Severity: normal
Priority: P2
Component: mod_dav
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
Created attachment 40183
--> https://bz.apache.org/bugzilla/attachment.cgi?id=40183&action=edit
poc
Dear Apache HTTP Server Security Team,
I'd like to report a NULL pointer dereference in
modules/dav/main/ms_wdv.c::check_locked_by_other() on Apache HTTP Server trunk
(verified against ef0d4767b4, current as of 2026-05-14). A crafted DAV request
crashes the worker that handles it. The file is unreleased — git ls-tree 2.4.67
modules/dav/main/ confirms it does not ship in 2.4.x — but I'm flagging it now
because the function has been broken since the MS-WDV support was first
committed in 2023 (bed50350e4) and survived every subsequent patch to the file
(bd61fb9492 PROPPATCH OOM fix, 5bf7c9c34e cleanup, a62c08dd33 PROPPATCH heap
over-read fix).
Summary
check_locked_by_other() is invoked from dav_mswdv_preprocessing() for every
PUT, DELETE, MOVE, or LOCK request once DAVMSext WDV is configured. It declares
dav_lockdb *lockdb = NULL; and then has two goto out branches that fire before
open_lockdb() runs — one when dav_get_resource() returns err, one when
open_lockdb() itself fails. Both branches leave lockdb at its NULL initializer.
The cleanup label then dereferences it unconditionally:
static dav_error *check_locked_by_other(request_rec *r)
{
const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
dav_lockdb *lockdb = NULL;
...
if ((err = dav_get_resource(r, 0, 0, &resource)) != NULL)
goto out; /* lockdb still
NULL */
if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL)
goto out; /* lockdb still
NULL */
...
out:
(*lockdb->hooks->close_lockdb)(lockdb); /* NULL deref →
SIGSEGV */
return err;
}
The sister function mswdv_combined_lock() elsewhere in the same file shows the
correct pattern — if (lockdb) (*lockdb->hooks->close_lockdb)(lockdb);
(ms_wdv.c:509-510) — so the guard was simply omitted from
check_locked_by_other() when it was originally written. hooks is the first
field of dav_lockdb (mod_dav.h:1349-1356), so the NULL deref reads from virtual
address 0 and is a clean SIGSEGV on Linux/glibc.
Reachability
The most reliable trigger is making dav_get_resource() fail. The FS provider's
dav_fs_get_resource() (modules/dav/fs/repos.c:791) returns HTTP_NOT_FOUND "The
URL contains extraneous path components" whenever the request's filesystem leaf
(r->finfo) is a regular file and r->path_info is non-empty. So a request to a
URL whose last filesystem-resolvable component is a file plus any extra path
components walks straight into the error path → goto out → NULL deref. No body
or extra headers required; the only precondition besides MS-WDV being enabled
is that an attacker-namable regular file exists under the DAV mount (PUT to
create one is itself a covered method, so this is bootstrap-able from zero
state, as the PoC below shows).
check_locked_by_other() runs from dav_handler in the handler phase, so any auth
required by the parent <Directory> block must pass first. The PoC uses Require
all granted to keep the harness minimal; against a deployment that auth-gates
the DAV location, the bug is still reachable, but the attacker must hold
whichever low-priv credential the deployment grants PUT/DELETE/MOVE/LOCK to.
Impact
DoS. Each crafted request kills one worker. Apache's parent respawns it, so
individual requests are recoverable, but the crash is deterministic and can be
repeated to sustain the denial.
On mpm_prefork, every request kills one process slot until the parent respawns
it; sustained request volume keeps workers churning between SEGV and respawn.
On mpm_event / mpm_worker, the SEGV kills the multi-threaded worker, dropping
every in-flight connection on it along with the targeted one.
The NULL is dereferenced at offset 0 of a constant struct field
(dav_lockdb.hooks), so there is no useful write or read primitive — the impact
is bounded to availability. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L = 5.3
(drop PR:N → PR:L if the deployment requires auth).
Reproduction (against trunk ef0d4767b4, built with --enable-mods-shared='dav
dav_fs dav_lock', MPM=prefork)
vuln.conf for a <Directory> carrying MS-WDV:
DavLockDB /opt/httpd/var/DavLock
Alias /dav /var/dav
<Directory "/var/dav">
DAV on
DAVMSext WDV
Require all granted
</Directory>
Two requests:
$ curl -sS -X PUT --data 'x' http://target:4018/dav/poc.txt
$ curl -sS -X PUT --data '' http://target:4018/dav/poc.txt/extra
curl: (52) Empty reply from server
$ docker logs vuln-008-httpd 2>&1 | tail -1
[Thu May 14 18:42:37.651481 2026] [core:notice] [pid 1:tid 1] AH00051: child
pid 8 exit signal Segmentation fault (11), possible coredump in /opt/httpd
The first PUT seeds a regular file so the FS provider can take the "extraneous
path components" branch on the second request; the second PUT triggers the
crash. Apache prefork respawns the worker afterwards, so a follow-up GET
/dav/poc.txt returns 200 — the bug is per-request, not a one-shot listener
kill.
A self-contained Docker reproducer and a Python PoC (with allowlist guard) is
attached to this email.
Suggested patch
One line, mirroring the existing guard at line 509 of the same file:
```
--- a/modules/dav/main/ms_wdv.c
+++ b/modules/dav/main/ms_wdv.c
@@ -154,7 +154,8 @@ static dav_error *check_locked_by_other(request_rec *r)
/* Let lock method fail the request */
out:
- (*lockdb->hooks->close_lockdb)(lockdb);
+ if (lockdb)
+ (*lockdb->hooks->close_lockdb)(lockdb);
return err;
}
```
Both goto out paths above the open_lockdb() call become safe after this change,
and the function continues to behave identically on the success path where
lockdb is non-NULL.
What is your take on this? Since ms_wdv.c has never shipped in a 2.4.x release,
my read is that this is a trunk-only defect that should be fixed before any
release pulls the file in.
Kind regards,
Lucian Nitescu
--
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]