gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/shared_volume_test.go (about) 1 // Copyright 2019 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 container 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "testing" 25 26 "gvisor.dev/gvisor/pkg/sentry/control" 27 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 28 "gvisor.dev/gvisor/pkg/test/testutil" 29 "gvisor.dev/gvisor/runsc/config" 30 ) 31 32 // TestSharedVolume checks that modifications to a volume mount are propagated 33 // into and out of the sandbox. 34 func TestSharedVolume(t *testing.T) { 35 conf := testutil.TestConfig(t) 36 conf.Overlay2.Set("none") 37 conf.FileAccess = config.FileAccessShared 38 39 // Main process just sleeps. We will use "exec" to probe the state of 40 // the filesystem. 41 spec := testutil.NewSpecWithArgs("sleep", "1000") 42 43 dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") 44 if err != nil { 45 t.Fatalf("TempDir failed: %v", err) 46 } 47 48 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 49 if err != nil { 50 t.Fatalf("error setting up container: %v", err) 51 } 52 defer cleanup() 53 54 // Create and start the container. 55 args := Args{ 56 ID: testutil.RandomContainerID(), 57 Spec: spec, 58 BundleDir: bundleDir, 59 } 60 c, err := New(conf, args) 61 if err != nil { 62 t.Fatalf("error creating container: %v", err) 63 } 64 defer c.Destroy() 65 if err := c.Start(conf); err != nil { 66 t.Fatalf("error starting container: %v", err) 67 } 68 69 // File that will be used to check consistency inside/outside sandbox. 70 filename := filepath.Join(dir, "file") 71 72 // File does not exist yet. Reading from the sandbox should fail. 73 argsTestFile := &control.ExecArgs{ 74 Filename: "/usr/bin/test", 75 Argv: []string{"test", "-f", filename}, 76 } 77 if ws, err := c.executeSync(conf, argsTestFile); err != nil { 78 t.Fatalf("unexpected error testing file %q: %v", filename, err) 79 } else if ws.ExitStatus() == 0 { 80 t.Errorf("test %q exited with code %v, wanted not zero", ws.ExitStatus(), err) 81 } 82 83 // Create the file from outside of the sandbox. 84 if err := ioutil.WriteFile(filename, []byte("foobar"), 0777); err != nil { 85 t.Fatalf("error writing to file %q: %v", filename, err) 86 } 87 88 // Now we should be able to test the file from within the sandbox. 89 if ws, err := c.executeSync(conf, argsTestFile); err != nil { 90 t.Fatalf("unexpected error testing file %q: %v", filename, err) 91 } else if ws.ExitStatus() != 0 { 92 t.Errorf("test %q exited with code %v, wanted zero", filename, ws.ExitStatus()) 93 } 94 95 // Rename the file from outside of the sandbox. 96 newFilename := filepath.Join(dir, "newfile") 97 if err := os.Rename(filename, newFilename); err != nil { 98 t.Fatalf("os.Rename(%q, %q) failed: %v", filename, newFilename, err) 99 } 100 101 // File should no longer exist at the old path within the sandbox. 102 if ws, err := c.executeSync(conf, argsTestFile); err != nil { 103 t.Fatalf("unexpected error testing file %q: %v", filename, err) 104 } else if ws.ExitStatus() == 0 { 105 t.Errorf("test %q exited with code %v, wanted not zero", filename, ws.ExitStatus()) 106 } 107 108 // We should be able to test the new filename from within the sandbox. 109 argsTestNewFile := &control.ExecArgs{ 110 Filename: "/usr/bin/test", 111 Argv: []string{"test", "-f", newFilename}, 112 } 113 if ws, err := c.executeSync(conf, argsTestNewFile); err != nil { 114 t.Fatalf("unexpected error testing file %q: %v", newFilename, err) 115 } else if ws.ExitStatus() != 0 { 116 t.Errorf("test %q exited with code %v, wanted zero", newFilename, ws.ExitStatus()) 117 } 118 119 // Delete the renamed file from outside of the sandbox. 120 if err := os.Remove(newFilename); err != nil { 121 t.Fatalf("error removing file %q: %v", filename, err) 122 } 123 124 // Renamed file should no longer exist at the old path within the sandbox. 125 if ws, err := c.executeSync(conf, argsTestNewFile); err != nil { 126 t.Fatalf("unexpected error testing file %q: %v", newFilename, err) 127 } else if ws.ExitStatus() == 0 { 128 t.Errorf("test %q exited with code %v, wanted not zero", newFilename, ws.ExitStatus()) 129 } 130 131 // Now create the file from WITHIN the sandbox. 132 argsTouch := &control.ExecArgs{ 133 Filename: "/usr/bin/touch", 134 Argv: []string{"touch", filename}, 135 KUID: auth.KUID(os.Getuid()), 136 KGID: auth.KGID(os.Getgid()), 137 } 138 if ws, err := c.executeSync(conf, argsTouch); err != nil { 139 t.Fatalf("unexpected error touching file %q: %v", filename, err) 140 } else if ws.ExitStatus() != 0 { 141 t.Errorf("touch %q exited with code %v, wanted zero", filename, ws.ExitStatus()) 142 } 143 144 // File should exist outside the sandbox. 145 if _, err := os.Stat(filename); err != nil { 146 t.Errorf("stat %q got error %v, wanted nil", filename, err) 147 } 148 149 // Delete the file from within the sandbox. 150 argsRemove := &control.ExecArgs{ 151 Filename: "/bin/rm", 152 Argv: []string{"rm", filename}, 153 } 154 if ws, err := c.executeSync(conf, argsRemove); err != nil { 155 t.Fatalf("unexpected error removing file %q: %v", filename, err) 156 } else if ws.ExitStatus() != 0 { 157 t.Errorf("remove %q exited with code %v, wanted zero", filename, ws.ExitStatus()) 158 } 159 160 // File should not exist outside the sandbox. 161 if _, err := os.Stat(filename); !os.IsNotExist(err) { 162 t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err) 163 } 164 } 165 166 func checkFile(conf *config.Config, c *Container, filename string, want []byte) error { 167 cpy := filename + ".copy" 168 if _, err := execute(conf, c, "/bin/cp", "-f", filename, cpy); err != nil { 169 return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err) 170 } 171 got, err := ioutil.ReadFile(cpy) 172 if err != nil { 173 return fmt.Errorf("error reading file %q: %v", filename, err) 174 } 175 if !bytes.Equal(got, want) { 176 return fmt.Errorf("file content inside the sandbox is wrong, got: %q, want: %q", got, want) 177 } 178 return nil 179 } 180 181 // TestSharedVolumeFile tests that changes to file content outside the sandbox 182 // is reflected inside. 183 func TestSharedVolumeFile(t *testing.T) { 184 conf := testutil.TestConfig(t) 185 conf.Overlay2.Set("none") 186 conf.FileAccess = config.FileAccessShared 187 188 // Main process just sleeps. We will use "exec" to probe the state of 189 // the filesystem. 190 spec := testutil.NewSpecWithArgs("sleep", "1000") 191 192 dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") 193 if err != nil { 194 t.Fatalf("TempDir failed: %v", err) 195 } 196 197 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 198 if err != nil { 199 t.Fatalf("error setting up container: %v", err) 200 } 201 defer cleanup() 202 203 // Create and start the container. 204 args := Args{ 205 ID: testutil.RandomContainerID(), 206 Spec: spec, 207 BundleDir: bundleDir, 208 } 209 c, err := New(conf, args) 210 if err != nil { 211 t.Fatalf("error creating container: %v", err) 212 } 213 defer c.Destroy() 214 if err := c.Start(conf); err != nil { 215 t.Fatalf("error starting container: %v", err) 216 } 217 218 // File that will be used to check consistency inside/outside sandbox. 219 filename := filepath.Join(dir, "file") 220 221 // Write file from outside the container and check that the same content is 222 // read inside. 223 want := []byte("host-") 224 if err := ioutil.WriteFile(filename, []byte(want), 0666); err != nil { 225 t.Fatalf("Error writing to %q: %v", filename, err) 226 } 227 if err := checkFile(conf, c, filename, want); err != nil { 228 t.Fatal(err.Error()) 229 } 230 231 // Append to file inside the container and check that content is not lost. 232 if _, err := execute(conf, c, "/bin/bash", "-c", "echo -n sandbox- >> "+filename); err != nil { 233 t.Fatalf("unexpected error appending file %q: %v", filename, err) 234 } 235 want = []byte("host-sandbox-") 236 if err := checkFile(conf, c, filename, want); err != nil { 237 t.Fatal(err.Error()) 238 } 239 240 // Write again from outside the container and check that the same content is 241 // read inside. 242 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0) 243 if err != nil { 244 t.Fatalf("Error openning file %q: %v", filename, err) 245 } 246 defer f.Close() 247 if _, err := f.Write([]byte("host")); err != nil { 248 t.Fatalf("Error writing to file %q: %v", filename, err) 249 } 250 want = []byte("host-sandbox-host") 251 if err := checkFile(conf, c, filename, want); err != nil { 252 t.Fatal(err.Error()) 253 } 254 255 // Shrink file outside and check that the same content is read inside. 256 if err := f.Truncate(5); err != nil { 257 t.Fatalf("Error truncating file %q: %v", filename, err) 258 } 259 want = want[:5] 260 if err := checkFile(conf, c, filename, want); err != nil { 261 t.Fatal(err.Error()) 262 } 263 } 264 265 // TestSharedVolumeOverlay tests that changes to a shared volume that is 266 // wrapped in an overlay are not visible externally. 267 func TestSharedVolumeOverlay(t *testing.T) { 268 conf := testutil.TestConfig(t) 269 conf.Overlay2.Set("all:dir=/tmp") 270 271 // File that will be used to check consistency inside/outside sandbox. 272 // Note that TmpDir() is set up as a shared volume by NewSpecWithArgs(). So 273 // changes inside TmpDir() should not be visible to the host. 274 filename := filepath.Join(testutil.TmpDir(), "file") 275 276 // Create a file in TmpDir() inside the container. 277 spec := testutil.NewSpecWithArgs("/bin/bash", "-c", "echo Hello > "+filename+"; test -f "+filename) 278 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 279 if err != nil { 280 t.Fatalf("error setting up container: %v", err) 281 } 282 defer cleanup() 283 284 // Create and start the container. 285 args := Args{ 286 ID: testutil.RandomContainerID(), 287 Spec: spec, 288 BundleDir: bundleDir, 289 } 290 c, err := New(conf, args) 291 if err != nil { 292 t.Fatalf("error creating container: %v", err) 293 } 294 defer c.Destroy() 295 if err := c.Start(conf); err != nil { 296 t.Fatalf("error starting container: %v", err) 297 } 298 299 if ws, err := c.Wait(); err != nil { 300 t.Errorf("failed to wait for container: %v", err) 301 } else if es := ws.ExitStatus(); es != 0 { 302 t.Errorf("subcontainer exited with non-zero status %d", es) 303 } 304 305 // Ensure that the file does not exist on the host. 306 if _, err := os.Stat(filename); !errors.Is(err, os.ErrNotExist) { 307 t.Errorf("file exists on host, stat %q got error %v, wanted ErrNotExist", filename, err) 308 } 309 }