Skip to content

mod_dav: Fix dav_get_timeout_string() overflowing an attacker-control… - #920

Open
arturobernalg wants to merge 1 commit into
apache:trunkfrom
arturobernalg:fix/httpd-65788-dav-timeout-overflow
Open

arturobernalg wants to merge 1 commit into
apache:trunkfrom
arturobernalg:fix/httpd-65788-dav-timeout-overflow

Conversation

@arturobernalg

@arturobernalg arturobernalg commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

dav_get_timeout_string() (modules/dav/main/util.c) parses a "Timeout: Second-" header with atol(), which has no overflow detection. A long enough digit string overflows it, and the later "now + expires" addition overflows time_t too, so the lock ends up stored with a nonsensical (already-expired) timeout instead of the large-but-finite one the client asked for -- a LOCK request that looks successful (200, a valid Lock-Token) silently grants a lock that is already expired.

Confirmed with a real build: a LOCK request with "Timeout: Second-99999999999999999999999999999999" got back "<D:timeout>Second-0</D:timeout>" before the fix (atol() saturates to LONG_MAX on this platform, and now + LONG_MAX overflows time_t to a large negative value), and "<D:timeout>Second-4294967295</D:timeout>" after it -- the RFC 4918 TimeOut maximum, matching the overflow handling the existing code comment already called for ("We need to handle overflow better: timeout will be <= 2^32 - 1") but never implemented.

Fix: parse with apr_strtoi64() (overflow-checked via errno), and clamp to 2^32 - 1 on overflow or on an in-range value above that maximum. A negative value (e.g. "Second--5") clamps to 0. A value with no digits at all (e.g. "Second-notanumber") also clamps to 0, matching what atol() already silently did for that input on trunk -- not Infinite, which would grant more than before instead of less (verified: trunk's actual current behavior for "Second-notanumber" is "Second-0", not "Infinite" as an earlier version of this PR incorrectly claimed).

The RFC 4918 maximum does not always fit a time_t: on platforms where time_t is 32 bits (still the glibc default for 32-bit Linux without _TIME_BITS=64, and standard on other 32-bit/legacy targets), casting 2^32 - 1 to time_t wraps to -1, and "now - 1" reintroduces the exact bug this change fixes, just through the clamp instead of atol(). Added DAV_TIME_T_MAX, computed from sizeof(time_t) rather than assumed, and cap "now + sec" against it regardless of the platform's time_t width. Verified under both a 64-bit and a 32-bit (-m32) build: the 64-bit behavior is unchanged, and the 32-bit cast no longer wraps negative.

Originally reported as BZ 65788 by Eric Covener, citing a finding from GHSL team member @antonio-morales.

PR 65788


  • This PR does not report or address a security vulnerability (see SECURITY.md). The original report is a public Bugzilla issue open since 2022 with no CVE assigned; this fixes a correctness bug (a lock silently granted with the wrong, already-expired timeout) rather than any privilege or data exposure.
  • Code follows the httpd style guide.
  • New log messages (level debug or higher) use an empty APLOGNO() tag; numbers are assigned by a committer at merge time. (No new log messages.)
  • If the change is user-visible, a changes-entries/*.txt file is included, following the template in README.CHANGES.
  • Test cases based on pyhttpd are included in test/modules/dav (test_004_lock_timeout_overflow.py, modeled on the existing DavLockDB/Dav On pattern in test_001_propdb_ns_limit.py in the same directory). Could not run it in this sandbox: its fakeprefix's apxs resolves to the distro-packaged httpd/apxs rather than this trunk build, so compiling the harness's own mod_aptest helper produces a module-magic-number mismatch against the trunk httpd under test ("expected signature 41503234 but saw 41503235") -- a sandbox-specific apxs/module-ABI mismatch unrelated to this fix. Verified instead against a real build and curl, as described above, including the exact strings (Second-4294967295, Second-600, Second-0) the test asserts on.

…led Timeout value.

dav_get_timeout_string() parses a "Timeout: Second-<N>" header with
atol(), which has no overflow detection. A long enough digit string
overflows it, and the later "now + expires" addition overflows time_t
too, so the lock ends up stored with a nonsensical (already-expired)
timeout instead of the large-but-finite one the client asked for -- a
LOCK request that looks successful (200, a valid Lock-Token) silently
grants a lock that is already expired.

Confirmed with a real build: a LOCK request with
"Timeout: Second-99999999999999999999999999999999" got back
"<D:timeout>Second-0</D:timeout>" before the fix (atol() saturates to
LONG_MAX, and now + LONG_MAX overflows time_t to a large negative
value), and "<D:timeout>Second-4294967295</D:timeout>" after it -- the
RFC 4918 TimeOut maximum, matching the overflow handling the existing
code comment already called for but never implemented.

Fix: parse with apr_strtoi64() (overflow-checked via errno), and clamp
to 2^32 - 1 on overflow or on an in-range value above that maximum. A
negative value (e.g. "Second--5") clamps to 0. A value with no digits
at all (e.g. "Second-notanumber") clamps to 0 too, matching what
atol() already silently did for that input -- not Infinite, which
would grant more than before instead of less.

The RFC 4918 maximum does not always fit a time_t: on platforms where
time_t is 32 bits (still the glibc default for 32-bit Linux without
_TIME_BITS=64, and standard on other 32-bit/legacy targets), casting
2^32 - 1 to time_t wraps to -1, and "now - 1" reintroduces the exact
bug this change fixes, just through the clamp instead of atol(). Added
DAV_TIME_T_MAX, computed from sizeof(time_t) rather than assumed, and
cap "now + sec" against it regardless of the platform's time_t width.
Verified under both a 64-bit and a 32-bit (-m32) build: the 64-bit
behavior is unchanged, and the 32-bit cast no longer wraps negative.

PR 65788
@arturobernalg
arturobernalg force-pushed the fix/httpd-65788-dav-timeout-overflow branch from c3ed751 to 2b29637 Compare November 1, 2026 18:36
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