Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto tls panic when pulling container images #191

Open
sipasing opened this issue May 15, 2024 · 14 comments
Open

crypto tls panic when pulling container images #191

sipasing opened this issue May 15, 2024 · 14 comments

Comments

@sipasing
Copy link
Contributor

sipasing commented May 15, 2024

When we try to pull container images in fips enabled environments, we see a crypto tls panic.

panic: tls: HKDF-Extract invocation failed unexpectedly
goroutine 221 [running]:
crypto/tls.(*cipherSuiteTLS13).extract(0x5651729bbfe0, {0x0?, 0x1?, 0x1?}, {0x0, 0x0, 0x0})
	go-go1.22.2/src/crypto/tls/key_schedule.go:100 +0x285
crypto/tls.(*clientHandshakeStateTLS13).establishHandshakeKeys(0xc0003bdbd0)
	go-go1.22.2/src/crypto/tls/handshake_client_tls13.go:388 +0xd3
crypto/tls.(*clientHandshakeStateTLS13).handshake(0xc0003bdbd0)
	go-go1.22.2/src/crypto/tls/handshake_client_tls13.go:90 +0x2bb
crypto/tls.(*Conn).clientHandshake(0xc000634708, {0x5651722c75f8, 0xc000696f00})
	go-go1.22.2/src/crypto/tls/handshake_client.go:265 +0x594
crypto/tls.(*Conn).handshakeContext(0xc000634708, {0x5651722c75c0, 0xc000810db0})
	go-go1.22.2/src/crypto/tls/conn.go:1553 +0x3cb
crypto/tls.(*Conn).HandshakeContext(...)
	go-go1.22.2/src/crypto/tls/conn.go:1493
net/http.(*persistConn).addTLS.func2()
	go-go1.22.2/src/net/http/transport.go:1573 +0x6e
created by net/http.(*persistConn).addTLS in goroutine 217
	go-go1.22.2/src/net/http/transport.go:1569 +0x309

I see the hashToMD function not able to find a match in the hash.Hash type switch here. and returning nil.

I instrumented a debug print statement inside the hashToMD function.

fmt.Printf("actual h type=%T\n", h)

and i see h type=*sha256.digest

But before the img pull command I also see cases of h type=*openssl.sha256Hash which seems to pass the type switch in hashToMD function. Not sure which type is expected or both.

If I skip the type switch and hard code "return cryptoHashToMD(crypto.SHA256)" , the panic disappears.
The code function flow seems to be
ExtractHKDF -> newHKDF -> hashToMD -> h type=*sha256.digest -> no match in type switch.

@sipasing
Copy link
Contributor Author

I have confirmed that exact same panic also occurs with go v1.21.x.

@ueno
Copy link
Collaborator

ueno commented May 15, 2024

