| // Copyright 2010 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build unix |
| |
| // Unix cryptographically secure pseudorandom number |
| // generator. |
| |
| package rand |
| |
| import ( |
| "crypto/internal/boring" |
| "errors" |
| "io" |
| "os" |
| "sync" |
| "sync/atomic" |
| "syscall" |
| "time" |
| ) |
| |
| const urandomDevice = "/dev/urandom" |
| |
| func init() { |
| if boring.Enabled { |
| Reader = boring.RandReader |
| return |
| } |
| Reader = &reader{} |
| } |
| |
| // A reader satisfies reads by reading from urandomDevice |
| type reader struct { |
| f io.Reader |
| mu sync.Mutex |
| used atomic.Uint32 // Atomic: 0 - never used, 1 - used, but f == nil, 2 - used, and f != nil |
| } |
| |
| // altGetRandom if non-nil specifies an OS-specific function to get |
| // urandom-style randomness. |
| var altGetRandom func([]byte) (err error) |
| |
| func warnBlocked() { |
| println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel") |
| } |
| |
| func (r *reader) Read(b []byte) (n int, err error) { |
| boring.Unreachable() |
| if r.used.CompareAndSwap(0, 1) { |
| // First use of randomness. Start timer to warn about |
| // being blocked on entropy not being available. |
| t := time.AfterFunc(time.Minute, warnBlocked) |
| defer t.Stop() |
| } |
| if altGetRandom != nil && altGetRandom(b) == nil { |
| return len(b), nil |
| } |
| if r.used.Load() != 2 { |
| r.mu.Lock() |
| if r.used.Load() != 2 { |
| f, err := os.Open(urandomDevice) |
| if err != nil { |
| r.mu.Unlock() |
| return 0, err |
| } |
| r.f = hideAgainReader{f} |
| r.used.Store(2) |
| } |
| r.mu.Unlock() |
| } |
| return io.ReadFull(r.f, b) |
| } |
| |
| // hideAgainReader masks EAGAIN reads from /dev/urandom. |
| // See golang.org/issue/9205 |
| type hideAgainReader struct { |
| r io.Reader |
| } |
| |
| func (hr hideAgainReader) Read(p []byte) (n int, err error) { |
| n, err = hr.r.Read(p) |
| if errors.Is(err, syscall.EAGAIN) { |
| err = nil |
| } |
| return |
| } |