gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/e2e/exec_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 integration provides end-to-end integration tests for runsc. These 16 // tests require docker and runsc to be installed on the machine. 17 // 18 // Each test calls docker commands to start up a container, and tests that it 19 // is behaving properly, with various runsc commands. The container is killed 20 // and deleted at the end. 21 22 package integration 23 24 import ( 25 "context" 26 "fmt" 27 "strconv" 28 "strings" 29 "testing" 30 "time" 31 32 "gvisor.dev/gvisor/pkg/abi/linux" 33 "gvisor.dev/gvisor/pkg/bits" 34 "gvisor.dev/gvisor/pkg/test/dockerutil" 35 "gvisor.dev/gvisor/pkg/test/testutil" 36 "gvisor.dev/gvisor/runsc/specutils" 37 ) 38 39 const ( 40 noCap = "0000000000000000" 41 netAdminOnlyCap = "0000000000001000" 42 ) 43 44 // Test that exec uses the exact same capability set as the container. 45 func TestExecCapabilities(t *testing.T) { 46 ctx := context.Background() 47 d := dockerutil.MakeContainer(ctx, t) 48 defer d.CleanUp(ctx) 49 50 // Start the container. 51 if err := d.Spawn(ctx, dockerutil.RunOpts{ 52 Image: "basic/alpine", 53 }, "sh", "-c", "cat /proc/self/status; sleep 200"); err != nil { 54 t.Fatalf("docker run failed: %v", err) 55 } 56 57 // Check that capability. 58 caps := []string{"CapInh", "CapPrm", "CapEff", "CapBnd"} 59 // Expected capabilities for non-root usres. 60 wantCaps := map[string]string{} 61 // For the root user. 62 for _, cap := range caps { 63 pattern := fmt.Sprintf("%s:\t([0-9a-f]+)\n", cap) 64 matches, err := d.WaitForOutputSubmatch(ctx, pattern, 5*time.Second) 65 if err != nil { 66 t.Fatalf("WaitForOutputSubmatch() timeout: %v", err) 67 } 68 if len(matches) != 2 { 69 t.Fatalf("There should be a match for the whole line and the capability bitmask") 70 } 71 want := fmt.Sprintf("%s:\t%s\n", cap, matches[1]) 72 t.Log("root capabilities:", want) 73 74 // Now check that exec'd process capabilities match the root. 75 got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "grep", fmt.Sprintf("%s:", cap), "/proc/self/status") 76 if err != nil { 77 t.Fatalf("docker exec failed: %v", err) 78 } 79 if got != want { 80 t.Errorf("wrong %s, got: %q, want: %q", cap, got, want) 81 } 82 // CapBnd and CpaInh are unchanged, other capabilities will 83 // be tranformed for non-root users. 84 wantCaps[cap] = fmt.Sprintf("%s:\t%s\n", cap, noCap) 85 if cap == "CapBnd" || cap == "CapInh" { 86 wantCaps[cap] = got 87 } 88 } 89 gid, uid, groupname, username := "1001", "1002", "gvisor-test", "gvisor-test" 90 // Add a new group. 91 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "addgroup", groupname, "--gid", gid); err != nil { 92 t.Fatalf("failed to create a new group: %v", err) 93 } 94 // Add a new user. 95 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "adduser", "--no-create-home", "--disabled-password", "--gecos", "", "--ingroup", groupname, username); err != nil { 96 t.Fatalf("failed to create a new user: %v", err) 97 } 98 for cap, want := range wantCaps { 99 got, err := d.Exec(ctx, dockerutil.ExecOpts{User: uid}, "grep", fmt.Sprintf("%s:", cap), "/proc/self/status") 100 if err != nil { 101 t.Fatalf("docker exec failed: %v", err) 102 } 103 t.Logf("%s: %v", cap, got) 104 // Format the matched capability. 105 if got != want { 106 t.Errorf("wrong %s, got: %q, want: %q", cap, got, want) 107 } 108 } 109 } 110 111 func TestFileCap(t *testing.T) { 112 ctx := context.Background() 113 d := dockerutil.MakeContainer(ctx, t) 114 defer d.CleanUp(ctx) 115 116 // Start the container. 117 if err := d.Spawn(ctx, dockerutil.RunOpts{ 118 Image: "basic/filecap", 119 CapAdd: []string{"NET_ADMIN"}, 120 }, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { 121 t.Fatalf("docker run failed: %v", err) 122 } 123 output, err := d.Exec(ctx, dockerutil.ExecOpts{User: "1001"}, "/mnt/cat", "/proc/self/status") 124 if err != nil { 125 t.Fatalf("docker exec failed: %v", err) 126 } 127 expectedCaps := fmt.Sprintf("CapInh:\t%s\nCapPrm:\t%s\nCapEff:\t%s\n", noCap, netAdminOnlyCap, netAdminOnlyCap) 128 if !strings.Contains(output, expectedCaps) { 129 t.Fatalf("can't find expected caps:\n %v, output: %v", expectedCaps, output) 130 } 131 } 132 133 func TestNoExpectedFileCap(t *testing.T) { 134 ctx := context.Background() 135 d := dockerutil.MakeContainer(ctx, t) 136 defer d.CleanUp(ctx) 137 138 // Start the container. 139 if err := d.Spawn(ctx, dockerutil.RunOpts{ 140 Image: "basic/filecap", 141 CapAdd: []string{"NET_RAW"}, 142 }, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { 143 t.Fatalf("docker run failed: %v", err) 144 } 145 output, err := d.Exec(ctx, dockerutil.ExecOpts{User: "1001"}, "/mnt/cat", "/proc/self/status") 146 if err == nil { 147 t.Fatalf("error not present") 148 } 149 if !strings.Contains(output, "operation not permitted") { 150 t.Fatalf("expected error: operation not permitted, got: %v", err) 151 } 152 } 153 154 // Test that 'exec --privileged' adds all capabilities, except for CAP_NET_RAW 155 // which is removed from the container when --net-raw=false. 156 func TestExecPrivileged(t *testing.T) { 157 ctx := context.Background() 158 d := dockerutil.MakeContainer(ctx, t) 159 defer d.CleanUp(ctx) 160 161 // Container will drop all capabilities. 162 opts := dockerutil.RunOpts{ 163 Image: "basic/alpine", 164 CapDrop: []string{"all"}, 165 } 166 167 // But if we are running with host network stack and raw sockets, then 168 // we require CAP_NET_RAW, so add that back. 169 if testutil.IsRunningWithHostNet() && testutil.IsRunningWithNetRaw() { 170 opts.CapAdd = []string{"NET_RAW"} 171 } 172 173 // Start the container. 174 if err := d.Spawn(ctx, opts, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil { 175 t.Fatalf("docker run failed: %v", err) 176 } 177 178 // Grab the capabilities from inside container. 179 matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second) 180 if err != nil { 181 t.Fatalf("WaitForOutputSubmatch() timeout: %v", err) 182 } 183 if len(matches) != 2 { 184 t.Fatalf("There should be a match for the whole line and the capability bitmask") 185 } 186 containerCaps, err := strconv.ParseUint(matches[1], 16, 64) 187 if err != nil { 188 t.Fatalf("failed to convert capabilities %q: %v", matches[1], err) 189 } 190 191 // Expect no capabilities, unless raw sockets configured. 192 var wantContainerCaps uint64 193 if testutil.IsRunningWithNetRaw() { 194 wantContainerCaps |= bits.MaskOf64(int(linux.CAP_NET_RAW)) 195 } 196 if containerCaps != wantContainerCaps { 197 t.Fatalf("Container caps got %x want %x", containerCaps, wantContainerCaps) 198 } 199 200 // Check that 'exec --privileged' adds all capabilities except 201 // CAP_NET_RAW, unless raw sockets configured. 202 got, err := d.Exec(ctx, dockerutil.ExecOpts{ 203 Privileged: true, 204 }, "grep", "CapEff:", "/proc/self/status") 205 if err != nil { 206 t.Fatalf("docker exec failed: %v", err) 207 } 208 wantCaps := specutils.AllCapabilitiesUint64() 209 if !testutil.IsRunningWithNetRaw() { 210 wantCaps &= ^bits.MaskOf64(int(linux.CAP_NET_RAW)) 211 } 212 wantStr := fmt.Sprintf("CapEff:\t%016x\n", wantCaps) 213 if got == wantStr { 214 // All good. 215 return 216 } 217 // Older versions of Docker don't support CAP_PERFMON, _BPF, or 218 // _CHECKPOINT_RESTORE. Mask those and see if we are equal. 219 oldWantCaps := wantCaps 220 for _, cap := range []linux.Capability{linux.CAP_PERFMON, linux.CAP_BPF, linux.CAP_CHECKPOINT_RESTORE} { 221 oldWantCaps = oldWantCaps &^ bits.MaskOf64(int(cap)) 222 } 223 oldWantStr := fmt.Sprintf("CapEff:\t%016x\n", oldWantCaps) 224 if got != oldWantStr { 225 t.Errorf("Wrong capabilities, got: %q, want: %q or %q. Make sure runsc is not using '--net-raw'", got, wantStr, oldWantStr) 226 } 227 } 228 229 func TestExecJobControl(t *testing.T) { 230 ctx := context.Background() 231 d := dockerutil.MakeContainer(ctx, t) 232 defer d.CleanUp(ctx) 233 234 // Start the container. 235 if err := d.Spawn(ctx, dockerutil.RunOpts{ 236 Image: "basic/alpine", 237 }, "sleep", "1000"); err != nil { 238 t.Fatalf("docker run failed: %v", err) 239 } 240 241 p, err := d.ExecProcess(ctx, dockerutil.ExecOpts{UseTTY: true}, "/bin/sh") 242 if err != nil { 243 t.Fatalf("docker exec failed: %v", err) 244 } 245 246 if _, err = p.Write(time.Second, []byte("sleep 100 | cat\n")); err != nil { 247 t.Fatalf("error exit: %v", err) 248 } 249 time.Sleep(time.Second) 250 251 if _, err = p.Write(time.Second, []byte{0x03}); err != nil { 252 t.Fatalf("error exit: %v", err) 253 } 254 255 if _, err = p.Write(time.Second, []byte("exit $(expr $? + 10)\n")); err != nil { 256 t.Fatalf("error exit: %v", err) 257 } 258 259 want := 140 260 got, err := p.WaitExitStatus(ctx) 261 if err != nil { 262 t.Fatalf("wait for exit failed with: %v", err) 263 } else if got != want { 264 t.Fatalf("wait for exit returned: %d want: %d", got, want) 265 } 266 } 267 268 // Test that failure to exec returns proper error message. 269 func TestExecError(t *testing.T) { 270 ctx := context.Background() 271 d := dockerutil.MakeContainer(ctx, t) 272 defer d.CleanUp(ctx) 273 274 // Start the container. 275 if err := d.Spawn(ctx, dockerutil.RunOpts{ 276 Image: "basic/alpine", 277 }, "sleep", "1000"); err != nil { 278 t.Fatalf("docker run failed: %v", err) 279 } 280 281 // Attempt to exec a binary that doesn't exist. 282 out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "no_can_find") 283 if err == nil { 284 t.Fatalf("docker exec didn't fail") 285 } 286 if want := `error finding executable "no_can_find" in PATH`; !strings.Contains(out, want) { 287 t.Fatalf("docker exec wrong error, got: %s, want: .*%s.*", out, want) 288 } 289 } 290 291 // Test that exec inherits environment from run. 292 func TestExecEnv(t *testing.T) { 293 ctx := context.Background() 294 d := dockerutil.MakeContainer(ctx, t) 295 defer d.CleanUp(ctx) 296 297 // Start the container with env FOO=BAR. 298 if err := d.Spawn(ctx, dockerutil.RunOpts{ 299 Image: "basic/alpine", 300 Env: []string{"FOO=BAR"}, 301 }, "sleep", "1000"); err != nil { 302 t.Fatalf("docker run failed: %v", err) 303 } 304 305 // Exec "echo $FOO". 306 got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $FOO") 307 if err != nil { 308 t.Fatalf("docker exec failed: %v", err) 309 } 310 if got, want := strings.TrimSpace(got), "BAR"; got != want { 311 t.Errorf("bad output from 'docker exec'. Got %q; Want %q.", got, want) 312 } 313 } 314 315 // TestRunEnvHasHome tests that run always has HOME environment set. 316 func TestRunEnvHasHome(t *testing.T) { 317 // Base alpine image does not have any environment variables set. 318 ctx := context.Background() 319 d := dockerutil.MakeContainer(ctx, t) 320 defer d.CleanUp(ctx) 321 322 // Exec "echo $HOME". The 'bin' user's home dir is '/bin'. 323 got, err := d.Run(ctx, dockerutil.RunOpts{ 324 Image: "basic/alpine", 325 User: "bin", 326 }, "/bin/sh", "-c", "echo $HOME") 327 if err != nil { 328 t.Fatalf("docker run failed: %v", err) 329 } 330 331 // Check that the directory matches. 332 if got, want := strings.TrimSpace(got), "/bin"; got != want { 333 t.Errorf("bad output from 'docker run'. Got %q; Want %q.", got, want) 334 } 335 } 336 337 // Test that exec always has HOME environment set, even when not set in run. 338 func TestExecEnvHasHome(t *testing.T) { 339 // Base alpine image does not have any environment variables set. 340 ctx := context.Background() 341 d := dockerutil.MakeContainer(ctx, t) 342 defer d.CleanUp(ctx) 343 344 if err := d.Spawn(ctx, dockerutil.RunOpts{ 345 Image: "basic/alpine", 346 }, "sleep", "1000"); err != nil { 347 t.Fatalf("docker run failed: %v", err) 348 } 349 350 // Exec "echo $HOME", and expect to see "/root". 351 got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $HOME") 352 if err != nil { 353 t.Fatalf("docker exec failed: %v", err) 354 } 355 if want := "/root"; !strings.Contains(got, want) { 356 t.Errorf("wanted exec output to contain %q, got %q", want, got) 357 } 358 359 // Create a new user with a home directory. 360 newUID := 1234 361 newHome := "/foo/bar" 362 cmd := fmt.Sprintf("mkdir -p -m 777 %q && adduser foo -D -u %d -h %q", newHome, newUID, newHome) 363 if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", cmd); err != nil { 364 t.Fatalf("docker exec failed: %v", err) 365 } 366 367 // Execute the same as the new user and expect newHome. 368 got, err = d.Exec(ctx, dockerutil.ExecOpts{ 369 User: strconv.Itoa(newUID), 370 }, "/bin/sh", "-c", "echo $HOME") 371 if err != nil { 372 t.Fatalf("docker exec failed: %v", err) 373 } 374 if want := newHome; !strings.Contains(got, want) { 375 t.Errorf("wanted exec output to contain %q, got %q", want, got) 376 } 377 }