Summary:
A memory leak bug (CVE-2025-25199) was discovered in Microsoft’s go-crypto-winnative—the Go crypto backend for Windows using Cryptography API: Next Generation (CNG). Before the fix in commit f49c8e1379ea4b147d5bff1b3be5bff45792e41, the cng.TLS1PRF function failed to release key handles, causing a small memory leak each time it was called.
##### This post breaks down what happened, shows vulnerable and patched code, and demonstrates how you can exploit the vulnerability.
What Is go-crypto-winnative?
go-crypto-winnative is a Go library that provides native cryptographic support on Windows by wrapping the CNG API (also known as Windows Cryptography API: Next Generation).
Many Go applications on Windows using this package for advanced crypto operations rely on it for safe, fast, native algorithms—especially when interacting with the TLS stack.
The Vulnerability: Memory Leak in TLS1PRF
The specific problem was with the TLS1PRF function. Each time you called this function, it would allocate a CNG key handle, but forgot to release it after use.
Why Does It Matter?
A "small" memory leak might not sound serious, but in long-running services (think web servers or clients using TLS extensively), these unreleased handles add up. Eventually, you’ll see memory usage climb, possibly causing performance degradation or even resource exhaustion.
Here’s a simplified version of how the vulnerable function worked
import "github.com/microsoft/go-crypto-winnative/cng"
func usePRF(secret, label, seed []byte) ([]byte, error) {
key, err := cng.CreateKey(secret)
if err != nil {
return nil, err
}
// DO: some PRF step with key
output, err := cng.TLS1PRF(key, label, seed)
if err != nil {
return nil, err
}
// Oops: key.Close() (i.e., handle release) missing!
return output, nil
}
The missing key.Close() (or however the handle should be cleaned up) caused the leak. Internally, Windows keeps the key handle open, using memory for each call.
The patch adds explicit handle release after PRF is done
func usePRF(secret, label, seed []byte) ([]byte, error) {
key, err := cng.CreateKey(secret)
if err != nil {
return nil, err
}
defer key.Close() // Now, the key handle will always be released
output, err := cng.TLS1PRF(key, label, seed)
if err != nil {
return nil, err
}
return output, nil
}
See the fix in context:
➡️ Fix Commit on GitHub
Exploit Details: How to Trigger the Leak
You can trigger this bug simply by calling TLS1PRF many times in a loop. This simulates what a real application might do during many TLS handshakes.
Sample PoC
package main
import (
"fmt"
"github.com/microsoft/go-crypto-winnative/cng"
)
func main() {
secret := []byte("mysecret")
label := []byte("PRF label")
seed := []byte("PRF seed")
for i := ; i < 100000; i++ {
// The following would leak a key handle per call (on vulnerable versions)
key, _ := cng.CreateKey(secret)
cng.TLS1PRF(key, label, seed)
// key.Close() not called -- leaks the key handle!
// Add fmt.Println(i) for progress indicator
}
fmt.Println("Done! Check task manager for growing memory.")
}
What You’ll See:
*Memory used by your process keeps increasing. On some systems, keep running this and you can hit Windows handle count limits or see your app get sluggish or crash.*
Who’s Affected
- Projects using github.com/microsoft/go-crypto-winnative < ..-20250211154640-f49c8e1379ea
You use Go on non-Windows platforms (does not use this backend)
- You’re on Go versions/builds with the fix applied
`
go get github.com/microsoft/go-crypto-winnative@..-20250211154640-f49c8e1379ea
Or update Microsoft Go (Windows) to 1.23.6-2 or 1.22.12-2.
- Get latest binaries here.
References
- CVE report on GitHub Advisory Database
- Upstream Fix Commit
- Go Crypto Winnative Changelog
- Microsoft Go Releases
Final Thoughts
Memory leaks like CVE-2025-25199 are sneaky because they’re hard to notice until your application starts misbehaving. If you’re building Go apps for Windows and use the native crypto backend, update today.
Questions? Check out the original GitHub repository or comment below.
Timeline
Published on: 02/12/2025 18:15:27 UTC