From b0840ab25686f82922be2928f78cb13e697129fe Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sun, 15 Feb 2026 11:00:31 +0000 Subject: [PATCH] emit headers on Not Modified per RFC-9110, fix #1330 (#1340) also fix a small information disclosure where a client with valid token, but no encryption keys, can use Not Modified has an oracle to know if etag matches or not Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1340 Co-authored-by: trinity-1686a Co-committed-by: trinity-1686a --- src/api/s3/get.rs | 52 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/api/s3/get.rs b/src/api/s3/get.rs index 5854bc80..ec641f25 100644 --- a/src/api/s3/get.rs +++ b/src/api/s3/get.rs @@ -120,16 +120,42 @@ fn getobject_override_headers( fn handle_http_precondition( version: &ObjectVersion, version_meta: &ObjectVersionMeta, + meta_inner: &ObjectVersionMetaInner, + encryption: EncryptionParams, req: &Request<()>, ) -> Result>, Error> { let precondition_headers = PreconditionHeaders::parse(req)?; if let Some(status_code) = precondition_headers.check(version, &version_meta.etag)? { + let mut response = object_headers( + version, + version_meta, + meta_inner, + encryption, + ChecksumMode { enabled: false }, + ); + if let Some(header_map) = response.headers_mut() { + use http::header; + let headers_to_keep: Vec<_> = header_map + .drain() + .filter(|(k, _v)| { + k.as_ref().is_some_and(|k| { + [ + header::CONTENT_LOCATION, + header::DATE, + header::ETAG, + header::VARY, + header::CACHE_CONTROL, + header::EXPIRES, + ] + .contains(k) + }) + }) + .collect(); + header_map.extend(headers_to_keep); + } Ok(Some( - Response::builder() - .status(status_code) - .body(empty_body()) - .unwrap(), + response.status(status_code).body(empty_body()).unwrap(), )) } else { Ok(None) @@ -178,10 +204,6 @@ pub async fn handle_head_without_ctx( _ => unreachable!(), }; - if let Some(res) = handle_http_precondition(object_version, version_meta, req)? { - return Ok(res); - } - let (encryption, headers) = EncryptionParams::check_decrypt( &garage, req.headers(), @@ -189,6 +211,12 @@ pub async fn handle_head_without_ctx( OekDerivationInfo::for_object(&object, object_version), )?; + if let Some(res) = + handle_http_precondition(object_version, version_meta, &headers, encryption, req)? + { + return Ok(res); + } + let checksum_mode = checksum_mode(req); if let Some(part_number) = part_number { @@ -305,10 +333,6 @@ pub async fn handle_get_without_ctx( ObjectVersionData::FirstBlock(meta, _) => meta, }; - if let Some(res) = handle_http_precondition(last_v, last_v_meta, req)? { - return Ok(res); - } - let (enc, headers) = EncryptionParams::check_decrypt( &garage, req.headers(), @@ -316,6 +340,10 @@ pub async fn handle_get_without_ctx( OekDerivationInfo::for_object(&object, last_v), )?; + if let Some(res) = handle_http_precondition(last_v, last_v_meta, &headers, enc, req)? { + return Ok(res); + } + let checksum_mode = checksum_mode(req); let handle_get_info = HandleGetInfo {