From 84c99023e5894769954ef9319608935caf525fa0 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 5 Jun 2026 14:59:10 +0000 Subject: [PATCH] util: check RNG error before slicing url-safe random string A crypto/rand failure sliced empty output and panicked. --- hscontrol/util/string.go | 13 +++++++++++-- hscontrol/util/string_random_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 hscontrol/util/string_random_test.go diff --git a/hscontrol/util/string.go b/hscontrol/util/string.go index b0f0376a..2bcb515f 100644 --- a/hscontrol/util/string.go +++ b/hscontrol/util/string.go @@ -32,9 +32,18 @@ func GenerateRandomBytes(n int) ([]byte, error) { func GenerateRandomStringURLSafe(n int) (string, error) { b, err := GenerateRandomBytes(n) - uenc := base64.RawURLEncoding.EncodeToString(b) + return encodeRandomURLSafe(b, n, err) +} - return uenc[:n], err +// encodeRandomURLSafe URL-safe base64-encodes b and truncates to n. It checks +// err first: on an RNG failure b is nil, so slicing the empty encoding would +// panic instead of returning the ("", err) the caller is promised. +func encodeRandomURLSafe(b []byte, n int, err error) (string, error) { + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(b)[:n], nil } // GenerateRandomStringDNSSafe returns a DNS-safe diff --git a/hscontrol/util/string_random_test.go b/hscontrol/util/string_random_test.go new file mode 100644 index 00000000..c697841b --- /dev/null +++ b/hscontrol/util/string_random_test.go @@ -0,0 +1,24 @@ +package util + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestEncodeRandomURLSafeChecksErrorFirst ensures the URL-safe random string +// encoder returns ("", err) on an RNG failure instead of slicing the empty +// base64 of nil bytes and panicking. +func TestEncodeRandomURLSafeChecksErrorFirst(t *testing.T) { + require.NotPanics(t, func() { + s, err := encodeRandomURLSafe(nil, 32, assert.AnError) + assert.Empty(t, s) + require.Error(t, err) + }) + + s, err := encodeRandomURLSafe(bytes.Repeat([]byte{0x1}, 32), 32, nil) + require.NoError(t, err) + assert.Len(t, s, 32) +}