From 06fde6e2f6161b9f79cc04e8c23bebc5c83e32a7 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 6 Oct 2026 08:51:31 -0700 Subject: [PATCH] s3api: reject malformed copy-source and multipart PUT parameters A malformed X-Amz-Copy-Source or a non-numeric partNumber fell through to the plain PutObject route and stored the body as a regular object. Answer them with InvalidArgument-class errors instead of writing data. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- weed/s3api/s3api_object_handlers_multipart.go | 6 ++++++ weed/s3api/s3api_object_handlers_put.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/weed/s3api/s3api_object_handlers_multipart.go b/weed/s3api/s3api_object_handlers_multipart.go index 65b3cc90fd..d665392368 100644 --- a/weed/s3api/s3api_object_handlers_multipart.go +++ b/weed/s3api/s3api_object_handlers_multipart.go @@ -328,6 +328,12 @@ func (s3a *S3ApiServer) ListObjectPartsHandler(w http.ResponseWriter, r *http.Re // PutObjectPartHandler - Put an object part in a multipart upload. func (s3a *S3ApiServer) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) { bucket, object := s3_constants.GetBucketAndObject(r) + if src := r.Header.Get("X-Amz-Copy-Source"); src != "" { + // Well-formed copy sources are claimed by the CopyObjectPart route; what + // is left here has no "bucket/key" form and is not a plain PutObjectPart. + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidArgument) + return + } _, err := validateContentMd5(r.Header) if err != nil { s3err.WriteErrorResponse(w, r, s3err.ErrInvalidDigest) diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index deb9680747..e92c17107f 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -86,6 +86,20 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request) // http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html bucket, object := s3_constants.GetBucketAndObject(r) + + // A copy-source header that did not satisfy the CopyObject route is + // malformed (it needs a '/' separating source bucket from key), and a + // partNumber/uploadId pair that did not satisfy the UploadPart route is + // malformed too; neither should be silently written as a plain object. + if r.Header.Get("X-Amz-Copy-Source") != "" { + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidCopySource) + return + } + if r.URL.Query().Has("partNumber") || r.URL.Query().Has("uploadId") { + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidArgument) + return + } + _, err := validateContentMd5(r.Header) if err != nil { s3err.WriteErrorResponse(w, r, s3err.ErrInvalidDigest)