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