From d552dd4deecbef1fe66dc72b14f30e6098744bc8 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:36:23 +0000 Subject: [PATCH 1/3] fix: eliminate serverInfo map data race and XMSSMT nil-deref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit computePowNonces mutated serverInfo.RequiredProofOfWork in place under the lock, but getServerInfo hands callers a struct copy that shares that map and handlers read it without the lock — a concurrent map read/write that the race detector flags and that can crash the process with "fatal error: concurrent map read and map write". Build the map fresh and publish it by replacing the reference under the lock so readers always see an immutable snapshot. processAtumRequest logged but swallowed errors from CreateXMSSMTTimestamp, leaving resp.Stamp nil and then dereferencing it at resp.Stamp.ServerUrl. Return an error response to the client instead. Add a -race regression test for the serverInfo map access. Co-Authored-By: Claude Opus 4.8 --- main.go | 25 ++++++++++++++++++++----- main_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index c8cd29a..6463cf3 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ type AlgPkPair struct { type yamlBinary []byte const ErrorUnsupportedSigAlg atum.ErrorCode = "unsupported signature algorithm" +const ErrorInternalError atum.ErrorCode = "internal error" func (yb *yamlBinary) UnmarshalText(buf []byte) error { buf, err := base64.StdEncoding.DecodeString(string(buf)) @@ -232,23 +233,31 @@ func computePowNonces() { h.Read(nonce) log.Printf("Proof of work nonce: %s", base64.StdEncoding.EncodeToString(nonce)) - serverInfoLock.Lock() - defer serverInfoLock.Unlock() - + // Build a fresh map and publish it by replacing the reference rather than + // mutating the existing one in place. getServerInfo hands out a struct + // copy that shares this map, and handlers read it without holding the + // lock; mutating in place would race with those reads (concurrent map + // read and map write). A wholesale replacement under the lock means + // readers always observe an immutable snapshot. + powReqs := make(map[atum.SignatureAlgorithm]pow.Request) if conf.Ed25519PowDifficulty != nil { - serverInfo.RequiredProofOfWork[atum.Ed25519] = pow.Request{ + powReqs[atum.Ed25519] = pow.Request{ Difficulty: *conf.Ed25519PowDifficulty, Nonce: nonce, Alg: pow.Sha2BDay, } } if conf.XMSSMTPowDifficulty != nil { - serverInfo.RequiredProofOfWork[atum.XMSSMT] = pow.Request{ + powReqs[atum.XMSSMT] = pow.Request{ Difficulty: *conf.XMSSMTPowDifficulty, Nonce: nonce, Alg: pow.Sha2BDay, } } + + serverInfoLock.Lock() + defer serverInfoLock.Unlock() + serverInfo.RequiredProofOfWork = powReqs } func powNonceRevolver() { @@ -343,7 +352,13 @@ func processAtumRequest(req atum.Request) (resp atum.Response) { ts, err := stamper.CreateXMSSMTTimestamp( xmssmtSk, xmssmtPk, tsTime, req.Nonce) if err != nil { + // ts is nil on error; falling through would nil-deref at + // resp.Stamp.ServerUrl below. Return an error to the client + // instead of crashing the request. log.Printf("CreateXMSSMTTimestamp: %v", err) + resp.SetError(ErrorInternalError) + resp.Info = info + return } resp.Stamp = ts default: diff --git a/main_test.go b/main_test.go index 7b0b049..75d314c 100644 --- a/main_test.go +++ b/main_test.go @@ -4,9 +4,13 @@ import ( "bytes" "encoding/json" "io" + "log" "net/http" "net/http/httptest" + "os" + "sync" "testing" + "time" "github.com/bwesterb/go-atum" "github.com/bwesterb/go-pow" @@ -79,3 +83,50 @@ func TestSignSmoke(t *testing.T) { t.Fatal("signature did not verify") } } + +// TestServerInfoConcurrentAccess guards against the data race between +// powNonceRevolver (which calls computePowNonces) and the request/serverInfo +// handlers (which read serverInfo.RequiredProofOfWork via getServerInfo). +// Run with -race: before the fix, the in-place map mutation in +// computePowNonces races with the map reads here. +func TestServerInfoConcurrentAccess(t *testing.T) { + log.SetOutput(io.Discard) + defer log.SetOutput(os.Stderr) + + conf = Conf{PowWindow: time.Hour, PowKey: make([]byte, 32)} + diff := uint32(16) + conf.Ed25519PowDifficulty = &diff + conf.XMSSMTPowDifficulty = &diff + serverInfo = atum.ServerInfo{ + RequiredProofOfWork: make(map[atum.SignatureAlgorithm]pow.Request), + } + + const iters = 200 + var wg sync.WaitGroup + + // Writers: mimic the nonce revolver. + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < iters; j++ { + computePowNonces() + } + }() + } + + // Readers: mimic serverInfoHandler / processAtumRequest reading the map. + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < iters; j++ { + info := getServerInfo() + _, _ = json.Marshal(info) + _ = info.RequiredProofOfWork[atum.XMSSMT] + } + }() + } + + wg.Wait() +} From df80fc0cf9afee6f0fc7cc1b7c1f8db20138d849 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:33:42 +0000 Subject: [PATCH 2/3] fix: bump golang.org/x/crypto to v0.52.0 to clear critical CVEs The Delivery pipeline's "Scan Image" (Anchore/Grype, --fail-on critical) step failed: the built image (scratch + static Go binary) carried 7 critical advisories from golang.org/x/crypto v0.51.0, all fixed in v0.52.0 (GO-2026-5005/5006/5017/5019/5020/5021/5023). - Bump golang.org/x/crypto v0.51.0 -> v0.52.0 (pulls golang.org/x/sys v0.44.0 -> v0.45.0). x/crypto is a direct import (ed25519, sha3). - Bump toolchain go1.25.10 -> go1.25.11 to clear the remaining High stdlib advisories (CVE-2026-42504, GO-2026-5038); below the critical gate but makes the published image vulnerability-clean. Verified locally: go build, go vet, go test -race ./... all pass, and grype --fail-on critical --only-fixed reports no vulnerabilities on the rebuilt static binary. Co-Authored-By: Claude Opus 4.8 --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index dec7031..6efdf16 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/bwesterb/atumd go 1.25.0 -toolchain go1.25.10 +toolchain go1.25.11 require ( github.com/bwesterb/go-atum v1.1.5 @@ -10,7 +10,7 @@ require ( github.com/bwesterb/go-xmssmt v1.5.2 github.com/go-chi/cors v1.2.2 github.com/prometheus/client_golang v1.23.2 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -34,6 +34,6 @@ require ( github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index f448c0a..d635af1 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -109,8 +109,8 @@ golang.org/x/sys v0.0.0-20220519141025-dcacdad47464/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 13cd3c23c0f71cf09f2b1a29f0ac63323762e9fc Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Fri, 12 Jun 2026 10:52:14 +0200 Subject: [PATCH 3/3] ci: run tests with -race to guard the serverInfo data race --- .github/workflows/delivery.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/delivery.yml b/.github/workflows/delivery.yml index 208d480..74f52f6 100644 --- a/.github/workflows/delivery.yml +++ b/.github/workflows/delivery.yml @@ -31,7 +31,7 @@ jobs: go-version-file: go.mod - name: Run tests - run: go test ./... + run: go test -race ./... publish-docker-image: needs: test