golang/go · critical

tls: short read from Rand:

Error message

tls: short read from Rand: 

What it means

Thrown by makeClientHello when io.ReadFull fails to read 32 random bytes for the ClientHello random field from config.rand(). By default, config.rand() returns crypto/rand.Reader, which reads from the operating system's CSPRNG (/dev/urandom on Linux, RtlGenRandom on Windows). A failure indicates the system entropy source is unavailable or a custom Rand reader is broken.

Source

Thrown at src/crypto/tls/handshake_client.go:107

	if hello.vers > VersionTLS12 {
		hello.vers = VersionTLS12
	}

	if c.handshakes > 0 {
		hello.secureRenegotiation = c.clientFinished[:]
	}

	hello.cipherSuites = config.cipherSuites(hasAESGCMHardwareSupport)
	// Don't advertise TLS 1.2-only cipher suites unless we're attempting TLS 1.2.
	if maxVersion < VersionTLS12 {
		hello.cipherSuites = slices.DeleteFunc(hello.cipherSuites, func(id uint16) bool {
			return cipherSuiteByID(id).flags&suiteTLS12 != 0
		})
	}

	_, err := io.ReadFull(config.rand(), hello.random)
	if err != nil {
		return nil, nil, nil, errors.New("tls: short read from Rand: " + err.Error())
	}

	// A random session ID is used to detect when the server accepted a ticket
	// and is resuming a session (see RFC 5077). In TLS 1.3, it's always set as
	// a compatibility measure (see RFC 8446, Section 4.1.2).
	//
	// The session ID is not set for QUIC connections (see RFC 9001, Section 8.4).
	if c.quic == nil {
		hello.sessionId = make([]byte, 32)
		if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
			return nil, nil, nil, errors.New("tls: short read from Rand: " + err.Error())
		}
	}

	if maxVersion >= VersionTLS12 {
		hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms(minVersion, maxVersion)
		hello.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert(minVersion, maxVersion)
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Do not set config.Rand at all — let the library use crypto/rand.Reader by default
  2. If using a custom Rand reader, ensure it implements io.Reader correctly and always returns the requested number of bytes or an error
  3. Ensure /dev/urandom is available and readable in the runtime environment (check container/chroot/jail configuration)
  4. If running in FIPS mode, verify the FIPS entropy module is properly initialized

Example fix

// before — broken custom Rand reader
config := &tls.Config{Rand: brokenReader}
// after — use default crypto/rand.Reader
config := &tls.Config{} // Rand defaults to crypto/rand.Reader
Defensive patterns

Strategy: validation

Validate before calling

// Do not set config.Rand unless you have a specific reason.
// If you must, validate it can provide sufficient bytes:
func validateRandReader(r io.Reader) error {
    buf := make([]byte, 32)
    n, err := io.ReadFull(r, buf)
    if err != nil {
        return fmt.Errorf("Rand reader failed: %w", err)
    }
    if n != 32 {
        return fmt.Errorf("Rand reader returned %d bytes, expected 32", n)
    }
    return nil
}

Try / catch

// Prefer not setting config.Rand at all.
// If a custom Rand is needed for testing, wrap it safely:
//
//   type safeRand struct{ r io.Reader }
//   func (s *safeRand) Read(p []byte) (int, error) {
//       return io.ReadFull(s.r, p) // guarantees full read or error
//   }

Prevention

When it happens

Trigger: config.rand() returns an error or premature EOF when asked for 32 bytes. This can happen with a custom config.Rand reader that does not implement io.ReadFull semantics, or when the OS entropy source (e.g., /dev/urandom) is inaccessible.

Common situations: Setting config.Rand to a broken or non-blocking-safe reader. Running in a heavily sandboxed container or chroot where /dev/urandom is not mounted. A FIPS module failure in fips-only mode. Extremely rare kernel entropy subsystem failures. Setting config.Rand to a deterministic reader for testing that returns fewer bytes than expected.

Understand the failure class

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/f52e9e66c667c32e. Report an issue: GitHub.