fix: reject unknown response-* query parameters in GetObject and HeadObject

getobject and headobject silently ignored any response-* query parameter outside the six overridable headers. they now return 400 InvalidArgument naming the parameter and its value, like s3, and the rest tests skipped for this issue are enabled.

Fixes #2075
This commit is contained in:
dualfroz committed 2026-10-07 19:40:28 +02:00
1 parent 5a14104c85
commit 86ff18f8c1
13 files changed
+208 -6

No files matched your search

+4
View File
@@ -1305,6 +1305,10 @@ func GetObject_overrides_fail_public(s *S3Conf) error {
}, withAnonymousClient())
}
func GetObject_overrides_fail_invalid_header(s *S3Conf) error {
return testInvalidResponseOverride(s, "GetObject_overrides_fail_invalid_header", http.MethodGet)
}
func GetObject_invalid_part_number(s *S3Conf) error {
testName := "GetObject_invalid_part_number"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+4
View File
@@ -1511,6 +1511,10 @@ func HeadObject_overrides_fail_public(s *S3Conf) error {
}, withAnonymousClient())
}
func HeadObject_overrides_fail_invalid_header(s *S3Conf) error {
return testInvalidResponseOverride(s, "HeadObject_overrides_fail_invalid_header", http.MethodHead)
}
func HeadObject_empty_version_id(s *S3Conf) error {
return testEmptyVersionId(s, "HeadObject_empty_version_id", http.MethodHead, "", nil)
}
+4
View File
@@ -246,6 +246,7 @@ func TestHeadObject(ts *TestState) {
ts.Run(HeadObject_overrides_success)
ts.Run(HeadObject_overrides_presign_success)
ts.Run(HeadObject_overrides_fail_public)
ts.Run(HeadObject_overrides_fail_invalid_header)
ts.Run(HeadObject_range_and_part_number)
ts.Run(HeadObject_mp_part_number_exceeds_parts_count)
ts.Run(HeadObject_mp_part_number_success)
@@ -294,6 +295,7 @@ func TestGetObject(ts *TestState) {
ts.Run(GetObject_overrides_success)
ts.Run(GetObject_overrides_presign_success)
ts.Run(GetObject_overrides_fail_public)
ts.Run(GetObject_overrides_fail_invalid_header)
ts.Run(GetObject_invalid_part_number)
ts.Run(GetObject_range_and_part_number)
ts.Run(GetObject_mp_part_number_exceeds_parts_count)
@@ -3064,6 +3066,7 @@ func GetIntTests() IntTests {
"HeadObject_overrides_success": HeadObject_overrides_success,
"HeadObject_overrides_presign_success": HeadObject_overrides_presign_success,
"HeadObject_overrides_fail_public": HeadObject_overrides_fail_public,
"HeadObject_overrides_fail_invalid_header": HeadObject_overrides_fail_invalid_header,
"HeadObject_range_and_part_number": HeadObject_range_and_part_number,
"HeadObject_mp_part_number_exceeds_parts_count": HeadObject_mp_part_number_exceeds_parts_count,
"HeadObject_mp_part_number_success": HeadObject_mp_part_number_success,
@@ -3100,6 +3103,7 @@ func GetIntTests() IntTests {
"GetObject_overrides_success": GetObject_overrides_success,
"GetObject_overrides_presign_success": GetObject_overrides_presign_success,
"GetObject_overrides_fail_public": GetObject_overrides_fail_public,
"GetObject_overrides_fail_invalid_header": GetObject_overrides_fail_invalid_header,
"GetObject_invalid_part_number": GetObject_invalid_part_number,
"GetObject_range_and_part_number": GetObject_range_and_part_number,
"GetObject_mp_part_number_exceeds_parts_count": GetObject_mp_part_number_exceeds_parts_count,
+57
View File
@@ -612,6 +612,63 @@ func testEmptyVersionId(s *S3Conf, testName, method, subresource string, body []
})
}
// testInvalidResponseOverride verifies GetObject or HeadObject rejects a
// response-* query parameter that isn't an overridable response header. The
// SDK only sends the known overrides, so the request is signed and sent by
// hand.
func testInvalidResponseOverride(s *S3Conf, testName, method string) error {
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
_, err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
for _, test := range []struct {
query string
expected s3err.InvalidArgumentError
}{
{
query: "response-fdsjkljj=dummy",
expected: s3err.GetInvalidArgResponseOverride("response-fdsjkljj", "dummy"),
},
// a valid override doesn't let an unknown one through
{
query: "response-content-type=text%2Fplain&response-invalid=dummy",
expected: s3err.GetInvalidArgResponseOverride("response-invalid", "dummy"),
},
} {
req, err := createSignedReq(method, s.endpoint,
fmt.Sprintf("%v/%v?%v", bucket, obj, test.query), s.awsID, s.awsSecret,
"s3", s.awsRegion, "", nil, time.Now(), nil)
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
// HEAD responses carry no body: only the status code is verifiable
if method == http.MethodHead {
resp.Body.Close()
if resp.StatusCode != test.expected.StatusCode() {
return fmt.Errorf("%v: expected response status code to be %v, instead got %v",
test.query, test.expected.StatusCode(), resp.StatusCode)
}
continue
}
if err := checkHTTPResponseApiErr(resp, test.expected); err != nil {
return fmt.Errorf("%v: %w", test.query, err)
}
}
return nil
})
}
func checkIAMAuthRequest(s *S3Conf, req *http.Request, expected iamerr.APIError) error {
resp, err := s.httpClient.Do(req)
if err != nil {
-3
View File
@@ -119,9 +119,6 @@ source ./tests/setup.sh
# tags: curl,GetObject,invalid-header,response-headers
@test "REST - GetObject - response query - invalid response type" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2075"
fi
run setup_bucket_and_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name file_name <<< "$output"
-3
View File
@@ -103,9 +103,6 @@ source ./tests/drivers/string.sh
# tags: curl,HeadObject,invalid-query
@test "REST - HeadObject - invalid request header type" {
if [ "$DIRECT" != "true" ]; then
skip "https://github.com/versity/versitygw/issues/2075"
fi
run setup_bucket_and_add_file_v3 "$BUCKET_ONE_NAME"
assert_success
read -r bucket_name file_name <<< "$output"