Skip to content

Reject negative offsets in MemMapFs - #654

Open
youdie006 wants to merge 1 commit into
spf13:masterfrom
youdie006:memfs-negative-offset
Open

Reject negative offsets in MemMapFs#654
youdie006 wants to merge 1 commit into
spf13:masterfrom
youdie006:memfs-negative-offset

Conversation

@youdie006

Copy link
Copy Markdown

MemMapFs accepts a negative seek offset and then panics on the next read, where OsFs returns an
error.

Reading the last N bytes of a file shorter than N is the usual way to hit it:

f.Seek(-8, io.SeekEnd)   // 5-byte file
f.Read(buf)              // panic: slice bounds out of range [-3:]

Same three calls through both backends of the shared suite:

           Seek(-1, SeekStart)   Seek(-8, SeekEnd) then Read      ReadAt(buf, -1)
OsFs       error                 error from Seek, read unaffected error
MemMapFs   n=-1, no error        panic: slice bounds out of range [-3:]   panic: [-1:]

OsFs is the correct half. io.Seeker says "Seeking to an offset before the start of the file is
an error", and os.File returns EINVAL for the seek and negative offset for ReadAt.

Cause

mem/file.go:275 stores whatever the arithmetic produces:

case io.SeekEnd:
    atomic.StoreInt64(&f.at, int64(len(f.fileData.data))+offset)

and mem/file.go:233 then slices with it:

copy(b, f.fileData.data[off:off+int64(n)])

ReadAt and WriteAt have the same gap for a caller-supplied negative off.

Change

A negative-offset guard in Seek, ReadAt and WriteAt, returning the ErrOutOfRange sentinel
this file already uses for Truncate(-1) (mem/file.go:261). Seek now computes into a local and
validates before storing, which also makes an unknown whence an error rather than a silent no-op —
os.File rejects that too.

Test

Added to afero_test.go, which runs over Fss = []Fs{&MemMapFs{}, &OsFs{}}, so OsFs defines the
expected behaviour rather than me asserting it. With only mem/file.go reverted, OsFs passes and
MemMapFs fails on the seeks and then panics on the ReadAt:

afero_test.go:775: *afero.MemMapFs: Seek(-1, SeekStart) returned no error
afero_test.go:778: *afero.MemMapFs: Seek(-8, SeekEnd) on a 5-byte file returned no error
panic: runtime error: slice bounds out of range [-1:]
    github.com/spf13/afero/mem.(*File).ReadAt

With the change both backends pass. go test ./... and go test -race ./ ./mem are green, and
gofmt -l lists neither file.


Disclosure: prepared with AI assistance; I verified the backend comparison and the red/green runs
myself.

Seek stored a negative position without complaint and ReadAt/WriteAt
sliced with it, so the standard read-the-last-N-bytes idiom panicked with
a slice bounds error on a file shorter than N. OsFs returns an error for
all three.
@CLAassistant

CLAassistant commented Sep 3, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants