Re: httpd: HTTP suffix-range requests omits file contents
Andrew Kloet <[email protected]> Fri, 03 Jul 2026 22:27:00 +0000
| Newsgroups | gmane.os.openbsd.bugs |
|---|---|
| Message-ID | <[email protected]> |
On June 18, 2026 7:44:31 PM UTC, Andrew Kloet <andrew@kloet=2Enet> wrote:
>>Synopsis: HTTP suffix-range requests omits file contents
>>Category: user
>>Environment:
> System : OpenBSD 7=2E9
> Details : OpenBSD 7=2E9 (GENERIC=2EMP) #449: Wed May 6 13:17:25 MDT=
2026
> deraadt@amd64=2Eopenbsd=2Eorg:/usr/src/sys/arch/amd64/compile/GENERIC=
=2EMP
>
> Architecture: OpenBSD=2Eamd64
> Machine : amd64
>>Description:
> There is a logic flaw inside httpd's parse_range_sepc when
> processing HTTP Byte-Range Requests=2E
>
> When a client issues a suffix-range request (Range: bytes=3D-num to
> request the trailing num bytes of a resource) where the requested
> count is larger than the target file size, an overflow guard caps
> r->end to size - 1 prematurely=2E
>
> Because this truncation happens before the evaluation block
> calculates the starting byte offset (r->start =3D size - r->end), the
> resulting offset math evaluates to 1 instead of 0=2E As a result,
> httpd serves a 206 Partial Content response that completely omits
> the very first byte (offset 0) of the target file=2E
>>How-To-Repeat:
> # Create an 11-byte file
> $ echo "0123456789" > /var/www/htdocs/test
>
> # Send an HTTP request with a suffix range exceeding the file size=2E
> # Note the response headers show an incorrect range and omit the
> # leading character
> $ curl -v -H "Range: bytes=3D-15" http://127=2E0=2E0=2E1/test
> < HTTP/1=2E1 206 Partial Content
> < Content-Range: bytes 1-10/11
> < Content-Length: 10
> 123456789
>>Fix:
> Decouple the suffix range processing from standard ranges within
> parse_range_spec so that ceiling adjustments to r->end don't corrupt
> the initial offset computation=2E
>
>--- a/usr=2Esbin/httpd/server_file=2Ec
>+++ b/usr=2Esbin/httpd/server_file=2Ec
>@@ -797,15 +797,18 @@ parse_range_spec(char *str, size_t size, struct ran=
ge *r)
> if ((start_str_len =3D=3D 0) && (end_str_len =3D=3D 0))
> return (0);
>=20
>- if (end_str_len) {
>+ if (start_str_len =3D=3D 0) {
> r->end =3D strtonum(end_str, 0, LLONG_MAX, &errstr);
>- if (errstr)
>+ if (errstr || r->end =3D=3D 0)
> return (0);
>=20
>- if ((size_t)r->end >=3D size)
>- r->end =3D size - 1;
>- } else
>+ if ((size_t)r->end > size)
>+ r->start =3D 0;
>+ else
>+ r->start =3D size - r->end;
> r->end =3D size - 1;
>+ return (1);
>+ }
>=20
> if (start_str_len) {
> r->start =3D strtonum(start_str, 0, LLONG_MAX, &errstr);
>@@ -814,11 +817,17 @@ parse_range_spec(char *str, size_t size, struct ran=
ge *r)
>=20
> if ((size_t)r->start >=3D size)
> return (0);
>- } else {
>- r->start =3D size - r->end;
>- r->end =3D size - 1;
> }
>=20
>+ if (end_str_len) {
>+ r->end =3D strtonum(end_str, 0, LLONG_MAX, &errstr);
>+ if (errstr)
>+ return (0);
>+ if ((size_t)r->end >=3D size)
>+ r->end =3D size - 1;
>+ } else
>+ r->end =3D size - 1;
>+
> if (r->end < r->start)
> return (0);
bump