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]

Reply via email to