Skip to content

crypto: cryptocustomrand change breaks boringcrypto #76672

@stapelberg

Description

@stapelberg

Go version

go version go1.26-devel_32a9804c7b Wed Dec 3 00:22:35 2025 -0800 X:boringcrypto linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/usr/local/google/home/stapelberg/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/usr/local/google/home/stapelberg/.config/go/env'
GOEXE=''
GOEXPERIMENT='boringcrypto'
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3365639457=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/usr/local/google/home/stapelberg/tmp/boring/go.mod'
GOMODCACHE='/usr/local/google/home/stapelberg/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/usr/local/google/home/stapelberg/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/google/home/stapelberg/upstream-go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/usr/local/google/home/stapelberg/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/google/home/stapelberg/upstream-go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.26-devel_32a9804c7b Wed Dec 3 00:22:35 2025 -0800 X:boringcrypto'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

A number of Google-internal tests started failing after https://go.dev/cl/724480 landed (b/464496549), panicing in crypto/internal/boring.UnreachableExceptTests().

I created a reproducer program (not a test!) which results in the same panic:

package main

import (
	"crypto/rand"
	"crypto/x509"
	"encoding/pem"
	"log"
	"math/big"
)

// Test key, generated using: openssl ecparam -name prime256v1 -genkey -noout
var ecdsaKey = `
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIOtosx1zBk7wTk10pFGUValwxaaCE2dVz7QkiRM68xcIoAoGCCqGSM49
AwEHoUQDQgAEomkkF/PRrQVH2LjK/pjygJQZ78I0GU+QpeAMBN98vvUptkpLf8Nk
9+2Tf5YuWP7fjHeQSImRKdfdZV6MkgaUOw==
-----END EC PRIVATE KEY-----
`

func main() {
	block, _ := pem.Decode([]byte(ecdsaKey))
	if block == nil {
		log.Fatal("failed to decode PEM block containing private key")
	}

	priv, err := x509.ParseECPrivateKey(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}

	templ := x509.Certificate{
		SerialNumber: big.NewInt(1),
	}

	_, err = x509.CreateCertificate(rand.Reader, &templ, &templ, priv.Public(), priv)
	if err != nil {
		log.Fatal(err)
	}
}

What did you see happen?

When I run the above reproducer program with Go at commit 32a9804, it works:

% ~/upstream-go/bin/go version
go version go1.26-devel_32a9804c7b Wed Dec 3 00:22:35 2025 -0800 linux/amd64
% ~/upstream-go/bin/go run cryptrand.go                 
%

But when I build with GOEXPERIMENT=boringcrypto, I get:

% ~/upstream-go/bin/go version         
go version go1.26-devel_32a9804c7b Wed Dec 3 00:22:35 2025 -0800 X:boringcrypto linux/amd64
% ~/upstream-go/bin/go run cryptrand.go                 
boringcrypto: unexpected code execution in /tmp/go-build3034746071/b001/exe/cryptrand
panic: boringcrypto: invalid code execution

goroutine 1 [running]:
crypto/internal/boring.UnreachableExceptTests()
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/internal/boring/boring.go:57 +0xc5
crypto/ecdsa.SignASN1({0x71f7d8, 0x8faf40}, 0x6ee49f64750, {0x6ee49e9c480, 0x20, 0x20})
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/ecdsa/ecdsa.go:390 +0x65
crypto/ecdsa.(*PrivateKey).Sign(0x6ee49f64750, {0x71f6d8?, 0x71df38?}, {0x6ee49e9c480, 0x20, 0x20}, {0x71f738?, 0x728288?})
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/ecdsa/ecdsa.go:328 +0x65
crypto.SignMessage({0x7f532e4acdc0, 0x6ee49f64750}, {0x71f6d8, 0x71df38}, {0x6ee49f6a140, 0x9a, 0x9a}, {0x71f738, 0x728288})
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/crypto.go:254 +0x13b
crypto/x509.signTBS({0x6ee49f6a140, 0x9a, 0x9a}, {0x7f532e4acdc0, 0x6ee49f64750}, 0xa, {0x71f6d8, 0x71df38})
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/x509/x509.go:1599 +0x257
crypto/x509.CreateCertificate({0x71f6d8, 0x71df38}, 0x6ee49f79988, 0x6ee49f79988, {0x6fe320, 0x6ee49f64750}, {0x701d40?, 0x6ee49f64750})
	/usr/local/google/home/stapelberg/upstream-go/src/crypto/x509/x509.go:1800 +0xd9d
main.main()
	/usr/local/google/home/stapelberg/tmp/boring/cryptrand.go:35 +0x19d
exit status 2

Running the reproducer with GODEBUG=cryptocustomrand=1 makes it work.

What did you expect to see?

I expected no panic :)

Looking at the code, I wonder if the r == boring.RandReader check no longer triggers (by accident?)

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.release-blocker

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions