From 5b7cc097e3300324aa7718b5a4e7b6c69e02c871 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Fri, 7 Aug 2026 13:51:16 +0100 Subject: [PATCH] multipart: fix chunked uploads storing truncated objects when the source ends early If the source supplied fewer bytes than its declared size, the multipart upload was completed anyway, storing a truncated object and reporting a successful upload. Check the number of bytes read from the source against the declared size before finalising and abort the upload with an error if they do not match. This affects all backends which use lib/multipart for chunked uploads: azureblob, b2, drime, internxt, oracleobjectstorage, s3 and shade. This was found by the new FsPutShortEOF and TestRcatSizeShortEOF integration tests. --- lib/multipart/multipart.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/multipart/multipart.go b/lib/multipart/multipart.go index b8d0a6a84..f7e3734ec 100644 --- a/lib/multipart/multipart.go +++ b/lib/multipart/multipart.go @@ -121,6 +121,13 @@ func UploadMultipart(ctx context.Context, src fs.ObjectInfo, in io.Reader, opt U return nil, err } + // Check the source supplied the number of bytes it declared before + // finalising, otherwise a truncated object would be created on the + // backend. Returning an error here aborts the upload. + if size >= 0 && off != size { + return nil, fmt.Errorf("multipart upload: expected %d bytes in input, but got %d: %w", size, off, io.ErrUnexpectedEOF) + } + err = chunkWriter.Close(ctx) if err != nil { return nil, fmt.Errorf("multipart upload: failed to finalise: %w", err)