From 98031616cce2e98f4e5140f491085c977795c239 Mon Sep 17 00:00:00 2001 From: Leonard Liubich Date: Fri, 16 Oct 2026 13:09:08 +0300 Subject: [PATCH] sn/object: Fix unchecked known length of unfinished objects in PUT According to API, unknown length of unprepared object must be set to 0xFFFFFFFFFFFFFFFF. Otherwise, number of payload bytes sent must equal the field value. Previously, PUT server never checked the payload length of unfinished objects. However, clients could leave the value unspecified (zero) due to carelessness. Therefore, this adds a zero-exclusion check. Fixes #4208. Signed-off-by: Leonard Liubich --- CHANGELOG.md | 1 + pkg/services/object/server.go | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cbaeb812..6ccf7df96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Changelog for NeoFS Node - Setting eACL without API version in CLI (#4229) - Unnecessary local shard relocation of EC parts by policer (#4236) - GC stalling on expired objects protected by LOCKs stored only on other shards (#4237) +- Unchecked known payload length of unfinished objects in PUT (#4233) ### Changed - SN now requires requests to have API version >= 2.22 (#4212) diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index ba9cec44db..dd60bc66b0 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "math" "slices" "strings" "sync" @@ -309,7 +310,8 @@ type putStream struct { initReq *protoobject.PutRequest chunkReqs []*protoobject.PutRequest - expBytes, recvBytes uint64 // payload + knownPayloadLength bool + expBytes, recvBytes uint64 // payload (undefined if knownPayloadLength is false) } func newIntermediatePutStream(signer ecdsa.PrivateKey, base *putsvc.Streamer, ctx context.Context, log *zap.Logger) *putStream { @@ -378,11 +380,24 @@ func (x *putStream) forwardInitRequest(req *protoobject.PutRequest, initPart *pr return fmt.Errorf("could not init object put stream: %w", err) } + ln := initPart.Header.GetPayloadLength() + + // Max is a special value defined in the protocol. Zero is also treated as + // unknown because server never prohibited such requests, but clients often + // forgot to explicitly set the value. + x.knownPayloadLength = ln > 0 && ln != math.MaxUint64 + if x.knownPayloadLength { + x.expBytes = ln + } + if x.cacheReqs = initPart.Signature != nil; !x.cacheReqs { return nil } - x.expBytes = initPart.Header.GetPayloadLength() + if ln == math.MaxUint64 { + return putsvc.ErrExceedingMaxSize + } + if m := x.base.MaxObjectSize(); x.expBytes > m { return putsvc.ErrExceedingMaxSize } @@ -391,7 +406,7 @@ func (x *putStream) forwardInitRequest(req *protoobject.PutRequest, initPart *pr } func (x *putStream) forwardChunkRequest(req *protoobject.PutRequest, c []byte) error { - if x.cacheReqs { + if x.knownPayloadLength { if x.recvBytes += uint64(len(c)); x.recvBytes > x.expBytes { return putsvc.ErrWrongPayloadSize } @@ -407,7 +422,7 @@ func (x *putStream) forwardChunkRequest(req *protoobject.PutRequest, c []byte) e } func (x *putStream) close() (*protoobject.PutResponse, error) { - if x.cacheReqs && x.recvBytes != x.expBytes { + if x.knownPayloadLength && x.recvBytes != x.expBytes { return nil, putsvc.ErrWrongPayloadSize }