diff options
Diffstat (limited to 'internal/player/password.go')
| -rw-r--r-- | internal/player/password.go | 25 |
1 files changed, 25 insertions, 0 deletions
diff --git a/internal/player/password.go b/internal/player/password.go index 9be39fb..5a43e1b 100644 --- a/internal/player/password.go +++ b/internal/player/password.go @@ -31,6 +31,23 @@ const ( argonSaltLen = 16 ) +// Sanity bounds applied to Argon2id parameters parsed from a stored hash. +// HashPassword always writes the constants above, but a hand-edited or +// corrupted account file can carry arbitrary numbers; without these bounds a +// value like 4294967295 (or a negative that wraps through uint32) makes +// argon2.IDKey request a multi-TiB allocation and kills the process instead of +// failing the login. Anything outside these ranges is rejected before hashing. +const ( + argonMinTime = 1 + argonMaxTime = 8 + argonMinMemory = 8 * 1024 // KiB (8 MiB) + argonMaxMemory = 256 * 1024 // KiB (256 MiB) + argonMinThreads = 1 + argonMaxThreads = 16 + argonMinKeyLen = 16 + argonMaxKeyLen = 64 +) + // hashLimiter bounds concurrent Argon2id computations. Each hash costs // ~64 MiB and tens of milliseconds, so an unbounded burst of logins (or // login attempts) would spike memory and CPU in parallel. Password work @@ -81,6 +98,14 @@ func CheckPassword(password, stored string) bool { if errT != nil || errM != nil || errP != nil || errSalt != nil || errKey != nil { return false } + if t < argonMinTime || t > argonMaxTime || + m < argonMinMemory || m > argonMaxMemory || + p < argonMinThreads || p > argonMaxThreads || + len(expected) < argonMinKeyLen || len(expected) > argonMaxKeyLen { + // Out-of-range parameters: fail the check instead of trusting the + // stored values and requesting an enormous allocation. + return false + } acquireHashSlot() key := argon2.IDKey([]byte(password), salt, uint32(t), uint32(m), uint8(p), uint32(len(expected))) releaseHashSlot() |
