crypto/rand exposes an io.Reader variable Reader as "a global, shared instance of a cryptographically strong pseudo-random generator." Furthermore, crypto/rand.Read implicitly uses Reader for its crypto source.
This seems problematic to me because then any package can just overwrite crypto/rand.Reader to point to some other object, affecting the security of any packages that rely on crypto/rand.Read or crypto/rand.Reader for security, e.g. x/crypto/nacl.
One can say that a language can never ultimately defend against code running in your same process, but I think it should be possible to write something that depends on crypto/rand for security that wouldn't require auditing other packages for a single malicious variable write.
The main API flaw here, IMO, is that Reader is an io.Reader variable, whereas it should be a function that returns an io.Reader. A new API would look something like:
// Reader returns an io.Reader that reads from a cryptographically strong pseudo-random number generator.
func Reader() io.Reader
// Read is a helper function that calls Reader() and then passes it to io.ReadFull.
func Read(b byte) (n int, err error)
Alas, with the Go 1 compatibility guarantee Reader would have to remain, and Read would still have to use Reader. But the above could be added as new functions, say MakeReader() and SafeRead(). And the standard library (and other important external packages like x/crypto/nacl) could be changed to use those safe functions.
 Without this flaw, a malicious package would have to use the unsafe package to poke around in the internals of crypto/rand, or call out to the external OS to e.g. try to redirect access to the random device, which seems easier to audit for than a write to crypto/rand.Reader. Of course, I'm already assuming that a project worried about this is vendoring all of its dependencies.
The text was updated successfully, but these errors were encountered:
Yes, if you care about security, you're already auditing all vendored/dependency code. However, there's a difference in required effort between auditing for packages that e.g. make calls to unsafe, try to make system calls, or load DLLs, and auditing for packages that try to sneak in a single write to a global variable.
I don't think the benefits of overriding crypto/rand.Reader globally for tests outweigh the downsides of having one package's crypto be breakable by another package. It is easy enough to plumb through an io.Reader through the code you want to test.
Just wanted to add that, one of the many reasons that Go is fantastic is that it makes good choices and sane defaults for many things, so people don't make mistakes even if they don't have the awareness to, for example, audit vendoring.
This is similar to saying a class has secure member variables because they're private in other languages. Most operating systems don't even support this kind of security in the same user context, let alone process or package scope. If you're running untrusted code there are bigger things to worry about than package variables.
Note that my threat model isn't arbitrary untrusted code, so the comparison to OS-level security isn't really applicable -- it's third-party code that has to be audited before inclusion in a project. It's reasonably easy to audit the dependencies of a package, i.e. to check that it doesn't use unsafe, cgo, reflect, etc., and to perform greater scrutiny if so. It's a bit harder to audit its access of external package variables, and this bug demonstrates that the potential consequences of something slipping through the cracks isn't just weird stuff happening (like setting io.EOF to nil), but a compromise of a RNG that packages depend on for security.
Someone else mentioned that this has been brought up before for e.g. http.DefaultClient. The difference in this case is that one could create their own instance of http.Client and be diligent about using that. However, that's impossible in the case of crypto/rand.
On the other hand, it's not infeasible to write a tool that automates looking for code that modifies external package variables (except for reflection, unsafe, etc.). Although it's unfortunate that such a tool has to be written.