Skip to content

bug: Panic in FirstN and LastN with negative n : slice bounds out of range #5614

@kajaaz

Description

@kajaaz

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:

  1. 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]
}
  1. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions