github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/nsenter/nsenter_test.go (about)

     1  package nsenter
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/opencontainers/runc/libcontainer"
    15  	"github.com/vishvananda/netlink/nl"
    16  	"golang.org/x/sys/unix"
    17  )
    18  
    19  func TestNsenterValidPaths(t *testing.T) {
    20  	args := []string{"nsenter-exec"}
    21  	parent, child := newPipe(t)
    22  
    23  	namespaces := []string{
    24  		// join pid ns of the current process
    25  		fmt.Sprintf("pid:/proc/%d/ns/pid", os.Getpid()),
    26  	}
    27  	cmd := &exec.Cmd{
    28  		Path:       os.Args[0],
    29  		Args:       args,
    30  		ExtraFiles: []*os.File{child},
    31  		Env:        []string{"_LIBCONTAINER_INITPIPE=3"},
    32  		Stdout:     os.Stdout,
    33  		Stderr:     os.Stderr,
    34  	}
    35  
    36  	if err := cmd.Start(); err != nil {
    37  		t.Fatalf("nsenter failed to start: %v", err)
    38  	}
    39  	child.Close()
    40  
    41  	// write cloneFlags
    42  	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
    43  	r.AddData(&libcontainer.Int32msg{
    44  		Type:  libcontainer.CloneFlagsAttr,
    45  		Value: uint32(unix.CLONE_NEWNET),
    46  	})
    47  	r.AddData(&libcontainer.Bytemsg{
    48  		Type:  libcontainer.NsPathsAttr,
    49  		Value: []byte(strings.Join(namespaces, ",")),
    50  	})
    51  	if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	initWaiter(t, parent)
    56  
    57  	if err := cmd.Wait(); err != nil {
    58  		t.Fatalf("nsenter error: %v", err)
    59  	}
    60  
    61  	reapChildren(t, parent)
    62  }
    63  
    64  func TestNsenterInvalidPaths(t *testing.T) {
    65  	args := []string{"nsenter-exec"}
    66  	parent, child := newPipe(t)
    67  
    68  	namespaces := []string{
    69  		fmt.Sprintf("pid:/proc/%d/ns/pid", -1),
    70  	}
    71  	cmd := &exec.Cmd{
    72  		Path:       os.Args[0],
    73  		Args:       args,
    74  		ExtraFiles: []*os.File{child},
    75  		Env:        []string{"_LIBCONTAINER_INITPIPE=3"},
    76  	}
    77  
    78  	if err := cmd.Start(); err != nil {
    79  		t.Fatalf("nsenter failed to start: %v", err)
    80  	}
    81  	child.Close()
    82  
    83  	// write cloneFlags
    84  	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
    85  	r.AddData(&libcontainer.Int32msg{
    86  		Type:  libcontainer.CloneFlagsAttr,
    87  		Value: uint32(unix.CLONE_NEWNET),
    88  	})
    89  	r.AddData(&libcontainer.Bytemsg{
    90  		Type:  libcontainer.NsPathsAttr,
    91  		Value: []byte(strings.Join(namespaces, ",")),
    92  	})
    93  	if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	initWaiter(t, parent)
    98  	if err := cmd.Wait(); err == nil {
    99  		t.Fatalf("nsenter exits with a zero exit status")
   100  	}
   101  }
   102  
   103  func TestNsenterIncorrectPathType(t *testing.T) {
   104  	args := []string{"nsenter-exec"}
   105  	parent, child := newPipe(t)
   106  
   107  	namespaces := []string{
   108  		fmt.Sprintf("net:/proc/%d/ns/pid", os.Getpid()),
   109  	}
   110  	cmd := &exec.Cmd{
   111  		Path:       os.Args[0],
   112  		Args:       args,
   113  		ExtraFiles: []*os.File{child},
   114  		Env:        []string{"_LIBCONTAINER_INITPIPE=3"},
   115  	}
   116  
   117  	if err := cmd.Start(); err != nil {
   118  		t.Fatalf("nsenter failed to start: %v", err)
   119  	}
   120  	child.Close()
   121  
   122  	// write cloneFlags
   123  	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
   124  	r.AddData(&libcontainer.Int32msg{
   125  		Type:  libcontainer.CloneFlagsAttr,
   126  		Value: uint32(unix.CLONE_NEWNET),
   127  	})
   128  	r.AddData(&libcontainer.Bytemsg{
   129  		Type:  libcontainer.NsPathsAttr,
   130  		Value: []byte(strings.Join(namespaces, ",")),
   131  	})
   132  	if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	initWaiter(t, parent)
   137  	if err := cmd.Wait(); err == nil {
   138  		t.Fatalf("nsenter error: %v", err)
   139  	}
   140  }
   141  
   142  func TestNsenterChildLogging(t *testing.T) {
   143  	args := []string{"nsenter-exec"}
   144  	parent, child := newPipe(t)
   145  	logread, logwrite := newPipe(t)
   146  
   147  	namespaces := []string{
   148  		// join pid ns of the current process
   149  		fmt.Sprintf("pid:/proc/%d/ns/pid", os.Getpid()),
   150  	}
   151  	cmd := &exec.Cmd{
   152  		Path:       os.Args[0],
   153  		Args:       args,
   154  		ExtraFiles: []*os.File{child, logwrite},
   155  		Env:        []string{"_LIBCONTAINER_INITPIPE=3", "_LIBCONTAINER_LOGPIPE=4"},
   156  		Stdout:     os.Stdout,
   157  		Stderr:     os.Stderr,
   158  	}
   159  
   160  	if err := cmd.Start(); err != nil {
   161  		t.Fatalf("nsenter failed to start: %v", err)
   162  	}
   163  	child.Close()
   164  	logwrite.Close()
   165  
   166  	// write cloneFlags
   167  	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
   168  	r.AddData(&libcontainer.Int32msg{
   169  		Type:  libcontainer.CloneFlagsAttr,
   170  		Value: uint32(unix.CLONE_NEWNET),
   171  	})
   172  	r.AddData(&libcontainer.Bytemsg{
   173  		Type:  libcontainer.NsPathsAttr,
   174  		Value: []byte(strings.Join(namespaces, ",")),
   175  	})
   176  	if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	initWaiter(t, parent)
   181  
   182  	getLogs(t, logread)
   183  	if err := cmd.Wait(); err != nil {
   184  		t.Fatalf("nsenter error: %v", err)
   185  	}
   186  
   187  	reapChildren(t, parent)
   188  }
   189  
   190  func init() {
   191  	if strings.HasPrefix(os.Args[0], "nsenter-") {
   192  		os.Exit(0)
   193  	}
   194  }
   195  
   196  func newPipe(t *testing.T) (parent *os.File, child *os.File) {
   197  	t.Helper()
   198  	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
   199  	if err != nil {
   200  		t.Fatal("socketpair failed:", err)
   201  	}
   202  	parent = os.NewFile(uintptr(fds[1]), "parent")
   203  	child = os.NewFile(uintptr(fds[0]), "child")
   204  	t.Cleanup(func() {
   205  		parent.Close()
   206  		child.Close()
   207  	})
   208  	return
   209  }
   210  
   211  // initWaiter reads back the initial \0 from runc init
   212  func initWaiter(t *testing.T, r io.Reader) {
   213  	inited := make([]byte, 1)
   214  	n, err := r.Read(inited)
   215  	if err == nil {
   216  		if n < 1 {
   217  			err = errors.New("short read")
   218  		} else if inited[0] != 0 {
   219  			err = fmt.Errorf("unexpected %d != 0", inited[0])
   220  		} else {
   221  			return
   222  		}
   223  	}
   224  	t.Fatalf("waiting for init preliminary setup: %v", err)
   225  }
   226  
   227  func reapChildren(t *testing.T, parent *os.File) {
   228  	t.Helper()
   229  	decoder := json.NewDecoder(parent)
   230  	decoder.DisallowUnknownFields()
   231  	var pid struct {
   232  		Pid2 int `json:"stage2_pid"`
   233  		Pid1 int `json:"stage1_pid"`
   234  	}
   235  	if err := decoder.Decode(&pid); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	// Reap children.
   240  	_, _ = unix.Wait4(pid.Pid1, nil, 0, nil)
   241  	_, _ = unix.Wait4(pid.Pid2, nil, 0, nil)
   242  
   243  	// Sanity check.
   244  	if pid.Pid1 == 0 || pid.Pid2 == 0 {
   245  		t.Fatal("got pids:", pid)
   246  	}
   247  }
   248  
   249  func getLogs(t *testing.T, logread *os.File) {
   250  	logsDecoder := json.NewDecoder(logread)
   251  	logsDecoder.DisallowUnknownFields()
   252  	var logentry struct {
   253  		Level string `json:"level"`
   254  		Msg   string `json:"msg"`
   255  	}
   256  
   257  	for {
   258  		if err := logsDecoder.Decode(&logentry); err != nil {
   259  			if errors.Is(err, io.EOF) {
   260  				return
   261  			}
   262  			t.Fatal("init log decoding error:", err)
   263  		}
   264  		t.Logf("logentry: %+v", logentry)
   265  		if logentry.Level == "" || logentry.Msg == "" {
   266  			t.Fatalf("init log: empty log entry: %+v", logentry)
   267  		}
   268  	}
   269  }