Skip to content

csplit: panic on overflow in {N} repetition count and /regex/OFF offset #12495

@leeewee

Description

@leeewee

Affects: uutils/coreutils main (verified at commit aaf4a353c, 2026-05-28)
Severity: panic + abort on user-supplied input that GNU csplit rejects cleanly

Summary

csplit panics with a Rust backtrace instead of returning a clean error when
the user supplies a pattern argument whose embedded integer exceeds the target
type's range. Two distinct sites in src/uu/csplit/src/patterns.rs call
.unwrap() on a str::parse::<usize>() / str::parse::<i32>():

  • line 123 — the {N} repetition count
  • line 136 — the /regex/OFF (and %regex%OFF) line offset

Reproduction

$ csplit - 1 '{99999999999999999999999999999999}' < /etc/hostname
thread 'main' panicked at src/uu/csplit/src/patterns.rs:123:83:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: PosOverflow }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted (core dumped)
$ echo $?
134

$ csplit - '/x/-2147483649' < /etc/hostname
thread 'main' panicked at src/uu/csplit/src/patterns.rs:136:47:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: NegOverflow }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted (core dumped)
$ echo $?
134

GNU coreutils 8.30 on the same inputs:

$ /usr/bin/csplit - 1 '{99999999999999999999999999999999}' < /etc/hostname
csplit: '{99999999999999999999999999999999'}: integer required between '{' and '}'
$ echo $?
1

$ /usr/bin/csplit - '/x/-2147483649' < /etc/hostname
csplit: '/x/-2147483649': line number out of range
$ echo $?
1

Offending code

src/uu/csplit/src/patterns.rs

// line 123 — {N} repetition count
if let Some(times) = r.name("TIMES") {
    ExecutePattern::Times(times.as_str().parse::<usize>().unwrap() + 1)
} else { ... }

// line 136 — /regex/OFF offset
let offset = match captures.name("OFFSET") {
    None => 0,
    Some(m) => m.as_str().parse().unwrap(),
};

Both should propagate a CsplitError::InvalidPattern (or a more specific
variant) instead of unwrapping.

Suggested fix

// {N} repetition count
if let Some(times) = r.name("TIMES") {
    let n: usize = times
        .as_str()
        .parse()
        .map_err(|_| CsplitError::InvalidPattern(next_item.to_owned()))?;
    ExecutePattern::Times(n.checked_add(1)
        .ok_or_else(|| CsplitError::InvalidPattern(next_item.to_owned()))?)
} else { ExecutePattern::Always }

// /regex/OFF offset
let offset = match captures.name("OFFSET") {
    None => 0,
    Some(m) => m
        .as_str()
        .parse()
        .map_err(|_| CsplitError::InvalidPattern(arg.to_owned()))?,
};

(The + 1 overflow on usize::MAX does not panic in release builds because
overflow checks are off — but it still produces silently wrong behavior
(Times(0)). The checked_add makes that a clean error in both profiles.)

Found by our static analysis tooling.

Metadata

Metadata

Assignees

No one assigned

    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