clockworklabs/SpacetimeDB · error · io::Error

invalid seek to a negative or overflowing position

Error message

invalid seek to a negative or overflowing position

What it means

Segment implements io::Seek by translating SeekFrom::End/Current into base_pos + signed offset via checked_add_signed. SeekFrom::Start(n) always succeeds (any u64 is a valid absolute position in the in-memory store). For End/Current seeks, if the signed offset makes the result negative or exceed u64::MAX, the seek returns ErrorKind::InvalidInput, 'invalid seek to a negative or overflowing position', mirroring std's own Cursor seek semantics.

Source

Thrown at crates/dst/src/sim/commitlog.rs:356

    }
}

impl io::Seek for Segment {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        let (base_pos, offset) = match pos {
            io::SeekFrom::Start(n) => {
                self.pos = n;
                return Ok(n);
            }
            io::SeekFrom::End(n) => (self.len() as u64, n),
            io::SeekFrom::Current(n) => (self.pos, n),
        };
        match base_pos.checked_add_signed(offset) {
            Some(n) => {
                self.pos = n;
                Ok(n)
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid seek to a negative or overflowing position",
            )),
        }
    }
}

impl SegmentLen for Segment {
    fn segment_len(&mut self) -> io::Result<u64> {
        Ok(self.len() as u64)
    }
}

impl FileLike for Segment {
    fn fsync(&mut self) -> io::Result<()> {
        Ok(())
    }

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Clamp negative rewinds: use seek(SeekFrom::Current(-n)) only after verifying pos >= n, or compute the absolute target and use SeekFrom::Start
  2. Validate lengths/offsets parsed from the data before turning them into seek offsets
  3. For End-relative seeks, confirm segment len >= |negative offset|

Example fix

// before
seg.seek(io::SeekFrom::Current(-(header_len as i64)))?; // InvalidInput if pos < header_len

// after
let pos = seg.stream_position()?;
let target = pos.checked_sub(header_len as u64)
    .ok_or_else(|| anyhow!("record header crosses segment start"))?
    .min(seg.stream_len()?);
seg.seek(io::SeekFrom::Start(target))?;
Defensive patterns

Strategy: validation

Validate before calling

async fn checked_seek<S: std::io::Seek + std::io::Read>(s: &mut S, target: i64) -> std::io::Result<u64> {
    let (base, len) = (s.stream_position()?, s.stream_len()?);
    let abs = i128::from(base) + i128::from(target);
    anyhow::ensure!((0..=i128::from(len)).contains(&abs), "seek out of range");
    s.seek(std::io::SeekFrom::Start(abs as u64))
}

Try / catch

match seg.seek(io::SeekFrom::Current(off)) {
    Ok(p) => p,
    Err(ref e) if e.kind() == std::io::ErrorKind::InvalidInput => {
        return Err(anyhow::anyhow!("rewound past segment start (pos={}, off={})", pos, off));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: seek(SeekFrom::Current(negative)) when the current position is smaller than |offset| (would go below zero); seek(SeekFrom::End(negative)) past the start of the data; seek with a positive offset that overflows u64 from a large base; rewinding a fresh, zero-length segment with SeekFrom::End(-1) or more.

Common situations: Generic log-reading code that rewinds by a fixed byte count (e.g. re-reading a trailer/record header) without checking it cannot precede the segment start; parsers computing offsets from untrusted length prefixes; copying std::io::Cursor patterns onto Segment.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/f32b747524378e04. Report an issue: GitHub.