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