diff --git a/coderd/userpassword/userpassword.go b/coderd/userpassword/userpassword.go index 6f0da0e9aa..fa16a2c89e 100644 --- a/coderd/userpassword/userpassword.go +++ b/coderd/userpassword/userpassword.go @@ -14,6 +14,8 @@ import ( "golang.org/x/crypto/pbkdf2" "golang.org/x/exp/slices" "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/util/lazy" ) var ( @@ -38,8 +40,15 @@ var ( defaultSaltSize = 16 // The simulated hash is used when trying to simulate password checks for - // users that don't exist. - simulatedHash, _ = Hash("hunter2") + // users that don't exist. It's meant to preserve the timing of the hash + // comparison. + simulatedHash = lazy.New(func() string { + h, err := Hash("hunter2") + if err != nil { + panic(err) + } + return h + }) ) // Make password hashing much faster in tests. @@ -65,7 +74,9 @@ func init() { func Compare(hashed string, password string) (bool, error) { // If the hased password provided is empty, simulate comparing a real hash. if hashed == "" { - hashed = simulatedHash + // TODO: this seems ripe for creating a vulnerability where + // hunter2 can log into any account. + hashed = simulatedHash.Load() } if len(hashed) < hashLength { diff --git a/coderd/util/lazy/value.go b/coderd/util/lazy/value.go new file mode 100644 index 0000000000..f53848a0dd --- /dev/null +++ b/coderd/util/lazy/value.go @@ -0,0 +1,28 @@ +// Package lazy provides a lazy value implementation. +// It's useful especially in global variable initialization to avoid +// slowing down the program startup time. +package lazy + +import ( + "sync" + "sync/atomic" +) + +type Value[T any] struct { + once sync.Once + fn func() T + cached atomic.Pointer[T] +} + +func (v *Value[T]) Load() T { + v.once.Do(func() { + vv := v.fn() + v.cached.Store(&vv) + }) + return *v.cached.Load() +} + +// New creates a new lazy value with the given load function. +func New[T any](fn func() T) *Value[T] { + return &Value[T]{fn: fn} +}