From d7d5d4c1a72929e4436a14bb2a83ae6a03f54d3d Mon Sep 17 00:00:00 2001 From: Nagendra Mohan Date: Wed, 19 Aug 2026 22:55:43 +0530 Subject: [PATCH 1/2] tail: don't panic on '-c +N' with a huge N MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tail -c +N FILE` on a regular file larger than the block size seeks to byte N-1. When N-1 exceeds what the OS can seek to (any offset above i64::MAX always fails with EINVAL), the `.unwrap()` aborted the process. Clamp the start offset to the file length. A start beyond the end of the file produces no output (matching GNU `tail`), and clamping also avoids seeking to an offset the OS cannot represent — so the pathological case yields empty output instead of panicking. Behavior is unchanged for valid offsets. Adds a regression test (verified failing before the fix). Closes #13887. --- src/uu/tail/src/tail.rs | 8 +++++++- tests/by-util/test_tail.rs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index db1b6a5456..1714537b10 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -480,7 +480,13 @@ fn bounded_tail(file: &mut File, settings: &Settings) -> UResult<()> { FilterMode::Bytes(Signum::Positive(count)) if count > &1 => { // GNU `tail` seems to index bytes and lines starting at 1, not // at 0. It seems to treat `+0` and `+1` as the same thing. - file.seek(SeekFrom::Start(*count - 1)).unwrap(); + // + // Clamp the start offset to the file length. A start beyond the end of the file + // produces no output; clamping also avoids seeking to an offset the OS cannot + // represent (anything above `i64::MAX` fails with `EINVAL`), which previously + // panicked via `.unwrap()`. See #13887. + let len = file.metadata()?.len(); + file.seek(SeekFrom::Start((*count - 1).min(len)))?; } _ => {} } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 985d3702ab..60c2198b38 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -857,6 +857,22 @@ fn test_bytes_single() { .stdout_is_fixture("foobar_bytes_single.expected"); } +#[test] +fn test_positive_bytes_overflowing_offset_does_not_panic() { + // Regression test for #13887: `tail -c +N` on a regular file larger than the block size seeks + // to byte N-1. A very large N (greater than `i64::MAX`) makes the seek fail with `EINVAL`; + // `tail` used to `.unwrap()` and abort. That start is past the end of the file, so the output + // must simply be empty. + let (at, mut ucmd) = at_and_ucmd!(); + // Larger than sane_blksize (~4 KiB) so the seek code path is taken. + at.write("big", &"x".repeat(8192)); + ucmd.arg("-c") + .arg("+18446744073709551615") // u64::MAX + .arg("big") + .succeeds() + .no_output(); +} + #[test] fn test_bytes_stdin() { new_ucmd!() From bdcabfdc5bd7605e0ed3ff7514987330b7ff214b Mon Sep 17 00:00:00 2001 From: Nagendra Mohan Date: Thu, 20 Aug 2026 09:14:22 +0530 Subject: [PATCH 2/2] tail: shorten the code comment per review Signed-off-by: Nagendra Mohan --- src/uu/tail/src/tail.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 1714537b10..25a467fc51 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -480,11 +480,8 @@ fn bounded_tail(file: &mut File, settings: &Settings) -> UResult<()> { FilterMode::Bytes(Signum::Positive(count)) if count > &1 => { // GNU `tail` seems to index bytes and lines starting at 1, not // at 0. It seems to treat `+0` and `+1` as the same thing. - // - // Clamp the start offset to the file length. A start beyond the end of the file - // produces no output; clamping also avoids seeking to an offset the OS cannot - // represent (anything above `i64::MAX` fails with `EINVAL`), which previously - // panicked via `.unwrap()`. See #13887. + // Clamp to the file length so a start past EOF (or above `i64::MAX`) yields empty + // output instead of panicking on an `EINVAL` seek (#13887). let len = file.metadata()?.len(); file.seek(SeekFrom::Start((*count - 1).min(len)))?; }