Thank you for looking into it! I tried that with a simple TLS client (compiled with the Go compiler built from the go1.22.2-1-openssl-fips, but it works under under GOLANG_FIPS=1 OPENSSL_FORCE_FIPS_MODE=1:

sh-5.1$ GOLANG_FIPS=1 OPENSSL_FORCE_FIPS_MODE=1 ./client --server-name fedoraproject.org --address fedoraproject.org:443
2024/05/15 08:12:04 Connected to 152.19.134.198:443 with: VersionTLS13 [TLS_AES_256_GCM_SHA384]

ExtractHKDF -> newHKDF -> hashToMD -> h type=*sha256.digest -> no match in type switch.

Would it be possible to set a breakpoint in crypto.sha256.New() to see how it ends up with a non-boring hash implementation?

@sipasing
Copy link
Contributor Author

Would it be possible to set a breakpoint in crypto.sha256.New() to see how it ends up with a non-boring hash implementation?

I can definitely try the non-boring path. But first, let me share a script that helps reproduce the error in fips enabled env. see attached.

You would need a linux amd64 machine (fedora/rh/centos shud work). Please let me know if the script doesn't work in ur env and i would be happy to make necessary modifications to it. Please rm the txt extension, github wont let me attach otherwise.

repro-tls-panic.sh.txt

@derekparker
Copy link
Contributor

@sipasing thanks for opening this issue and for the reproducer! As I mentioned over slack (and @ueno mentioned on this thread) somehow a non-FIPS (non-boring) hash is being selected and passed down into the FIPS module. Setting a breakpoint as @ueno mentioned could be helpful for us to understand how we're getting into this situation.

@sipasing
Copy link
Contributor Author

Got it. On it. Do u have any preference on gdb or delve ? Or a simple debug print is sufficient ?

@derekparker
Copy link
Contributor

Got it. On it. Do u have any preference on gdb or delve ? Or a simple debug print is sufficient ?

Delve is preferred, a print statement could work as well.

@sipasing
Copy link
Contributor Author

sipasing commented May 15, 2024

I added these print statement inside src/crypto/sha256/sha256.go ?

 func New() hash.Hash {
+   fmt.Println("crypt.sha256.New")
+     _, file, no, ok := runtime.Caller(1)
+    if ok {
+        fmt.Printf("called from %s#%d\n", file, no)
+    }
        if boring.Enabled() {
+        fmt.Println("boring.Enabled")
                return boring.NewSHA256()
        }
+
+    fmt.Println("new digest")
        d := new(digest)
        d.Reset()

and i get

quay.io/quay/busybox:latest: resolving      |--------------------------------------| 
elapsed: 0.3 s               total:   0.0 B (0.0 B/s)                                         
crypt.sha256.New
called from go-go1.21.7/src/vendor/github.com/golang-fips/openssl-fips/openssl/hmac.go#64
boring.Enabled

right before the panic.

For delve, I would need to attach to the go binary and then execute the ctr img pull command ? . working on it.

@sipasing
Copy link
Contributor Author

To add to previous comment, hmac.go#64 is

func NewHMAC(h func() hash.Hash, key []byte) hash.Hash {                        
 64     ch := h()                                                                   
 65     md := hashToMD(ch)  

@sipasing
Copy link
Contributor Author

sipasing commented May 15, 2024

to find the caller of NewHMAC, I further added another printf inside hmac.go#64

 func NewHMAC(h func() hash.Hash, key []byte) hash.Hash {
+   fmt.Println("openssl.NewHMAC")
+     _, file, no, ok := runtime.Caller(1)
+    if ok {
+        fmt.Printf("called from %s#%d\n", file, no)
+    }

and i get

openssl.NewHMAC
called from go-go1.21.7/src/crypto/hmac/hmac.go#131
crypt.sha256.New
called from go-go1.21.7/src/vendor/github.com/golang-fips/openssl-fips/openssl/hmac.go#70
boring.Enabled

hmac.go#131 is

129 func New(h func() hash.Hash, key []byte) hash.Hash {                            
130     if boring.Enabled() {                                                       
131         hm := boring.NewHMAC(h, key)

@sipasing
Copy link
Contributor Author

sipasing commented May 15, 2024

Delve output

dlv exec ./ctr -- image pull quay.io/quay/busybox:latest
Type 'help' for list of commands.
(dlv) b myLoopingStuff src/crypto/sha256/sha256.go:153
Breakpoint myLoopingStuff set at 0x55e7df6e8346 for crypto/sha256.New() go-go1.21.7/src/crypto/sha256/sha256.go:153
(dlv) continue
quay.io/quay/busybox:latest: resolving      |--------------------------------------| 
elapsed: 0.1 s               total:   0.0 B (0.0 B/s)                                         
quay.io/quay/busybox:latest: resolving      |--------------------------------------| 
elapsed: 0.2 s               total:   0.0 B (0.0 B/s)                                         
src/crypto/hmac/hmac.New
called from go-go1.21.7/src/crypto/tls/prf.go#28
boring.Enabled
openssl.NewHMAC
called from go-go1.21.7/src/crypto/hmac/hmac.go#139
> [myLoopingStuff] crypto/sha256.New() go-go1.21.7/src/crypto/sha256/sha256.go:153 (hits goroutine(183):1 total:1) (PC: 0x55e7df6e8346)
   148:	// New returns a new hash.Hash computing the SHA256 checksum. The Hash
   149:	// also implements encoding.BinaryMarshaler and
   150:	// encoding.BinaryUnmarshaler to marshal and unmarshal the internal
   151:	// state of the hash.
   152:	func New() hash.Hash {
=> 153:	   fmt.Println("crypt.sha256.New")
   154:	     _, file, no, ok := runtime.Caller(1)
   155:	    if ok {
   156:	        fmt.Printf("called from %s#%d\n", file, no)
   157:	    }
   158:		if boring.Enabled() {
(dlv) bt
 0  0x000055e7df6e8346 in crypto/sha256.New
    at go-go1.21.7/src/crypto/sha256/sha256.go:153
 1  0x000055e7df5f00a6 in vendor/github.com/golang-fips/openssl-fips/openssl.NewHMAC
    at go-go1.21.7/src/vendor/github.com/golang-fips/openssl-fips/openssl/hmac.go:70
 2  0x000055e7df72f4e2 in crypto/hmac.New
    at go-go1.21.7/src/crypto/hmac/hmac.go:139
 3  0x000055e7df788278 in crypto/tls.pHash
    at go-go1.21.7/src/crypto/tls/prf.go:28
 4  0x000055e7df788ec9 in crypto/tls.prf12.func1
    at go-go1.21.7/src/crypto/tls/prf.go:73
 5  0x000055e7df789644 in crypto/tls.extMasterFromPreMasterSecret
    at go-go1.21.7/src/crypto/tls/prf.go:123
 6  0x000055e7df767c26 in crypto/tls.(*clientHandshakeState).doFullHandshake
    at go-go1.21.7/src/crypto/tls/handshake_client.go:658
 7  0x000055e7df76642a in crypto/tls.(*clientHandshakeState).handshake
    at go-go1.21.7/src/crypto/tls/handshake_client.go:495
 8  0x000055e7df764006 in crypto/tls.(*Conn).clientHandshake
    at go-go1.21.7/src/crypto/tls/handshake_client.go:276
 9  0x000055e7df797085 in crypto/tls.(*Conn).clientHandshake-fm
    at <autogenerated>:1
10  0x000055e7df760212 in crypto/tls.(*Conn).handshakeContext
    at go-go1.21.7/src/crypto/tls/conn.go:1552
11  0x000055e7df75fb37 in crypto/tls.(*Conn).HandshakeContext
    at go-go1.21.7/src/crypto/tls/conn.go:1492
12  0x000055e7df893cbd in net/http.(*persistConn).addTLS.func2
    at go-go1.21.7/src/net/http/transport.go:1555
13  0x000055e7df281661 in runtime.goexit
    at go-go1.21.7/src/runtime/asm_amd64.s:1650

@sipasing
Copy link
Contributor Author

Also right before the panic, dlv shows hash from github.com/minio/sha256-simd.digest.

(dlv) b go-go1.21.7/src/vendor/github.com/golang-fips/openssl-fips/openssl/hmac.go:23
 18:	)
    19:	
    20:	// hashToMD converts a hash.Hash implementation from this package
    21:	// to a BoringCrypto *C.GO_EVP_MD.
    22:	func hashToMD(h hash.Hash) *C.GO_EVP_MD {
=>  23:		switch h.(type) {
    24:		case *sha1Hash:
    25:			return C._goboringcrypto_EVP_sha1()
    26:		case *sha224Hash:
    27:			return C._goboringcrypto_EVP_sha224()
    28:		case *sha256Hash:
(dlv) p h
hash.Hash(*github.com/minio/sha256-simd.digest) *{
	h: [8]uint32 [1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],
	x: [64]uint8 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
	nx: 0,
	len: 0,}
(dlv) c

@derekparker
Copy link
Contributor

Ah, ok. So given that last comment it seems that the hash is of type *github.com/minio/sha256-simd.digest which is not produced within the bounds of the FIPS module (e.g. not from OpenSSL), so that's why the panic is happening. The error message could be better here, but the behavior is correct.

@dbenoit17
Copy link
Collaborator

dbenoit17 commented May 21, 2024

Overall this is not something we can support. All crypto, including hashes, must be generated by the OpenSSL FIPS module. Upstream Go boringcrypto has the same constraints. I'd suggest to open a bug against the library or application that pulls in minio/sha256-simd and suggest to add Go boringcrypto support via build tags similarly to how it has been implemented in minio-go, for example:

minio/minio-go#1697
minio/minio-go#1700

@sipasing
Copy link
Contributor Author

Thanks for the pointers. I am still investigating ways in which I can manually replace hashes generated by minio/sha256-simd in containerd (the application) with OpenSSL hashes.

I kept this ticket open because I wanted to improve on the error messages. Currently there is a panic generated , but maybe we can improve it to also say that the hash is not supported or something to that effect ??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants