Bug 1216400 (CVE-2021-31807)

Summary: VUL-0: CVE-2021-31807: squid: Partial Content Parsing Use-After-Free
Product: [Novell Products] SUSE Security Incidents Reporter: Alexander Bergmann <abergmann>
Component: IncidentsAssignee: Adam Majer <amajer>
Status: NEW --- QA Contact: Security Team bot <security-team>
Severity: Normal    
Priority: P3 - Medium    
Version: unspecified   
Target Milestone: ---   
Hardware: Other   
OS: Other   
URL: https://smash.suse.de/issue/382409/
Whiteboard:
Found By: --- Services Priority:
Business Priority: Blocker: ---
Marketing QA Status: --- IT Deployment: ---
Bug Depends on:    
Bug Blocks: 1216398    

Description Alexander Bergmann 2023-10-19 06:53:23 UTC
Squid-Security-Audit
====================

Partial Content Parsing Use-After-Free

The HTTP request header ‘Range’ is used when a client wishes to partially download (also known as byte serving) a file from a compliant HTTP server (RFC 7233 states that Range header handling is optional in HTTP/1.1). Examples for usage include resuming downloads, segmented updates, and video playback (being able to skip to the middle of a video without downloading the whole video first).

Squid is able to handle both multi-part ranges (such as, bytes 0-50 AND 100-150), and single part ranges (bytes 0-50 ONLY), and their subsequent ‘206 Partial Content’ responses.
The Issue

When a client requests a multi-part range, checks are incorrectly delayed for whether they are valid and well-formed until after the response has been received. This means that once a response is received, invalid ranges are deleted:

    for (iterator pos (begin()); pos != end(); ++pos) {
        if ((*pos)->canonize(clen))
            copy.push_back (*pos);
        else
            delete (*pos);
    }

Here, pos is a pointer to a range, as specified by the client in the request.

However, despite being deleted, they are once again used in HttpHdrRangeIter::updateSpec:

void
HttpHdrRangeIter::updateSpec()
{ 
    assert (debt_size == 0);
    assert (valid);

    if (pos != end) {
        debt(currentSpec()->length);
    }
}

void HttpHdrRangeIter::debt(int64_t newDebt)
{   
    debugs(64, 3, "HttpHdrRangeIter::debt: was " << debt_size << " now " << newDebt);
    debt_size = newDebt;
}

Assuming no malice in the UaF, an assert will happen right afterwards in Http::Stream::canPackMoreRanges: assert(!http->range_iter.debt() == !http->range_iter.currentSpec());.

The following request can be made to Squid to cause this bug:

GET https://i.imgur.com/p9CXk3M.png HTTP/1.0
Range:bytes=0-0,-4,-0

This bug was assigned CVE-2021-31807.

References:
https://megamansec.github.io/Squid-Security-Audit/range-uaf.html