Files
visor/test/root/runsc_test.go
T
Ayush RanjanandgVisor bot 03ab255e51 Preserve sandbox process env vars only for --TESTONLY-unsafe-nonroot.
Earlier setCapsAndCallSelf() and callSelfAsNobody() were unconditionally
clearing the sandbox process's env vars. But in some cases we want to preserve
it. For example, when running runsc-race, we want to set GLIBC_TUNABLES for the
sandbox process. Note that runsc-race requires --TESTONLY-unsafe-nonroot=true.

Right now, when --TESTONLY-unsafe-nonroot is set, boot process does not end up
calling setCapsAndCallSelf() or callSelfAsNobody(). So the env vars are
retained as desired. But with directfs, those methods are called in spite of
--TESTONLY-unsafe-nonroot. So this change is required to preserve env vars with
--directfs && --TESTONLY-unsafe-nonroot.

This change also adds a test to verify that the sandbox process is running with
no leaked environment variables.

PiperOrigin-RevId: 533170470
2023-05-18 10:24:09 -07:00

179 lines
4.6 KiB
Go

// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package root
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/cenkalti/backoff"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/specutils"
)
// TestDoKill checks that when "runsc do..." is killed, the sandbox process is
// also terminated. This ensures that parent death signal is propagate to the
// sandbox process correctly.
func TestDoKill(t *testing.T) {
// Make the sandbox process be reparented here when it's killed, so we can
// wait for it.
if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err)
}
cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000")
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = buf
cmd.Start()
var pid int
findSandbox := func() error {
var err error
pid, err = sandboxPid(cmd.Process.Pid)
if err != nil {
return &backoff.PermanentError{Err: err}
}
if pid == 0 {
return fmt.Errorf("sandbox process not found")
}
return nil
}
if err := testutil.Poll(findSandbox, 10*time.Second); err != nil {
t.Fatalf("failed to find sandbox: %v", err)
}
t.Logf("Found sandbox, pid: %d", pid)
if err := cmd.Process.Kill(); err != nil {
t.Fatalf("failed to kill run process: %v", err)
}
cmd.Wait()
t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String())
ch := make(chan struct{})
go func() {
defer func() { ch <- struct{}{} }()
t.Logf("Waiting for sandbox process (%d) termination", pid)
if _, err := unix.Wait4(pid, nil, 0, nil); err != nil {
t.Errorf("error waiting for sandbox process (%d): %v", pid, err)
}
}()
select {
case <-ch:
// Done
case <-time.After(5 * time.Second):
t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid)
}
}
// sandboxPid looks for the sandbox process inside the process tree starting
// from "pid". It returns 0 and no error if no sandbox process is found. It
// returns error if anything failed.
func sandboxPid(pid int) (int, error) {
cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid))
buf := &bytes.Buffer{}
cmd.Stdout = buf
if err := cmd.Start(); err != nil {
return 0, err
}
ps, err := cmd.Process.Wait()
if err != nil {
return 0, err
}
if ps.ExitCode() == 1 {
// pgrep returns 1 when no process is found.
return 0, nil
}
var children []int
for _, line := range strings.Split(buf.String(), "\n") {
if len(line) == 0 {
continue
}
child, err := strconv.Atoi(line)
if err != nil {
return 0, err
}
cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline"))
if err != nil {
if os.IsNotExist(err) {
// Raced with process exit.
continue
}
return 0, err
}
args := strings.SplitN(string(cmdline), "\x00", 2)
if len(args) == 0 {
return 0, fmt.Errorf("malformed cmdline file: %q", cmdline)
}
// The sandbox process has the first argument set to "runsc-sandbox".
if args[0] == "runsc-sandbox" {
return child, nil
}
children = append(children, child)
}
// Sandbox process wasn't found, try another level down.
for _, pid := range children {
sand, err := sandboxPid(pid)
if err != nil {
return 0, err
}
if sand != 0 {
return sand, nil
}
// Not found, continue the search.
}
return 0, nil
}
// Tests that the sandbox process is running with no environment variables. We
// don't want to leak any env vars from the caller to the sandbox process.
func TestSandboxProcessEnv(t *testing.T) {
ctx := context.Background()
d := dockerutil.MakeContainer(ctx, t)
defer d.CleanUp(ctx)
runOpts := dockerutil.RunOpts{Image: "basic/alpine"}
if err := d.Spawn(ctx, runOpts, "sleep", "infinity"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(fmt.Sprintf("/proc/%d/environ", pid))
if err != nil {
t.Fatal(err)
}
if len(got) != 0 {
t.Errorf("sandbox process's environment is not empty: got %s", string(got))
}
}