>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=-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 = 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=-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 == 0) && (end_str_len == 0))
                return (0);
 
-       if (end_str_len) {
+       if (start_str_len == 0) {
                r->end = strtonum(end_str, 0, LLONG_MAX, &errstr);
-               if (errstr)
+               if (errstr || r->end == 0)
                        return (0);
 
-               if ((size_t)r->end >= size)
-                       r->end = size - 1;
-       } else
+               if ((size_t)r->end > size)
+                       r->start = 0;
+               else
+                       r->start = size - r->end;
                r->end = size - 1;
+               return (1);
+       }
 
        if (start_str_len) {
                r->start = strtonum(start_str, 0, LLONG_MAX, &errstr);
@@ -814,11 +817,17 @@ parse_range_spec(char *str, size_t size, struct range *r)
 
                if ((size_t)r->start >= size)
                        return (0);
-       } else {
-               r->start = size - r->end;
-               r->end = size - 1;
        }
 
+       if (end_str_len) {
+               r->end = strtonum(end_str, 0, LLONG_MAX, &errstr);
+               if (errstr)
+                       return (0);
+               if ((size_t)r->end >= size)
+                       r->end = size - 1;
+       } else
+               r->end = size - 1;
+
        if (r->end < r->start)
                return (0);

Reply via email to