| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "github.com/carlosframework/platform/internal/rootstate" |
| 5 | "golang.org/x/sys/unix" |
| 6 | "os" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | func TestPlan14PersistentChrootMountBoundary(t *testing.T) { |
| 11 | if !plan14RootFixture(t) { |
| 12 | return |
| 13 | } |
| 14 | for _, p := range []string{"/data", "/view"} { |
| 15 | if e := os.Mkdir(p, 0755); e != nil { |
| 16 | t.Fatal(e) |
| 17 | } |
| 18 | } |
| 19 | if e := unix.Mount("/data", "/view", "", unix.MS_BIND, ""); e != nil { |
| 20 | t.Fatal(e) |
| 21 | } |
| 22 | defer unix.Unmount("/view", 0) |
| 23 | f, e := os.Open("/view") |
| 24 | if e != nil { |
| 25 | t.Fatal(e) |
| 26 | } |
| 27 | defer f.Close() |
| 28 | before, e := rootstate.ObjectIdentity(f) |
| 29 | if e != nil || before.MountLineage == "" { |
| 30 | t.Fatal(before, e) |
| 31 | } |
| 32 | if e := unix.Mount("/view", "/view", "", unix.MS_BIND, ""); e != nil { |
| 33 | t.Fatal(e) |
| 34 | } |
| 35 | second, e := os.Open("/view") |
| 36 | if e != nil { |
| 37 | t.Fatal(e) |
| 38 | } |
| 39 | after, e := rootstate.ObjectIdentity(second) |
| 40 | second.Close() |
| 41 | unmount := unix.Unmount("/view", 0) |
| 42 | if e != nil || unmount != nil || before.Same(after) { |
| 43 | t.Fatal("substituted mount", e, unmount, before, after) |
| 44 | } |
| 45 | restored, e := rootstate.ObjectIdentity(f) |
| 46 | if e != nil || !before.Same(restored) { |
| 47 | t.Fatal("restored mount", e) |
| 48 | } |
| 49 | if err := unix.Unmount("/view", unix.MNT_DETACH); err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | if err := unix.Mount("/data", "/view", "", unix.MS_BIND, ""); err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | replacement, err := os.Open("/view") |
| 56 | if err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | replaced, err := rootstate.ObjectIdentity(replacement) |
| 60 | replacement.Close() |
| 61 | if err != nil || replaced.MountUnique == before.MountUnique || !replaced.SameMountNamespace(before) || replaced.MountLineage != before.MountLineage || replaced.Filesystem != before.Filesystem || replaced.Handle != before.Handle || replaced.Inode != before.Inode { |
| 62 | t.Fatal("replacement did not preserve geometry and persistent object", before, replaced, err) |
| 63 | } |
| 64 | if before.Same(replaced) { |
| 65 | t.Fatal("replacement mount with unchanged geometry accepted") |
| 66 | } |
| 67 | replaced.Mount = before.Mount |
| 68 | if before.Same(replaced) { |
| 69 | t.Fatal("replacement mount accepted with a recycled legacy number") |
| 70 | } |
| 71 | for _, path := range []string{"/source", "/parent", "/parent/view"} { |
| 72 | if err := os.Mkdir(path, 0755); err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | } |
| 76 | if err := unix.Mount("/source", "/parent/view", "", unix.MS_BIND, ""); err != nil { |
| 77 | t.Fatal(err) |
| 78 | } |
| 79 | defer unix.Unmount("/parent/view", 0) |
| 80 | capture := func() rootstate.FileIdentity { |
| 81 | f, err := os.Open("/parent/view") |
| 82 | if err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | defer f.Close() |
| 86 | id, err := rootstate.ObjectIdentity(f) |
| 87 | if err != nil { |
| 88 | t.Fatal(err) |
| 89 | } |
| 90 | return id |
| 91 | } |
| 92 | child := capture() |
| 93 | if err := unix.Mount("/parent", "/parent", "", unix.MS_BIND|unix.MS_REC, ""); err != nil { |
| 94 | t.Fatal(err) |
| 95 | } |
| 96 | defer unix.Unmount("/parent", unix.MNT_DETACH) |
| 97 | rebound := capture() |
| 98 | if child.MountRoot != rebound.MountRoot || child.MountPoint != rebound.MountPoint || child.Filesystem != rebound.Filesystem || child.Handle != rebound.Handle || child.Inode != rebound.Inode { |
| 99 | t.Fatal("recursive bind changed the child fixture", child, rebound) |
| 100 | } |
| 101 | if child.Same(rebound) || rebound.Same(child) { |
| 102 | t.Fatal("recursive ancestor substitution accepted") |
| 103 | } |
| 104 | } |
| 105 | |