github.com/opencontainers/runtime-tools@v0.9.0/validation/linux_ns_path/linux_ns_path.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "runtime" 8 "syscall" 9 "time" 10 11 "github.com/mndrix/tap-go" 12 rspec "github.com/opencontainers/runtime-spec/specs-go" 13 "github.com/opencontainers/runtime-tools/specerror" 14 "github.com/opencontainers/runtime-tools/validation/util" 15 ) 16 17 func waitForState(stateCheckFunc func() error) error { 18 timeout := 3 * time.Second 19 alarm := time.After(timeout) 20 ticker := time.Tick(200 * time.Millisecond) 21 for { 22 select { 23 case <-alarm: 24 return fmt.Errorf("failed to reach expected state within %v", timeout) 25 case <-ticker: 26 if err := stateCheckFunc(); err == nil { 27 return nil 28 } 29 } 30 } 31 } 32 33 func checkNamespacePath(t *tap.T, unsharePid int, ns string) error { 34 testNsPath := fmt.Sprintf("/proc/%d/ns/%s", os.Getpid(), ns) 35 testNsInode, err := os.Readlink(testNsPath) 36 if err != nil { 37 out, err2 := exec.Command("sh", "-c", fmt.Sprintf("ls -la /proc/%d/ns/", os.Getpid())).CombinedOutput() 38 return fmt.Errorf("cannot read namespace link for the test process: %s\n%v\n%v", err, err2, string(out)) 39 } 40 41 var errNsPath error 42 43 unshareNsPath := "" 44 unshareNsInode := "" 45 46 doCheckNamespacePath := func() error { 47 specialChildren := "" 48 if ns == "pid" { 49 // Unsharing pidns does not move the process into the new 50 // pidns but the next forked process. 'unshare' is called with 51 // '--fork' so the pidns will be fully created and populated 52 // with a pid 1. 53 // 54 // However, finding out the pid of the child process is not 55 // trivial: it would require to parse 56 // /proc/$pid/task/$tid/children but that only works on kernels 57 // with CONFIG_PROC_CHILDREN (not all distros have that). 58 // 59 // It is easier to look at /proc/$pid/ns/pid_for_children on 60 // the parent process. Available since Linux 4.12. 61 specialChildren = "_for_children" 62 } 63 unshareNsPath = fmt.Sprintf("/proc/%d/ns/%s", unsharePid, ns+specialChildren) 64 unshareNsInode, err = os.Readlink(unshareNsPath) 65 if err != nil { 66 errNsPath = fmt.Errorf("cannot read namespace link for the unshare process: %s", err) 67 return errNsPath 68 } 69 70 if testNsInode == unshareNsInode { 71 errNsPath = fmt.Errorf("expected: %q, found: %q", testNsInode, unshareNsInode) 72 return errNsPath 73 } 74 75 return nil 76 } 77 78 // Since it takes some time until unshare switched to the new namespace, 79 // we should make a loop to check for the result up to 3 seconds. 80 if err := waitForState(doCheckNamespacePath); err != nil { 81 // we should return errNsPath instead of err, because errNsPath is what 82 // returned from the actual test function doCheckNamespacePath(), not 83 // waitForState(). 84 return errNsPath 85 } 86 87 g, err := util.GetDefaultGenerator() 88 if err != nil { 89 return fmt.Errorf("cannot get the default generator: %v", err) 90 } 91 92 rtns := util.GetRuntimeToolsNamespace(ns) 93 g.AddOrReplaceLinuxNamespace(rtns, unshareNsPath) 94 95 return util.RuntimeOutsideValidate(g, t, func(config *rspec.Spec, t *tap.T, state *rspec.State) error { 96 containerNsPath := fmt.Sprintf("/proc/%d/ns/%s", state.Pid, ns) 97 containerNsInode, err := os.Readlink(containerNsPath) 98 if err != nil { 99 out, err2 := exec.Command("sh", "-c", fmt.Sprintf("ls -la /proc/%d/ns/", state.Pid)).CombinedOutput() 100 return fmt.Errorf("cannot read namespace link for the container process: %s\n%v\n%v", err, err2, out) 101 } 102 if containerNsInode != unshareNsInode { 103 return fmt.Errorf("expected: %q, found: %q", unshareNsInode, containerNsInode) 104 } 105 return nil 106 }) 107 } 108 109 func testNamespacePath(t *tap.T, ns string, unshareOpt string) error { 110 // Calling 'unshare' (part of util-linux) is easier than doing it from 111 // Golang: mnt namespaces cannot be unshared from multithreaded 112 // programs. 113 cmd := exec.Command("unshare", unshareOpt, "--fork", "sleep", "10000") 114 // We shoud set Setpgid to true, to be able to allow the unshare process 115 // as well as its child processes to be killed by a single kill command. 116 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 117 err := cmd.Start() 118 if err != nil { 119 return fmt.Errorf("cannot run unshare: %s", err) 120 } 121 defer func() { 122 if cmd.Process != nil { 123 cmd.Process.Kill() 124 } 125 cmd.Wait() 126 syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) 127 }() 128 if cmd.Process == nil { 129 return fmt.Errorf("process failed to start") 130 } 131 132 return checkNamespacePath(t, cmd.Process.Pid, ns) 133 } 134 135 func main() { 136 t := tap.New() 137 t.Header(0) 138 139 cases := []struct { 140 name string 141 unshareOpt string 142 }{ 143 {"ipc", "--ipc"}, 144 {"mnt", "--mount"}, 145 {"net", "--net"}, 146 {"pid", "--pid"}, 147 {"uts", "--uts"}, 148 } 149 150 for _, c := range cases { 151 if "linux" != runtime.GOOS { 152 t.Skip(1, fmt.Sprintf("linux-specific namespace test: %s", c)) 153 } 154 155 err := testNamespacePath(t, c.name, c.unshareOpt) 156 t.Ok(err == nil, fmt.Sprintf("set %s namespace by path", c.name)) 157 if err != nil { 158 rfcError, errRfc := specerror.NewRFCError(specerror.NSProcInPath, err, rspec.Version) 159 if errRfc != nil { 160 continue 161 } 162 diagnostic := map[string]string{ 163 "actual": fmt.Sprintf("err == %v", err), 164 "expected": "err == nil", 165 "namespace type": c.name, 166 "level": rfcError.Level.String(), 167 "reference": rfcError.Reference, 168 } 169 t.YAML(diagnostic) 170 } 171 } 172 173 t.AutoPlan() 174 }