From 8b3e46b887a1c0fbe229c146e4fff9b1e78abe80 Mon Sep 17 00:00:00 2001 From: abtreece Date: Wed, 28 Oct 2026 08:50:35 -0500 Subject: [PATCH 1/2] chore: bump Go to 1.26.10 and go-jose, x/net for CVEs --- .github/workflows/codecov.yml | 2 +- .github/workflows/cross-platform.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/integration-tests.yml | 8 +- .github/workflows/release.yml | 2 +- .tool-versions | 2 +- CHANGELOG | 1 + README.md | 2 +- docker/Dockerfile.build | 2 +- docs/development.md | 12 +- docs/docker.md | 2 +- docs/installation.md | 4 +- go.mod | 6 +- go.sum | 8 +- .../go-jose/go-jose/v4/asymmetric.go | 26 +- .../go-jose/go-jose/v4/cipher/cbc_hmac.go | 6 +- .../github.com/go-jose/go-jose/v4/crypter.go | 101 +++++-- .../go-jose/go-jose/v4/json/scanner.go | 19 +- vendor/github.com/go-jose/go-jose/v4/jwe.go | 26 +- vendor/github.com/go-jose/go-jose/v4/jwk.go | 264 ++++++++++++++---- vendor/github.com/go-jose/go-jose/v4/jws.go | 44 +-- .../go-jose/go-jose/v4/jwt/claims.go | 15 + .../go-jose/go-jose/v4/jwt/errors.go | 2 +- .../github.com/go-jose/go-jose/v4/opaque.go | 5 + .../github.com/go-jose/go-jose/v4/shared.go | 37 ++- .../github.com/go-jose/go-jose/v4/signing.go | 111 +++++--- .../go-jose/go-jose/v4/symmetric.go | 10 +- .../go-jose/go-jose/v4/tests.Containerfile | 13 + .../go-jose/go-jose/v4/threat_model.md | 25 ++ vendor/golang.org/x/net/http2/flow.go | 103 +++++-- vendor/golang.org/x/net/http2/frame.go | 34 ++- vendor/golang.org/x/net/http2/server.go | 110 +++++--- vendor/golang.org/x/net/http2/transport.go | 142 ++++++---- .../golang.org/x/net/http2/transport_wrap.go | 214 ++++++++++++-- vendor/golang.org/x/net/http2/writesched.go | 7 +- .../x/net/internal/httpcommon/gzip.go | 132 +++++++++ vendor/modules.txt | 4 +- 37 files changed, 1139 insertions(+), 366 deletions(-) create mode 100644 vendor/github.com/go-jose/go-jose/v4/tests.Containerfile create mode 100644 vendor/github.com/go-jose/go-jose/v4/threat_model.md create mode 100644 vendor/golang.org/x/net/internal/httpcommon/gzip.go diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 38f56e624..396321a90 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Run go test for coverage run: go test -vet=off ./... -race -coverprofile=coverage.out -covermode=atomic diff --git a/.github/workflows/cross-platform.yml b/.github/workflows/cross-platform.yml index 1daa87c7b..77b14ed5c 100644 --- a/.github/workflows/cross-platform.yml +++ b/.github/workflows/cross-platform.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Run unit tests run: go test -mod=vendor ./pkg/... ./cmd/... diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3f50b5701..b62c0ec0e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Run E2E Tests run: go test -v -tags=e2e -timeout 15m ./test/e2e/... diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 859ec1288..6f1ec9630 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Build and Install run: | @@ -107,7 +107,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Build and Install run: | @@ -176,7 +176,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Build and Install run: | @@ -235,7 +235,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: "1.26.8" + go-version: "1.26.10" - name: Build and Install run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdbeda5b2..c3aab5c1f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26.8' + go-version: '1.26.10' - name: Set up QEMU uses: docker/setup-qemu-action@v4 diff --git a/.tool-versions b/.tool-versions index 459141f66..0767b00de 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -golang 1.26.8 +golang 1.26.10 diff --git a/CHANGELOG b/CHANGELOG index 9df900ffd..eb3471fe7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ ### [Unreleased] +* chore: bump Go toolchain to 1.26.10 and github.com/go-jose/go-jose/v4, golang.org/x/net to patch HIGH CVEs flagged by Trivy (stdlib CVE-2026-111943, CVE-2026-78667, CVE-2026-78669, CVE-2026-97031; x/net CVE-2026-78669; go-jose CVE-2026-116587) * chore: bump Go toolchain to 1.26.8 and golang.org/x/net, golang.org/x/text, golang.org/x/crypto, google.golang.org/grpc to patch HIGH CVEs flagged by Trivy (stdlib CVE-2026-27145, CVE-2026-33818, CVE-2026-39821, CVE-2026-39822, CVE-2026-42504, CVE-2026-46600, CVE-2026-56853, CVE-2026-56858, CVE-2026-56859, CVE-2026-56860, CVE-2026-56862; x/net CVE-2026-25681, CVE-2026-27136, CVE-2026-33814; x/text CVE-2026-56852; grpc GHSA-hrxh-6v49-42gf, CVE-2026-84304, CVE-2026-84445) (#636) * chore(deps): bump codecov/codecov-action from 6 to 7 (#629) * chore(deps): bump actions/setup-go from 6 to 7 (#633) diff --git a/README.md b/README.md index be025bf7c..8e3c268aa 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ See [Docker documentation](docs/docker.md) for complete usage including Docker C ### Building from Source -Go 1.26.8 is required to build confd. The module uses `go 1.26.0` for language compatibility and `toolchain go1.26.8` to pin the expected patch-level toolchain. +Go 1.26.10 is required to build confd. The module uses `go 1.26.0` for language compatibility and `toolchain go1.26.10` to pin the expected patch-level toolchain. ```bash git clone https://github.com/abtreece/confd.git diff --git a/docker/Dockerfile.build b/docker/Dockerfile.build index ae8abb97f..1b3345bb1 100644 --- a/docker/Dockerfile.build +++ b/docker/Dockerfile.build @@ -1,7 +1,7 @@ # Multi-stage build Dockerfile for CI testing # Builds confd from source and produces a minimal runtime image -FROM golang:1.26.8-alpine AS builder +FROM golang:1.26.10-alpine AS builder # Install build dependencies RUN apk add --no-cache git make diff --git a/docs/development.md b/docs/development.md index a78181199..2bc6bedd7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -20,11 +20,11 @@ This guide covers setting up a development environment, building, testing, and d | Tool | Version | Purpose | |------|---------|---------| -| Go | 1.26.8 | Build and test | +| Go | 1.26.10 | Build and test | | golangci-lint | latest | Linting | | make | any | Build automation | -The `go.mod` file keeps the language version at `go 1.26.0` and pins the expected patch-level compiler with `toolchain go1.26.8`. Keep `.tool-versions`, CI `setup-go` entries, Docker build images, and documentation aligned with that toolchain version. +The `go.mod` file keeps the language version at `go 1.26.0` and pins the expected patch-level compiler with `toolchain go1.26.10`. Keep `.tool-versions`, CI `setup-go` entries, Docker build images, and documentation aligned with that toolchain version. ### Optional Tools @@ -43,9 +43,9 @@ brew install go golangci-lint goreleaser **Linux:** ```bash -# Go 1.26.8 -wget https://go.dev/dl/go1.26.8.linux-amd64.tar.gz -sudo tar -C /usr/local -xzf go1.26.8.linux-amd64.tar.gz +# Go 1.26.10 +wget https://go.dev/dl/go1.26.10.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.26.10.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin # golangci-lint @@ -71,7 +71,7 @@ This creates `bin/confd` with the Git SHA embedded via ldflags. ```bash ./bin/confd --version -# Output: confd 0.41.2 (Git SHA: abc1234, Go Version: go1.26.8) +# Output: confd 0.41.2 (Git SHA: abc1234, Go Version: go1.26.10) ``` ### Project Structure diff --git a/docs/docker.md b/docs/docker.md index 35cd0401d..92dc37c0d 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -321,7 +321,7 @@ CMD ["consul", "--node", "http://consul:8500", "--watch"] ### Multi-stage Build from Source ```dockerfile -FROM golang:1.26.8-alpine AS builder +FROM golang:1.26.10-alpine AS builder RUN apk add --no-cache git make WORKDIR /src diff --git a/docs/installation.md b/docs/installation.md index 3915a2495..8e845a986 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -147,7 +147,7 @@ RUN CONFD_ARCH=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ #### Building from Source -Building from source requires the Go 1.26.8 toolchain. The module declares `go 1.26.0` for language compatibility and `toolchain go1.26.8` for the expected compiler patch version. +Building from source requires the Go 1.26.10 toolchain. The module declares `go 1.26.0` for language compatibility and `toolchain go1.26.10` for the expected compiler patch version. ```bash make build @@ -167,7 +167,7 @@ docker build -t confd:local -f docker/Dockerfile.build . Include confd in your own Docker image using a multi-stage build: ```dockerfile -FROM golang:1.26.8-alpine AS confd-builder +FROM golang:1.26.10-alpine AS confd-builder RUN apk add --no-cache git WORKDIR /src diff --git a/go.mod b/go.mod index 54465696c..c1f30663d 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/abtreece/confd go 1.26.0 -toolchain go1.26.8 +toolchain go1.26.10 require ( github.com/BurntSushi/toml v1.6.0 @@ -65,7 +65,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.6 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -131,7 +131,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/net v0.59.0 // indirect + golang.org/x/net v0.60.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.12.0 // indirect diff --git a/go.sum b/go.sum index a7c486562..245004b06 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeO github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.6 h1:sNRkftxFJxwsILiQukqp5z83QBSmntVwmgRY7AqBDO4= +github.com/go-jose/go-jose/v4 v4.1.6/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -417,8 +417,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go index 7784cd458..2b5607617 100644 --- a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go +++ b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go @@ -21,6 +21,7 @@ import ( "crypto/aes" "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha1" @@ -195,11 +196,11 @@ func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipi func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) { switch alg { case RSA1_5: - return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek) + return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek) case RSA_OAEP: - return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{}) + return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{}) case RSA_OAEP_256: - return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{}) + return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{}) } return nil, ErrUnsupportedAlgorithm @@ -288,9 +289,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm // TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the // random parameter is legacy and ignored, and it can be nil. // https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1 - out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed) + out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed) case PS256, PS384, PS512: - out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{ + out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, }) } @@ -391,7 +392,7 @@ func (ctx ecKeyGenerator) keySize() int { // Get a content encryption key for ECDH-ES func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { - priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader) + priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader) if err != nil { return nil, rawHeader{}, err } @@ -483,7 +484,7 @@ func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) return Signature{}, ErrUnsupportedAlgorithm } - sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0)) + sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0)) if err != nil { return Signature{}, err } @@ -533,7 +534,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) _, _ = hasher.Write(payload) hashed := hasher.Sum(nil) - r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed) + r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed) if err != nil { return Signature{}, err } @@ -571,12 +572,21 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a case ES256: keySize = 32 hash = crypto.SHA256 + if ctx.publicKey.Curve != elliptic.P256() { + return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key") + } case ES384: keySize = 48 hash = crypto.SHA384 + if ctx.publicKey.Curve != elliptic.P384() { + return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key") + } case ES512: keySize = 66 hash = crypto.SHA512 + if ctx.publicKey.Curve != elliptic.P521() { + return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key") + } default: return ErrUnsupportedAlgorithm } diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go index af029cec0..a301ad9e2 100644 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go @@ -51,6 +51,8 @@ func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) ( hash = sha512.New384 case 32: hash = sha512.New + default: + return nil, errors.New("go-jose/go-jose: invalid key size for CBC-HMAC") } return &cbcAEAD{ @@ -176,7 +178,9 @@ func padBuffer(buffer []byte, blockSize int) []byte { // Remove padding func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) { - if len(buffer)%blockSize != 0 { + // A padded buffer can't be empty because an empty input is padded with + // `blockSize` bytes, resulting in a non-empty ciphertext. + if len(buffer) == 0 || len(buffer)%blockSize != 0 { return nil, errors.New("go-jose/go-jose: invalid padding") } diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v4/crypter.go index 31290fc87..53636c57b 100644 --- a/vendor/github.com/go-jose/go-jose/v4/crypter.go +++ b/vendor/github.com/go-jose/go-jose/v4/crypter.go @@ -258,6 +258,10 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) { } recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key) + if err != nil { + return err + } + if recipient.KeyID != "" { recipientInfo.keyID = recipient.KeyID } @@ -270,10 +274,8 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) { } } - if err == nil { - ctx.recipients = append(ctx.recipients, recipientInfo) - } - return err + ctx.recipients = append(ctx.recipients, recipientInfo) + return nil } func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) { @@ -426,6 +428,13 @@ func (ctx *genericEncrypter) Options() EncrypterOptions { } } +func validateContentEncryptionKeySize(cek []byte, cipher contentCipher) error { + if len(cek) != cipher.keySize() { + return ErrInvalidKeySize + } + return nil +} + // Decrypt and validate the object and return the plaintext. This // function does not support multi-recipient. If you desire multi-recipient // decryption use DecryptMulti instead. @@ -490,21 +499,23 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) recipientHeaders := obj.mergedHeaders(&recipient) cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) - if err == nil { - // Found a valid CEK -- let's try to decrypt. - plaintext, err = cipher.decrypt(cek, authData, parts) + if err != nil { + return nil, ErrCryptoFailure + } + err = validateContentEncryptionKeySize(cek, cipher) + if err != nil { + return nil, ErrCryptoFailure } - if plaintext == nil { + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + if err != nil { return nil, ErrCryptoFailure } - // The "zip" header parameter may only be present in the protected header. - if comp := obj.protected.getCompression(); comp != "" { - plaintext, err = decompress(comp, plaintext) - if err != nil { - return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) - } + plaintext, err = obj.decompress(plaintext) + if err != nil { + return nil, err } return plaintext, nil @@ -559,31 +570,42 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade var plaintext []byte var headers rawHeader + if len(obj.recipients) == 0 { + return -1, Header{}, nil, errors.New("go-jose/go-jose: no recipients") + } + + // Loop sets `err` in the function scope; don't shadow it. for i, recipient := range obj.recipients { recipientHeaders := obj.mergedHeaders(&recipient) - cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) - if err == nil { - // Found a valid CEK -- let's try to decrypt. - plaintext, err = cipher.decrypt(cek, authData, parts) - if err == nil { - index = i - headers = recipientHeaders - break - } + var cek []byte + cek, err = decrypter.decryptKey(recipientHeaders, &recipient, generator) + if err != nil { + continue } + err = validateContentEncryptionKeySize(cek, cipher) + if err != nil { + continue + } + + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + if err != nil { + continue + } + + index = i + headers = recipientHeaders + break } - if plaintext == nil { + if err != nil { return -1, Header{}, nil, ErrCryptoFailure } - // The "zip" header parameter may only be present in the protected header. - if comp := obj.protected.getCompression(); comp != "" { - plaintext, err = decompress(comp, plaintext) - if err != nil { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) - } + plaintext, err = obj.decompress(plaintext) + if err != nil { + return -1, Header{}, nil, err } sanitized, err := headers.sanitized() @@ -593,3 +615,22 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade return index, sanitized, plaintext, err } + +// decompress decompresses plaintext using the protected "zip" header, if present. +// It returns plaintext unchanged when there is no protected header or "zip" value. +func (obj JSONWebEncryption) decompress(plaintext []byte) ([]byte, error) { + if obj.protected == nil { + return plaintext, nil + } + + comp := obj.protected.getCompression() + if comp == "" { + return plaintext, nil + } + + plaintext, err := decompress(comp, plaintext) + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) + } + return plaintext, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/json/scanner.go b/vendor/github.com/go-jose/go-jose/v4/json/scanner.go index ee6622e8c..3bd29f397 100644 --- a/vendor/github.com/go-jose/go-jose/v4/json/scanner.go +++ b/vendor/github.com/go-jose/go-jose/v4/json/scanner.go @@ -140,6 +140,10 @@ const ( parseArrayValue // parsing array value ) +// This limits the max nesting depth to prevent stack overflow. +// This is permitted by https://tools.ietf.org/html/rfc7159#section-9 +const maxNestingDepth = 10000 + // reset prepares the scanner for use. // It must be called before calling s.step. func (s *scanner) reset() { @@ -170,8 +174,13 @@ func (s *scanner) eof() int { } // pushParseState pushes a new parse state p onto the parse stack. -func (s *scanner) pushParseState(p int) { - s.parseState = append(s.parseState, p) +// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned. +func (s *scanner) pushParseState(c byte, newParseState int, successState int) int { + s.parseState = append(s.parseState, newParseState) + if len(s.parseState) <= maxNestingDepth { + return successState + } + return s.error(c, "exceeded max depth") } // popParseState pops a parse state (already obtained) off the stack @@ -211,12 +220,10 @@ func stateBeginValue(s *scanner, c byte) int { switch c { case '{': s.step = stateBeginStringOrEmpty - s.pushParseState(parseObjectKey) - return scanBeginObject + return s.pushParseState(c, parseObjectKey, scanBeginObject) case '[': s.step = stateBeginValueOrEmpty - s.pushParseState(parseArrayValue) - return scanBeginArray + return s.pushParseState(c, parseArrayValue, scanBeginArray) case '"': s.step = stateInString return scanBeginLiteral diff --git a/vendor/github.com/go-jose/go-jose/v4/jwe.go b/vendor/github.com/go-jose/go-jose/v4/jwe.go index 6102f9100..a51690107 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwe.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwe.go @@ -153,6 +153,10 @@ func ParseEncryptedJSON( keyEncryptionAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption, ) (*JSONWebEncryption, error) { + if len(input) == 0 { + return nil, errEmptyInput + } + var parsed rawJSONWebEncryption err := json.Unmarshal([]byte(input), &parsed) if err != nil { @@ -198,15 +202,6 @@ func (parsed *rawJSONWebEncryption) sanitized( } } - // Note: this must be called _after_ we parse the protected header, - // otherwise fields from the protected header will not get picked up. - var err error - mergedHeaders := obj.mergedHeaders(nil) - obj.Header, err = mergedHeaders.sanitized() - if err != nil { - return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders) - } - if len(parsed.Recipients) == 0 { obj.recipients = []recipientInfo{ { @@ -260,6 +255,15 @@ func (parsed *rawJSONWebEncryption) sanitized( } } + // Note: this must be called _after_ we parse the protected header, + // otherwise fields from the protected header will not get picked up. + mergedHeaders := obj.mergedHeaders(nil) + var err error + obj.Header, err = mergedHeaders.sanitized() + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %w", err) + } + obj.iv = parsed.Iv.bytes() obj.ciphertext = parsed.Ciphertext.bytes() obj.tag = parsed.Tag.bytes() @@ -291,6 +295,10 @@ func ParseEncryptedCompact( var parts [5]string var ok bool + if len(input) == 0 { + return nil, errEmptyInput + } + for i := range 4 { parts[i], input, ok = strings.Cut(input, ".") if !ok { diff --git a/vendor/github.com/go-jose/go-jose/v4/jwk.go b/vendor/github.com/go-jose/go-jose/v4/jwk.go index 164d6a161..8b7aa05ec 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwk.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwk.go @@ -107,7 +107,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { switch key := k.Key.(type) { case ed25519.PublicKey: - raw = fromEdPublicKey(key) + raw, err = fromEdPublicKey(key) case *ecdsa.PublicKey: raw, err = fromEcPublicKey(key) case *rsa.PublicKey: @@ -177,13 +177,29 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { // UnmarshalJSON reads a key from its JSON representation. // // Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values. -func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { +// +// Directly unmarshaling a JWK is not safe for untrusted input. +func (k *JSONWebKey) UnmarshalJSON(data []byte) error { + return k.unmarshalJSON(data, true) +} + +// unmarshalJSON reads a key from its JSON representation. +// +// If allowPrivate is false, a JWK with private key material is rejected and the +// function is safe for use against untrusted input. +func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) { var raw rawJSONWebKey err = json.Unmarshal(data, &raw) if err != nil { return err } + if !allowPrivate && + (raw.K != nil || raw.D != nil || raw.P != nil || raw.Q != nil || + raw.Dp != nil || raw.Dq != nil || raw.Qi != nil) { + return errors.New("go-jose/go-jose: JWK contains private parameters") + } + certs, err := parseCertificateChain(raw.X5c) if err != nil { return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err) @@ -207,21 +223,29 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { case "EC": if raw.D != nil { key, err = raw.ecPrivateKey() - if err == nil { - keyPub = key.(*ecdsa.PrivateKey).Public() + if err != nil { + return err } + keyPub = key.(*ecdsa.PrivateKey).Public() } else { key, err = raw.ecPublicKey() + if err != nil { + return err + } keyPub = key } case "RSA": if raw.D != nil { key, err = raw.rsaPrivateKey() - if err == nil { - keyPub = key.(*rsa.PrivateKey).Public() + if err != nil { + return err } + keyPub = key.(*rsa.PrivateKey).Public() } else { key, err = raw.rsaPublicKey() + if err != nil { + return err + } keyPub = key } case "oct": @@ -229,25 +253,28 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain") } key, err = raw.symmetricKey() + if err != nil { + return err + } case "OKP": if raw.Crv == "Ed25519" { if raw.D != nil { key, err = raw.edPrivateKey() - if err == nil { - keyPub = key.(ed25519.PrivateKey).Public() + if err != nil { + return err } + keyPub = key.(ed25519.PrivateKey).Public() } else { key, err = raw.edPublicKey() + if err != nil { + return err + } keyPub = key } } case "": // kty MUST be present - err = fmt.Errorf("go-jose/go-jose: missing json web key type") - } - - if err != nil { - return + return fmt.Errorf("go-jose/go-jose: missing json web key type") } if key == nil { @@ -388,11 +415,12 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) { func edThumbprintInput(ed ed25519.PublicKey) (string, error) { crv := "Ed25519" - if len(ed) > 32 { - return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)") + err := validateEd25519PublicKey(ed) + if err != nil { + return "", err } return fmt.Sprintf(edThumbprintTemplate, crv, - newFixedSizeBuffer(ed, 32).base64()), nil + newFixedSizeBuffer(ed, ed25519.PublicKeySize).base64()), nil } // Thumbprint computes the JWK Thumbprint of a key using the @@ -493,23 +521,39 @@ func (k *JSONWebKey) Valid() bool { return true } +// MaxRSAModulusBits is the largest RSA modulus size, in bits, that will be +// accepted when parsing a JWK. Applications that need larger keys can raise +// this limit. +// +// If modifying MaxRSAModulusBits, do it before any parsing occurs. +var MaxRSAModulusBits = 4096 + func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) { if key.N == nil || key.E == nil { return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values") } + n := key.N.bigInt() + if n.BitLen() > MaxRSAModulusBits { + return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large") + } + return &rsa.PublicKey{ - N: key.N.bigInt(), + N: n, E: key.E.toInt(), }, nil } -func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey { +func fromEdPublicKey(pub ed25519.PublicKey) (*rawJSONWebKey, error) { + err := validateEd25519PublicKey(pub) + if err != nil { + return nil, err + } return &rawJSONWebKey{ Kty: "OKP", Crv: "Ed25519", X: newBuffer(pub), - } + }, nil } func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey { @@ -604,21 +648,36 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) { return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", ")) } - privateKey := make([]byte, ed25519.PrivateKeySize) - copy(privateKey[0:32], key.D.bytes()) - copy(privateKey[32:], key.X.bytes()) - rv := ed25519.PrivateKey(privateKey) - return rv, nil + publicKey := key.X.bytes() + err := validateEd25519PublicKey(publicKey) + if err != nil { + return nil, err + } + + seed := key.D.bytes() + if len(seed) != ed25519.SeedSize { + return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, wrong length for d") + } + + privateKey := ed25519.NewKeyFromSeed(seed) + derivedPublicKey := privateKey.Public().(ed25519.PublicKey) + if !bytes.Equal(derivedPublicKey, publicKey) { + return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key, x does not match d") + } + + return privateKey, nil } func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) { if key.X == nil { return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value") } - publicKey := make([]byte, ed25519.PublicKeySize) - copy(publicKey[0:32], key.X.bytes()) - rv := ed25519.PublicKey(publicKey) - return rv, nil + publicKey := key.X.bytes() + err := validateEd25519PublicKey(publicKey) + if err != nil { + return nil, err + } + return ed25519.PublicKey(bytes.Clone(publicKey)), nil } func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) { @@ -642,16 +701,23 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) { return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", ")) } + n := key.N.bigInt() + if n.BitLen() > MaxRSAModulusBits { + return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large") + } + + p, q := key.P.bigInt(), key.Q.bigInt() + if p.BitLen()+q.BitLen() > n.BitLen()+1 || new(big.Int).Mul(p, q).Cmp(n) != 0 { + return nil, errors.New("go-jose/go-jose: invalid RSA private key, p * q != n") + } + rv := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ - N: key.N.bigInt(), + N: n, E: key.E.toInt(), }, - D: key.D.bigInt(), - Primes: []*big.Int{ - key.P.bigInt(), - key.Q.bigInt(), - }, + D: key.D.bigInt(), + Primes: []*big.Int{p, q}, } if key.Dp != nil { @@ -669,9 +735,17 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) { } func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) { - raw := fromEdPublicKey(ed25519.PublicKey(ed[32:])) + if len(ed) != ed25519.PrivateKeySize { + return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key length") + } - raw.D = newBuffer(ed[0:32]) + publicKey := ed.Public().(ed25519.PublicKey) + raw, err := fromEdPublicKey(publicKey) + if err != nil { + return nil, err + } + + raw.D = newBuffer(ed.Seed()) return raw, nil } @@ -810,7 +884,7 @@ var ( ErrJWKSKidNotFound = errors.New("go-jose/go-jose: JWK with matching kid not found in JWK Set") ) -func tryJWKS(key interface{}, headers ...Header) (interface{}, error) { +func tryJWKS(key interface{}, header Header) (interface{}, error) { var jwks JSONWebKeySet switch jwksType := key.(type) { @@ -823,16 +897,8 @@ func tryJWKS(key interface{}, headers ...Header) (interface{}, error) { return key, nil } - // Determine the KID to search for from the headers. - var kid string - for _, header := range headers { - if header.KeyID != "" { - kid = header.KeyID - break - } - } - - // If no KID is specified in the headers, reject. + // If no KID is specified in the header, reject. + kid := header.KeyID if kid == "" { return nil, ErrJWKSKidNotFound } @@ -846,3 +912,107 @@ func tryJWKS(key interface{}, headers ...Header) (interface{}, error) { return keys[0].Key, nil } + +// weakEd25519PublicKeys contains the low-order Ed25519 encodings accepted by +// Go's verifier that must not be accepted as JOSE verification keys. +var weakEd25519PublicKeys = map[[ed25519.PublicKeySize]byte]struct{}{ + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }: {}, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + }: {}, + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }: {}, + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + }: {}, + { + 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, + 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, + 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, + 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05, + }: {}, + { + 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, + 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, + 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, + 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85, + }: {}, + { + 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, + 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, + 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, + 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a, + }: {}, + { + 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, + 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, + 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, + 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa, + }: {}, + { + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + }: {}, + { + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }: {}, + { + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + }: {}, + { + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }: {}, + { + 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + }: {}, + { + 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }: {}, +} + +func validateEd25519PublicKey(publicKey ed25519.PublicKey) error { + if len(publicKey) != ed25519.PublicKeySize { + return fmt.Errorf("go-jose/go-jose: invalid Ed25519 public key, wrong length for x") + } + + var encoded [ed25519.PublicKeySize]byte + copy(encoded[:], publicKey) + _, ok := weakEd25519PublicKeys[encoded] + if ok { + return errors.New("go-jose/go-jose: invalid Ed25519 public key, low-order point") + } + + return nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/jws.go b/vendor/github.com/go-jose/go-jose/v4/jws.go index c40bd3ec1..18224df7d 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jws.go +++ b/vendor/github.com/go-jose/go-jose/v4/jws.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "strings" + "unicode" "github.com/go-jose/go-jose/v4/json" ) @@ -89,12 +90,12 @@ func ParseSigned( signature string, signatureAlgorithms []SignatureAlgorithm, ) (*JSONWebSignature, error) { - signature = stripWhitespace(signature) - if strings.HasPrefix(signature, "{") { + trimmed := strings.TrimLeftFunc(signature, unicode.IsSpace) + if strings.HasPrefix(trimmed, "{") { return ParseSignedJSON(signature, signatureAlgorithms) } - return parseSignedCompact(signature, nil, signatureAlgorithms) + return parseSignedCompact(stripWhitespace(signature), nil, signatureAlgorithms) } // ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is @@ -186,6 +187,10 @@ func ParseSignedJSON( input string, signatureAlgorithms []SignatureAlgorithm, ) (*JSONWebSignature, error) { + if len(input) == 0 { + return nil, errEmptyInput + } + var parsed rawJSONWebSignature err := json.Unmarshal([]byte(input), &parsed) if err != nil { @@ -270,17 +275,18 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo Signature: parsed.Signature, } + mergedHeaders := signature.mergedHeaders() + alg := mergedHeaders.getSignatureAlgorithm() + if !containsSignatureAlgorithm(signatureAlgorithms, alg) { + return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) + } + var err error - signature.Header, err = signature.mergedHeaders().sanitized() + signature.Header, err = mergedHeaders.sanitized() if err != nil { return nil, err } - alg := SignatureAlgorithm(signature.Header.Algorithm) - if !containsSignatureAlgorithm(signatureAlgorithms, alg) { - return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) - } - if signature.header != nil { signature.Unprotected, err = signature.header.sanitized() if err != nil { @@ -318,17 +324,20 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo return nil, ErrUnprotectedNonce } - var err error - obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized() - if err != nil { - return nil, err - } + obj.Signatures[i].header = sig.Header - alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm) + mergedHeaders := obj.Signatures[i].mergedHeaders() + alg := mergedHeaders.getSignatureAlgorithm() if !containsSignatureAlgorithm(signatureAlgorithms, alg) { return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms) } + var err error + obj.Signatures[i].Header, err = mergedHeaders.sanitized() + if err != nil { + return nil, err + } + if obj.Signatures[i].header != nil { obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized() if err != nil { @@ -354,7 +363,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo // Copy value of sig original := sig - obj.Signatures[i].header = sig.Header obj.Signatures[i].original = &original } @@ -369,6 +377,10 @@ func parseSignedCompact( payload []byte, signatureAlgorithms []SignatureAlgorithm, ) (*JSONWebSignature, error) { + if len(input) == 0 { + return nil, errEmptyInput + } + protected, s, ok := strings.Cut(input, tokenDelim) if !ok { // no period found return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go b/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go index e73412a39..ec199e29a 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go @@ -24,6 +24,11 @@ import ( "github.com/go-jose/go-jose/v4/json" ) +// maxNumericDate bounds an accepted NumericDate (in seconds). It is far past +// any real timestamp but safely below where an int64 conversion or time.Unix +// would overflow and wrap Time() to a bogus instant. +const maxNumericDate = 1 << 62 + // Claims represents public claim values (as specified in RFC 7519). type Claims struct { Issuer string `json:"iss,omitempty"` @@ -69,6 +74,16 @@ func (n *NumericDate) UnmarshalJSON(b []byte) error { return ErrUnmarshalNumericDate } + // Reject values large enough to overflow either the int64 conversion below + // or time.Unix in Time(). time.Unix adds a ~62e9-second offset internally, + // so a value near the int64 limit wraps and compares as a time in the + // past; for "nbf" that lets a not-yet-valid token pass the not-before + // check instead of being rejected. maxNumericDate (2^62 seconds) is far + // beyond any real timestamp yet safely below both overflow points. + if f >= maxNumericDate || f <= -maxNumericDate { + return ErrUnmarshalNumericDate + } + *n = NumericDate(f) return nil } diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/errors.go b/vendor/github.com/go-jose/go-jose/v4/jwt/errors.go index 27388e544..b7e1e4bfb 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwt/errors.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwt/errors.go @@ -47,7 +47,7 @@ var ErrNotValidYet = errors.New("go-jose/go-jose/jwt: validation failed, token n var ErrExpired = errors.New("go-jose/go-jose/jwt: validation failed, token is expired (exp)") // ErrIssuedInTheFuture indicates that the iat field is in the future. -var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation field, token issued in the future (iat)") +var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation failed, token issued in the future (iat)") // ErrInvalidContentType indicates that token requires JWT cty header. var ErrInvalidContentType = errors.New("go-jose/go-jose/jwt: expected content type to be JWT (cty header)") diff --git a/vendor/github.com/go-jose/go-jose/v4/opaque.go b/vendor/github.com/go-jose/go-jose/v4/opaque.go index 429427232..91a42fc1d 100644 --- a/vendor/github.com/go-jose/go-jose/v4/opaque.go +++ b/vendor/github.com/go-jose/go-jose/v4/opaque.go @@ -46,6 +46,11 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI return recipientSigInfo{}, ErrUnsupportedAlgorithm } + pk := signer.Public() + if pk != nil && !pk.IsPublic() { + return recipientSigInfo{}, ErrNotPublic + } + return recipientSigInfo{ sigAlg: alg, publicKey: signer.Public, diff --git a/vendor/github.com/go-jose/go-jose/v4/shared.go b/vendor/github.com/go-jose/go-jose/v4/shared.go index 35130b3aa..1e8bf8821 100644 --- a/vendor/github.com/go-jose/go-jose/v4/shared.go +++ b/vendor/github.com/go-jose/go-jose/v4/shared.go @@ -80,6 +80,12 @@ var ( // ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose. ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header") + + // errEmptyInput is returned when go-jose was asked to parse an empty string. + errEmptyInput = errors.New("go-jose/go-jose: empty input") + + // ErrNotPublic indicates a private key was passed where a public key was expected + ErrNotPublic = errors.New("go-jose/go-jose: public key was unexpectedly not public") ) // Key management algorithms @@ -291,9 +297,10 @@ func (parsed rawHeader) getEPK() (*JSONWebKey, error) { if v == nil { return nil, nil } - var epk *JSONWebKey - err := json.Unmarshal(*v, &epk) - if err != nil { + // RFC 7518 Section 4.6.1.1: the "epk" header is the ephemeral public key + // of the originator. Reject private key material before parsing it. + epk := new(JSONWebKey) + if err := epk.unmarshalJSON(*v, false); err != nil { return nil, err } return epk, nil @@ -319,20 +326,6 @@ func (parsed rawHeader) getTag() (*byteBuffer, error) { return parsed.getByteBuffer(headerTag) } -// getJWK extracts parsed "jwk" from the raw JSON. -func (parsed rawHeader) getJWK() (*JSONWebKey, error) { - v := parsed[headerJWK] - if v == nil { - return nil, nil - } - var jwk *JSONWebKey - err := json.Unmarshal(*v, &jwk) - if err != nil { - return nil, err - } - return jwk, nil -} - // getCritical extracts parsed "crit" from the raw JSON. If omitted, it // returns an empty slice. func (parsed rawHeader) getCritical() ([]string, error) { @@ -418,10 +411,14 @@ func (parsed rawHeader) sanitized() (h Header, err error) { } switch k { case headerJWK: - var jwk *JSONWebKey - err = json.Unmarshal(*v, &jwk) + // RFC 7515 Section 4.1.3 and RFC 7516 Section 4.1.5: the "jwk" + // header is the public key that corresponds to the key used to + // sign the JWS or encrypt the JWE. Reject private key material + // before parsing it. + jwk := new(JSONWebKey) + err = jwk.unmarshalJSON(*v, false) if err != nil { - err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v)) + err = fmt.Errorf("failed to unmarshal JWK: %w", err) return } h.JSONWebKey = jwk diff --git a/vendor/github.com/go-jose/go-jose/v4/signing.go b/vendor/github.com/go-jose/go-jose/v4/signing.go index 5dbd04c27..5baf75cb2 100644 --- a/vendor/github.com/go-jose/go-jose/v4/signing.go +++ b/vendor/github.com/go-jose/go-jose/v4/signing.go @@ -130,6 +130,8 @@ type payloadVerifier interface { verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error } +var errInvalidVerificationKey = errors.New("go-jose/go-jose: invalid verification key") + type genericSigner struct { recipients []recipientSigInfo nonceSource NonceSource @@ -138,11 +140,21 @@ type genericSigner struct { } type recipientSigInfo struct { - sigAlg SignatureAlgorithm + sigAlg SignatureAlgorithm + // publicKey returns a synthetic JSONWebKey for the signer. + // For opaque signers, it calls OpaqueSigner.Public(). publicKey func() *JSONWebKey signer payloadSigner } +// getPublicKey gets the public key, with a nil check on the func. +func (r recipientSigInfo) getPublicKey() *JSONWebKey { + if r.publicKey == nil { + return nil + } + return r.publicKey() +} + func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey { return func() *JSONWebKey { return jwk @@ -178,14 +190,23 @@ func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) { func newVerifier(verificationKey interface{}) (payloadVerifier, error) { switch verificationKey := verificationKey.(type) { case ed25519.PublicKey: + if len(verificationKey) == 0 { + return nil, errInvalidVerificationKey + } return &edEncrypterVerifier{ publicKey: verificationKey, }, nil case *rsa.PublicKey: + if verificationKey == nil { + return nil, errInvalidVerificationKey + } return &rsaEncrypterVerifier{ publicKey: verificationKey, }, nil case *ecdsa.PublicKey: + if verificationKey == nil { + return nil, errInvalidVerificationKey + } return &ecEncrypterVerifier{ publicKey: verificationKey, }, nil @@ -196,6 +217,9 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) { case JSONWebKey: return newVerifier(verificationKey.Key) case *JSONWebKey: + if verificationKey == nil { + return nil, errInvalidVerificationKey + } return newVerifier(verificationKey.Key) case OpaqueVerifier: return &opaqueVerifier{verifier: verificationKey}, nil @@ -240,18 +264,18 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn if err != nil { return recipientSigInfo{}, err } - if recipient.publicKey != nil && recipient.publicKey() != nil { + if recipientPubKey := recipient.getPublicKey(); recipientPubKey != nil { + // This should be impossible, but let's check anyway. + if !recipientPubKey.IsPublic() { + return recipientSigInfo{}, ErrNotPublic + } + // recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo // was created for the inner key (such as a RSA or ECDSA public key). It contains // the pub key for embedding, but doesn't have extra params like key id. publicKey := signingKey - publicKey.Key = recipient.publicKey().Key + publicKey.Key = recipientPubKey.Key recipient.publicKey = staticPublicKey(&publicKey) - - // This should be impossible, but let's check anyway. - if !recipient.publicKey().IsPublic() { - return recipientSigInfo{}, errors.New("go-jose/go-jose: public key was unexpectedly not public") - } } return recipient, nil } @@ -266,7 +290,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) { headerAlgorithm: string(recipient.sigAlg), } - if recipient.publicKey != nil && recipient.publicKey() != nil { + if recipientPubKey := recipient.getPublicKey(); recipientPubKey != nil { // We want to embed the JWK or set the kid header, but not both. Having a protected // header that contains an embedded JWK while also simultaneously containing the kid // header is confusing, and at least in ACME the two are considered to be mutually @@ -274,11 +298,11 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) { // result of the JOSE spec. We've decided that this library will only include one or // the other to avoid this confusion. // - // See https://github.com/go-jose/go-jose/issues/157 for more context. + // See https://github.com/square/go-jose/issues/157 for more context. if ctx.embedJWK { - protected[headerJWK] = recipient.publicKey() + protected[headerJWK] = recipientPubKey } else { - keyID := recipient.publicKey().KeyID + keyID := recipientPubKey.KeyID if keyID != "" { protected[headerKeyID] = keyID } @@ -390,7 +414,13 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte { // The verificationKey argument must have one of the types allowed for the // verificationKey argument of JSONWebSignature.Verify(). func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error { - key, err := tryJWKS(verificationKey, obj.headers()...) + if len(obj.Signatures) > 1 { + return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one") + } + + signature := obj.Signatures[0] + + key, err := tryJWKS(verificationKey, signature.Header) if err != nil { return err } @@ -399,12 +429,6 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter return err } - if len(obj.Signatures) > 1 { - return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one") - } - - signature := obj.Signatures[0] - if signature.header != nil { // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, // 4.1.11. "crit" (Critical) Header Parameter @@ -432,11 +456,11 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter headers := signature.mergedHeaders() alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) - if err == nil { - return nil + if err != nil { + return ErrCryptoFailure } - return ErrCryptoFailure + return nil } // VerifyMulti validates (one of the multiple) signatures on the object and @@ -467,16 +491,6 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa // The verificationKey argument must have one of the types allowed for the // verificationKey argument of JSONWebSignature.Verify(). func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) { - key, err := tryJWKS(verificationKey, obj.headers()...) - if err != nil { - return -1, Signature{}, err - } - verifier, err := newVerifier(key) - if err != nil { - return -1, Signature{}, err - } - -outer: for i, signature := range obj.Signatures { if signature.header != nil { // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, @@ -484,20 +498,31 @@ outer: // "When used, this Header Parameter MUST be integrity // protected; therefore, it MUST occur only within the JWS // Protected Header." - err = signature.header.checkNoCritical() + err := signature.header.checkNoCritical() if err != nil { - continue outer + continue } } if signature.protected != nil { // Check for only supported critical headers - err = signature.protected.checkSupportedCritical(supportedCritical) + err := signature.protected.checkSupportedCritical(supportedCritical) if err != nil { - continue outer + continue } } + // If the verification key is a JWK Set, pick a key based on this signature's + // "kid" header. If no match, skip this signature. + key, err := tryJWKS(verificationKey, signature.Header) + if err != nil { + continue + } + verifier, err := newVerifier(key) + if err != nil { + continue + } + input, err := obj.computeAuthData(payload, &signature) if err != nil { continue @@ -506,18 +531,12 @@ outer: headers := signature.mergedHeaders() alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) - if err == nil { - return i, signature, nil + if err != nil { + continue } + + return i, signature, nil } return -1, Signature{}, ErrCryptoFailure } - -func (obj JSONWebSignature) headers() []Header { - headers := make([]Header, len(obj.Signatures)) - for i, sig := range obj.Signatures { - headers[i] = sig.Header - } - return headers -} diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric.go b/vendor/github.com/go-jose/go-jose/v4/symmetric.go index f2ff29e17..ca597d6a1 100644 --- a/vendor/github.com/go-jose/go-jose/v4/symmetric.go +++ b/vendor/github.com/go-jose/go-jose/v4/symmetric.go @@ -34,8 +34,8 @@ import ( josecipher "github.com/go-jose/go-jose/v4/cipher" ) -// RandReader is a cryptographically secure random number generator (stubbed out in tests). -var RandReader = rand.Reader +// randReader is a cryptographically secure random number generator (stubbed out in tests). +var randReader = rand.Reader const ( // RFC7518 recommends a minimum of 1,000 iterations: @@ -153,7 +153,7 @@ func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) { // getRandomSalt generates a new salt of the given size. func getRandomSalt(size int) ([]byte, error) { salt := make([]byte, size) - _, err := io.ReadFull(RandReader, salt) + _, err := io.ReadFull(randReader, salt) if err != nil { return nil, err } @@ -198,7 +198,7 @@ func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo // Generate a random key for the given content cipher func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) { key := make([]byte, ctx.size) - _, err := io.ReadFull(RandReader, key) + _, err := io.ReadFull(randReader, key) if err != nil { return nil, rawHeader{}, err } @@ -238,7 +238,7 @@ func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) { // Initialize a new nonce iv := make([]byte, aead.NonceSize()) - _, err = io.ReadFull(RandReader, iv) + _, err = io.ReadFull(randReader, iv) if err != nil { return nil, err } diff --git a/vendor/github.com/go-jose/go-jose/v4/tests.Containerfile b/vendor/github.com/go-jose/go-jose/v4/tests.Containerfile new file mode 100644 index 000000000..b45da99bd --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/tests.Containerfile @@ -0,0 +1,13 @@ +# This containerfile can be used to run the go-jose tests and cli + +FROM golang:1.27-trixie +ENV GOTOOLCHAIN=local + +COPY . /src +WORKDIR /src + +# Install the jose-util cli, which will also build the library +RUN go build -o /usr/local/bin/jose-util ./jose-util + +# By default, run the tests +CMD ["go", "test", "./..."] diff --git a/vendor/github.com/go-jose/go-jose/v4/threat_model.md b/vendor/github.com/go-jose/go-jose/v4/threat_model.md new file mode 100644 index 000000000..c9e66d4c2 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/threat_model.md @@ -0,0 +1,25 @@ +# Threat model + +## What this project does and where untrusted input enters +go-jose is a Go implementation of JOSE family of algorithms. + +It is a library which will parse attacker-controlled data in network-reachable +services. + +## Components that matter most / least +The primary attack surface of interest is parsing and validating JWS, JWE, JWK, +etc when this library is used to implement authentication and authorization. + +## How to exercise it +Testing is primarily done by Golang unit tests, which can be run with the +tests.Container file's default CMD. + +## How you rate severity +Any authentication bypass enabled by a go-jose issue is a high+ severity. + +Denial-of-service attacks are of interest when the functions may be used in a protocol like OIDC, ACME, etc +which could have a server processing attacker-controlled data. + +## Anything to leave alone +Any bug which only affects the jose-util CLI tool, but it can be used to exercise the library. + diff --git a/vendor/golang.org/x/net/http2/flow.go b/vendor/golang.org/x/net/http2/flow.go index b7dbd1869..5066a3991 100644 --- a/vendor/golang.org/x/net/http2/flow.go +++ b/vendor/golang.org/x/net/http2/flow.go @@ -10,6 +10,9 @@ package http2 // flow control window update. const inflowMinRefresh = 4 << 10 +// maxFlowWindow is the maximum size of a flow control window. +const maxFlowWindow = (1 << 31) - 1 + // inflow accounts for an inbound flow control window. // It tracks both the latest window sent to the peer (used for enforcement) // and the accumulated unsent window. @@ -74,47 +77,99 @@ func takeInflows(f1, f2 *inflow, n uint32) bool { return true } -// outflow is the outbound flow control window's size. +// connOutflow is connection-level outbound flow control. +type connOutflow struct { + initial int32 // SETTINGS_INITIAL_WINDOW_SIZE, changes with settings updates + n int32 // connection-level flow control window + flowErr bool // set when a flow control error is encountered +} + +func (f *connOutflow) init() { + f.initial = initialWindowSize // initial stream window size + f.n = initialWindowSize // current connection window size +} + +func (f *connOutflow) changeInitialWindowSize(size int64) bool { + if size > maxFlowWindow { + f.flowErr = true + return false + } + f.initial = int32(size) + return true +} + +func (f *connOutflow) add(n int32) bool { + sum := int64(f.n) + int64(n) + if sum > maxFlowWindow { + f.flowErr = true + return false + } + f.n += n + return true +} + +// outflow is the stream-level outbound flow control window's size. type outflow struct { _ incomparable - // n is the number of DATA bytes we're allowed to send. - // An outflow is kept both on a conn and a per-stream. - n int32 + // delta is the difference between the stream's flow control window and + // the connection's initial window size (conn.initial). + // + // Another view is that delta is the number of flow control bytes provided to this + // stream in WINDOW_UPDATE frames, less the number of bytes sent on the stream. + delta int32 // conn points to the shared connection-level outflow that is - // shared by all streams on that conn. It is nil for the outflow - // that's on the conn directly. - conn *outflow + // shared by all streams on that conn. + conn *connOutflow } -func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf } - -func (f *outflow) available() int32 { - n := f.n - if f.conn != nil && f.conn.n < n { - n = f.conn.n +func (f *outflow) available() (int32, bool) { + if f.conn == nil { + return maxFlowWindow, true // only happens in tests } - return n + if f.conn.flowErr { + // Block all sending once any stream observes a flow control error. + return 0, false + } + n := int64(f.conn.initial) + int64(f.delta) + if n > maxFlowWindow { + f.conn.flowErr = true + return 0, false + } + return min(int32(n), f.conn.n), true } func (f *outflow) take(n int32) { - if n > f.available() { - panic("internal error: took too much") + if f.conn == nil { + return // only happens in tests } - f.n -= n - if f.conn != nil { - f.conn.n -= n + avail, _ := f.available() + if n > avail { + panic("internal error: took too much") } + f.delta -= n + f.conn.n -= n } // add adds n bytes (positive or negative) to the flow control window. // It returns false if the sum would exceed 2^31-1. func (f *outflow) add(n int32) bool { - sum := f.n + n - if (sum > n) == (f.n > 0) { - f.n = sum - return true + if f.conn == nil { + return true // only happens in tests + } + avail := int64(f.conn.initial) + int64(f.delta) + if avail > maxFlowWindow { + // An earlier change to the initial window pushed this stream over the limit. + // This is a connection-level flow control error. + f.conn.flowErr = true + return false } - return false + if avail+int64(n) > maxFlowWindow { + // This update would push the stream over the limit. + // This is a stream-level flow control error. + return false + } + f.delta += n + return true } diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index be75badcc..3363bbafc 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -1711,7 +1711,8 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { mh := &MetaHeadersFrame{ HeadersFrame: hf, } - var remainSize = fr.maxHeaderListSize() + var headersRemainSize = fr.maxHeaderListSize() + var trailersRemainSize = fr.maxHeaderListSize() var sawRegular bool var invalid error // pseudo header field errors @@ -1743,14 +1744,29 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { return } - size := hf.Size() - if size > remainSize { + var remainSize *uint32 + var size uint32 + if hf.Name == "trailer" { + remainSize = &trailersRemainSize + fieldCount := strings.Count(hf.Value, ",") + 1 + // Rather than actually constructing hpack.HeaderField for each + // trailer field and cumulatively adding its Size, just do the math + // manually to avoid unnecessary work. This does make it so + // whitespaces after comma are counted against the budget, but that + // should be innocuous. + size = uint32(len(hf.Value)-fieldCount+1) + uint32(fieldCount)*hpack.HeaderField{}.Size() + } else { + remainSize = &headersRemainSize + size = hf.Size() + } + if size > *remainSize { hdec.SetEmitEnabled(false) mh.Truncated = true - remainSize = 0 + headersRemainSize = 0 + trailersRemainSize = 0 return } - remainSize -= size + *remainSize -= size mh.Fields = append(mh.Fields, hf) }) @@ -1766,10 +1782,10 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { // skip parsing the fragment and close the connection. // // "Too much" is either any CONTINUATION frame after we've already - // exceeded the max header list size (in which case remainSize is 0), - // or a frame whose encoded size is more than twice the remaining - // header list bytes we're willing to accept. - if int64(len(frag)) > int64(2*remainSize) { + // exceeded the max header list size (if so, both budgets are 0), or a + // frame whose encoded size is more than twice the remaining header + // list bytes we're willing to accept. + if int64(len(frag)) > 2*int64(headersRemainSize+trailersRemainSize) { if VerboseLogs { log.Printf("http2: header list too large") } diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index a7d2053b6..a6cd66226 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -280,7 +280,6 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" advMaxStreams: conf.MaxConcurrentStreams, - initialStreamSendWindowSize: initialWindowSize, initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxFrameSize: initialMaxFrameSize, pingTimeout: conf.PingTimeout, @@ -320,7 +319,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // These start at the RFC-specified defaults. If there is a higher // configured value for inflow, that will be updated when we send a // WINDOW_UPDATE shortly after sending SETTINGS. - sc.flow.add(initialWindowSize) + sc.flow.init() sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) @@ -427,7 +426,7 @@ type serverConn struct { wroteFrameCh chan frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes bodyReadCh chan bodyReadMsg // from handlers -> serve serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop - flow outflow // conn-wide (not stream-specific) outbound flow control + flow connOutflow // conn-wide (not stream-specific) outbound flow control inflow inflow // conn-wide inbound flow control tlsState *tls.ConnectionState // shared by all handlers, like net/http remoteAddrStr string @@ -441,6 +440,9 @@ type serverConn struct { sawFirstSettings bool // got the initial SETTINGS frame after the preface needToSendSettingsAck bool unackedSettings int // how many SETTINGS have we sent without ACKs? + pendingEncoderTableSize bool // peer changed SETTINGS_HEADER_TABLE_SIZE; apply to hpackEncoder before the next frame write + encoderTableSizeMin uint32 // smallest SETTINGS_HEADER_TABLE_SIZE since the last apply + encoderTableSize uint32 // latest SETTINGS_HEADER_TABLE_SIZE queuedControlFrames int // control frames in the writeSched queue clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit) advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client @@ -451,7 +453,6 @@ type serverConn struct { maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes streams map[uint32]*stream unstartedHandlers []unstartedHandler - initialStreamSendWindowSize int32 initialStreamRecvWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) @@ -521,7 +522,8 @@ type stream struct { // immutable: sc *serverConn id uint32 - body *pipe // non-nil if expecting DATA frames + body *pipe // non-nil if expecting DATA frames + reqBody *requestBody cw closeWaiter // closed wait stream transitions to closed state ctx context.Context cancelCtx func() @@ -1172,6 +1174,16 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) { sc.writingFrame = true sc.needsFrameFlush = true + if sc.pendingEncoderTableSize { + // hpackEncoder may be in use by writeFrameAsync, so SETTINGS + // changes to it are deferred until no frame is being written. + // Replaying the smallest size before the latest one keeps the + // encoder's view identical to having applied every change + // (RFC 7541, Section 4.2). + sc.pendingEncoderTableSize = false + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSizeMin) + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSize) + } if wr.write.staysWithinBuffer(sc.bw.Available()) { sc.writingFrameAsync = false err := wr.write.writeFrame(sc) @@ -1271,6 +1283,11 @@ func (sc *serverConn) scheduleFrameWrite() { } sc.inFrameScheduleLoop = true for !sc.writingFrameAsync { + if sc.flow.flowErr && (!sc.inGoAway || sc.goAwayCode == ErrCodeNo) { + sc.inGoAway = true + sc.needToSendGoAway = true + sc.goAwayCode = ErrCodeFlowControl + } if sc.needToSendGoAway { sc.needToSendGoAway = false sc.startFrameWrite(FrameWriteRequest{ @@ -1294,6 +1311,9 @@ func (sc *serverConn) scheduleFrameWrite() { sc.startFrameWrite(wr) continue } + if sc.flow.flowErr { + continue + } } if sc.needsFrameFlush { sc.startFrameWrite(FrameWriteRequest{write: flushFrameWriter{}}) @@ -1526,6 +1546,10 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error { return nil } if !st.flow.add(int32(f.Increment)) { + if st.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return sc.countError("bad_flow", ConnectionError(ErrCodeFlowControl)) + } return sc.countError("bad_flow", streamError(f.StreamID, ErrCodeFlowControl)) } default: // connection-level flow control @@ -1584,10 +1608,6 @@ func (sc *serverConn) closeStream(st *stream, err error) { } } if p := st.body; p != nil { - // Return any buffered unread bytes worth of conn-level flow control. - // See golang.org/issue/16481 - sc.sendWindowUpdate(nil, p.Len()) - p.CloseWithError(err) } if e, ok := err.(StreamError); ok { @@ -1641,7 +1661,12 @@ func (sc *serverConn) processSetting(s Setting) error { } switch s.ID { case SettingHeaderTableSize: - sc.hpackEncoder.SetMaxDynamicTableSize(s.Val) + // Applied by startFrameWrite; see comment there. + if !sc.pendingEncoderTableSize || s.Val < sc.encoderTableSizeMin { + sc.encoderTableSizeMin = s.Val + } + sc.encoderTableSize = s.Val + sc.pendingEncoderTableSize = true case SettingEnablePush: sc.pushEnabled = s.Val != 0 case SettingMaxConcurrentStreams: @@ -1672,28 +1697,14 @@ func (sc *serverConn) processSetting(s Setting) error { func (sc *serverConn) processSettingInitialWindowSize(val uint32) error { sc.serveG.check() - // Note: val already validated to be within range by - // processSetting's Valid call. - - // "A SETTINGS frame can alter the initial flow control window - // size for all current streams. When the value of - // SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST - // adjust the size of all stream flow control windows that it - // maintains by the difference between the new value and the - // old value." - old := sc.initialStreamSendWindowSize - sc.initialStreamSendWindowSize = int32(val) - growth := int32(val) - old // may be negative - for _, st := range sc.streams { - if !st.flow.add(growth) { - // 6.9.2 Initial Flow Control Window Size - // "An endpoint MUST treat a change to - // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow - // control window to exceed the maximum size as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR." - return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) - } + if !sc.flow.changeInitialWindowSize(int64(val)) { + // 6.9.2 Initial Flow Control Window Size + // "An endpoint MUST treat a change to + // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow + // control window to exceed the maximum size as a + // connection error (Section 5.4.1) of type + // FLOW_CONTROL_ERROR." + return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) } return nil } @@ -1966,7 +1977,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { if st.reqTrailer != nil { st.trailer = make(http.Header) } - st.body = req.Body.(*requestBody).pipe // may be nil + st.body = st.reqBody.pipe // may be nil st.declBodyBytes = req.ContentLength handler := sc.handler.ServeHTTP @@ -1989,7 +2000,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) } - return sc.scheduleHandler(id, rw, req, handler) + return sc.scheduleHandler(st, rw, req, handler) } func (sc *serverConn) upgradeRequest(req *http.Request) { @@ -2102,7 +2113,6 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority } st.cw.Init() st.flow.conn = &sc.flow // link to conn-level counter - st.flow.add(sc.initialStreamSendWindowSize) st.inflow.init(sc.initialStreamRecvWindowSize) if sc.hs.WriteTimeout > 0 { st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) @@ -2184,7 +2194,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res } else { req.ContentLength = -1 } - req.Body.(*requestBody).pipe = &pipe{ + st.reqBody.pipe = &pipe{ b: &dataBuffer{expected: req.ContentLength}, } } @@ -2204,7 +2214,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol)) } - body := &requestBody{ + st.reqBody = &requestBody{ conn: sc, stream: st, needsContinue: res.NeedsContinue, @@ -2220,7 +2230,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server ProtoMinor: 0, TLS: tlsState, Host: rp.Authority, - Body: body, + Body: st.reqBody, Trailer: res.Trailer, }).WithContext(st.ctx) rw := sc.newResponseWriter(st, req) @@ -2244,11 +2254,12 @@ type unstartedHandler struct { rw *responseWriter req *http.Request handler func(http.ResponseWriter, *http.Request) + body *pipe } // scheduleHandler starts a handler goroutine, // or schedules one to start as soon as an existing handler finishes. -func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { +func (sc *serverConn) scheduleHandler(st *stream, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { sc.serveG.check() maxHandlers := sc.advMaxStreams if sc.curHandlers < maxHandlers { @@ -2260,10 +2271,11 @@ func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req * return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm)) } sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{ - streamID: streamID, + streamID: st.id, rw: rw, req: req, handler: handler, + body: st.body, }) return nil } @@ -2277,6 +2289,10 @@ func (sc *serverConn) handlerDone() { u := sc.unstartedHandlers[i] if sc.streams[u.streamID] == nil { // This stream was reset before its goroutine had a chance to start. + if u.body != nil { + u.body.BreakWithError(errClosedBody) + sc.sendWindowUpdate(nil, u.body.Len()) + } continue } if sc.curHandlers >= maxHandlers { @@ -2298,6 +2314,12 @@ func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler didPanic := true defer func() { rw.rws.stream.cancelCtx() + if b := rw.rws.stream.reqBody; b != nil { + // Closing the body refunds flow control credit for any unconsumed data. + // (reqBody is nil for Upgrade: h2c requests, but those do not use flow + // control for the request body.) + b.Close() + } if req.MultipartForm != nil { req.MultipartForm.RemoveAll() } @@ -2396,7 +2418,7 @@ func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int, err error) { func (sc *serverConn) noteBodyRead(st *stream, n int) { sc.serveG.check() sc.sendWindowUpdate(nil, n) // conn-level - if st.state != stateHalfClosedRemote && st.state != stateClosed { + if st != nil && st.state != stateHalfClosedRemote && st.state != stateClosed { // Don't send this WINDOW_UPDATE if the stream is closed // remotely. sc.sendWindowUpdate(st, n) @@ -2444,6 +2466,9 @@ func (b *requestBody) Close() error { b.closeOnce.Do(func() { if b.pipe != nil { b.pipe.BreakWithError(errClosedBody) + if unread := b.pipe.Len(); unread > 0 { + b.conn.noteBodyReadFromHandler(nil, unread, errClosedBody) + } } }) return nil @@ -2461,9 +2486,6 @@ func (b *requestBody) Read(p []byte) (n int, err error) { if err == io.EOF { b.sawEOF = true } - if b.conn == nil { - return - } b.conn.noteBodyReadFromHandler(b.stream, n, err) return } diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 088eb9bef..d894c6019 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -27,6 +27,7 @@ import ( "net/http" "net/http/httptrace" "net/textproto" + "slices" "strconv" "sync" "sync/atomic" @@ -203,7 +204,7 @@ type ClientConn struct { mu sync.Mutex // guards following cond *sync.Cond // hold mu; broadcast on flow/closed changes - flow outflow // our conn-level flow control quota (cs.outflow is per stream) + flow connOutflow // our conn-level flow control quota (cs.outflow is per stream) inflow inflow // peer's conn-level flow control doNotReuse bool // whether conn is marked to not be reused for any future requests closing bool @@ -227,7 +228,6 @@ type ClientConn struct { maxConcurrentStreams uint32 peerMaxHeaderListSize uint64 peerMaxHeaderTableSize uint32 - initialWindowSize uint32 initialStreamRecvWindowSize int32 readIdleTimeout time.Duration pingTimeout time.Duration @@ -472,7 +472,6 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook readerDone: make(chan struct{}), nextStreamID: 1, maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests, @@ -497,7 +496,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook } cc.cond = sync.NewCond(&cc.mu) - cc.flow.add(int32(initialWindowSize)) + cc.flow.init() // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. @@ -876,6 +875,33 @@ func (cc *ClientConn) sendGoAway() error { return nil } +func (cc *ClientConn) goAwayAndClose(code ErrCode) { + cc.mu.Lock() + closed := cc.closed + cc.closing = true + cc.closed = true + cc.mu.Unlock() + if closed { + return + } + if f := cc.fr.countError; f != nil { + f(fmt.Sprintf("conn_close_error_%s", code.stringToken())) + } + done := make(chan struct{}) + go func() { + defer close(done) + cc.wmu.Lock() + cc.fr.WriteGoAway(0, code, nil) + cc.bw.Flush() + cc.wmu.Unlock() + }() + select { + case <-done: + case <-time.After(250 * time.Millisecond): + } + cc.closeForError(fmt.Errorf("http2: closing connection with %v", code)) +} + // closes the client connection immediately. In-flight requests are interrupted. // err is sent to streams. func (cc *ClientConn) closeForError(err error) { @@ -1319,7 +1345,9 @@ func (cs *clientStream) cleanupWriteRequest(err error) { } if err != nil { cs.abortStream(err) // possibly redundant, but harmless - if cs.sentHeaders { + if ce, ok := err.(ConnectionError); ok { + cc.goAwayAndClose(ErrCode(ce)) + } else if cs.sentHeaders { if se, ok := err.(StreamError); ok { if se.Cause != errFromPeer { cc.writeStreamReset(cs.ID, se.Code, false, err) @@ -1648,8 +1676,12 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) return 0, errRequestCanceled default: } - if a := cs.flow.available(); a > 0 { - take := a + avail, ok := cs.flow.available() + if !ok { + return 0, ConnectionError(ErrCodeFlowControl) + } + if avail > 0 { + take := avail if int(take) > maxBytes { take = int32(maxBytes) // can't truncate int; take is int32 @@ -1710,8 +1742,7 @@ type resAndError struct { // requires cc.mu be held. func (cc *ClientConn) addStreamLocked(cs *clientStream) { - cs.flow.add(int32(cc.initialWindowSize)) - cs.flow.setConnFlow(&cc.flow) + cs.flow.conn = &cc.flow cs.inflow.init(cc.initialStreamRecvWindowSize) cs.ID = cc.nextStreamID cc.nextStreamID += 2 @@ -2095,18 +2126,42 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra return nil, nil } + // Delete various headers that might mess up framing for HTTP/1. This is + // not a problem for HTTP/2, but someone might use HTTP/2 transport as a + // reverse proxy which forwards the response to an HTTP/1 client. Our + // HTTP/1 client transport will properly reject improper headers such as + // multiple conflicting Content-Length headers, but other implementations + // might not. + // TODO: just reject such responses? We deleted them for compatibility + // since this was done in a security fix (go.dev/issue/81115). However, + // rejecting them seems entirely reasonable and relatively safe. + + // Connection-specific header fields must not appear in an HTTP/2 message, + // and any message containing them is malformed. RFC 9113, Section 8.2.2. + for _, k := range connHeaders { + delete(res.Header, k) + } res.ContentLength = -1 - if clens := res.Header["Content-Length"]; len(clens) == 1 { - if cl, err := strconv.ParseUint(clens[0], 10, 63); err == nil { - res.ContentLength = int64(cl) + if clens, ok := res.Header["Content-Length"]; ok { + // Repeated Content-Length values may be collapsed into one only if + // they are identical per RFC 9110 Section 8.6. + // No need to trim whitespace, HTTP/2 header values must not have + // extraneous whitespace per RFC 9113 Section 8.2.1. + // Non-canonical headers are already rejected by our framer at this + // point. + conflicting := slices.ContainsFunc(clens[1:], func(clen string) bool { + return clen != clens[0] + }) + cl, err := strconv.ParseUint(clens[0], 10, 63) + if conflicting || err != nil { + delete(res.Header, "Content-Length") } else { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. + res.Header["Content-Length"] = clens[:1] + res.ContentLength = int64(cl) } - } else if len(clens) > 1 { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } else if f.StreamEnded() && !cs.isHead { + } + + if res.ContentLength < 0 && f.StreamEnded() && !cs.isHead { res.ContentLength = 0 } @@ -2417,6 +2472,10 @@ const ( func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream { rl.cc.mu.Lock() defer rl.cc.mu.Unlock() + return rl.streamByIDLocked(id, headerOrData) +} + +func (rl *clientConnReadLoop) streamByIDLocked(id uint32, headerOrData bool) *clientStream { if headerOrData { // Work around an unfortunate gRPC behavior. // See comment on ClientConn.rstStreamPingsBlocked for details. @@ -2499,24 +2558,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { case SettingMaxHeaderListSize: cc.peerMaxHeaderListSize = uint64(s.Val) case SettingInitialWindowSize: - // Values above the maximum flow-control - // window size of 2^31-1 MUST be treated as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR. - if s.Val > math.MaxInt32 { + if !cc.flow.changeInitialWindowSize(int64(s.Val)) { return ConnectionError(ErrCodeFlowControl) } - - // Adjust flow control of currently-open - // frames by the difference of the old initial - // window size and this one. - delta := int32(s.Val) - int32(cc.initialWindowSize) - for _, cs := range cc.streams { - cs.flow.add(delta) - } cc.cond.Broadcast() - - cc.initialWindowSize = s.Val case SettingHeaderTableSize: cc.henc.SetMaxDynamicTableSize(s.Val) cc.peerMaxHeaderTableSize = s.Val @@ -2558,29 +2603,30 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { cc := rl.cc - cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame) - if f.StreamID != 0 && cs == nil { - return nil - } - cc.mu.Lock() defer cc.mu.Unlock() - - fl := &cc.flow - if cs != nil { - fl = &cs.flow - } - if !fl.add(int32(f.Increment)) { - // For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR - if cs != nil { + if f.StreamID == 0 { + if !cc.flow.add(int32(f.Increment)) { + return ConnectionError(ErrCodeFlowControl) + } + } else { + cs := rl.streamByIDLocked(f.StreamID, notHeaderOrDataFrame) + if cs == nil { + return nil + } + if !cs.flow.add(int32(f.Increment)) { + if cs.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return ConnectionError(ErrCodeFlowControl) + } + // For stream, the sender sends RST_STREAM with + // an error code of FLOW_CONTROL_ERROR. rl.endStreamErrorLocked(cs, StreamError{ StreamID: f.StreamID, Code: ErrCodeFlowControl, }) return nil } - - return ConnectionError(ErrCodeFlowControl) } cc.cond.Broadcast() return nil diff --git a/vendor/golang.org/x/net/http2/transport_wrap.go b/vendor/golang.org/x/net/http2/transport_wrap.go index 34dfb3cca..eeaa95f03 100644 --- a/vendor/golang.org/x/net/http2/transport_wrap.go +++ b/vendor/golang.org/x/net/http2/transport_wrap.go @@ -119,33 +119,27 @@ type http2TransportContextKey struct{} // DialFromContext dials a new connection using the http2.Transport's DialTLS/DialTLSContext. func (t transportConfig) DialFromContext(ctx context.Context, network, address string) (net.Conn, error) { - if ctx.Value(http2TransportContextKey{}) == nil { + dial, _ := ctx.Value(http2TransportContextKey{}).(*transportRoundTripState) + if dial == nil { // We're being called from a RoundTrip that did not start with an http2.Transport. // Use the http.Transport's dialer. return nil, errors.ErrUnsupported } - - tlsConf := t.t.TLSClientConfig - if tlsConf == nil { - tlsConf = &tls.Config{} - } else { - tlsConf = tlsConf.Clone() - } - if !slices.Contains(tlsConf.NextProtos, "h2") { - tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) - } - if tlsConf.ServerName == "" { - host, _, err := net.SplitHostPort(address) - if err == nil { - tlsConf.ServerName = host - } - } - return t.t.dialTLS(ctx, network, address, tlsConf) + return t.t.dialForRoundTrip(ctx, dial, network, address) } type transportInternal struct { initOnce sync.Once lazyt1 *http.Transport + + dialMu sync.Mutex + dials map[string]*transportDialState +} + +type transportDialState struct { + dialc chan struct{} // close when dial returns + dialErr error // dial result, set before dialc is closed + rtdonec chan struct{} // closed when RoundTrip initiating the dial returns } func (t *Transport) init() *http.Transport { @@ -167,6 +161,52 @@ func (t *Transport) configure(t1 *http.Transport) { } } +// transportRoundTripState is the state of the dial for an http2.Transport.RoundTrip. +type transportRoundTripState struct { + mu sync.Mutex + rtdone bool // set when RoundTrip returns + rtdonec chan struct{} // closed when RoundTrip returns + gotconnc chan struct{} // closed when GotConn hook is called +} + +// startDial is called when a dial starts. +// +// It returns done=true if the RoundTrip has already returned, +// in which case we should skip dialing. +func (dial *transportRoundTripState) startDial() (gotconnc chan struct{}, done bool) { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.rtdone { + return nil, true + } + if dial.rtdonec == nil { + dial.rtdonec = make(chan struct{}) + } + dial.gotconnc = make(chan struct{}) + return dial.gotconnc, false +} + +// gotConn is called when RoundTrip gets a connection. +// This may happen multiple times per RoundTrip, if a request is retried. +func (dial *transportRoundTripState) gotConn() { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.gotconnc != nil { + close(dial.gotconnc) + dial.gotconnc = nil + } +} + +// roundTripDone is called when RoundTrip returns. +func (dial *transportRoundTripState) roundTripDone() { + dial.mu.Lock() + defer dial.mu.Unlock() + dial.rtdone = true + if dial.rtdonec != nil { + close(dial.rtdonec) + } +} + func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { t1 := t.init() @@ -186,12 +226,146 @@ func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res // Both http.Transport and http2.Transport allow the user to provide a custom // dial function, and historically you only get the dial function from the // Transport you're calling RoundTrip on. - ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t) + // + // In addition, http2.Transport coalesces dials, which http.Transport historically + // has not. + dial := &transportRoundTripState{} + defer dial.roundTripDone() + ctx := context.WithValue(req.Context(), http2TransportContextKey{}, dial) + ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + GotConn: func(httptrace.GotConnInfo) { + // Tell dialForRoundTrip that we have received a connection. + dial.gotConn() + }, + }) req = req.WithContext(ctx) - return t1.RoundTrip(req) } +var errCoalescedDialAbandoned = errors.New("http2: abandoned coalesced dial") + +const coalescedDialRetryTimeout = 50 * time.Millisecond + +// dialForRoundTrip is called (indirectly) by net/http when dialing a new connection +// for a request call which originated as an http2.Transport.RoundTrip call. +// +// It uses the http2.Transport's Dial hooks and coalesces dials. +func (t *Transport) dialForRoundTrip(ctx context.Context, dial *transportRoundTripState, network, address string) (net.Conn, error) { + gotconnc, rtdone := dial.startDial() + if rtdone { + // RoundTrip returned, no need for this dial to proceed. + return nil, errCoalescedDialAbandoned + } + + var state *transportDialState + for { + // The first dial to an address registers itself with t.dials. + // When the dial finishes, it records the outcome and removes itself from t.dials + // so future dials will start a new round of coalescing. + // + // Subsequent dials observe that a dial is in progress, and skip dialing. + t.dialMu.Lock() + if t.dials == nil { + t.dials = make(map[string]*transportDialState) + } + leader := t.dials[address] + if leader == nil { + // No entry in t.dials to coalesce with. Add ourselves as leader. + state = &transportDialState{ + dialc: make(chan struct{}), + rtdonec: dial.rtdonec, + } + t.dials[address] = state + } + t.dialMu.Unlock() + if leader == nil { + // We are the leader, so we should dial for real. + break + } + + // Coalesce with a previous dial. + var ( + done = false + leaderdialc = leader.dialc + leaderrtdonec = leader.rtdonec + timerc <-chan time.Time + ) + for !done { + if leaderdialc == nil && leaderrtdonec == nil && timerc == nil { + // The leader finished dialing, and its RoundTrip finished. + // + // There are several possibilities, which reduce to: + // - The RoundTrip we are dialing for is about to receive + // a connection (possibly the one the leader just dialed), + // but we haven't observed it yet. + // - There's something wrong with the leader's connection, + // such as a TLS handshake failure. + // + // We have no good way to distinguish between these cases. + // + // Wait a short time for the leader's connection (or some + // other usable connection) to be delivered to our RoundTrip. + // If it is not, start a dial of our own. + timerc = time.After(coalescedDialRetryTimeout) + } + select { + case <-leaderdialc: + // The leader's dial completed. + if leader.dialErr != nil { + return nil, leader.dialErr + } + leaderdialc = nil + case <-leaderrtdonec: + // The leader's RoundTrip completed. + leaderrtdonec = nil + case <-timerc: + // The leader's dial and RoundTrip completed, and some time + // has passed. Assume we're not getting a connection and + // restart the dial process. + done = true + case <-dial.rtdonec: + // Our RoundTrip returned. + return nil, errCoalescedDialAbandoned + case <-gotconnc: + // Our RoundTrip got a connection. + return nil, errCoalescedDialAbandoned + case <-ctx.Done(): + // net/http abandoned the dial. + return nil, ctx.Err() + } + } + } + + // Dial for real. + tlsConf := t.TLSClientConfig + if tlsConf == nil { + tlsConf = &tls.Config{} + } else { + tlsConf = tlsConf.Clone() + } + if !slices.Contains(tlsConf.NextProtos, "h2") { + tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) + } + if tlsConf.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err == nil { + tlsConf.ServerName = host + } + } + nc, err := t.dialTLS(ctx, network, address, tlsConf) + + // Remove this dial from t.dials, so future dial attempts will not coalesce with it. + t.dialMu.Lock() + delete(t.dials, address) + t.dialMu.Unlock() + + // Notify any dial coalesced with this one that we are done. + state.dialErr = err + close(state.dialc) + + return nc, err +} + func (t *Transport) closeIdleConnections() { t1 := t.init() t1.CloseIdleConnections() diff --git a/vendor/golang.org/x/net/http2/writesched.go b/vendor/golang.org/x/net/http2/writesched.go index 36ad5e32b..f1903a8c6 100644 --- a/vendor/golang.org/x/net/http2/writesched.go +++ b/vendor/golang.org/x/net/http2/writesched.go @@ -79,10 +79,11 @@ func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteReque } // Might need to split after applying limits. - allowed := wr.stream.flow.available() - if n < allowed { - allowed = n + avail, ok := wr.stream.flow.available() + if !ok { + return empty, empty, 0 } + allowed := min(n, avail) if wr.stream.sc.maxFrameSize < allowed { allowed = wr.stream.sc.maxFrameSize } diff --git a/vendor/golang.org/x/net/internal/httpcommon/gzip.go b/vendor/golang.org/x/net/internal/httpcommon/gzip.go new file mode 100644 index 000000000..819dd650c --- /dev/null +++ b/vendor/golang.org/x/net/internal/httpcommon/gzip.go @@ -0,0 +1,132 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "compress/flate" + "compress/gzip" + "errors" + "io" + "io/fs" + "sync" +) + +var errConcurrentRead = errors.New("http: concurrent read on response body") + +// incomparable is a zero-width, non-comparable type. Adding it to a struct +// makes that struct also non-comparable, and generally doesn't add +// any size (as long as it's first). +type incomparable [0]func() + +// GzipReader wraps a response body so it can lazily +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. +type GzipReader struct { + _ incomparable + Body io.ReadCloser // underlying Response.Body + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader // stores gzip reader from the pool between reads + zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close +} + +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { return 0, io.EOF } +func (eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = eofReader{} + zr.Reset(r) + gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *GzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr != nil { + return nil, gz.zerr + } + if gz.zr == nil { + // gzipPoolGet might block indefinitely since it reads the gzip header. + // Therefore, drop mu temporarily when using gzipPoolGet. + // We set zerr to errConcurrentRead to prevent concurrent read + // even when mu is temporarily dropped. + gz.zerr = errConcurrentRead + gz.mu.Unlock() + zr, err := gzipPoolGet(gz.Body) + gz.mu.Lock() + // Guard against Close being called while gzipPoolGet is running. + if gz.zerr != errConcurrentRead { + if zr != nil { + gzipPoolPut(zr) + } + return nil, gz.zerr + } + gz.zr, gz.zerr = zr, err + if gz.zerr != nil { + return nil, gz.zerr + } + } + ret := gz.zr + gz.zr, gz.zerr = nil, errConcurrentRead + return ret, nil +} + +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *GzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == errConcurrentRead { + gz.zr, gz.zerr = zr, nil + } else { // fs.ErrClosed + gzipPoolPut(zr) + } +} + +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *GzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + gzipPoolPut(gz.zr) + gz.zr = nil + } + gz.zerr = fs.ErrClosed +} + +func (gz *GzipReader) Read(p []byte) (n int, err error) { + zr, err := gz.acquire() + if err != nil { + return 0, err + } + defer gz.release(zr) + + return zr.Read(p) +} + +func (gz *GzipReader) Close() error { + gz.close() + + return gz.Body.Close() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0df00bd4c..46ea59cd0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -243,7 +243,7 @@ github.com/felixge/httpsnoop ## explicit; go 1.23 github.com/fsnotify/fsnotify github.com/fsnotify/fsnotify/internal -# github.com/go-jose/go-jose/v4 v4.1.4 +# github.com/go-jose/go-jose/v4 v4.1.6 ## explicit; go 1.24.0 github.com/go-jose/go-jose/v4 github.com/go-jose/go-jose/v4/cipher @@ -613,7 +613,7 @@ golang.org/x/crypto/ssh/internal/bcrypt_pbkdf # golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa ## explicit; go 1.25.0 golang.org/x/exp/slices -# golang.org/x/net v0.59.0 +# golang.org/x/net v0.60.0 ## explicit; go 1.26.0 golang.org/x/net/http/httpguts golang.org/x/net/http2 From e8dd0b07edb81f08555b173571b7258c6d992b43 Mon Sep 17 00:00:00 2001 From: abtreece Date: Wed, 28 Oct 2026 08:52:41 -0500 Subject: [PATCH 2/2] docs: add PR number to CHANGELOG entry --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index eb3471fe7..f11d12570 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ ### [Unreleased] -* chore: bump Go toolchain to 1.26.10 and github.com/go-jose/go-jose/v4, golang.org/x/net to patch HIGH CVEs flagged by Trivy (stdlib CVE-2026-111943, CVE-2026-78667, CVE-2026-78669, CVE-2026-97031; x/net CVE-2026-78669; go-jose CVE-2026-116587) +* chore: bump Go toolchain to 1.26.10 and github.com/go-jose/go-jose/v4, golang.org/x/net to patch HIGH CVEs flagged by Trivy (stdlib CVE-2026-111943, CVE-2026-78667, CVE-2026-78669, CVE-2026-97031; x/net CVE-2026-78669; go-jose CVE-2026-116587) (#643) * chore: bump Go toolchain to 1.26.8 and golang.org/x/net, golang.org/x/text, golang.org/x/crypto, google.golang.org/grpc to patch HIGH CVEs flagged by Trivy (stdlib CVE-2026-27145, CVE-2026-33818, CVE-2026-39821, CVE-2026-39822, CVE-2026-42504, CVE-2026-46600, CVE-2026-56853, CVE-2026-56858, CVE-2026-56859, CVE-2026-56860, CVE-2026-56862; x/net CVE-2026-25681, CVE-2026-27136, CVE-2026-33814; x/text CVE-2026-56852; grpc GHSA-hrxh-6v49-42gf, CVE-2026-84304, CVE-2026-84445) (#636) * chore(deps): bump codecov/codecov-action from 6 to 7 (#629) * chore(deps): bump actions/setup-go from 6 to 7 (#633)