Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nsenter: correctly handle pidns orphaning #976

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions libcontainer/configs/namespaces_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 11 additions & 5 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,16 +1223,22 @@ 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{
// The user namespace *must* be done first.
configs.NEWUSER,
configs.NEWIPC,
configs.NEWUTS,
configs.NEWNET,
configs.NEWPID,
configs.NEWNS,
}
// 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 != "" {
Expand All @@ -1249,7 +1255,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
Expand Down
147 changes: 147 additions & 0 deletions libcontainer/nsenter/cmsg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright 2016 SUSE LLC
*
* 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.
*/

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "cmsg.h"

#define IB_DATA 'P'

#define error(fmt, ...) \
({ \
fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__); \
errno = ECOMM; \
return -errno; /* return value */ \
})

/*
* Sends a PID to the given @sockfd, which must be an AF_UNIX socket with
* SO_PASSCRED set. The caller should deal with synchronisation, as this
* implementation assumes that all of the syscall ordering tomfoolery has been
* handled. The peer of @sockfd is assumed to be in recvpid() when this is
* called. The non-ancillary data is set to be some dummy data.
*
* In order to send a PID which is not your own, you must have CAP_SYS_ADMIN.
* In addition, we also just send gete[ug]id().
*/
int sendpid(int sockfd, pid_t pid)
{
struct msghdr msg = {0};
struct iovec iov[1] = {{0}};
struct cmsghdr *cmsg;
struct ucred *credptr;
struct ucred cred = {
.pid = pid,
.uid = geteuid(),
.gid = getegid(),
};
char ibdata = IB_DATA;

union {
char buf[CMSG_SPACE(sizeof(cred))];
struct cmsghdr align;
} u;

/*
* We need to send some other data along with the ancillary data,
* otherwise the other side won't recieve any data. This is very
* well-hidden in the documentation (and only applies to
* SOCK_STREAM). See the bottom part of unix(7).
*/
iov[0].iov_base = &ibdata;
iov[0].iov_len = sizeof(ibdata);

msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));

credptr = (struct ucred *) CMSG_DATA(cmsg);
memcpy(credptr, &cred, sizeof(struct ucred));

return sendmsg(sockfd, &msg, 0);
}

/*
* Receives a PID from the given @sockfd, which must be an AF_UNIX socket with
* SO_PASSCRED set. The caller should deal with synchronisation, as this
* implementation assumes that all of the syscall ordering tomfoolery has been
* handled. The peer of @sockfd is assumed to be in sendpid() when this is
* called.
*/
pid_t recvpid(int sockfd)
{
struct msghdr msg = {0};
struct iovec iov[1] = {{0}};
struct cmsghdr *cmsg;
struct ucred *credptr;
char ibdata = '\0';
ssize_t ret;

union {
char buf[CMSG_SPACE(sizeof(*credptr))];
struct cmsghdr align;
} u;

/*
* We need to "recieve" the non-ancillary data even though we don't
* plan to use it at all. Otherwise, things won't work as expected.
* See unix(7) and other well-hidden documentation.
*/
iov[0].iov_base = &ibdata;
iov[0].iov_len = sizeof(ibdata);

msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);

ret = recvmsg(sockfd, &msg, 0);
if (ret < 0)
return ret;

cmsg = CMSG_FIRSTHDR(&msg);
if (!cmsg)
error("recvfd: got NULL from CMSG_FIRSTHDR");
if (cmsg->cmsg_level != SOL_SOCKET)
error("recvfd: expected SOL_SOCKET in cmsg: %d", cmsg->cmsg_level);
if (cmsg->cmsg_type != SCM_CREDENTIALS)
error("recvfd: expected SCM_CREDENTIALS in cmsg: %d", cmsg->cmsg_type);
if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
error("recvfd: expected correct CMSG_LEN in cmsg: %lu", cmsg->cmsg_len);

credptr = (struct ucred *) CMSG_DATA(cmsg);
if (!credptr || !credptr->pid)
error("recvfd: recieved invalid pointer");

return credptr->pid;
}
43 changes: 43 additions & 0 deletions libcontainer/nsenter/cmsg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2016 SUSE LLC
*
* 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.
*/

#ifndef NSENTER_CMSG_H
#define NSENTER_CMSG_H

#include <sys/types.h>

/*
* Sends a PID to the given @sockfd, which must be an AF_UNIX socket with
* SO_PASSCRED set. The caller should deal with synchronisation, as this
* implementation assumes that all of the syscall ordering tomfoolery has been
* handled. The peer of @sockfd is assumed to be in recvpid() when this is
* called. The non-ancillary data is set to be some dummy data.
*
* In order to send a PID which is not your own, you must have CAP_SYS_ADMIN.
* In addition, we also just send gete[ug]id().
*/
int sendpid(int sockfd, pid_t pid);

/*
* Receives a PID from the given @sockfd, which must be an AF_UNIX socket with
* SO_PASSCRED set. The caller should deal with synchronisation, as this
* implementation assumes that all of the syscall ordering tomfoolery has been
* handled. The peer of @sockfd is assumed to be in sendpid() when this is
* called.
*/
pid_t recvpid(int sockfd);

#endif /* !defined(NSENTER_CMSG_H) */
32 changes: 32 additions & 0 deletions libcontainer/nsenter/namespace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef NSENTER_NAMESPACE_H
#define NSENTER_NAMESPACE_H

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sched.h>

/* All of these are taken from include/uapi/linux/sched.h */
#ifndef CLONE_NEWNS
# define CLONE_NEWNS 0x00020000 /* New mount namespace group */
#endif
#ifndef CLONE_NEWCGROUP
# define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */
#endif
#ifndef CLONE_NEWUTS
# define CLONE_NEWUTS 0x04000000 /* New utsname namespace */
#endif
#ifndef CLONE_NEWIPC
# define CLONE_NEWIPC 0x08000000 /* New ipc namespace */
#endif
#ifndef CLONE_NEWUSER
# define CLONE_NEWUSER 0x10000000 /* New user namespace */
#endif
#ifndef CLONE_NEWPID
# define CLONE_NEWPID 0x20000000 /* New pid namespace */
#endif
#ifndef CLONE_NEWNET
# define CLONE_NEWNET 0x40000000 /* New network namespace */
#endif

#endif /* NSENTER_NAMESPACE_H */
44 changes: 42 additions & 2 deletions libcontainer/nsenter/nsenter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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],
Expand Down
Loading