Describe the bug
core/stringx.Substr validates start and stop individually against [0, len(str)] but does not check that start <= stop. When start > stop (both values individually within bounds), the slice expression rs[start:stop] panics at runtime with slice bounds out of range. The function's error-returning signature gives callers a false sense of safety: they reasonably expect all invalid inputs to produce an error, not an unrecovered panic.
To Reproduce
-
The code is
package main
import (
"errors"
"fmt"
)
var (
ErrInvalidStartPosition = errors.New("start position is invalid")
ErrInvalidStopPosition = errors.New("stop position is invalid")
)
// verbatim copy of core/stringx/strings.go
func Substr(str string, start, stop int) (string, error) {
rs := []rune(str)
length := len(rs)
if start < 0 || start > length {
return "", ErrInvalidStartPosition
}
if stop < 0 || stop > length {
return "", ErrInvalidStopPosition
}
return string(rs[start:stop]), nil
}
func main() {
fmt.Println(Substr("hello", 0, 3)) // works fine
fmt.Println(Substr("hello", 3, 2)) // panics: start > stop, both in range
}
-
The error is
$ go run main.go
hel <nil>
panic: runtime error: slice bounds out of range [3:2]
goroutine 1 [running]:
main.Substr(...)
/tmp/sandbox2887458380/prog.go:23
main.main()
/tmp/sandbox2887458380/prog.go:28 +0x1bb
Expected behavior
Substr("hello", 3, 2) should return ("", ErrInvalidStopPosition) (or a dedicated ErrInvalidRange error) instead of panicking. All invalid input combinations should produce an error value; the function should never panic.
Screenshots
N/A
Environments (please complete the following information):
- OS: Linux
- go-zero version: v1.10.1
More description
The function checks start and stop independently against the string length, but never checks that start <= stop. The Go slice expression rs[start:stop] requires start <= stop at runtime — violating this causes a panic rather than returning an error.
The fix is to add one extra guard between the two existing range checks:
// core/stringx/strings.go
func Substr(str string, start, stop int) (string, error) {
rs := []rune(str)
length := len(rs)
if start < 0 || start > length {
return "", ErrInvalidStartPosition
}
if stop < 0 || stop > length {
return "", ErrInvalidStopPosition
}
// ADD THIS: reject start > stop before the slice operation
if start > stop {
return "", ErrInvalidStopPosition
}
return string(rs[start:stop]), nil
}
Without this guard, any caller passing start > stop (both values otherwise in range) gets a panic instead of an error. This can happen from reversed pagination parameters, off-by-one errors in slice computations, or user-controlled indices. In a web server or microservice built on go-zero, an unrecovered panic in a handler goroutine will kill the request and may crash the service process.
Note: the existing test suite in core/stringx/strings_test.go covers start < 0, start > length, stop < 0, and stop > length but has no test case for start > stop, so this path was not caught by tests.
Discovery method
This bug was found using Zorya, a concolic execution engine for Go binaries. Zorya ran symbolic exploration on a standalone binary wrapping the slice bounds logic and had Z3 produce a satisfying assignment within 183 seconds.
The exact command used:
zorya zorya_substr_real \
--mode function 0x4b72c0 \
--thread-scheduling main-only \
--lang go \
--compiler gc \
--arg "0200000000000000 0a00000000000000" \
--negate-path-exploration
--arg "0200000000000000 0a00000000000000" provides concrete seed values (start=2, stop=10, both as little-endian 8-byte hex) from which Zorya initialises symbolic variables. Z3 witness: start=2, stop=1; both pass the individual range checks, but rs[2:1] panics. Confirmed natively with go run.
Describe the bug
core/stringx.Substrvalidatesstartandstopindividually against[0, len(str)]but does not check thatstart <= stop. Whenstart > stop(both values individually within bounds), the slice expressionrs[start:stop]panics at runtime withslice bounds out of range. The function's error-returning signature gives callers a false sense of safety: they reasonably expect all invalid inputs to produce an error, not an unrecovered panic.To Reproduce
The code is
The error is
Expected behavior
Substr("hello", 3, 2)should return("", ErrInvalidStopPosition)(or a dedicatedErrInvalidRangeerror) instead of panicking. All invalid input combinations should produce an error value; the function should never panic.Screenshots
N/A
Environments (please complete the following information):
More description
The function checks
startandstopindependently against the string length, but never checks thatstart <= stop. The Go slice expressionrs[start:stop]requiresstart <= stopat runtime — violating this causes a panic rather than returning an error.The fix is to add one extra guard between the two existing range checks:
Without this guard, any caller passing
start > stop(both values otherwise in range) gets a panic instead of an error. This can happen from reversed pagination parameters, off-by-one errors in slice computations, or user-controlled indices. In a web server or microservice built on go-zero, an unrecovered panic in a handler goroutine will kill the request and may crash the service process.Note: the existing test suite in
core/stringx/strings_test.gocoversstart < 0,start > length,stop < 0, andstop > lengthbut has no test case forstart > stop, so this path was not caught by tests.Discovery method
This bug was found using Zorya, a concolic execution engine for Go binaries. Zorya ran symbolic exploration on a standalone binary wrapping the slice bounds logic and had Z3 produce a satisfying assignment within 183 seconds.
The exact command used:
--arg "0200000000000000 0a00000000000000"provides concrete seed values (start=2,stop=10, both as little-endian 8-byte hex) from which Zorya initialises symbolic variables. Z3 witness:start=2, stop=1; both pass the individual range checks, butrs[2:1]panics. Confirmed natively withgo run.