diff --git a/pkg/abi/linux/fs.go b/pkg/abi/linux/fs.go index edc90e54c..28942326d 100644 --- a/pkg/abi/linux/fs.go +++ b/pkg/abi/linux/fs.go @@ -24,6 +24,7 @@ const ( EXT_SUPER_MAGIC = 0xef53 FUSE_SUPER_MAGIC = 0x65735546 MQUEUE_MAGIC = 0x19800202 + NSFS_MAGIC = 0x6e736673 OVERLAYFS_SUPER_MAGIC = 0x794c7630 PIPEFS_MAGIC = 0x50495045 PROC_SUPER_MAGIC = 0x9fa0 diff --git a/pkg/sentry/fsimpl/nsfs/BUILD b/pkg/sentry/fsimpl/nsfs/BUILD new file mode 100644 index 000000000..07f20d601 --- /dev/null +++ b/pkg/sentry/fsimpl/nsfs/BUILD @@ -0,0 +1,39 @@ +load("//tools:defs.bzl", "go_library") +load("//tools/go_generics:defs.bzl", "go_template_instance") + +package(default_applicable_licenses = ["//:license"]) + +licenses(["notice"]) + +go_template_instance( + name = "inode_refs", + out = "inode_refs.go", + package = "nsfs", + prefix = "inode", + template = "//pkg/refs:refs_template", + types = { + "T": "Inode", + }, +) + +go_library( + name = "nsfs", + srcs = [ + "inode_refs.go", + "nsfs.go", + ], + visibility = ["//pkg/sentry:internal"], + deps = [ + "//pkg/abi/linux", + "//pkg/atomicbitops", + "//pkg/context", + "//pkg/errors/linuxerr", + "//pkg/hostarch", + "//pkg/refs", + "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/kernel/auth", + "//pkg/sentry/kernel/time", + "//pkg/sentry/vfs", + "//pkg/sync", + ], +) diff --git a/pkg/sentry/fsimpl/nsfs/nsfs.go b/pkg/sentry/fsimpl/nsfs/nsfs.go new file mode 100644 index 000000000..eb2466db8 --- /dev/null +++ b/pkg/sentry/fsimpl/nsfs/nsfs.go @@ -0,0 +1,205 @@ +// Copyright 2023 The gVisor Authors. +// +// 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. + +// Package nsfs provides the filesystem implementation backing +// Kernel.NsfsMount. +package nsfs + +import ( + "fmt" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +// +stateify savable +type filesystemType struct{} + +// Name implements vfs.FilesystemType.Name. +func (filesystemType) Name() string { + return "nsfs" +} + +// Release implements vfs.FilesystemType.Release. +func (filesystemType) Release(ctx context.Context) {} + +// GetFilesystem implements vfs.FilesystemType.GetFilesystem. +func (filesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { + panic("nsfs.filesystemType.GetFilesystem should never be called") +} + +// +stateify savable +type filesystem struct { + kernfs.Filesystem + + devMinor uint32 +} + +// NewFilesystem sets up and returns a new vfs.Filesystem implemented by nsfs. +func NewFilesystem(vfsObj *vfs.VirtualFilesystem) (*vfs.Filesystem, error) { + devMinor, err := vfsObj.GetAnonBlockDevMinor() + if err != nil { + return nil, err + } + fs := &filesystem{ + devMinor: devMinor, + } + fs.Filesystem.VFSFilesystem().Init(vfsObj, filesystemType{}, fs) + return fs.Filesystem.VFSFilesystem(), nil +} + +// Release implements vfs.FilesystemImpl.Release. +func (fs *filesystem) Release(ctx context.Context) { + fs.Filesystem.VFSFilesystem().VirtualFilesystem().PutAnonBlockDevMinor(fs.devMinor) + fs.Filesystem.Release(ctx) +} + +// MountOptions implements vfs.FilesystemImpl.MountOptions. +func (fs *filesystem) MountOptions() string { + return "" +} + +// Inode implements kernfs.Inode. +// +// +stateify savable +type Inode struct { + kernfs.InodeAttrs + kernfs.InodeNotAnonymous + kernfs.InodeNotDirectory + kernfs.InodeNotSymlink + kernfs.InodeWatches + inodeRefs + + locks vfs.FileLocks + namespace Namespace + + mnt *vfs.Mount +} + +// DecRef implements kernfs.Inode.DecRef. +func (i *Inode) DecRef(ctx context.Context) { + i.inodeRefs.DecRef(func() { i.namespace.Destroy(ctx) }) +} + +// Keep implements kernfs.Inode.Keep. +func (i *Inode) Keep() bool { + return false +} + +// Namespace is the namespace interface. +type Namespace interface { + Type() string + Destroy(ctx context.Context) +} + +// NewInode creates a new nsfs inode. +func NewInode(ctx context.Context, mnt *vfs.Mount, namespace Namespace) *Inode { + fs := mnt.Filesystem().Impl().(*filesystem) + creds := auth.CredentialsFromContext(ctx) + i := &Inode{ + namespace: namespace, + mnt: mnt, + } + i.InodeAttrs.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.Filesystem.NextIno(), nsfsMode) + i.InitRefs() + return i +} + +const nsfsMode = linux.S_IFREG | linux.ModeUserRead | linux.ModeGroupRead | linux.ModeOtherRead + +// Namespace returns the namespace associated with the inode. +func (i *Inode) Namespace() Namespace { + return i.namespace +} + +// Name returns the inode name that is used to implement readlink() of +// /proc/pid/ns/ files. +func (i *Inode) Name() string { + return fmt.Sprintf("%s:[%d]", i.namespace.Type(), i.Ino()) +} + +// VirtualDentry returns VirtualDentry for the inode. +func (i *Inode) VirtualDentry() vfs.VirtualDentry { + dentry := &kernfs.Dentry{} + mnt := i.mnt + fs := mnt.Filesystem().Impl().(*filesystem) + i.IncRef() + mnt.IncRef() + dentry.Init(&fs.Filesystem, i) + vd := vfs.MakeVirtualDentry(mnt, dentry.VFSDentry()) + return vd +} + +// Mode implements kernfs.Inode.Mode. +func (i *Inode) Mode() linux.FileMode { + return nsfsMode +} + +// SetStat implements kernfs.Inode.SetStat. +// +// Linux sets S_IMMUTABLE to nsfs inodes that prevents any attribute changes on +// them. +func (i *Inode) SetStat(ctx context.Context, vfsfs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error { + return linuxerr.EPERM +} + +// namespace FD is a synthetic file that represents a namespace in +// /proc/[pid]/ns/*. +// +// +stateify savable +type namespaceFD struct { + vfs.FileDescriptionDefaultImpl + vfs.LockFD + + vfsfd vfs.FileDescription + inode *Inode +} + +// Stat implements vfs.FileDescriptionImpl.Stat. +func (fd *namespaceFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) { + vfs := fd.vfsfd.VirtualDentry().Mount().Filesystem() + return fd.inode.Stat(ctx, vfs, opts) +} + +// SetStat implements vfs.FileDescriptionImpl.SetStat. +func (fd *namespaceFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error { + vfs := fd.vfsfd.VirtualDentry().Mount().Filesystem() + creds := auth.CredentialsFromContext(ctx) + return fd.inode.SetStat(ctx, vfs, creds, opts) +} + +// Release implements vfs.FileDescriptionImpl.Release. +func (fd *namespaceFD) Release(ctx context.Context) { + fd.inode.DecRef(ctx) +} + +// Open implements kernfs.Inode.Open. +func (i *Inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { + fd := &namespaceFD{inode: i} + i.IncRef() + fd.LockFD.Init(&i.locks) + if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), d.VFSDentry(), &vfs.FileDescriptionOptions{}); err != nil { + return nil, err + } + return &fd.vfsfd, nil +} + +// StatFS implements kernfs.Inode.StatFS. +func (i *Inode) StatFS(ctx context.Context, fs *vfs.Filesystem) (linux.Statfs, error) { + return vfs.GenericStatFS(linux.NSFS_MAGIC), nil +} diff --git a/pkg/sentry/fsimpl/proc/BUILD b/pkg/sentry/fsimpl/proc/BUILD index d966dacac..e21a1ad6a 100644 --- a/pkg/sentry/fsimpl/proc/BUILD +++ b/pkg/sentry/fsimpl/proc/BUILD @@ -91,6 +91,7 @@ go_library( "//pkg/safemem", "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/fsimpl/lock", + "//pkg/sentry/fsimpl/nsfs", "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/fsimpl/proc/task.go b/pkg/sentry/fsimpl/proc/task.go index 4ab06eb1d..0a2a280ab 100644 --- a/pkg/sentry/fsimpl/proc/task.go +++ b/pkg/sentry/fsimpl/proc/task.go @@ -72,7 +72,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns "mounts": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mountsData{fs: fs, task: task}), "net": fs.newTaskNetDir(ctx, task), "ns": fs.newTaskOwnedDir(ctx, task, fs.NextIno(), 0511, map[string]kernfs.Inode{ - "net": fs.newFakeNamespaceSymlink(ctx, task, fs.NextIno(), "net"), + "net": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWNET), "pid": fs.newPIDNamespaceSymlink(ctx, task, fs.NextIno()), "user": fs.newFakeNamespaceSymlink(ctx, task, fs.NextIno(), "user"), }), diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go index 7b7803ddf..a4c7cc986 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -27,6 +27,7 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/safemem" "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/limits" @@ -1230,7 +1231,18 @@ func (i *mountsData) Generate(ctx context.Context, buf *bytes.Buffer) error { type namespaceSymlink struct { kernfs.StaticSymlink - task *kernel.Task + task *kernel.Task + nsType int +} + +func (fs *filesystem) newNamespaceSymlink(ctx context.Context, task *kernel.Task, ino uint64, nsType int) kernfs.Inode { + inode := &namespaceSymlink{task: task, nsType: nsType} + + // Note: credentials are overridden by taskOwnedInode. + inode.Init(ctx, task.Credentials(), linux.UNNAMED_MAJOR, fs.devMinor, ino, "") + + taskInode := &taskOwnedInode{Inode: inode, owner: task} + return taskInode } func (fs *filesystem) newPIDNamespaceSymlink(ctx context.Context, task *kernel.Task, ino uint64) kernfs.Inode { @@ -1258,11 +1270,29 @@ func (fs *filesystem) newFakeNamespaceSymlink(ctx context.Context, task *kernel. return taskInode } +func (s *namespaceSymlink) getInode(t *kernel.Task) *nsfs.Inode { + switch s.nsType { + case linux.CLONE_NEWNET: + return t.GetNetworkNamespace().GetInode() + default: + panic("unknown namespace") + } +} + // Readlink implements kernfs.Inode.Readlink. func (s *namespaceSymlink) Readlink(ctx context.Context, mnt *vfs.Mount) (string, error) { if err := checkTaskState(s.task); err != nil { return "", err } + if s.nsType != 0 { + inode := s.getInode(s.task) + if inode == nil { + return "", linuxerr.ENOENT + } + target := inode.Name() + inode.DecRef(ctx) + return target, nil + } return s.StaticSymlink.Readlink(ctx, mnt) } @@ -1272,6 +1302,14 @@ func (s *namespaceSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.Vir return vfs.VirtualDentry{}, "", err } + if s.nsType != 0 { + inode := s.getInode(s.task) + if inode == nil { + return vfs.VirtualDentry{}, "", linuxerr.ENOENT + } + defer inode.DecRef(ctx) + return inode.VirtualDentry(), "", nil + } // Create a synthetic inode to represent the namespace. fs := mnt.Filesystem().Impl().(*filesystem) nsInode := &namespaceInode{} diff --git a/pkg/sentry/fsutil/BUILD b/pkg/sentry/fsutil/BUILD index 3591067d0..b7ddc3fe2 100644 --- a/pkg/sentry/fsutil/BUILD +++ b/pkg/sentry/fsutil/BUILD @@ -100,7 +100,6 @@ go_library( "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", "//pkg/sentry/pgalloc", - "//pkg/sentry/socket/unix/transport", "//pkg/sentry/usage", "//pkg/state", "//pkg/sync", diff --git a/pkg/sentry/inet/BUILD b/pkg/sentry/inet/BUILD index 089b18db8..ee5dfe6ca 100644 --- a/pkg/sentry/inet/BUILD +++ b/pkg/sentry/inet/BUILD @@ -44,6 +44,8 @@ go_library( "//pkg/atomicbitops", "//pkg/context", "//pkg/refs", + "//pkg/sentry/fsimpl/nsfs", + "//pkg/sentry/kernel/auth", "//pkg/tcpip", "//pkg/tcpip/stack", ], diff --git a/pkg/sentry/inet/namespace.go b/pkg/sentry/inet/namespace.go index e66dbaa9f..5e006c6ae 100644 --- a/pkg/sentry/inet/namespace.go +++ b/pkg/sentry/inet/namespace.go @@ -14,11 +14,17 @@ package inet +import ( + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" +) + // Namespace represents a network namespace. See network_namespaces(7). // // +stateify savable type Namespace struct { - namespaceRefs + inode *nsfs.Inode // stack is the network stack implementation of this network namespace. stack Stack `state:"nosave"` @@ -32,38 +38,68 @@ type Namespace struct { // isRoot indicates whether this is the root network namespace. isRoot bool + + userNS *auth.UserNamespace } // NewRootNamespace creates the root network namespace, with creator // allowing new network namespaces to be created. If creator is nil, no // networking will function if the network is namespaced. -func NewRootNamespace(stack Stack, creator NetworkStackCreator) *Namespace { +func NewRootNamespace(stack Stack, creator NetworkStackCreator, userNS *auth.UserNamespace) *Namespace { n := &Namespace{ stack: stack, creator: creator, isRoot: true, + userNS: userNS, } - n.InitRefs() return n } +// UserNamespace returns the user namespace associated with this namespace. +func (n *Namespace) UserNamespace() *auth.UserNamespace { + return n.userNS +} + +// SetInode sets the nsfs `inode` to the namespace. +func (n *Namespace) SetInode(inode *nsfs.Inode) { + n.inode = inode +} + +// GetInode returns the nsfs inode associated with this namespace. +func (n *Namespace) GetInode() *nsfs.Inode { + return n.inode +} + // NewNamespace creates a new network namespace from the root. -func NewNamespace(root *Namespace) *Namespace { +func NewNamespace(root *Namespace, userNS *auth.UserNamespace) *Namespace { n := &Namespace{ creator: root.creator, + userNS: userNS, } n.init() - n.InitRefs() return n } +// Destroy implements nsfs.Namespace.Destroy. +func (n *Namespace) Destroy(ctx context.Context) { + if s := n.Stack(); s != nil { + s.Destroy() + } +} + +// Type implements nsfs.Namespace.Type. +func (n *Namespace) Type() string { + return "net" +} + +// IncRef increments the Namespace's refcount. +func (n *Namespace) IncRef() { + n.inode.IncRef() +} + // DecRef decrements the Namespace's refcount. -func (n *Namespace) DecRef() { - n.namespaceRefs.DecRef(func() { - if s := n.Stack(); s != nil { - s.Destroy() - } - }) +func (n *Namespace) DecRef(ctx context.Context) { + n.inode.DecRef(ctx) } // Stack returns the network stack of n. Stack may return nil if no network diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index 43110a895..c4394cdf5 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -354,6 +354,7 @@ go_library( "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsimpl/mqfs", + "//pkg/sentry/fsimpl/nsfs", "//pkg/sentry/fsimpl/pipefs", "//pkg/sentry/fsimpl/sockfs", "//pkg/sentry/fsimpl/timerfd", diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index e97793692..9a7b4b568 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -47,6 +47,7 @@ import ( "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/timerfd" @@ -274,6 +275,9 @@ type Kernel struct { // syscalls (as opposed to named pipes created by mknod()). pipeMount *vfs.Mount + // nsfsMount is the Mount used for namespaces. + nsfsMount *vfs.Mount + // shmMount is the Mount used for anonymous files created by the // memfd_create() syscalls. It is analogous to Linux's shm_mnt. shmMount *vfs.Mount @@ -395,7 +399,7 @@ func (k *Kernel) Init(args InitKernelArgs) error { k.rootAbstractSocketNamespace = args.RootAbstractSocketNamespace k.rootNetworkNamespace = args.RootNetworkNamespace if k.rootNetworkNamespace == nil { - k.rootNetworkNamespace = inet.NewRootNamespace(nil, nil) + k.rootNetworkNamespace = inet.NewRootNamespace(nil, nil, args.RootUserNamespace) } k.runningTasksCond.L = &k.runningTasksMu k.cpuClockTickerWakeCh = make(chan struct{}, 1) @@ -439,6 +443,15 @@ func (k *Kernel) Init(args InitKernelArgs) error { pipeMount := k.vfs.NewDisconnectedMount(pipeFilesystem, nil, &vfs.MountOptions{}) k.pipeMount = pipeMount + nsfsFilesystem, err := nsfs.NewFilesystem(&k.vfs) + if err != nil { + return fmt.Errorf("failed to create nsfs filesystem: %v", err) + } + defer nsfsFilesystem.DecRef(ctx) + nsfsMount := k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{}) + k.nsfsMount = nsfsMount + k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, nsfsMount, k.rootNetworkNamespace)) + tmpfsOpts := vfs.GetFilesystemOptions{ InternalData: tmpfs.FilesystemOpts{ // See mm/shmem.c:shmem_init() => vfs_kern_mount(flags=SB_KERNMOUNT). @@ -1607,6 +1620,11 @@ func (k *Kernel) PipeMount() *vfs.Mount { return k.pipeMount } +// NsfsMount returns the nsfs mount. +func (k *Kernel) NsfsMount() *vfs.Mount { + return k.nsfsMount +} + // ShmMount returns the tmpfs mount. func (k *Kernel) ShmMount() *vfs.Mount { return k.shmMount @@ -1630,12 +1648,13 @@ func (k *Kernel) Release() { ctx := k.SupervisorContext() k.hostMount.DecRef(ctx) k.pipeMount.DecRef(ctx) + k.nsfsMount.DecRef(ctx) k.shmMount.DecRef(ctx) k.socketMount.DecRef(ctx) k.vfs.Release(ctx) k.timekeeper.Destroy() k.vdso.Release(ctx) - k.RootNetworkNamespace().DecRef() + k.RootNetworkNamespace().DecRef(ctx) } // PopulateNewCgroupHierarchy moves all tasks into a newly created cgroup diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index c29b76123..200dc1f9c 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -506,7 +506,9 @@ type Task struct { numaPolicy linux.NumaPolicy numaNodeMask uint64 - // netns is the task's network namespace. netns is never nil. + // netns is the task's network namespace. It has to be changed under mu + // so that GetNetworkNamespace can take a reference before it is + // released. netns inet.NamespaceAtomicPtr // If rseqPreempted is true, before the next call to p.Switch(), diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index b9ede9006..5a2fa27cc 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -21,9 +21,12 @@ import ( "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/seccheck" pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" ) @@ -117,14 +120,16 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { }) defer cu.Clean() - netns := t.NetworkNamespace() + netns := t.netns.Load() if args.Flags&linux.CLONE_NEWNET != 0 { - netns = inet.NewNamespace(netns) + netns = inet.NewNamespace(netns, userns) + inode := nsfs.NewInode(t, t.k.nsfsMount, netns) + netns.SetInode(inode) } else { netns.IncRef() } cu.Add(func() { - netns.DecRef() + netns.DecRef(t) }) // TODO(b/63601033): Implement CLONE_NEWNS. @@ -405,6 +410,38 @@ func (r *runSyscallAfterVforkStop) execute(t *Task) taskRunState { return (*runSyscallExit)(nil) } +// Setns reassociates thread with the specified namespace. +func (t *Task) Setns(fd *vfs.FileDescription, flags int32) error { + d, ok := fd.Dentry().Impl().(*kernfs.Dentry) + if !ok { + return linuxerr.EINVAL + } + i, ok := d.Inode().(*nsfs.Inode) + if !ok { + return linuxerr.EINVAL + } + + switch ns := i.Namespace().(type) { + case *inet.Namespace: + if flags != 0 && flags != linux.CLONE_NEWNET { + return linuxerr.EINVAL + } + if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, ns.UserNamespace()) || + !t.Credentials().HasCapability(linux.CAP_SYS_ADMIN) { + return linuxerr.EPERM + } + oldNS := t.NetworkNamespace() + ns.IncRef() + t.mu.Lock() + t.netns.Store(ns) + t.mu.Unlock() + oldNS.DecRef(t) + return nil + default: + return linuxerr.EINVAL + } +} + // Unshare changes the set of resources t shares with other tasks, as specified // by flags. // @@ -456,7 +493,7 @@ func (t *Task) Unshare(flags int32) error { if err != nil { return err } - // Need to reload creds, becaue t.SetUserNamespace() changed task credentials. + // Need to reload creds, because t.SetUserNamespace() changed task credentials. creds = t.Credentials() } haveCapSysAdmin := t.HasCapability(linux.CAP_SYS_ADMIN) @@ -466,13 +503,18 @@ func (t *Task) Unshare(flags int32) error { } t.childPIDNamespace = t.tg.pidns.NewChild(t.UserNamespace()) } - var oldNETNS *inet.Namespace if flags&linux.CLONE_NEWNET != 0 { if !haveCapSysAdmin { return linuxerr.EPERM } - oldNETNS = t.netns.Load() - t.netns.Store(inet.NewNamespace(t.netns.Load())) + netns := t.NetworkNamespace() + netns = inet.NewNamespace(netns, t.UserNamespace()) + netnsInode := nsfs.NewInode(t, t.k.nsfsMount, netns) + netns.SetInode(netnsInode) + t.mu.Lock() + netns = t.netns.Swap(netns) + t.mu.Unlock() + netns.DecRef(t) } t.mu.Lock() // Can't defer unlock: DecRefs must occur without holding t.mu. @@ -511,9 +553,6 @@ func (t *Task) Unshare(flags int32) error { if oldIPCNS != nil { oldIPCNS.DecRef(t) } - if oldNETNS != nil { - oldNETNS.DecRef() - } if oldFDTable != nil { oldFDTable.DecRef(t) } diff --git a/pkg/sentry/kernel/task_exit.go b/pkg/sentry/kernel/task_exit.go index 30d299bd8..21f02b82e 100644 --- a/pkg/sentry/kernel/task_exit.go +++ b/pkg/sentry/kernel/task_exit.go @@ -288,13 +288,13 @@ func (*runExitMain) execute(t *Task) taskRunState { mntns := t.mountNamespace t.mountNamespace = nil ipcns := t.ipcns - netns := t.NetworkNamespace() + netns := t.netns.Swap(nil) t.mu.Unlock() if mntns != nil { mntns.DecRef(t) } ipcns.DecRef(t) - netns.DecRef() + netns.DecRef(t) // If this is the last task to exit from the thread group, release the // thread group's resources. diff --git a/pkg/sentry/kernel/task_net.go b/pkg/sentry/kernel/task_net.go index e31e2b2e8..c6698fb9f 100644 --- a/pkg/sentry/kernel/task_net.go +++ b/pkg/sentry/kernel/task_net.go @@ -36,3 +36,17 @@ func (t *Task) NetworkContext() inet.Stack { func (t *Task) NetworkNamespace() *inet.Namespace { return t.netns.Load() } + +// GetNetworkNamespace takes a reference on the task network namespace and +// returns it. It can return nil if the task isn't alive. +func (t *Task) GetNetworkNamespace() *inet.Namespace { + // t.mu is required to be sure that the network namespace will not be + // released. + t.mu.Lock() + netns := t.netns.Load() + if netns != nil { + netns.IncRef() + } + t.mu.Unlock() + return netns +} diff --git a/pkg/sentry/kernel/task_start.go b/pkg/sentry/kernel/task_start.go index 0a8c8c04a..d9dbde37e 100644 --- a/pkg/sentry/kernel/task_start.go +++ b/pkg/sentry/kernel/task_start.go @@ -117,7 +117,7 @@ func (ts *TaskSet) NewTask(ctx context.Context, cfg *TaskConfig) (*Task, error) cfg.FSContext.DecRef(ctx) cfg.FDTable.DecRef(ctx) cfg.IPCNamespace.DecRef(ctx) - cfg.NetworkNamespace.DecRef() + cfg.NetworkNamespace.DecRef(ctx) if cfg.MountNamespace != nil { cfg.MountNamespace.DecRef(ctx) } diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index 5f3e1819f..31c17af42 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -442,7 +442,7 @@ func (s *sock) Release(ctx context.Context) { _ = t.BlockWithDeadline(ch, true, deadline) } } - s.namespace.DecRef() + s.namespace.DecRef(ctx) } // Epollable implements FileDescriptionImpl.Epollable. diff --git a/pkg/sentry/socket/unix/transport/BUILD b/pkg/sentry/socket/unix/transport/BUILD index 019bd04ac..975de8102 100644 --- a/pkg/sentry/socket/unix/transport/BUILD +++ b/pkg/sentry/socket/unix/transport/BUILD @@ -97,7 +97,6 @@ go_library( "//pkg/log", "//pkg/refs", "//pkg/sentry/hostfd", - "//pkg/sentry/inet", "//pkg/sentry/uniqueid", "//pkg/sync", "//pkg/sync/locking", diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index d6159f28c..17bb0e1fd 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -360,7 +360,7 @@ var AMD64 = &kernel.SyscallTable{ 305: syscalls.CapError("clock_adjtime", linux.CAP_SYS_TIME, "", nil), 306: syscalls.Supported("syncfs", Syncfs), 307: syscalls.Supported("sendmmsg", SendMMsg), - 308: syscalls.ErrorWithEvent("setns", linuxerr.EOPNOTSUPP, "Needs filesystem support", []string{"gvisor.dev/issue/140"}), // TODO(b/29354995) + 308: syscalls.Supported("setns", Setns), 309: syscalls.Supported("getcpu", Getcpu), 310: syscalls.ErrorWithEvent("process_vm_readv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), // TODO(b/260724654) 311: syscalls.ErrorWithEvent("process_vm_writev", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), // TODO(b/260724654) @@ -683,7 +683,7 @@ var ARM64 = &kernel.SyscallTable{ 265: syscalls.Error("open_by_handle_at", linuxerr.EOPNOTSUPP, "Not supported by gVisor filesystems", nil), 266: syscalls.CapError("clock_adjtime", linux.CAP_SYS_TIME, "", nil), 267: syscalls.Supported("syncfs", Syncfs), - 268: syscalls.ErrorWithEvent("setns", linuxerr.EOPNOTSUPP, "Needs filesystem support", []string{"gvisor.dev/issue/140"}), // TODO(b/29354995) + 268: syscalls.Supported("setns", Setns), 269: syscalls.Supported("sendmmsg", SendMMsg), 270: syscalls.ErrorWithEvent("process_vm_readv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), // TODO(b/260724654) 271: syscalls.ErrorWithEvent("process_vm_writev", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/158"}), // TODO(b/260724654) diff --git a/pkg/sentry/syscalls/linux/sys_mount.go b/pkg/sentry/syscalls/linux/sys_mount.go index 7b5a6a71d..eef4ae60f 100644 --- a/pkg/sentry/syscalls/linux/sys_mount.go +++ b/pkg/sentry/syscalls/linux/sys_mount.go @@ -76,7 +76,7 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, return 0, nil, err } var sourceTpop taskPathOperation - sourceTpop, err = getTaskPathOperation(t, linux.AT_FDCWD, sourcePath, disallowEmptyPath, nofollowFinalSymlink) + sourceTpop, err = getTaskPathOperation(t, linux.AT_FDCWD, sourcePath, disallowEmptyPath, followFinalSymlink) if err != nil { return 0, nil, err } diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index 9b448821f..ddae15cd3 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -443,6 +443,20 @@ func SetTidAddress(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (u return uintptr(t.ThreadID()), nil, nil } +// Setns implements linux syscall setns(2). +func Setns(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + fd := args[0].Int() + + file := t.GetFile(fd) + if file == nil { + return 0, nil, linuxerr.EBADF + } + defer file.DecRef(t) + + flags := args[1].Int() + return 0, nil, t.Setns(file, flags) +} + // Unshare implements linux syscall unshare(2). func Unshare(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { flags := args[0].Int() diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 3ebe7f9f2..8384bd682 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -383,12 +383,6 @@ func New(args Args) (*Loader, error) { return nil, fmt.Errorf("enabling strace: %w", err) } - // Create root network namespace/stack. - netns, err := newRootNetworkNamespace(args.Conf, tk, k) - if err != nil { - return nil, fmt.Errorf("creating network: %w", err) - } - // Create capabilities. caps, err := specutils.Capabilities(args.Conf.EnableRaw, args.Spec.Process.Capabilities) if err != nil { @@ -409,6 +403,12 @@ func New(args Args) (*Loader, error) { caps, auth.NewRootUserNamespace()) + // Create root network namespace/stack. + netns, err := newRootNetworkNamespace(args.Conf, tk, k, creds.UserNamespace) + if err != nil { + return nil, fmt.Errorf("creating network: %w", err) + } + if args.NumCPU == 0 { args.NumCPU = runtime.NumCPU() } @@ -1212,7 +1212,7 @@ func (l *Loader) WaitExit() linux.WaitStatus { return l.k.GlobalInit().ExitStatus() } -func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, uniqueID stack.UniqueID) (*inet.Namespace, error) { +func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, uniqueID stack.UniqueID, userns *auth.UserNamespace) (*inet.Namespace, error) { // Create an empty network stack because the network namespace may be empty at // this point. Netns is configured before Run() is called. Netstack is // configured using a control uRPC message. Host network is configured inside @@ -1226,7 +1226,7 @@ func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, uniqueID st return nil, fmt.Errorf("configuring network=host with raw sockets requires CAP_NET_RAW capability") } // No network namespacing support for hostinet yet, hence creator is nil. - return inet.NewRootNamespace(hostinet.NewStack(), nil), nil + return inet.NewRootNamespace(hostinet.NewStack(), nil, userns), nil case config.NetworkNone, config.NetworkSandbox: s, err := newEmptySandboxNetworkStack(clock, uniqueID, conf.AllowPacketEndpointWrite) @@ -1238,7 +1238,7 @@ func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, uniqueID st uniqueID: uniqueID, allowPacketEndpointWrite: conf.AllowPacketEndpointWrite, } - return inet.NewRootNamespace(s, creator), nil + return inet.NewRootNamespace(s, creator, userns), nil default: panic(fmt.Sprintf("invalid network configuration: %v", conf.Network)) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 597c403b2..f11cd874f 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4281,6 +4281,8 @@ cc_binary( gtest, ":ip_socket_test_util", "//test/util:capability_util", + "//test/util:file_descriptor", + "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", diff --git a/test/syscalls/linux/network_namespace.cc b/test/syscalls/linux/network_namespace.cc index 61809062a..5bff89ffa 100644 --- a/test/syscalls/linux/network_namespace.cc +++ b/test/syscalls/linux/network_namespace.cc @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "gtest/gtest.h" #include "test/syscalls/linux/ip_socket_test_util.h" #include "test/util/capability_util.h" +#include "test/util/file_descriptor.h" +#include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -36,6 +40,56 @@ TEST(NetworkNamespaceTest, LoopbackExists) { }); } +TEST(NetworkNamespaceTest, Setns) { + // TODO(b/267210840): Fix this tests for hostinet. + SKIP_IF(IsRunningWithHostinet()); + + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + struct stat st; + uint64_t netns1, netns2, netns3; + const FileDescriptor nsfd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/net", O_RDONLY)); + + ASSERT_THAT(stat("/proc/thread-self/ns/net", &st), SyscallSucceeds()); + netns1 = st.st_ino; + + ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/net", &st), SyscallSucceeds()); + netns2 = st.st_ino; + EXPECT_NE(netns1, netns2); + + ASSERT_THAT(setns(nsfd.get(), CLONE_NEWNET), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/net", &st), SyscallSucceeds()); + netns3 = st.st_ino; + EXPECT_EQ(netns1, netns3); + + ASSERT_NE(ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), 0); +} + +TEST(NetworkNamespaceTest, BindMount) { + // TODO(b/267210840): Fix this tests for hostinet. + SKIP_IF(IsRunningWithHostinet()); + + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + ASSERT_THAT( + mount("/proc/self/ns/net", file.path().c_str(), NULL, MS_BIND, NULL), + SyscallSucceedsWithValue(0)); + + const FileDescriptor nsfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDONLY)); + ASSERT_THAT(umount2(file.path().c_str(), MNT_DETACH), + SyscallSucceedsWithValue(0)); + ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0)); + ASSERT_THAT(setns(nsfd.get(), CLONE_NEWNET), SyscallSucceedsWithValue(0)); + + ASSERT_NE(ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), 0); +} + } // namespace } // namespace testing } // namespace gvisor