diff --git a/pkg/blobs/local_storage.go b/pkg/blobs/local_storage.go index 94449914fae3..4c96644aed51 100644 --- a/pkg/blobs/local_storage.go +++ b/pkg/blobs/local_storage.go @@ -59,7 +59,11 @@ func (l *LocalStorage) prependExternalIODir(path string) (string, error) { } func (l *LocalStorage) ensureContained(realPath, inputPath string) error { - if !strings.HasPrefix(realPath, l.externalIODir) { + // Compare on path boundaries rather than raw string prefixes: the latter + // also accepts siblings of the I/O directory whose name merely starts with + // it, e.g. "/data/backups-archive" for an I/O directory of "/data/backups". + rel, err := filepath.Rel(l.externalIODir, realPath) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { return errors.Errorf("local file access to paths outside of external-io-dir is not allowed: %s", inputPath) } return nil diff --git a/pkg/blobs/local_storage_test.go b/pkg/blobs/local_storage_test.go index d4f9d1013ca6..ea580fefdc7f 100644 --- a/pkg/blobs/local_storage_test.go +++ b/pkg/blobs/local_storage_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDirectoryNormalization(t *testing.T) { @@ -24,3 +25,46 @@ func TestDirectoryNormalization(t *testing.T) { assert.Equal(t, expected, l.externalIODir) } + +func TestPrependExternalIODir(t *testing.T) { + externalIODir := filepath.Join(t.TempDir(), "backups") + l, err := NewLocalStorage(externalIODir) + if err != nil { + t.Fatal(err) + } + + for _, tc := range []struct { + name string + path string + expectedErr string + }{ + { + name: "inside", + path: "test/file.csv", + }, + { + name: "parent", + path: "../file.csv", + expectedErr: "outside of external-io-dir is not allowed", + }, + { + name: "sibling with the io-dir as a name prefix", + path: "../backups-archive/file.csv", + expectedErr: "outside of external-io-dir is not allowed", + }, + { + name: "rooted sibling with the io-dir as a name prefix", + path: "/../backups-archive/file.csv", + expectedErr: "outside of external-io-dir is not allowed", + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := l.prependExternalIODir(tc.path) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +}