Skip to content

mod_dav: avoid signed overflow parsing the Timeout header - #853

Open
arshsmith1 wants to merge 2 commits into
apache:trunkfrom
arshsmith1:dav-lock-timeout-overflow
Open

arshsmith1 wants to merge 2 commits into
apache:trunkfrom
arshsmith1:dav-lock-timeout-overflow

Conversation

@arshsmith1

Copy link
Copy Markdown

dav_get_timeout_string() turns the LOCK Timeout: Second-N header into an expiry with now + atol(val), so a large N such as Second-9223372036854775807 overflows time_t (UBSan: signed integer overflow at util.c:586) and wraps into the past: the LOCK returns 200 with a lock token, but the lock has already expired and a second client can lock the resource straight away. Negative or malformed values (Second--100, Second-abc) give the same already-expired lock. The seconds are now parsed with apr_strtoi64(), a value that is not understood is skipped like any other unknown TimeType, and anything above the RFC 4918 maximum of 2^32-1 is capped; the new test_002_lock_timeout.py cases fail without the change.


  • This PR does not report or address a security vulnerability
    (see SECURITY.md and
    https://www.apache.org/security/#reporting-a-vulnerability).
  • 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 (see docs/log-message-tags/README).
  • If the change is user-visible, a changes-entries/*.txt file is included,
    following the template in README.CHANGES (not needed otherwise).
  • Test cases based on pyhttpd are included where appropriate, in the
    test/modules/xxx directory matching the modules/xxx module source,
    or test/modules/core for core server changes. On Unix, verify these
    pass locally with e.g. make check-pytest PYTEST_DIRS=test/modules/xxx.

dav_get_timeout_string() added an unbounded atol() of the Second- value
to the current time, so a huge value overflowed time_t (undefined
behaviour) and in practice wrapped to a lock which had already expired;
a negative or malformed value likewise expired the lock immediately.
Parse the seconds strictly, skip a value which is not understood, and
cap it at the 2^32-1 maximum allowed by RFC 4918.
@arturobernalg

Copy link
Copy Markdown
Member

I think this still needs to account for the range of time_t, rather than only the RFC's 32-bit timeout limit.

dav_get_timeout_string() returns time_t, and the patch still does:

expires = (time_t)secs;
now = time(NULL);
return now + expires;

On a build with a signed 32-bit time_t, a value such as Second-2147483647 is representable by time_t, but now + expires overflows. Second-4294967295, which is the RFC maximum, cannot be represented by a signed 32-bit time_t in the first place.

So I think the protocol limit (2^32-1) and the maximum representable absolute expiry need to be handled separately. It would also be good to have boundary tests for 4294967295 and 4294967296.

Capping the Timeout at the RFC 4918 maximum of 2^32-1 is not enough
when time_t is a signed 32-bit type: now + timeout can still overflow.
Clamp the seconds so that the absolute expiry fits in time_t, and add
boundary tests for 2^31-1, 2^32-1 and 2^32.
@arshsmith1

Copy link
Copy Markdown
Author

Agreed, the RFC cap alone doesn't cover a signed 32-bit time_t. The two limits are now applied separately: the seconds are first capped at 2^32-1, then clamped to TIME_T_MAX - now (computed in apr_int64_t from sizeof(time_t)), so now + expires can't overflow on either width. On a 32-bit time_t a large timeout just ends up expiring at the end of the representable range.

Added boundary cases for Second-4294967295, Second-4294967296 and Second-2147483647. The expected value allows for the extra clamp on a 32-bit time_t, so the test holds on both. test/modules/dav passes locally (23 tests), built in maintainer mode.

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.

2 participants