| index 4e905c139..ca1135232 100644 |
| --- a/internal/hostfiles/hostfiles_test.go |
| +++ b/internal/hostfiles/hostfiles_test.go |
| @@ -680,3 +680,143 @@ func TestTwoFailedCommitsThenAStageRecognisesTheAppliedOne(t *testing.T) { |
| t.Fatalf("third commit = %d, %v; want 3, nil", seq, err) |
| } |
| } |
| + |
| +// putHostile publishes a good manifest with one file "a", lets bend alter |
| +// the stored manifest, and returns a files directory that already holds a |
| +// file Restore would remove or replace if it acted. |
| +func putHostile(t *testing.T, bend func(m *Manifest)) (release.Store, string) { |
| + t.Helper() |
| + ctx, s := context.Background(), release.NewDirStore(t.TempDir()) |
| + p := open(t, s) |
| + stage(t, p, "a", "good") |
| + stage(t, p, "gone", "soon") |
| + commit(t, p) |
| + if err := p.StageDelete("gone"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := p.StagePlatform(ctx, "item", []byte("plat")); err != nil { |
| + t.Fatal(err) |
| + } |
| + commit(t, p) |
| + m, etag, _, err := load(ctx, s, pfx) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + bend(&m) |
| + body, _ := json.Marshal(m) |
| + if _, err := s.Put(ctx, ManifestKey(pfx), body, etag); err != nil { |
| + t.Fatal(err) |
| + } |
| + dir := t.TempDir() |
| + if err := os.WriteFile(filepath.Join(dir, "gone"), []byte("still here"), 0o600); err != nil { |
| + t.Fatal(err) |
| + } |
| + return s, dir |
| +} |
| + |
| +func TestRestoreRefusesAnEntryWhoseObjectOrHashIsMalformedAndTouchesNothing(t *testing.T) { |
| + cases := map[string]func(m *Manifest){ |
| + "uppercase sha": func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.SHA256 = strings.ToUpper(e.SHA256) |
| + e.Object = objectKey(e.Seq, e.SHA256) |
| + m.Entries["a"] = e |
| + }, |
| + "short sha": func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.SHA256, e.Object = "aa", objectKey(e.Seq, "aa") |
| + m.Entries["a"] = e |
| + }, |
| + "object elsewhere": func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.Object = "../../other/files/g1/" + e.Object |
| + m.Entries["a"] = e |
| + }, |
| + "object with another seq": func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.Object = objectKey(e.Seq+1, e.SHA256) |
| + m.Entries["a"] = e |
| + }, |
| + "platform object elsewhere": func(m *Manifest) { |
| + e := m.Platform["item"] |
| + e.Object = "objects/elsewhere" |
| + m.Platform["item"] = e |
| + }, |
| + "platform sha not hex": func(m *Manifest) { |
| + e := m.Platform["item"] |
| + e.SHA256 = strings.Repeat("g", 64) |
| + e.Object = objectKey(e.Seq, e.SHA256) |
| + m.Platform["item"] = e |
| + }, |
| + } |
| + for name, bend := range cases { |
| + s, dir := putHostile(t, bend) |
| + if _, _, err := Restore(context.Background(), s, pfx, dir); err == nil { |
| + t.Fatalf("%s: Restore accepted the manifest", name) |
| + } |
| + if got := read(t, dir, "gone"); got != "still here" { |
| + t.Fatalf("%s: gone = %q, want it untouched", name, got) |
| + } |
| + if _, err := os.Stat(filepath.Join(dir, "a")); !os.IsNotExist(err) { |
| + t.Fatalf("%s: a was written", name) |
| + } |
| + } |
| +} |
| + |
| +func TestReadPlatformRefusesAMalformedEntry(t *testing.T) { |
| + s, _ := putHostile(t, func(m *Manifest) { |
| + e := m.Platform["item"] |
| + e.Object = "objects/elsewhere" |
| + m.Platform["item"] = e |
| + }) |
| + ctx := context.Background() |
| + // The right bytes at the wrong key: only the key check can refuse it. |
| + if _, err := s.Put(ctx, pfx+"objects/elsewhere", []byte("plat"), ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + m, _, err := Load(ctx, s, pfx) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := ReadPlatform(ctx, s, pfx, m, "item"); err == nil { |
| + t.Fatal("ReadPlatform accepted an object key that is not its own") |
| + } |
| +} |
| + |
| +func TestRestoreRefusesAnEntryWhoseSizeIsOutOfRangeAndTouchesNothing(t *testing.T) { |
| + for _, size := range []int64{-1, MaxFileBytes + 1} { |
| + s, dir := putHostile(t, func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.Size = size |
| + m.Entries["a"] = e |
| + }) |
| + if _, _, err := Restore(context.Background(), s, pfx, dir); err == nil { |
| + t.Fatalf("size %d: Restore accepted the manifest", size) |
| + } |
| + if got := read(t, dir, "gone"); got != "still here" { |
| + t.Fatalf("size %d: gone = %q, want it untouched", size, got) |
| + } |
| + } |
| +} |
| + |
| +func TestFetchRefusesAnObjectWhoseLengthIsNotTheRecordedSize(t *testing.T) { |
| + s, dir := putHostile(t, func(m *Manifest) { |
| + e := m.Entries["a"] |
| + e.Size = 3 // the object is four bytes, and its hash is right |
| + m.Entries["a"] = e |
| + p := m.Platform["item"] |
| + p.Size = 5 |
| + m.Platform["item"] = p |
| + }) |
| + ctx := context.Background() |
| + if _, _, err := Restore(ctx, s, pfx, dir); err == nil || !strings.Contains(err.Error(), "size") { |
| + t.Fatalf("Restore err = %v, want a size mismatch", err) |
| + } |
| + m, _, err := Load(ctx, s, pfx) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := ReadPlatform(ctx, s, pfx, m, "item"); err == nil || !strings.Contains(err.Error(), "size") { |
| + t.Fatalf("ReadPlatform err = %v, want a size mismatch", err) |
| + } |
| +} |