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 }