diff --git a/backend/local/local.go b/backend/local/local.go index 8e4a6afd2..a8199f67e 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -1423,6 +1423,13 @@ func (o *Object) openTranslatedLink(offset, limit int64) (lrc io.ReadCloser, err if err != nil { return nil, err } + // Clamp offset into range to avoid panic + if offset < 0 { + offset = 0 + } + if offset > int64(len(linkdst)) { + offset = int64(len(linkdst)) + } return readers.NewLimitedReadCloser(io.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil } diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go index 82612be07..f8d1f4359 100644 --- a/backend/local/local_internal_test.go +++ b/backend/local/local_internal_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "math" "os" "path" "path/filepath" @@ -210,6 +211,32 @@ func TestSymlink(t *testing.T) { require.NoError(t, in.Close()) } +// TestSymlinkRangeBeyondEnd checks range requests on a translated +// symlink's target string don't panic. +func TestSymlinkRangeBeyondEnd(t *testing.T) { + ctx := context.Background() + r := fstest.NewRun(t) + f := r.Flocal.(*Fs) + linksMode(f) + + const target = "file.txt" + require.NoError(t, putLink(ctx, f, "symlink.txt", target)) + + o, err := f.NewObject(ctx, "symlink.txt"+fs.LinkSuffix) + require.NoError(t, err) + + // An offset just past the end and a wildly large offset must both read + // empty rather than panicking. + for _, start := range []int64{int64(len(target)), int64(len(target)) + 1, math.MaxInt64} { + in, err := o.Open(ctx, &fs.RangeOption{Start: start, End: -1}) + require.NoError(t, err) + contents, err := io.ReadAll(in) + require.NoError(t, err) + require.Empty(t, string(contents)) + require.NoError(t, in.Close()) + } +} + func TestSymlinkError(t *testing.T) { m := configmap.Simple{ "links": "true",