google / uuid

Go package for UUIDs based on RFC 4122 and DCE 1.1: Authentication and Security Services.
BSD 3-Clause "New" or "Revised" License
5.24k stars 363 forks source link

Deprecate EnableRandPool and DisableRandPool #86

Open rittneje opened 3 years ago

rittneje commented 3 years ago

v1.3.0 introduced the EnableRandPool and DisableRandPool functions as a means of optimizing UUID generation at the expense of security. However, there are two main issues with this.

First, as documented, neither of these are thread-safe. However, the described solution is not feasible:

// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.

I cannot in general easily account for what all the package initializers of all of my transitive dependencies are doing. Instead of a function called a runtime, this opt-in behavior should be controlled by a build tag, so the pool can be enabled at compile time. Then there is no race condition. (You could also replace the poolEnabled bool with an atomically read/written uint32, which at least resolves the race condition. But the build tag is more air tight.)

Second, as documented, the optimization is not suitable for security sensitive applications. However, again, I cannot account for what all of my transitive dependencies are doing. And in general, only having a global flag that effectively disables security seems like a rather poor design choice. It would be preferable if the decision could be made by the client on a case-by-case basis.

Going forward, it probably makes sense to deprecate the various global constructor functions and replace them with a factory object, similar to net.Dialer. Then this factory can easily be configured with various options that accumulate over time without needing to make new constructor functions.

rittneje commented 3 years ago

ping @pborman

joewreschnig commented 3 years ago

The pool API looks pretty dangerous and unnecessary to me also.

The speed gain comes almost entirely from the buffered I/O, which did not require a new API. The remaining allocation is the UUID itself which could be improved in a generally-useful way by having a function to fill a received pointer:

func NewRandomFromReader(r io.Reader) (UUID, error) {
    var uuid UUID
    return uuid, ReadRandom(r, &uuid)
}

func ReadRandom(r io.Reader, uuid *UUID) error {
    _, err := io.ReadFull(r, uuid[:])
    if err != nil {
        return err
    }
    uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
    uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
    return nil
}
func BenchmarkUUID_NewBufio(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        r := bufio.NewReaderSize(rander, randPoolSize)
        for pb.Next() {
            _, err := NewRandomFromReader(r)
            if err != nil {
                b.Fatal(err)
            }
        }
    })
}

func BenchmarkUUID_ReadRandom(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        r := bufio.NewReaderSize(rander, randPoolSize)
        var uuid UUID
        for pb.Next() {
            err := ReadRandom(r, &uuid)
            if err != nil {
                b.Fatal(err)
            }
        }
    })
}
BenchmarkUUID_NewBufio-4         6769986           176.0 ns/op        16 B/op          1 allocs/op
BenchmarkUUID_ReadRandom-4       7622210           155.6 ns/op         0 B/op          0 allocs/op

These are also lock-free (or as lock-free as the reader allows) approaches. On my system ReadRandom consistently beats NewPooled, I assume because of less lock contention.

pborman commented 3 years ago

As mentioned i a code review, these should be limited to package main. Libraries that want a different generator should call a different function. The good new is, that unlike Version 1 UUIDs, Version 4 uuids can be safely generated by multiple packages. Version 1 UUIDs are based on state and it is possible (though unlikely) for two packages to produce either the same UUIDs or UUIDs in which time goes backwards.