Describe the bug
FirstN and LastN in core/stringx/strings.go panic with a Go runtime
"slice bounds out of range" error when n is a negative integer.
Both functions guard against n > len(s) but do not check n < 0.
Because Go integer comparisons are signed, len(s) < n evaluates to false
when n = -1 (e.g. 64 < -1 == false), so the guard is bypassed and the
unsafe slice expression executes, causing a panic.
To Reproduce
Steps to reproduce the behavior, if applicable:
- The code is
package main
import "fmt"
func FirstN(s string, n int) string {
if len(s) < n {
return s
}
return s[:n] // PANIC when n < 0
}
func LastN(s string, n int) string {
if len(s) < n {
return s
}
return s[len(s)-n:] // PANIC when n < 0
}
func main() {
s := "hello world"
fmt.Println(FirstN(s, -1)) // panic: slice bounds out of range [:-1]
fmt.Println(LastN(s, -1)) // panic: slice bounds out of range [12:11]
}
- The error is
panic: runtime error: slice bounds out of range [:-1]
goroutine 1 [running]:
main.FirstN(...)
repro.go:8 +0x24
main.main()
repro.go:22 +0x5e
exit status 2
Expected behavior
FirstN(s, -1) and LastN(s, -1) should return an empty string "" (or
return an error) rather than panicking. Negative values of n are not
meaningful as a length and should be handled gracefully.
Environments:
- OS: Linux (reproducible on any OS)
- go-zero version: confirmed on current
main (core/stringx/strings.go)
- goctl version: N/A (core library issue)
More description
Impact:
Any go-zero REST handler or gRPC service that passes an unchecked integer
parameter (from a JSON body, URL query param, or gRPC field) into
stringx.FirstN or stringx.LastN can be crashed with a single malformed
request containing n < 0.
- In Go's default HTTP server,
net/http recovers panicking handler goroutines
and returns a 500, so the process survives — but the request is aborted and
the behaviour is undefined.
- If these functions are called outside a handler (middleware, background
worker, init path, scheduled job), there is no recover and the entire
process crashes.
- If the application wraps panics with a custom handler that calls
os.Exit,
the process crashes regardless.
Suggested fix:
func FirstN(s string, n int) string {
if n < 0 {
return ""
}
if len(s) < n {
return s
}
return s[:n]
}
func LastN(s string, n int) string {
if n < 0 {
return ""
}
if len(s) < n {
return s
}
return s[len(s)-n:]
}
This bug was found using Zorya, a concolic execution engine for Go binaries. Zorya symbolized the n parameter in standalone harness functions mirroring FirstN and LastN verbatim, and had Z3 produce satisfying assignments that trigger both panics.
FirstN : function 0x4b7200:
zorya zorya_firstN_real \
--mode function 0x4b7200 \
--thread-scheduling main-only \
--lang go \
--compiler gc \
--arg "0500000000000000" \
--negate-path-exploration
--arg "0500000000000000" is the little-endian int64 encoding of 5 (a safe, non-panicking seed).
Z3 witness: n = 0x8000000000000000 = math.MinInt64 = -9223372036854775808
→ globalStr[:-9223372036854775808] → panic: runtime error: slice bounds out of range (elapsed: 47 s)
LastN : function 0x4b72a0:
zorya zorya_firstN_real \
--mode function 0x4b72a0 \
--thread-scheduling main-only \
--lang go \
--compiler gc \
--arg "0500000000000000" \
--negate-path-exploration
Z3 witness: n = -9223372036854775748 (0x800000000000003c)
→ globalStr[len(globalStr) - (-9223372036854775748):] = globalStr[9223372036854775812:] → panic: runtime error: slice bounds out of range (elapsed: 51 s)
Describe the bug
FirstNandLastNincore/stringx/strings.gopanic with a Go runtime"slice bounds out of range" error when
nis a negative integer.Both functions guard against
n > len(s)but do not checkn < 0.Because Go integer comparisons are signed,
len(s) < nevaluates tofalsewhen
n = -1(e.g.64 < -1 == false), so the guard is bypassed and theunsafe slice expression executes, causing a panic.
To Reproduce
Steps to reproduce the behavior, if applicable:
Expected behavior
FirstN(s, -1)andLastN(s, -1)should return an empty string""(orreturn an error) rather than panicking. Negative values of
nare notmeaningful as a length and should be handled gracefully.
Environments:
main(core/stringx/strings.go)More description
Impact:
Any go-zero REST handler or gRPC service that passes an unchecked integer
parameter (from a JSON body, URL query param, or gRPC field) into
stringx.FirstNorstringx.LastNcan be crashed with a single malformedrequest containing
n < 0.net/httprecovers panicking handler goroutinesand returns a 500, so the process survives — but the request is aborted and
the behaviour is undefined.
worker, init path, scheduled job), there is no
recoverand the entireprocess crashes.
os.Exit,the process crashes regardless.
Suggested fix:
This bug was found using Zorya, a concolic execution engine for Go binaries. Zorya symbolized the
nparameter in standalone harness functions mirroringFirstNandLastNverbatim, and had Z3 produce satisfying assignments that trigger both panics.FirstN: function0x4b7200:--arg "0500000000000000"is the little-endian int64 encoding of5(a safe, non-panicking seed).Z3 witness:
n = 0x8000000000000000=math.MinInt64=-9223372036854775808→
globalStr[:-9223372036854775808]→panic: runtime error: slice bounds out of range(elapsed: 47 s)LastN: function0x4b72a0:Z3 witness:
n = -9223372036854775748(0x800000000000003c)→
globalStr[len(globalStr) - (-9223372036854775748):]=globalStr[9223372036854775812:]→panic: runtime error: slice bounds out of range(elapsed: 51 s)