httpd: HTTP suffix-range requests omits file contents

Andrew Kloet <[email protected]> Thu, 18 Jun 2026 15:44:31 -0400
Newsgroups gmane.os.openbsd.bugs
Message-ID <[email protected]>
>Synopsis:      HTTP suffix-range requests omits file contents
>Category:      user
>Environment:
	System      : OpenBSD 7.9
	Details     : OpenBSD 7.9 (GENERIC.MP) #449: Wed May  6 13:17:25 MDT 2026
			[email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

	Architecture: OpenBSD.amd64
	Machine     : amd64
>Description:
	There is a logic flaw inside httpd's parse_range_sepc when
	processing HTTP Byte-Range Requests.

	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.

	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. As a result,
	httpd serves a 206 Partial Content response that completely omits
	the very first byte (offset 0) of the target file.
>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.
	# Note the response headers show an incorrect range and omit the
	# leading character
	$ curl -v -H "Range: bytes=3D-15" http://127.0.0.1/test
	< HTTP/1.1 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.

--- a/usr.sbin/httpd/server_file.c
+++ b/usr.sbin/httpd/server_file.c
@@ -797,15 +797,18 @@ parse_range_spec(char *str, size_t size, struct range=
 *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 range=
 *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);