From e50ea9fb3ec97600c0fe5e28a7eb1b115d6172a4 Mon Sep 17 00:00:00 2001 From: bintangakbarRK Date: Thu, 6 Aug 2026 16:19:37 +0700 Subject: [PATCH 1/2] fix: bump quic-go fork pin to include the CVE-2025-59530 fix The replace directive pins the QUIC stack to a fork based on quic-go v0.45, which predates the fix for CVE-2025-59530 (GHSA-47m2-4cr7-mhcw): a server that sends HANDSHAKE_DONE before the handshake actually finishes makes the client drop Handshake keys before Initial keys, and the next undecryptable packet trips a panic in the connection path (remote client crash). The fork pin is bumped to a commit that backports quic-go#5354 onto the exact previously-pinned fork state (verified: the only source change is the 7-line drop-initial-keys fix in connection.go). --- go.mod | 2 +- go.sum | 4 ++-- vendor/github.com/quic-go/quic-go/connection.go | 7 +++++++ vendor/modules.txt | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f995381af15..004a948aaba 100644 --- a/go.mod +++ b/go.mod @@ -109,4 +109,4 @@ replace github.com/prometheus/golang_client => github.com/prometheus/golang_clie replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 // This fork is based on quic-go v0.45 -replace github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd +replace github.com/quic-go/quic-go => github.com/stareezy-1/quic-go v0.0.0-20260806091559-d3ee2305a943 diff --git a/go.sum b/go.sum index 1252fd3b834..95274a8d200 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd h1:VdYI5zFQ2h1/qzoC6rhyPx479bkF8i177Qpg4Q2n1vk= -github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/cloudflare/backoff v0.0.0-20240920015135-e46b80a3a7d0 h1:pRcxfaAlK0vR6nOeQs7eAEvjJzdGXl8+KaBlcvpQTyQ= github.com/cloudflare/backoff v0.0.0-20240920015135-e46b80a3a7d0/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= @@ -189,6 +187,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stareezy-1/quic-go v0.0.0-20260806091559-d3ee2305a943 h1:67fF8SB/JinU0QavrnZ8I3+J6GMtK2b1I2IMQfUdF3U= +github.com/stareezy-1/quic-go v0.0.0-20260806091559-d3ee2305a943/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/vendor/github.com/quic-go/quic-go/connection.go b/vendor/github.com/quic-go/quic-go/connection.go index 413266a5c12..b055666ccef 100644 --- a/vendor/github.com/quic-go/quic-go/connection.go +++ b/vendor/github.com/quic-go/quic-go/connection.go @@ -851,6 +851,13 @@ func (s *connection) handleHandshakeComplete(now time.Time) error { } func (s *connection) handleHandshakeConfirmed(now time.Time) error { + // Drop initial keys. + // On the client side, this should have happened when sending the first Handshake packet, + // but this is not guaranteed if the server misbehaves. + // See CVE-2025-59530 for more details. + if err := s.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil { + return err + } if err := s.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil { return err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 2b4b8d61c88..e5dbcd235b6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -203,7 +203,7 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/quic-go/quic-go v0.52.0 => github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd +# github.com/quic-go/quic-go v0.52.0 => github.com/stareezy-1/quic-go v0.0.0-20260806091559-d3ee2305a943 ## explicit; go 1.23 github.com/quic-go/quic-go github.com/quic-go/quic-go/internal/ackhandler @@ -571,4 +571,4 @@ zombiezen.com/go/capnproto2/std/capnp/rpc # github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d # github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1 # gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 -# github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd +# github.com/quic-go/quic-go => github.com/stareezy-1/quic-go v0.0.0-20260806091559-d3ee2305a943 From 679ac06878667d071aa929016f396dba616c7eb6 Mon Sep 17 00:00:00 2001 From: bintangakbarRK Date: Thu, 6 Aug 2026 16:22:06 +0700 Subject: [PATCH 2/2] fix: release the app token lock when the fetch completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getToken acquired the per-app lock file and held it for the whole process lifetime. When access login fetches a token, verifies it at the edge, and finds it invalid (e.g. with Enable Binding Cookie enabled), it removes the token and fetches again in the same process — acquireLockFile then found its own lock (own PID alive), printed 'Another cloudflared process (pid N)' with its own PID, and hung until lockTimeout. Release the app and org token locks when the fetch completes. The lock still serializes concurrent transfers across processes; a same-process re-fetch can now acquire it again. releaseLockFile only removes the lock if it is still owned by this process (PID and start time match), so a lock reclaimed by another process is left alone. Closes #1692. --- token/lockfile_test.go | 33 +++++++++++++++++++++++++++++++++ token/token.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/token/lockfile_test.go b/token/lockfile_test.go index eec54276f7e..4cf6c6de5d6 100644 --- a/token/lockfile_test.go +++ b/token/lockfile_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -112,3 +113,35 @@ func TestNewSelfLockContent(t *testing.T) { assert.Equal(t, int32(os.Getpid()), content.PID) // nolint: gosec assert.Positive(t, content.StartTime) } + +func TestReleaseLockFileAllowsReacquire(t *testing.T) { + // Regression test for the `access login` self-deadlock (issue #1692): + // the token lock was held for the whole process lifetime, so a second + // fetch in the same process (after an invalid token was removed) waited + // forever on its own lock. After release, the same process can acquire + // the lock again. + log := zerolog.Nop() + path := filepath.Join(t.TempDir(), "token") + + require.NoError(t, acquireLockFile(path, &log)) + releaseLockFile(path, &log) + require.NoError(t, acquireLockFile(path, &log), "must be able to re-acquire after release") + releaseLockFile(path, &log) +} + +func TestReleaseLockFileDoesNotRemoveOtherProcessLock(t *testing.T) { + // Releasing must not remove a lock owned by another process. + log := zerolog.Nop() + dir := t.TempDir() + path := filepath.Join(dir, "token") + lockPath := path + ".lock" + + other := lockContent{PID: 2147483647, StartTime: 1000000000000} + data, err := json.Marshal(other) + require.NoError(t, err) + require.NoError(t, os.WriteFile(lockPath, data, 0600)) + + releaseLockFile(path, &log) + _, err = os.Stat(lockPath) + require.NoError(t, err, "another process's lock must not be removed") +} diff --git a/token/token.go b/token/token.go index fab8d247532..9e0cb9fd95d 100644 --- a/token/token.go +++ b/token/token.go @@ -164,6 +164,32 @@ func acquireLockFile(tokenPath string, log *zerolog.Logger) error { } } +// releaseLockFile removes the lock file for the given token path if it is +// still owned by this process. Locks are held for the duration of a single +// token acquisition; releasing them lets a later re-fetch in the same +// process (e.g. after an invalid token was removed by the caller) acquire +// the lock again instead of deadlocking on its own lock. +func releaseLockFile(tokenPath string, log *zerolog.Logger) { + lockPath := tokenPath + ".lock" + _, content, err := isLockFileStale(lockPath) + if err != nil { + return + } + self, err := newSelfLockContent() + if err != nil { + return + } + if content.PID != self.PID || content.StartTime != self.StartTime { + // Another process reclaimed the lock; leave it alone. + return + } + if err := os.Remove(lockPath); err != nil && !os.IsNotExist(err) { + log.Debug().Err(err).Str("path", lockPath).Msg("failed to release lock file") + return + } + log.Debug().Str("path", lockPath).Msg("lock file released") +} + // readAuthURL reads the auth URL companion file for the given token path. // Returns the URL string, or empty string if the file doesn't exist or // can't be read. @@ -290,6 +316,10 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, autoClose boo if err = acquireLockFile(appTokenPath, log); err != nil { return "", errors.Wrap(err, "failed to acquire app token lock") } + // Release the lock when this fetch completes: holding it for the whole + // process lifetime makes a same-process re-fetch (after an invalid token + // was removed) deadlock on its own lock (issue #1692). + defer releaseLockFile(appTokenPath, log) // check to see if another process has gotten a token while we waited for the lock if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil { @@ -308,6 +338,7 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, autoClose boo if err = acquireLockFile(orgTokenPath, log); err != nil { return "", errors.Wrap(err, "failed to acquire org token lock") } + defer releaseLockFile(orgTokenPath, log) // check if an org token has been created since the lock was acquired orgToken, err = GetOrgTokenIfExists(appInfo.AuthDomain) }