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