gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/root/runsc_test.go (about)

     1  // Copyright 2020 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
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/cenkalti/backoff"
    31  	"golang.org/x/sys/unix"
    32  	"gvisor.dev/gvisor/pkg/test/dockerutil"
    33  	"gvisor.dev/gvisor/pkg/test/testutil"
    34  	"gvisor.dev/gvisor/runsc/specutils"
    35  )
    36  
    37  // TestDoKill checks that when "runsc do..." is killed, the sandbox process is
    38  // also terminated. This ensures that parent death signal is propagate to the
    39  // sandbox process correctly.
    40  func TestDoKill(t *testing.T) {
    41  	// Make the sandbox process be reparented here when it's killed, so we can
    42  	// wait for it.
    43  	if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
    44  		t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err)
    45  	}
    46  
    47  	cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000")
    48  	buf := &bytes.Buffer{}
    49  	cmd.Stdout = buf
    50  	cmd.Stderr = buf
    51  	cmd.Start()
    52  
    53  	var pid int
    54  	findSandbox := func() error {
    55  		var err error
    56  		pid, err = sandboxPid(cmd.Process.Pid)
    57  		if err != nil {
    58  			return &backoff.PermanentError{Err: err}
    59  		}
    60  		if pid == 0 {
    61  			return fmt.Errorf("sandbox process not found")
    62  		}
    63  		return nil
    64  	}
    65  	if err := testutil.Poll(findSandbox, 10*time.Second); err != nil {
    66  		t.Fatalf("failed to find sandbox: %v", err)
    67  	}
    68  	t.Logf("Found sandbox, pid: %d", pid)
    69  
    70  	if err := cmd.Process.Kill(); err != nil {
    71  		t.Fatalf("failed to kill run process: %v", err)
    72  	}
    73  	cmd.Wait()
    74  	t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String())
    75  
    76  	ch := make(chan struct{})
    77  	go func() {
    78  		defer func() { ch <- struct{}{} }()
    79  		t.Logf("Waiting for sandbox process (%d) termination", pid)
    80  		if _, err := unix.Wait4(pid, nil, 0, nil); err != nil {
    81  			t.Errorf("error waiting for sandbox process (%d): %v", pid, err)
    82  		}
    83  	}()
    84  	select {
    85  	case <-ch:
    86  		// Done
    87  	case <-time.After(5 * time.Second):
    88  		t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid)
    89  	}
    90  }
    91  
    92  // sandboxPid looks for the sandbox process inside the process tree starting
    93  // from "pid". It returns 0 and no error if no sandbox process is found. It
    94  // returns error if anything failed.
    95  func sandboxPid(pid int) (int, error) {
    96  	cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid))
    97  	buf := &bytes.Buffer{}
    98  	cmd.Stdout = buf
    99  	if err := cmd.Start(); err != nil {
   100  		return 0, err
   101  	}
   102  	ps, err := cmd.Process.Wait()
   103  	if err != nil {
   104  		return 0, err
   105  	}
   106  	if ps.ExitCode() == 1 {
   107  		// pgrep returns 1 when no process is found.
   108  		return 0, nil
   109  	}
   110  
   111  	var children []int
   112  	for _, line := range strings.Split(buf.String(), "\n") {
   113  		if len(line) == 0 {
   114  			continue
   115  		}
   116  		child, err := strconv.Atoi(line)
   117  		if err != nil {
   118  			return 0, err
   119  		}
   120  
   121  		cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline"))
   122  		if err != nil {
   123  			if os.IsNotExist(err) {
   124  				// Raced with process exit.
   125  				continue
   126  			}
   127  			return 0, err
   128  		}
   129  		args := strings.SplitN(string(cmdline), "\x00", 2)
   130  		if len(args) == 0 {
   131  			return 0, fmt.Errorf("malformed cmdline file: %q", cmdline)
   132  		}
   133  		// The sandbox process has the first argument set to "runsc-sandbox".
   134  		if args[0] == "runsc-sandbox" {
   135  			return child, nil
   136  		}
   137  
   138  		children = append(children, child)
   139  	}
   140  
   141  	// Sandbox process wasn't found, try another level down.
   142  	for _, pid := range children {
   143  		sand, err := sandboxPid(pid)
   144  		if err != nil {
   145  			return 0, err
   146  		}
   147  		if sand != 0 {
   148  			return sand, nil
   149  		}
   150  		// Not found, continue the search.
   151  	}
   152  	return 0, nil
   153  }
   154  
   155  // Tests that the sandbox process is running with no environment variables. We
   156  // don't want to leak any env vars from the caller to the sandbox process.
   157  func TestSandboxProcessEnv(t *testing.T) {
   158  	ctx := context.Background()
   159  	d := dockerutil.MakeContainer(ctx, t)
   160  	defer d.CleanUp(ctx)
   161  
   162  	runOpts := dockerutil.RunOpts{Image: "basic/alpine"}
   163  	if err := d.Spawn(ctx, runOpts, "sleep", "infinity"); err != nil {
   164  		t.Fatalf("docker run failed: %v", err)
   165  	}
   166  
   167  	pid, err := d.SandboxPid(ctx)
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	got, err := os.ReadFile(fmt.Sprintf("/proc/%d/environ", pid))
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  	if len(got) != 0 {
   176  		t.Errorf("sandbox process's environment is not empty: got %s", string(got))
   177  	}
   178  }