pola-rs/polars · error · std::io::Error

InvalidInput

InvalidInput

Error message

invalid seek to a negative or overflowing position

What it means

FixedSizeChunkedBytesCursor (polars-utils' Seek impl over sliced chunked bytes) rejects seeks whose resolved position is negative or overflows: for SeekFrom::End/Current, base_pos.checked_add_signed(offset) returned None. SeekFrom::Start is instead clamped to total_size, so only relative and end-relative seeks can fail.

Source

Thrown at crates/polars-utils/src/chunked_bytes_cursor.rs:128

impl<'a, T> std::io::Seek for FixedSizeChunkedBytesCursor<'a, T> {
    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
        // Mostly copied from io::Cursor::seek().
        use std::io::SeekFrom;

        let (base_pos, offset) = match pos {
            SeekFrom::Start(n) => {
                self.position = usize::try_from(n).unwrap().min(self.total_size);
                return Ok(self.position as u64);
            },
            SeekFrom::End(n) => (self.total_size as u64, n),
            SeekFrom::Current(n) => (self.position as u64, n),
        };
        match base_pos.checked_add_signed(offset) {
            Some(n) => {
                self.position = usize::try_from(n).unwrap();
                Ok(self.position as u64)
            },
            None => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "invalid seek to a negative or overflowing position",
            )),
        }
    }
}

View on GitHub (pinned to df599052da)

Solutions

  1. Compute the target position with checked arithmetic and clamp it to 0..=len, then seek with SeekFrom::Start
  2. Treat offsets read from file headers as untrusted: range-check them and report corruption instead of seeking
  3. For 'end minus n' patterns, verify n <= total_size before seeking
  4. Prefer u64/i128 intermediate math when combining positions and signed deltas

Example fix

// before
let pos = header.footer_offset(); // may be garbage
cursor.seek(SeekFrom::Current(pos))?;

// after
let target: i128 = cursor.stream_position()? as i128 + pos as i128;
if !(0..=total_len as i128).contains(&target) {
    return Err(corrupt_file_error());
}
cursor.seek(SeekFrom::Start(target as u64))?;
Defensive patterns

Strategy: validation

Validate before calling

use std::io::{Seek, SeekFrom};

fn checked_seek(cursor: &mut impl Seek, from: SeekFrom, len: u64) -> std::io::Result<u64> {
    let base = match from {
        SeekFrom::Start(n) => n as i128,
        SeekFrom::End(n) => len as i128 + n as i128,
        SeekFrom::Current(n) => cursor.stream_position()? as i128 + n as i128,
    };
    if !(0..=len as i128).contains(&base) {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "seek out of range"));
    }
    cursor.seek(SeekFrom::Start(base as u64))
}

Prevention

When it happens

Trigger: Seek(SeekFrom::End(-n)) or Seek(SeekFrom::Current(-n)) where n exceeds the current/end position (target below 0), or a huge positive offset overflowing u64 - typically readers computing positions from truncated or corrupt headers.

Common situations: Parsing corrupt/truncated IPC or parquet-like data where a stored offset is bogus; mixing up byte offsets with row indices; passing a negative delta from user-supplied seek parameters; off-by-one when seeking to a footer position.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/0becb1e9a4254398. Report an issue: GitHub.