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]