Bug 66034 - Read beyond bounds via mod_substitute.c [zhbug_httpd_22.1]
Summary: Read beyond bounds via mod_substitute.c [zhbug_httpd_22.1]
Status: NEW
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: mod_substitute (show other bugs)
Version: 2.5-HEAD
Hardware: All All
: P2 normal (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
Keywords: FixedInTrunk, PatchAvailable
Depends on:
Reported: 2022-04-26 20:40 UTC by generalbugs
Modified: 2022-04-27 06:44 UTC (History)
0 users


Note You need to log in before you can comment on or make changes to this bug.
Description generalbugs 2022-04-26 20:40:53 UTC
There is a read-beyond-bounds bug in do_pattmatch() (modules/filters/mod_substitute.c) in httpd 2.4.53+ [1] when built with PCRE2 [2]. The bug is caused by a sign extension of an |int| to an |apr_size_t| , which causes lengths >= 0x80000000 to become 0xffffffffxxxxxxxx with a consequent read beyond bounds.

Attached is a POC that demonstrates the bug.

The bug is in line 278 (v2.4.53), which truncates |apr_size_t bytes| to |int left|. The call to ap_regexec_len() on lines 282-83 then sign-extends |left| to an |apr_size_t|: 

   127: static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
   128:                                  apr_bucket_brigade *mybb,
   129:                                  apr_pool_t *pool)
   130: {
   158:         for (b = APR_BRIGADE_FIRST(mybb);
   159:              b != APR_BRIGADE_SENTINEL(mybb);
   160:              b = APR_BUCKET_NEXT(b)) {
   168:             if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
   169:                     == APR_SUCCESS) {
   277:                 else if (script->regexp) {
   278:                     int left = bytes;
   279:                     const char *pos = buff;
   280:                     char *repl;
   281:                     apr_size_t space_left = cfg->max_line_length;
   282:                     while (!ap_regexec_len(script->regexp, pos, left,
   283:                                        AP_MAX_REG_MATCH, regm, 0)) {

(ap_regexec_len() takes an |apr_size_t| for argument 3).

Use the POC thusly:

   1. Copy the httpd lines into httpd.conf.
   2. Create a file 0x80000000 bytes long filled with 'a'.
   3. Create an empty file /bug22.1/bug22.1.htm in an httpd installation's server root.
   4. Start httpd (built with PCRE2) and attach a debugger to it.
   5. Set a BP on do_pattmatch() line 278, above.
   6. Send the long file to httpd using

      curl -v -i -X POST -T <long file's path> -k <httpd's IP address>/bug22.1/bug22.1.htm

   7. When the BP fires (this will take ~10-60m, depending on CPU power), examine |bytes| and notice it's 0x80000000.
   8. Step into ap_regexec_len() and notice that its |len| argument is 0xffffffff80000000.
   9. Proceed. httpd will die with a read beyond bounds in pcre2_match_*() on a call to memchr() while searching the long string for the first character of the search string ('f').

In mitigation, (1) the bug can be reached only (a) if the administrator allows mod_substitute to process lines >= 0x80000000 bytes long and (b) that amount of data is available to be sent as a response body (such as via mod_reflector or as a file present on the server); and (2) httpd probably will die before sending any out-of-bounds data back to the attacker.

[1] The bug is still present in trunk. The security team asked me to file this bug publicly, on the idea that the circumstances in which it could cause an issue are too extreme to keep it secret.

[2] This appears to have become the default as of v2.4.53 : https://dlcdn.apache.org/httpd/CHANGES_2.4.53 .

-------- httpd lines ----------------------------------------------------
<Location /bug22.1>
    SetHandler reflector
    SetOutputFilter substitute
    AddOutputFilter substitute *.htm
    SubstituteMaxLineLength 4g
    Substitute "s/foo/bar/i"
-------- httpd lines ----------------------------------------------------
Comment 1 Ruediger Pluem 2022-04-27 06:44:14 UTC
Fixed in trunk as r1900307.