godev/internal/storage: reject unusual FS object names

General security hardening:

Run the name provided to NewFSObject through filepath.Localize,
which requires that it be a valid io/fs path (no .. components)
and also representable on the local filesystem (no opening NUL
on Windows).

Change-Id: I00e7797e55daf4bfb010a71c0fa2b6446a6a6964
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/848192
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/godev/internal/storage/storage.go b/godev/internal/storage/storage.go
index a96585e..a269114 100644
--- a/godev/internal/storage/storage.go
+++ b/godev/internal/storage/storage.go
@@ -166,11 +166,16 @@
 
 type FSObject struct {
 	filename string
+	err      error
 }
 
 func NewFSObject(b *FSBucket, name string) ObjectHandle {
-	filename := filepath.Join(b.dir, b.bucket, filepath.FromSlash(name))
-	return &FSObject{filename}
+	localName, err := filepath.Localize(name)
+	if err != nil {
+		return &FSObject{err: fmt.Errorf("invalid object name %q: %w", name, err)}
+	}
+	filename := filepath.Join(b.dir, b.bucket, localName)
+	return &FSObject{filename: filename}
 }
 
 func (o *FSObject) Filename() string {
@@ -178,6 +183,9 @@
 }
 
 func (o *FSObject) NewReader(ctx context.Context) (io.ReadCloser, error) {
+	if o.err != nil {
+		return nil, o.err
+	}
 	r, err := os.Open(o.filename)
 	if errors.Is(err, os.ErrNotExist) {
 		return nil, ErrObjectNotExist
@@ -186,6 +194,9 @@
 }
 
 func (o *FSObject) NewWriter(ctx context.Context) (io.WriteCloser, error) {
+	if o.err != nil {
+		return nil, o.err
+	}
 	if err := os.MkdirAll(filepath.Dir(o.filename), os.ModePerm); err != nil {
 		return nil, err
 	}
diff --git a/godev/internal/storage/storage_test.go b/godev/internal/storage/storage_test.go
index 5fc8eb6..951a8b9 100644
--- a/godev/internal/storage/storage_test.go
+++ b/godev/internal/storage/storage_test.go
@@ -32,6 +32,14 @@
 		t.Fatal(err)
 	}
 	runTest(t, ctx, s)
+	for _, invalid := range []string{"../escape", "a/../../escape", "/abs"} {
+		if err := write(ctx, s, invalid, writeData); err == nil {
+			t.Errorf("write(%q) succeeded, want error", invalid)
+		}
+		if _, err := read(ctx, s, invalid); err == nil {
+			t.Errorf("read(%q) succeeded, want error", invalid)
+		}
+	}
 }
 
 func runTest(t *testing.T, ctx context.Context, s BucketHandle) {