From ddc26427c2f2cdc462a025195c2ab01e98f4137d Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Fri, 11 Sep 2026 12:47:23 +0000 Subject: [PATCH] moby-engine: patch for CVE-2026-78662, CVE-2026-56855, CVE-2026-84304, CVE-2026-37236 [HIGH] Backport fixes for four CVEs in vendored Go dependencies. Each was verified against the actual v25.0.3 vendored tree rather than from advisory metadata alone. CVE-2026-78662 golang.org/x/crypto/ssh v0.17.0 Patch30 CVE-2026-56855 golang.org/x/crypto/ssh v0.17.0 Patch31 CVE-2026-84304 google.golang.org/grpc v1.58.3 Patch32 CVE-2026-37236 grpc-gateway/v2 v2.16.0 Patch33 Notes for future rebases: Patch30 must stay ordered before Patch31; it adds the sync/atomic import that Patch31 relies on. Applying Patch31 alone fails to build with "undefined: atomic". OSV lists 86efde54 as the fix for CVE-2026-78662, but that commit actually fixes CVE-2026-56855. The correct fix for CVE-2026-78662 is a6cdac6084 (Gerrit CL 826504). Patch31 squashes two further upstream commits beyond the CVE fix itself: - 3c7c86938f45, the channel half of CVE-2026-39830. Only the mux half was carried previously. Without it, returning an error for unexpected message types breaks every SendRequest(wantReply=true), so exec, shell, pty-req and subsystem would fail the connection with "ssh: unexpected message type 99". - e3e62d9601ec (golang/go#79658), which fixes a busy-loop in the drain path introduced by the above. Once the channel is torn down and ch.msg is closed, "case <-ch.msg" becomes permanently ready and spins at 100% CPU. The same defect already ships in the mux.go drain loop from CVE-2026-39830.patch, so the comma-ok guard is applied to both call sites. Upstream's published diff for e3e62d9 only touches channel.go; the mux.go guard is taken from x/crypto master. CVE-2026-84304 is hand-adapted rather than cherry-picked. Upstream targets grpc >= 1.83 and its mem package, which does not exist here, so receive-buffer compaction is reimplemented against 1.58.3's bytes.Buffer and sync.Pool design. grpc is deliberately not bumped: 1.58.3 to 1.83.1 spans 25 minor releases and would cascade into buildkit, containerd and swarmkit. CVE-2026-37236 is ported faithfully as opt-in, matching upstream, via WithDisableHTTPMethodOverride. grpc-gateway is vendored but unreachable from dockerd, so deviating from upstream behaviour in vendored code adds risk with no benefit. Verified: all four apply in spec order under patch -p1 --fuzz=0, gofmt clean, only the intended vendored files change, and cmd/dockerd and cmd/docker-proxy build. The busy-loop was reproduced and then confirmed fixed A/B. Signed-off-by: Muhammad Falak R Wani --- SPECS/moby-engine/CVE-2026-37236.patch | 87 +++++++ SPECS/moby-engine/CVE-2026-56855.patch | 177 +++++++++++++ SPECS/moby-engine/CVE-2026-78662.patch | 109 ++++++++ SPECS/moby-engine/CVE-2026-84304.patch | 332 +++++++++++++++++++++++++ SPECS/moby-engine/moby-engine.spec | 9 +- 5 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 SPECS/moby-engine/CVE-2026-37236.patch create mode 100644 SPECS/moby-engine/CVE-2026-56855.patch create mode 100644 SPECS/moby-engine/CVE-2026-78662.patch create mode 100644 SPECS/moby-engine/CVE-2026-84304.patch diff --git a/SPECS/moby-engine/CVE-2026-37236.patch b/SPECS/moby-engine/CVE-2026-37236.patch new file mode 100644 index 00000000000..b898f091b9a --- /dev/null +++ b/SPECS/moby-engine/CVE-2026-37236.patch @@ -0,0 +1,87 @@ +From 72123cd4f32545f6e1376873f412dcdcbcf29acc Mon Sep 17 00:00:00 2001 +From: Andrew Z Allen +Date: Fri, 6 Mar 2026 23:50:33 -0700 +Subject: [PATCH] Add WithDisableHTTPMethodOverride ServeMux option (#6447) + +Add a new server option that disables the X-HTTP-Method-Override header +handling independently of the path length fallback. This allows users to +prevent POST requests from having their method overridden via the header +while still allowing the POST-to-GET fallback for form-urlencoded requests. + +This work is inspired by the security researcher Mariusz Maik. Thank you for +your hard work and tireless bughunting! + +[Azure Linux backport note] This addresses CVE-2026-37236 / GHSA-6gx8-r37x-4vw8 +(X-HTTP-Method-Override access-control bypass in +github.com/grpc-ecosystem/grpc-gateway/v2). moby v25.0.3 vendors grpc-gateway +v2.16.0, so the upstream hunks did not apply cleanly and were rebased onto the +v2.16.0 layout: + * The ServeMux struct in v2.16.0 ends with disablePathLengthFallback followed + by unescapingMode (it has no writeContentLength/disableChunkedEncoding + fields), so the new disableHTTPMethodOverride field is inserted immediately + before unescapingMode. + * v2.16.0 has no WithWriteContentLength option (the upstream anchor), so the + new WithDisableHTTPMethodOverride option is inserted right after + WithDisablePathLengthFallback instead. + * In v2.16.0 ServeHTTP assigns r.Method before calling r.ParseForm(); that + existing ordering is preserved on purpose (the unrelated upstream ParseForm + reordering commit 5d1f4c1c62ec is NOT backported). Only the guard condition + gains the new "!s.disableHTTPMethodOverride" term. + +The fix is OPT-IN and does not change default behaviour: X-HTTP-Method-Override +handling is disabled only when the new WithDisableHTTPMethodOverride() +ServeMuxOption is passed to NewServeMux, exactly matching upstream v2.29.0. This +mirrors upstream rather than flipping the default (grpc-gateway is vendored dead +code in dockerd, which is an OTLP client and never constructs a ServeMux). The +upstream docs/ and runtime/mux_test.go hunks are omitted because docs and +_test.go files are not vendored; vendor/modules.txt, vendor.mod and vendor.sum +are intentionally left at v2.16.0. + +Upstream Patch Reference: https://github.com/grpc-ecosystem/grpc-gateway/commit/72123cd4f32545f6e1376873f412dcdcbcf29acc.patch +--- + vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/mux.go | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/mux.go b/vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/mux.go +index f451cb4..284eff5 100644 +--- a/vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/mux.go ++++ b/vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/mux.go +@@ -62,6 +62,7 @@ type ServeMux struct { + streamErrorHandler StreamErrorHandlerFunc + routingErrorHandler RoutingErrorHandlerFunc + disablePathLengthFallback bool ++ disableHTTPMethodOverride bool + unescapingMode UnescapingMode + } + +@@ -204,6 +205,19 @@ func WithDisablePathLengthFallback() ServeMuxOption { + } + } + ++// WithDisableHTTPMethodOverride returns a ServeMuxOption that disables the ++// X-HTTP-Method-Override header handling. ++// ++// When this option is used, the mux will no longer allow POST requests with ++// the X-HTTP-Method-Override header to override the HTTP method. The path ++// length fallback (POST with application/x-www-form-urlencoded falling back ++// to a matching GET handler) is not affected by this option. ++func WithDisableHTTPMethodOverride() ServeMuxOption { ++ return func(serveMux *ServeMux) { ++ serveMux.disableHTTPMethodOverride = true ++ } ++} ++ + // WithHealthEndpointAt returns a ServeMuxOption that will add an endpoint to the created ServeMux at the path specified by endpointPath. + // When called the handler will forward the request to the upstream grpc service health check (defined in the + // gRPC Health Checking Protocol). +@@ -320,7 +334,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path = r.URL.RawPath + } + +- if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) { ++ if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && !s.disableHTTPMethodOverride && s.isPathLengthFallback(r) { + r.Method = strings.ToUpper(override) + if err := r.ParseForm(); err != nil { + _, outboundMarshaler := MarshalerForRequest(s, r) +-- +2.45.4 diff --git a/SPECS/moby-engine/CVE-2026-56855.patch b/SPECS/moby-engine/CVE-2026-56855.patch new file mode 100644 index 00000000000..3c3b8497103 --- /dev/null +++ b/SPECS/moby-engine/CVE-2026-56855.patch @@ -0,0 +1,177 @@ +From 86efde54dc7069251a8b007026c500d28e4239ce Mon Sep 17 00:00:00 2001 +From: Nicola Murino +Date: Sat, 13 Jun 2026 11:48:20 +0200 +Subject: [PATCH] ssh: reject unexpected message types on established channels + +ch.msg is only read while the channel open or a channel request with a +reply is pending, so anything the default arm of channel.handlePacket +delivered to it was never consumed. The blocking send there let a +misbehaving peer fill the buffer with well-formed but unexpected message +types carrying a valid channel id and stall the mux read loop, +deadlocking the whole connection. + +No conforming peer sends such messages during the connection protocol. +Treat them as a protocol error and tear the connection down, as +handleUnknownChannelPacket already does for the same messages when the +channel id is not in use. + +Fixes CVE-2026-56855 +Fixes golang/go#81317 + +Change-Id: I87420dfe68fcb62a17df4b47dc5ffb6ccd72ba26 +Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826524 +Reviewed-by: Roland Shoemaker +LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com +Auto-Submit: Neal Patel +Reviewed-by: Nicholas Husin + +This backport squashes two required prerequisite commits into the same +patch so that CVE-2026-56855's one-line change neither regresses reply +handling nor ships a known busy-spin: + + * Prerequisite 1: golang/crypto 3c7c86938f4541c333d506f719388d9c42d4763d + "ssh: fix deadlock on unexpected channel responses" (the channel half + of CVE-2026-39830; the spec previously ported only the mux/global-request + half in CVE-2026-39830.patch). It adds the sentRequestPending atomic.Bool + gate, an explicit "case *channelRequestSuccessMsg, *channelRequestFailureMsg:" + arm in handlePacket (non-blocking send behind the pending gate), and the + gate-open/drain logic in SendRequest. In v0.17.0, + SendRequest(wantReply=true) receives its reply through the default arm of + handlePacket, so converting default into an error return WITHOUT this + prerequisite would make every exec/shell/pty-req/subsystem request with + WantReply=true tear the connection down with "ssh: unexpected message + type 99". The prerequisite adds the explicit success/failure arm first, + so those replies still reach ch.msg and SendRequest still works. + + * Prerequisite 2: golang/crypto e3e62d9601ec6fa737c081aead768f525f919802 + "ssh: fix spinloop in channel SendRequest drain on closed channel" + (golang/go#79658). The drain loop added by prerequisite 1 receives from + ch.msg WITHOUT the comma-ok flag. Once channel.close() closes ch.msg + (which mux.loop() does for every channel via dropAll() when the + connection is torn down), the receive succeeds immediately and forever, + the default arm is never taken, and the loop spins at 100% CPU and never + returns. This is reached by every SendRequest(wantReply=true) - + Session.Start/Shell/Setenv/Signal/RequestPty/WindowChange/Subsystem - on + a dropped connection; pre-patch that call fell through to sendMessage and + returned io.EOF promptly. The comma-ok idiom detects the closed channel + and breaks out of the drain loop. + + The SAME unguarded drain loop was introduced into mux.SendRequest (over + m.globalResponses) by the already-shipped CVE-2026-39830.patch, and + mux.loop() closes m.globalResponses on exit, so it has the identical + 100%-CPU spinloop on teardown, reached by + client.SendRequest(..., wantReply=true, ...). e3e62d9's published diff + carries only the ssh/channel.go hunk, but canonical golang.org/x/crypto + now guards mux.SendRequest with the same comma-ok idiom; this backport + applies it to ssh/mux.go as well. Shipping the channel.go fix alone would + leave a known connection-teardown DoS (busy-spin) in the mux path. + + * CVE-2026-56855 (86efde54dc7069251a8b007026c500d28e4239ce): replaces the + remaining "default: ch.msg <- msg" blocking send with a protocol error, + so a peer can no longer stall the mux read loop with well-formed but + unexpected message types. + +Squashing multiple upstream commits into one CVE patch file follows the +existing practice in this spec (see CVE-2026-17106.patch). + +Backport notes (Azure Linux, moby-engine 25.0.3, vendored golang.org/x/crypto v0.17.0): + * The "sync/atomic" import that sentRequestPending needs is already added + by CVE-2026-78662.patch (Patch30, applied first), so this patch does not + re-add it. + * Dropped every _test.go hunk from all three upstream commits: those test + files (ssh/mux_test.go) are not vendored. + +Upstream Patch Reference: https://github.com/golang/crypto/commit/86efde54dc7069251a8b007026c500d28e4239ce.patch +Prerequisite Patch Reference: https://github.com/golang/crypto/commit/3c7c86938f4541c333d506f719388d9c42d4763d.patch +Prerequisite Patch Reference: https://github.com/golang/crypto/commit/e3e62d9601ec6fa737c081aead768f525f919802.patch +--- + vendor/golang.org/x/crypto/ssh/channel.go | 42 ++++++++++++++++++++++++++++++- + vendor/golang.org/x/crypto/ssh/mux.go | 5 +++- + 2 files changed, 45 insertions(+), 2 deletions(-) + +diff --git a/vendor/golang.org/x/crypto/ssh/channel.go b/vendor/golang.org/x/crypto/ssh/channel.go +index cb4ff2b..4403564 100644 +--- a/vendor/golang.org/x/crypto/ssh/channel.go ++++ b/vendor/golang.org/x/crypto/ssh/channel.go +@@ -190,6 +190,12 @@ type channel struct { + // with WantReply=true outstanding. This lock is held by a + // goroutine that has such an outgoing request pending. + sentRequestMu sync.Mutex ++ // sentRequestPending is set to true while a SendRequest call with ++ // WantReply=true is in flight. handlePacket uses it as a gate: responses ++ // arriving while no request is pending are dropped to prevent a ++ // misbehaving peer from stalling the mux read loop by filling ch.msg ++ // with unsolicited channelRequestSuccess/Failure messages. ++ sentRequestPending atomic.Bool + + incomingRequests chan *Request + +@@ -483,8 +489,21 @@ func (ch *channel) handlePacket(packet []byte) error { + } + + ch.incomingRequests <- &req ++ case *channelRequestSuccessMsg, *channelRequestFailureMsg: ++ // Drop responses that arrive when no SendRequest is waiting, to ++ // prevent a malicious peer from filling ch.msg and stalling the ++ // mux read loop. The non-blocking send additionally protects the ++ // loop if a well-behaved caller is slow to read. ++ if !ch.sentRequestPending.Load() { ++ return nil ++ } ++ select { ++ case ch.msg <- msg: ++ default: ++ } + default: +- ch.msg <- msg ++ // No other message type is expected on an established channel. ++ return fmt.Errorf("ssh: unexpected message type %d on channel %d", packet[0], ch.localId) + } + return nil + } +@@ -620,6 +639,27 @@ func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (boo + if wantReply { + ch.sentRequestMu.Lock() + defer ch.sentRequestMu.Unlock() ++ ++ // Open the gate so that responses arriving while this request is in ++ // flight are allowed to reach ch.msg. Responses arriving while no ++ // request is pending are dropped by handlePacket. ++ ch.sentRequestPending.Store(true) ++ defer ch.sentRequestPending.Store(false) ++ ++ // Drain any spurious responses that may have been buffered. This ++ // prevents a previously buffered unexpected response from being ++ // consumed instead of the actual response for this request. ++ drain: ++ for { ++ select { ++ case _, ok := <-ch.msg: ++ if !ok { ++ break drain ++ } ++ default: ++ break drain ++ } ++ } + } + + msg := channelRequestMsg{ +diff --git a/vendor/golang.org/x/crypto/ssh/mux.go b/vendor/golang.org/x/crypto/ssh/mux.go +index 3bc4afb..5775881 100644 +--- a/vendor/golang.org/x/crypto/ssh/mux.go ++++ b/vendor/golang.org/x/crypto/ssh/mux.go +@@ -155,7 +155,10 @@ func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, [] + drain: + for { + select { +- case <-m.globalResponses: ++ case _, ok := <-m.globalResponses: ++ if !ok { ++ break drain ++ } + default: + break drain + } +-- +2.45.4 diff --git a/SPECS/moby-engine/CVE-2026-78662.patch b/SPECS/moby-engine/CVE-2026-78662.patch new file mode 100644 index 00000000000..d9fea5689a7 --- /dev/null +++ b/SPECS/moby-engine/CVE-2026-78662.patch @@ -0,0 +1,109 @@ +From a6cdac60840750226b15617ac8858be44361b36b Mon Sep 17 00:00:00 2001 +From: Nicola Murino +Date: Sat, 13 Jun 2026 11:54:07 +0200 +Subject: [PATCH] ssh: drop traffic on undecided channels + +A channel in the mux's chanList is not usable until it is established: +an outbound channel has no confirmed remote id until the peer's open +confirmation, and an inbound channel is not serviced by the application +until it is accepted. handlePacket processed any channel message on it, +so a misbehaving peer could flood channel requests and block the mux +read loop on the send to incomingRequests, deadlocking the connection, +or close an outbound channel before confirming it, making the victim +tear down a half-initialized channel and emit a close for remote id 0, +an unrelated channel of the peer. + +No such packet can be legitimate: the peer learns an inbound channel's +local id only from the confirmation we have not sent yet, and on an +outbound channel RFC 4254 lets it answer the open request only with a +confirmation or a failure. + +Add an established flag, set when the channel becomes usable: for an +outbound channel when the open response is received, for an inbound +channel by Accept before the confirmation is sent. Until then +handlePacket drops every packet other than the open response. The flag +is separate from decided, which Reject also sets: a rejected channel is +decided but must never carry traffic. + +Fixes CVE-2026-78662 +Fixes golang/go#81316 + +Change-Id: Ib0983bb216a49808a2db1f4a4d92ee9fe38a3c51 +Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826504 +Auto-Submit: Gopher Robot +LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com +Reviewed-by: Roland Shoemaker +Reviewed-by: Neal Patel +Reviewed-by: Nicholas Husin + +Backport notes (Azure Linux, moby-engine 25.0.3, vendored golang.org/x/crypto v0.17.0): + * Added "sync/atomic" to the ssh/channel.go import block. Upstream's + channel.go already imported sync/atomic (for an unrelated + sentRequestPending field that does not exist in v0.17.0), so the + upstream commit carried no import hunk; without this addition the + build fails with "channel.go: undefined: atomic". + * Dropped the upstream ssh/mux_test.go hunk: that test file is not + vendored, and it references unvendored helpers (memPipe, muxPair, + chanSize) and Go 1.22 range-over-int syntax. + +Upstream Patch Reference: https://github.com/golang/crypto/commit/a6cdac60840750226b15617ac8858be44361b36b.patch +--- + vendor/golang.org/x/crypto/ssh/channel.go | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +diff --git a/vendor/golang.org/x/crypto/ssh/channel.go b/vendor/golang.org/x/crypto/ssh/channel.go +index 77bac19..cb4ff2b 100644 +--- a/vendor/golang.org/x/crypto/ssh/channel.go ++++ b/vendor/golang.org/x/crypto/ssh/channel.go +@@ -11,6 +11,7 @@ import ( + "io" + "log" + "sync" ++ "sync/atomic" + ) + + const ( +@@ -172,6 +173,12 @@ type channel struct { + // (for outbound channels) or received (for inbound channels). + decided bool + ++ // established is set to true once the channel is open and may carry normal ++ // channel traffic: for an outbound channel when the peer's open ++ // confirmation is received, for an inbound channel when the local side ++ // accepts it. It is set and read from different goroutines. ++ established atomic.Bool ++ + // direction contains either channelOutbound, for channels created + // locally, or channelInbound, for channels created by the peer. + direction channelDirection +@@ -410,10 +417,20 @@ func (ch *channel) responseMessageReceived() error { + return errors.New("ssh: duplicate response received for channel") + } + ch.decided = true ++ ch.established.Store(true) + return nil + } + + func (ch *channel) handlePacket(packet []byte) error { ++ // Only the open response is expected before the channel is established. ++ if !ch.established.Load() { ++ switch packet[0] { ++ case msgChannelOpenConfirm, msgChannelOpenFailure: ++ default: ++ return nil ++ } ++ } ++ + switch packet[0] { + case msgChannelData, msgChannelExtendedData: + return ch.handleData(packet) +@@ -518,6 +535,7 @@ func (ch *channel) Accept() (Channel, <-chan *Request, error) { + MaxPacketSize: ch.maxIncomingPayload, + } + ch.decided = true ++ ch.established.Store(true) + if err := ch.sendMessage(confirm); err != nil { + return nil, nil, err + } +-- +2.45.4 diff --git a/SPECS/moby-engine/CVE-2026-84304.patch b/SPECS/moby-engine/CVE-2026-84304.patch new file mode 100644 index 00000000000..2daf2e7e086 --- /dev/null +++ b/SPECS/moby-engine/CVE-2026-84304.patch @@ -0,0 +1,332 @@ +From 8cfeca0e1ee5ea0980dcc320e20240fa1079ec77 Mon Sep 17 00:00:00 2001 +From: Arjan Singh Bal <46515553+arjan-bal@users.noreply.github.com> +Date: Wed, 19 Aug 2026 11:48:55 +0530 +Subject: [PATCH] Cherry-pick #9331 to v1.83.x (#9333) + +transport: restrict memory overhead of buffering small data frames. + +Fixes CVE-2026-84304 (GHSA-vp52-pcj8-j9qc, CWE-400, CVSS v4.0 8.7 HIGH). +The HTTP/2 transport stores every received DATA frame as a separate recvMsg in +the unbounded recvBuffer.backlog slice. HTTP/2 flow control bounds the total +*payload* bytes but not the fixed per-frame bookkeeping overhead, so an +unauthenticated peer sending a flood of tiny (e.g. 1-byte) DATA frames across +many multiplexed streams can inflate the heap far beyond the flow-control limit, +causing OOM / runtime panic and a remote denial of service. moby v25.0.3 vendors +google.golang.org/grpc v1.58.3, which is affected. + +The upstream fix adds receive-buffer compaction: it tracks the trailing run of +small, uncompacted messages (uncompactedSuffixLen / uncompactedBytes) and, once +that run's estimated heap size is both large in absolute terms and dominated by +per-message overhead (memory utilization < 50%), coalesces the run into a single +buffer. The feature is guarded by the environment variable +GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION (default on). + +THIS IS A HAND-ADAPTED BACKPORT, NOT A CLEAN CHERRY-PICK. The upstream change +(grpc-go PR #9331, master commit 7354d9c8debb4bcf2225bf429857078de310c176; +cherry-picked to v1.83.x as commit 8cfeca0e1ee5ea0980dcc320e20240fa1079ec77 and +released in v1.83.1) is written against grpc-go >= 1.83's +google.golang.org/grpc/mem and internal/mem buffer-pool architecture. NEITHER +package exists in the v1.58.3 tree vendored by moby v25.0.3, so the fix has been +re-implemented against 1.58.3's *bytes.Buffer + sync.Pool design. Adaptations +made versus upstream: + + * recvMsg.buffer is a plain *bytes.Buffer here (not a refcounted mem.Buffer): + compaction copies payloads with bytes.Buffer.Bytes()/Write and recycles the + coalesced small buffers via bufferPool.put() instead of mem.Buffer.Free(). + * internal/mem.BufferPoolingThreshold does not exist; a local + "const bufferPoolingThreshold = 1 << 10" is hard-coded in transport.go. + * recvBuffer carries a *bufferPool (this version's sync.Pool of *bytes.Buffer) + instead of a mem.BufferPool. newRecvBuffer() is changed to take the pool and + its three call sites (http2_client.go, http2_server.go, handler_server.go) + are updated. serverHandlerTransport has no buffer pool, so it passes nil and + the compaction path is nil-safe (falls back to new(bytes.Buffer)). + * recvMsgSize is estimated as int(unsafe.Sizeof(recvMsg{}) + + unsafe.Sizeof(bytes.Buffer{})) -- the closer, conservative analogue of + upstream's unsafe.Sizeof(recvMsg{}) + unsafe.Sizeof([]byte{}) for the + *bytes.Buffer design. + * internal/envconfig already provides the boolFromEnv helper, so + EnableReceiveBufferCompaction is appended to the existing var block. + * Only the security-relevant compaction logic is ported. The upstream buffer + -pool refactoring plumbing (internal/mem/buffer_pool.go, mem/buffer_pool.go, + mem/buffers.go) and the unrelated 1.83-era buffer-Free() leak fix on the + "b.err != nil" path are intentionally omitted, as is transport_test.go (moby + strips _test.go files). google.golang.org/grpc is deliberately NOT bumped + from 1.58.3, and vendor/modules.txt / vendor.mod / vendor.sum are unchanged. + +Original PR: #9331 + +RELEASE NOTES: +* transport: restrict memory overhead of buffering small data frames. + +Upstream Patch Reference: https://github.com/grpc/grpc-go/commit/8cfeca0e1ee5ea0980dcc320e20240fa1079ec77.patch +--- + vendor/google.golang.org/grpc/internal/envconfig/envconfig.go | 7 ++++++ + vendor/google.golang.org/grpc/internal/transport/handler_server.go | 2 +- + vendor/google.golang.org/grpc/internal/transport/http2_client.go | 2 +- + vendor/google.golang.org/grpc/internal/transport/http2_server.go | 2 +- + vendor/google.golang.org/grpc/internal/transport/transport.go | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- + 5 files changed, 149 insertions(+), 12 deletions(-) + +diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +index 3cf10dd..f0f62fb 100644 +--- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go ++++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +@@ -46,6 +46,13 @@ var ( + // ALTSMaxConcurrentHandshakes is the maximum number of concurrent ALTS + // handshakes that can be performed. + ALTSMaxConcurrentHandshakes = uint64FromEnv("GRPC_ALTS_MAX_CONCURRENT_HANDSHAKES", 100, 1, 100) ++ // EnableReceiveBufferCompaction enables coalescing of small buffered DATA ++ // frames in the transport receive buffer to bound their memory overhead. ++ // ++ // This environment variable serves as an escape hatch to disable the ++ // feature if unforeseen issues arise, and it will be removed in a future ++ // release. ++ EnableReceiveBufferCompaction = boolFromEnv("GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION", true) + ) + + func boolFromEnv(envVar string, def bool) bool { +diff --git a/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/vendor/google.golang.org/grpc/internal/transport/handler_server.go +index 98f80e3..b210b91 100644 +--- a/vendor/google.golang.org/grpc/internal/transport/handler_server.go ++++ b/vendor/google.golang.org/grpc/internal/transport/handler_server.go +@@ -373,7 +373,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace + id: 0, // irrelevant + requestRead: func(int) {}, + cancel: cancel, +- buf: newRecvBuffer(), ++ buf: newRecvBuffer(nil), // serverHandlerTransport has no buffer pool + st: ht, + method: req.URL.Path, + recvCompress: req.Header.Get("grpc-encoding"), +diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go +index badab8a..36ac941 100644 +--- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go ++++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go +@@ -461,7 +461,7 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { + done: make(chan struct{}), + method: callHdr.Method, + sendCompress: callHdr.SendCompress, +- buf: newRecvBuffer(), ++ buf: newRecvBuffer(t.bufferPool), + headerChan: make(chan struct{}), + contentSubtype: callHdr.ContentSubtype, + doneFunc: callHdr.DoneFunc, +diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go +index c06db67..554e9b8 100644 +--- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go ++++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go +@@ -367,7 +367,7 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( + } + t.maxStreamID = streamID + +- buf := newRecvBuffer() ++ buf := newRecvBuffer(t.bufferPool) + s := &Stream{ + id: streamID, + st: t, +diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go +index 74a811f..d7020ea 100644 +--- a/vendor/google.golang.org/grpc/internal/transport/transport.go ++++ b/vendor/google.golang.org/grpc/internal/transport/transport.go +@@ -31,10 +31,12 @@ import ( + "sync" + "sync/atomic" + "time" ++ "unsafe" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/internal/channelz" ++ "google.golang.org/grpc/internal/envconfig" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/resolver" +@@ -43,7 +45,39 @@ import ( + "google.golang.org/grpc/tap" + ) + +-const logLevel = 2 ++const ( ++ logLevel = 2 ++ ++ // bufferPoolingThreshold mirrors the (unexported) buffer-pooling threshold ++ // used by later grpc-go releases (internal/mem.BufferPoolingThreshold, which ++ // does not exist at this vendored version). Payloads at or below this size ++ // are heap-allocated rather than pooled, so it doubles as the number of tiny ++ // DATA frames we allow to accumulate in the backlog before coalescing them ++ // into a single buffer. ++ bufferPoolingThreshold = 1 << 10 ++ ++ // recvMsgSize estimates the per-message heap overhead of a backlog entry. ++ // It accounts for the recvMsg struct itself plus the bytes.Buffer it points ++ // to, and deliberately ignores the (>=64 byte) backing array, keeping the ++ // estimate conservative. Upstream (grpc-go >=1.83) uses ++ // unsafe.Sizeof(recvMsg{}) + unsafe.Sizeof([]byte{}); with this version's ++ // *bytes.Buffer design, unsafe.Sizeof(bytes.Buffer{}) is the closer analogue ++ // of the pointed-to allocation. ++ recvMsgSize = int(unsafe.Sizeof(recvMsg{}) + unsafe.Sizeof(bytes.Buffer{})) ++ ++ // utilizationFactor controls when memory utilization is considered ++ // acceptable. When backlogHeapSize <= utilizationFactor*payloadBytes (i.e. ++ // at least 50% of the buffered heap is real payload), compaction is skipped. ++ utilizationFactor = 2 ++) ++ ++var ( ++ // compactionThreshold allows accumulating up to bufferPoolingThreshold ++ // (~1024) one-byte payloads before compaction is triggered. This coalesces ++ // the many small heap allocations into a single buffer, enabling buffer ++ // reuse while avoiding frequent copying for short bursts of frames. ++ compactionThreshold = bufferPoolingThreshold * (recvMsgSize + 1) ++) + + type bufferPool struct { + pool sync.Pool +@@ -87,20 +121,32 @@ type recvBuffer struct { + c chan recvMsg + mu sync.Mutex + backlog []recvMsg +- err error +-} +- +-func newRecvBuffer() *recvBuffer { ++ // uncompactedSuffixLen tracks the number of consecutive data messages at ++ // the tail of backlog that have not yet been compacted. ++ uncompactedSuffixLen int ++ // uncompactedBytes tracks the total payload bytes across the trailing ++ // uncompactedSuffixLen messages. ++ uncompactedBytes int ++ err error ++ // bufPool is the pool used to obtain the coalesced buffer during compaction ++ // and to recycle the small buffers that get compacted. It may be nil (e.g. ++ // for serverHandlerTransport, which has no buffer pool); the compaction code ++ // is nil-safe and falls back to a freshly allocated buffer in that case. ++ bufPool *bufferPool ++} ++ ++func newRecvBuffer(pool *bufferPool) *recvBuffer { + b := &recvBuffer{ +- c: make(chan recvMsg, 1), ++ c: make(chan recvMsg, 1), ++ bufPool: pool, + } + return b + } + + func (b *recvBuffer) put(r recvMsg) { + b.mu.Lock() ++ defer b.mu.Unlock() + if b.err != nil { +- b.mu.Unlock() + // An error had occurred earlier, don't accept more + // data or errors. + return +@@ -109,13 +155,89 @@ func (b *recvBuffer) put(r recvMsg) { + if len(b.backlog) == 0 { + select { + case b.c <- r: +- b.mu.Unlock() + return + default: + } + } + b.backlog = append(b.backlog, r) +- b.mu.Unlock() ++ b.compactBacklogLocked(r) ++} ++ ++// compactBacklogLocked bounds the memory overhead of buffering many small ++// HTTP/2 DATA frames. Every fragmented frame is stored as a separate recvMsg, ++// each carrying a *bytes.Buffer whose fixed overhead (recvMsg struct, the ++// bytes.Buffer struct and its minimum backing array) dwarfs a 1-byte payload. ++// Flow control bounds the total payload bytes but not this per-frame overhead, ++// so an unauthenticated peer flooding tiny frames across many streams can ++// inflate the heap far beyond the payload size (CVE-2026-84304 / GHSA-vp52- ++// pcj8-j9qc). ++// ++// To bound that overhead we track the trailing run of small uncompacted ++// messages and, once its estimated heap size is both large in absolute terms ++// and dominated by per-message overhead, coalesce the run into one buffer. ++// ++// b.mu must be held. ++func (b *recvBuffer) compactBacklogLocked(r recvMsg) { ++ if !envconfig.EnableReceiveBufferCompaction { ++ return ++ } ++ if r.buffer == nil { ++ // An error/EOF recvMsg carries no payload; reset suffix tracking so the ++ // compaction loop never dereferences a nil buffer. ++ b.uncompactedBytes = 0 ++ b.uncompactedSuffixLen = 0 ++ return ++ } ++ ++ b.uncompactedSuffixLen++ ++ b.uncompactedBytes += r.buffer.Len() ++ backlogHeapSize := b.uncompactedSuffixLen*recvMsgSize + b.uncompactedBytes ++ ++ // If the memory overhead is less than 50% of the heap usage (e.g., because ++ // a large DATA frame arrived), the average message size in the suffix is ++ // large enough that memory bloat is not a concern. Reset suffix tracking. ++ if backlogHeapSize <= utilizationFactor*b.uncompactedBytes { ++ b.uncompactedBytes = 0 ++ b.uncompactedSuffixLen = 0 ++ return ++ } ++ // Avoid compacting too frequently for short bursts of small frames. Wait ++ // until we have accumulated at least ~bufferPoolingThreshold small messages. ++ if backlogHeapSize <= compactionThreshold { ++ // Still can accumulate more payloads. ++ return ++ } ++ ++ // Coalesce the uncompacted suffix into a single buffer. Fall back to a ++ // freshly allocated buffer when no pool is available. ++ startIdx := len(b.backlog) - b.uncompactedSuffixLen ++ var newBuf *bytes.Buffer ++ if b.bufPool != nil { ++ newBuf = b.bufPool.get() ++ } else { ++ newBuf = new(bytes.Buffer) ++ } ++ newBuf.Reset() ++ newBuf.Grow(b.uncompactedBytes) ++ for i := startIdx; i < len(b.backlog); i++ { ++ m := b.backlog[i] ++ b.backlog[i] = recvMsg{} ++ newBuf.Write(m.buffer.Bytes()) ++ // The small buffers are no longer reachable by the reader once they are ++ // removed from the backlog, so recycle them here (the reader normally ++ // returns them via recvBufferReader.Read -> freeBuffer). They are not ++ // referenced elsewhere, so this cannot double-free. ++ if b.bufPool != nil { ++ b.bufPool.put(m.buffer) ++ } ++ } ++ b.backlog[startIdx] = recvMsg{buffer: newBuf} ++ b.backlog = b.backlog[:startIdx+1] ++ // After compaction, the suffix is replaced with a single message containing ++ // the combined payload, whose utilization is close to 1.0 (well below ++ // utilizationFactor). ++ b.uncompactedBytes = 0 ++ b.uncompactedSuffixLen = 0 + } + + func (b *recvBuffer) load() { +@@ -123,6 +245,14 @@ func (b *recvBuffer) load() { + if len(b.backlog) > 0 { + select { + case b.c <- b.backlog[0]: ++ // backlog[0] is only part of the tracked uncompacted suffix if the ++ // entire backlog currently consists of the suffix. If an earlier ++ // compaction or reset occurred, backlog[0] is already compacted and ++ // must not be subtracted from the counters. ++ if envconfig.EnableReceiveBufferCompaction && b.uncompactedSuffixLen == len(b.backlog) { ++ b.uncompactedSuffixLen-- ++ b.uncompactedBytes -= b.backlog[0].buffer.Len() ++ } + b.backlog[0] = recvMsg{} + b.backlog = b.backlog[1:] + default: +-- +2.45.4 diff --git a/SPECS/moby-engine/moby-engine.spec b/SPECS/moby-engine/moby-engine.spec index c77337ebe09..0b135dab300 100644 --- a/SPECS/moby-engine/moby-engine.spec +++ b/SPECS/moby-engine/moby-engine.spec @@ -3,7 +3,7 @@ Summary: The open-source application container engine Name: moby-engine Version: 25.0.3 -Release: 20%{?dist} +Release: 21%{?dist} License: ASL 2.0 Group: Tools/Container URL: https://mobyproject.org @@ -45,6 +45,10 @@ Patch26: CVE-2026-61712.patch Patch27: CVE-2026-75593.patch Patch28: CVE-2026-61711.patch Patch29: CVE-2026-17106.patch +Patch30: CVE-2026-78662.patch +Patch31: CVE-2026-56855.patch +Patch32: CVE-2026-84304.patch +Patch33: CVE-2026-37236.patch %{?systemd_requires} @@ -140,6 +144,9 @@ fi %{_unitdir}/* %changelog +* Fri Sep 11 2026 Muhammad Falak R Wani - 25.0.3-21 +- Patch for CVE-2026-78662, CVE-2026-56855, CVE-2026-84304, CVE-2026-37236 + * Thu Aug 27 2026 Jyoti Kanase - 25.0.3-20 - Patch for CVE-2026-61711, CVE-2026-61712, CVE-2026-75593, CVE-2026-17106