diff --git a/libcontainer/configs/namespaces_unix.go b/libcontainer/configs/namespaces_unix.go index b9c820d0..8beba9d3 100644 --- a/libcontainer/configs/namespaces_unix.go +++ b/libcontainer/configs/namespaces_unix.go @@ -22,8 +22,8 @@ var ( supportedNamespaces = make(map[NamespaceType]bool) ) -// nsToFile converts the namespace type to its filename -func nsToFile(ns NamespaceType) string { +// NsName converts the namespace type to its filename +func NsName(ns NamespaceType) string { switch ns { case NEWNET: return "net" @@ -50,7 +50,7 @@ func IsNamespaceSupported(ns NamespaceType) bool { if ok { return supported } - nsFile := nsToFile(ns) + nsFile := NsName(ns) // if the namespace type is unknown, just return false if nsFile == "" { return false @@ -84,7 +84,7 @@ func (n *Namespace) GetPath(pid int) string { if n.Path != "" { return n.Path } - return fmt.Sprintf("/proc/%d/ns/%s", pid, nsToFile(n.Type)) + return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type)) } func (n *Namespaces) Remove(t NamespaceType) bool { diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 29c8b343..34cac634 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -1223,16 +1223,21 @@ func (c *linuxContainer) currentState() (*State, error) { // can setns in order. func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceType]string) ([]string, error) { paths := []string{} - nsTypes := []configs.NamespaceType{ + order := []configs.NamespaceType{ configs.NEWIPC, configs.NEWUTS, configs.NEWNET, configs.NEWPID, configs.NEWNS, + configs.NEWUSER, } - // join userns if the init process explicitly requires NEWUSER - if c.config.Namespaces.Contains(configs.NEWUSER) { - nsTypes = append(nsTypes, configs.NEWUSER) + + // Remove namespaces that we don't need to join. + var nsTypes []configs.NamespaceType + for _, ns := range order { + if c.config.Namespaces.Contains(ns) { + nsTypes = append(nsTypes, ns) + } } for _, nsType := range nsTypes { if p, ok := namespaces[nsType]; ok && p != "" { @@ -1249,7 +1254,7 @@ func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceTyp if strings.ContainsRune(p, ',') { return nil, newSystemError(fmt.Errorf("invalid path %s", p)) } - paths = append(paths, p) + paths = append(paths, fmt.Sprintf("%s:%s", configs.NsName(nsType), p)) } } return paths, nil diff --git a/libcontainer/nsenter/nsenter_test.go b/libcontainer/nsenter/nsenter_test.go index 598e80b5..98b026f7 100644 --- a/libcontainer/nsenter/nsenter_test.go +++ b/libcontainer/nsenter/nsenter_test.go @@ -29,7 +29,7 @@ func TestNsenterValidPaths(t *testing.T) { namespaces := []string{ // join pid ns of the current process - fmt.Sprintf("/proc/%d/ns/pid", os.Getpid()), + fmt.Sprintf("pid:/proc/%d/ns/pid", os.Getpid()), } cmd := &exec.Cmd{ Path: os.Args[0], @@ -87,7 +87,47 @@ func TestNsenterInvalidPaths(t *testing.T) { namespaces := []string{ // join pid ns of the current process - fmt.Sprintf("/proc/%d/ns/pid", -1), + fmt.Sprintf("pid:/proc/%d/ns/pid", -1), + } + cmd := &exec.Cmd{ + Path: os.Args[0], + Args: args, + ExtraFiles: []*os.File{child}, + Env: []string{"_LIBCONTAINER_INITPIPE=3"}, + } + + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + // write cloneFlags + r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0) + r.AddData(&libcontainer.Int32msg{ + Type: libcontainer.CloneFlagsAttr, + Value: uint32(syscall.CLONE_NEWNET), + }) + r.AddData(&libcontainer.Bytemsg{ + Type: libcontainer.NsPathsAttr, + Value: []byte(strings.Join(namespaces, ",")), + }) + if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil { + t.Fatal(err) + } + + if err := cmd.Wait(); err == nil { + t.Fatalf("nsenter exits with a zero exit status") + } +} + +func TestNsenterIncorrectPathType(t *testing.T) { + args := []string{"nsenter-exec"} + parent, child, err := newPipe() + if err != nil { + t.Fatalf("failed to create pipe %v", err) + } + + namespaces := []string{ + // join pid ns of the current process + fmt.Sprintf("net:/proc/%d/ns/pid", os.Getpid()), } cmd := &exec.Cmd{ Path: os.Args[0], diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c index b93f827b..ce8fab38 100644 --- a/libcontainer/nsenter/nsexec.c +++ b/libcontainer/nsenter/nsexec.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -265,6 +266,44 @@ static void start_child(int pipenum, jmp_buf *env, int syncpipe[2], struct nlcon exit(0); } +/* Returns the clone(2) flag for a namespace, given the name of a namespace. */ +static int nsflag(char *name) +{ + if (false) + /* dummy */ ; +#ifdef CLONE_NEWCGROUP + else if (!strcmp(name, "cgroup")) + return CLONE_NEWCGROUP; +#endif +#ifdef CLONE_NEWIPC + else if (!strcmp(name, "ipc")) + return CLONE_NEWIPC; +#endif +#ifdef CLONE_NEWNS + else if (!strcmp(name, "mnt")) + return CLONE_NEWNS; +#endif +#ifdef CLONE_NEWNET + else if (!strcmp(name, "net")) + return CLONE_NEWNET; +#endif +#ifdef CLONE_NEWPID + else if (!strcmp(name, "pid")) + return CLONE_NEWPID; +#endif +#ifdef CLONE_NEWUSER + else if (!strcmp(name, "user")) + return CLONE_NEWUSER; +#endif +#ifdef CLONE_NEWUTS + else if (!strcmp(name, "uts")) + return CLONE_NEWUTS; +#endif + + /* If we don't recognise a name, fallback to 0. */ + return 0; +} + static void nl_parse(int fd, struct nlconfig_t *config) { size_t len, size; @@ -328,8 +367,13 @@ static void nl_parse(int fd, struct nlconfig_t *config) */ char *saveptr = NULL; char *ns = strtok_r(current, ",", &saveptr); - int *fds = NULL, num = 0, i; - char **paths = NULL; + int num = 0, i; + + struct namespace_t { + int fd; + int ns; + char *path; + } *nses = NULL; if (!ns || !strlen(current)) bail("ns paths are empty"); @@ -341,32 +385,39 @@ static void nl_parse(int fd, struct nlconfig_t *config) */ do { int fd; + char *path; - /* Resize fds. */ - num++; - fds = realloc(fds, num * sizeof(int)); - paths = realloc(paths, num * sizeof(char *)); + /* Resize the namespace array. */ + nses = realloc(nses, ++num * sizeof(struct namespace_t)); - fd = open(ns, O_RDONLY); + /* Split 'ns:path'. */ + path = strstr(ns, ":"); + if (!path) + bail("failed to parse %s", ns); + *path++ = '\0'; + + fd = open(path, O_RDONLY); if (fd < 0) bail("failed to open %s", ns); - fds[num - 1] = fd; - paths[num - 1] = ns; + nses[num - 1] = (struct namespace_t) { + .fd = fd, + .ns = nsflag(ns), + .path = path, + }; } while ((ns = strtok_r(NULL, ",", &saveptr)) != NULL); for (i = 0; i < num; i++) { - int fd = fds[i]; - char *path = paths[i]; + struct namespace_t ns = nses[i]; - if (setns(fd, 0) < 0) - bail("failed to setns to %s", path); + /* Actually join the namespaces. */ + if (setns(ns.fd, ns.ns) < 0) + bail("failed to setns to %s", ns.path); - close(fd); + close(ns.fd); } - free(fds); - free(paths); + free(nses); break; } case UIDMAP_ATTR: