gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/root/chroot_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package root is used for tests that requires sysadmin privileges run. 16 package root 17 18 import ( 19 "context" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strconv" 26 "strings" 27 "testing" 28 29 "gvisor.dev/gvisor/pkg/test/dockerutil" 30 ) 31 32 func getParentPID(childPID int) (int, error) { 33 cmd := fmt.Sprintf("grep PPid: %s | sed 's/PPid:\\s//'", procPath(strconv.Itoa(childPID), "status")) 34 parent, err := exec.Command("bash", "-c", cmd).CombinedOutput() 35 if err != nil { 36 return -1, fmt.Errorf("failed to fetch parent PID of %d: %v, out:\n%s", childPID, err, string(parent)) 37 } 38 parentPID, err := strconv.Atoi(strings.TrimSpace(string(parent))) 39 if err != nil { 40 return -1, fmt.Errorf("failed to parse PPID %q: %v", string(parent), err) 41 } 42 return parentPID, nil 43 } 44 45 // TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned 46 // up after the sandbox is destroyed. 47 func TestChroot(t *testing.T) { 48 ctx := context.Background() 49 d := dockerutil.MakeContainer(ctx, t) 50 defer d.CleanUp(ctx) 51 52 if err := d.Spawn(ctx, dockerutil.RunOpts{ 53 Image: "basic/alpine", 54 }, "sleep", "10000"); err != nil { 55 t.Fatalf("docker run failed: %v", err) 56 } 57 58 pid, err := d.SandboxPid(ctx) 59 if err != nil { 60 t.Fatalf("Docker.SandboxPid(): %v", err) 61 } 62 63 // Check that sandbox is chroot'ed. 64 procRoot := procPath(strconv.Itoa(pid), "root") 65 chroot, err := filepath.EvalSymlinks(procRoot) 66 if err != nil { 67 t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) 68 } 69 if chroot != "/" { 70 t.Errorf("sandbox is not chroot'd, it should be inside: /, got: %q", chroot) 71 } 72 73 path, err := filepath.EvalSymlinks(procPath(strconv.Itoa(pid), "cwd")) 74 if err != nil { 75 t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) 76 } 77 if chroot != path { 78 t.Errorf("sandbox current dir is wrong, want: %q, got: %q", chroot, path) 79 } 80 81 fi, err := ioutil.ReadDir(procRoot) 82 if err != nil { 83 t.Fatalf("error listing %q: %v", chroot, err) 84 } 85 if want, got := 2, len(fi); want != got { 86 t.Fatalf("chroot dir got %d entries, want %d", got, want) 87 } 88 89 // chroot dir is prepared by runsc and should contains only /etc and /proc. 90 for i, want := range []string{"etc", "proc"} { 91 if got := fi[i].Name(); got != want { 92 t.Errorf("chroot got child %v, want %v", got, want) 93 } 94 } 95 96 d.CleanUp(ctx) 97 } 98 99 func TestChrootGofer(t *testing.T) { 100 ctx := context.Background() 101 d := dockerutil.MakeContainer(ctx, t) 102 defer d.CleanUp(ctx) 103 104 if err := d.Spawn(ctx, dockerutil.RunOpts{ 105 Image: "basic/alpine", 106 }, "sleep", "10000"); err != nil { 107 t.Fatalf("docker run failed: %v", err) 108 } 109 110 // It's tricky to find gofers. Get sandbox PID first, then find parent. From 111 // parent get all immediate children, remove the sandbox, and everything else 112 // are gofers. 113 sandPID, err := d.SandboxPid(ctx) 114 if err != nil { 115 t.Fatalf("Docker.SandboxPid(): %v", err) 116 } 117 118 // Find sandbox's parent PID. 119 parentPID, err := getParentPID(sandPID) 120 if err != nil { 121 t.Fatalf("failed to fetch runsc parent PID: %v", err) 122 } 123 124 // Get all children from parent. 125 var childrenPIDs []int 126 procfsRoot := procPath() 127 procDirs, err := os.ReadDir(procfsRoot) 128 if err != nil { 129 t.Fatalf("cannot list processes in %s: %s", procfsRoot, err) 130 } 131 for _, procDir := range procDirs { 132 if !procDir.IsDir() { 133 continue 134 } 135 procPID, err := strconv.Atoi(procDir.Name()) 136 if err != nil { 137 // We only care about directories that are PIDs. 138 continue 139 } 140 // Now check if it is a child of parentPID. 141 parent, err := getParentPID(procPID) 142 if err != nil { 143 // Skip, this may be a race condition with a process that has since gone away. 144 t.Logf("Non-fatal warning: cannot get parent PID of %d (process likely gone): %v", procPID, err) 145 continue 146 } 147 if parent == parentPID { 148 t.Logf("runsc parent PID %d has child PID %d", parentPID, procPID) 149 childrenPIDs = append(childrenPIDs, procPID) 150 } 151 } 152 // Ensure we have seen at least one child PID. 153 if len(childrenPIDs) == 0 { 154 t.Fatalf("Found no children of runsc parent PID %d", parentPID) 155 } 156 157 // This where the root directory is mapped on the host and that's where the 158 // gofer must have chroot'd to. 159 root := "/root" 160 161 for _, childPID := range childrenPIDs { 162 if childPID == sandPID { 163 // Skip the sandbox, all other immediate children are gofers. 164 continue 165 } 166 167 // Check that gofer is chroot'ed. 168 chroot, err := filepath.EvalSymlinks(procPath(strconv.Itoa(childPID), "root")) 169 if err != nil { 170 t.Fatalf("error resolving /proc/<pid>/root symlink: %v", err) 171 } 172 if root != chroot { 173 t.Errorf("gofer chroot is wrong, want: %q, got: %q", root, chroot) 174 } 175 176 path, err := filepath.EvalSymlinks(procPath(strconv.Itoa(childPID), "cwd")) 177 if err != nil { 178 t.Fatalf("error resolving /proc/<pid>/cwd symlink: %v", err) 179 } 180 if root != path { 181 t.Errorf("gofer current dir is wrong, want: %q, got: %q", root, path) 182 } 183 } 184 }