Meng-Shuan Tsai created HDDS-16336:
--------------------------------------

             Summary: S3 GET inverted Range with start inside object yields 
negative Content-Length
                 Key: HDDS-16336
                 URL: https://issues.apache.org/jira/browse/HDDS-16336
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Meng-Shuan Tsai
            Assignee: Meng-Shuan Tsai


h3. What

Bug in S3 Gateway GetObject. When {{Range}} is inverted ({{start > end}}) but 
{{start}} is still inside the object (e.g. object length 10, {{Range: 
bytes=8-3}}), the gateway prepares HTTP 206 with a *negative* 
{{Content-Length}} and then fails while streaming with 
{{NegativeArraySizeException}}.

A satisfiable range ({{bytes=0-8}}) already returns 206. An unsatisfiable range 
whose start is past the last byte ({{bytes=11-20}}) already returns 416 
{{InvalidRange}}. This ticket is only the inverted-but-in-bounds case.

h3. Steps to reproduce

# Put a 10-byte object.
# {{GET}} with header {{Range: bytes=8-3}}.

Observed: request does not complete as 200/206/416; streaming allocates {{new 
byte[getIOBufferSize(-4)]}} and throws {{NegativeArraySizeException}}.

h3. Root cause

{{RangeHeaderParserUtil.parseRangeHeader}} does not check {{start > end}} when 
{{start < length}}:

[RangeHeaderParserUtil.java 
(L69–L81)|https://github.com/apache/ozone/blob/79464b60d7661e89a5d934623a6998f7aff6cbca/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/RangeHeaderParserUtil.java#L69-L81]

{code:java}
} else {
  if (end >= length) {
    end = length - 1;
  }
}
{code}

Result for {{bytes=8-3}}, length 10: {{start=8}}, {{end=3}}, 
{{readFull=false}}, {{inValidRange=false}}.

{{ObjectEndpoint.handleGetRequest}} then treats it as a partial read:

* {{copyLength = end - start + 1 = -4}} ([ObjectEndpoint.java 
L449|https://github.com/apache/ozone/blob/79464b60d7661e89a5d934623a6998f7aff6cbca/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java#L449])
* status 206, {{Content-Length: -4}}
* {{new byte[getIOBufferSize(-4)]}} → {{NegativeArraySizeException}} (not NPE)

{{TestRangeHeaderParserUtil}} covers {{bytes=11-8}} (start past EOF → 
{{readFull}}, 200) and {{bytes=11-10}} (both ends past EOF → {{inValidRange}}, 
416). There is no case for {{start < length && start > end}}.

h3. Expected (S3-compatible GET)

RFC 9110 says the server MAY ignore or reject an invalid ranges-specifier. AWS 
GetObject documents {{Range}} via RFC 9110; the InvalidRange error is 416, 
without spelling out inverted ranges.

Observed AWS GetObject (2026-08-30, {{ap-northeast-1}}, 10-byte object, 
presigned GET):

|| Range || HTTP || Notes ||
| {{bytes=8-3}} | *200* | full body, {{Content-Length: 10}}, no 
{{Content-Range}} (Range ignored) |
| {{bytes=11-20}} | *416* | {{InvalidRange}}, {{ActualObjectSize=10}} |
| {{bytes=0-8}} | *206* | {{Content-Range: bytes 0-8/10}} (control: Range 
works) |

Ozone should not crash. Proposed fix: when {{start < length && start > end}}, 
set {{readFull = true}} (ignore Range, HTTP 200), matching AWS GET and RFC MAY 
ignore.

h3. Proposed change

* Parser only: in the {{start < length}} branch, if {{start > end}} then 
{{readFull = true}} (and {{start=0}}, {{end=length-1}}). Do not put the 
inverted check before {{start >= length}}, or {{bytes=11-10}} would change from 
416 to 200.
* Unit test in {{TestRangeHeaderParserUtil}} for {{bytes=8-3}}. Keep existing 
{{bytes=11-8}} (read full) as-is.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to