| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/hostguard" |
| 10 | "github.com/carlosframework/platform/internal/rootstate" |
| 11 | ) |
| 12 | |
| 13 | func TestPlan14UnitPredecessorHandover(t *testing.T) { |
| 14 | for _, attack := range []string{"none", "not retired", "same owner", "shape", "replacement", "lost materialization"} { |
| 15 | t.Run(attack, func(t *testing.T) { |
| 16 | b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "") |
| 17 | shape.Program = strings.Repeat("a", 64) |
| 18 | b.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil } |
| 19 | b.installation = func(context.Context, string) (hostguard.RootInstallation, error) { return in, nil } |
| 20 | files := &plan14UnitFiles{} |
| 21 | state := &plan14UnitBindingFault{Store: &rollbackMemory{files: map[string][]byte{}}} |
| 22 | m := claimUnitMaterializer{idle: func(string) error { return nil }, state: state, read: files.read, create: files.create, replace: files.replace} |
| 23 | b.handover = m.handover |
| 24 | old, err := b.establish(t.Context(), in.Host) |
| 25 | if err != nil { |
| 26 | t.Fatal(err) |
| 27 | } |
| 28 | if err = m.install(t.Context(), old); err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | if attack != "not retired" { |
| 32 | if err = b.retire(t.Context(), old, func(claimUnitBinding) error { return m.retire(t.Context(), old, func() error { return nil }) }); err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | } |
| 36 | if attack != "same owner" { |
| 37 | in.Stamp++ |
| 38 | } |
| 39 | if attack == "shape" { |
| 40 | shape.Addr = "127.0.0.1:9999" |
| 41 | } |
| 42 | if attack == "replacement" { |
| 43 | files.id.Inode++ |
| 44 | } |
| 45 | original := m.replace |
| 46 | if attack == "lost materialization" { |
| 47 | m.replace = func(unit string, id rootstate.FileIdentity, body []byte) error { |
| 48 | if err := original(unit, id, body); err != nil { |
| 49 | return err |
| 50 | } |
| 51 | return os.ErrInvalid |
| 52 | } |
| 53 | b.handover = m.handover |
| 54 | } |
| 55 | next, err := b.establish(t.Context(), in.Host) |
| 56 | if attack != "none" && attack != "lost materialization" { |
| 57 | if err == nil { |
| 58 | t.Fatal("unsafe handover accepted", next) |
| 59 | } |
| 60 | return |
| 61 | } |
| 62 | if attack == "lost materialization" { |
| 63 | if err == nil { |
| 64 | t.Fatal("lost replacement reported success") |
| 65 | } |
| 66 | m.replace = original |
| 67 | b.handover = m.handover |
| 68 | next, err = b.establish(t.Context(), in.Host) |
| 69 | } |
| 70 | if err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | if next.Owner.Stamp != in.Stamp || next.Retired || next.Retiring { |
| 74 | t.Fatal(next) |
| 75 | } |
| 76 | if err = m.verify(next); err != nil { |
| 77 | t.Fatal(err) |
| 78 | } |
| 79 | if err = b.retire(t.Context(), old, func(claimUnitBinding) error { t.Fatal("predecessor reached successor"); return nil }); err == nil { |
| 80 | t.Fatal("stale retirement accepted") |
| 81 | } |
| 82 | if _, err = b.establish(t.Context(), in.Host); err != nil { |
| 83 | t.Fatal("replay", err) |
| 84 | } |
| 85 | }) |
| 86 | } |
| 87 | } |
| 88 | |