diff --git a/pkg/sentry/fsimpl/proc/task.go b/pkg/sentry/fsimpl/proc/task.go index 0a2a280ab..eef55957b 100644 --- a/pkg/sentry/fsimpl/proc/task.go +++ b/pkg/sentry/fsimpl/proc/task.go @@ -75,6 +75,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns "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"), + "ipc": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWIPC), }), "oom_score": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, newStaticFile("0\n")), "oom_score_adj": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &oomScoreAdj{task: task}), diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go index a4c7cc986..f2f6e3040 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -1274,6 +1274,11 @@ func (s *namespaceSymlink) getInode(t *kernel.Task) *nsfs.Inode { switch s.nsType { case linux.CLONE_NEWNET: return t.GetNetworkNamespace().GetInode() + case linux.CLONE_NEWIPC: + if ipcns := t.GetIPCNamespace(); ipcns != nil { + return ipcns.GetInode() + } + return nil default: panic("unknown namespace") } diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index c4394cdf5..75272d6ad 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -193,17 +193,6 @@ go_template_instance( }, ) -go_template_instance( - name = "ipc_namespace_refs", - out = "ipc_namespace_refs.go", - package = "kernel", - prefix = "IPCNamespace", - template = "//pkg/refs:refs_template", - types = { - "T": "IPCNamespace", - }, -) - go_template_instance( name = "process_group_refs", out = "process_group_refs.go", @@ -252,7 +241,6 @@ go_library( "fs_context.go", "fs_context_refs.go", "ipc_namespace.go", - "ipc_namespace_refs.go", "kcov.go", "kcov_unsafe.go", "kernel.go", diff --git a/pkg/sentry/kernel/ipc_namespace.go b/pkg/sentry/kernel/ipc_namespace.go index d75d501f1..0d47ad7ce 100644 --- a/pkg/sentry/kernel/ipc_namespace.go +++ b/pkg/sentry/kernel/ipc_namespace.go @@ -19,6 +19,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/sentry/fsimpl/mqfs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/mq" "gvisor.dev/gvisor/pkg/sentry/kernel/msgqueue" @@ -31,7 +32,7 @@ import ( // // +stateify savable type IPCNamespace struct { - IPCNamespaceRefs + inode *nsfs.Inode // User namespace which owns this IPC namespace. Immutable. userNS *auth.UserNamespace @@ -57,10 +58,37 @@ func NewIPCNamespace(userNS *auth.UserNamespace) *IPCNamespace { semaphores: semaphore.NewRegistry(userNS), shms: shm.NewRegistry(userNS), } - ns.InitRefs() return ns } +// Type implements nsfs.Namespace.Type. +func (i *IPCNamespace) Type() string { + return "ipc" +} + +// Destroy implements nsfs.Namespace.Destroy. +func (i *IPCNamespace) Destroy(ctx context.Context) { + i.shms.Release(ctx) + if i.posixQueues != nil { + i.posixQueues.Destroy(ctx) + } +} + +// SetInode sets the nsfs `inode` to the IPC namespace. +func (i *IPCNamespace) SetInode(inode *nsfs.Inode) { + i.inode = inode +} + +// GetInode returns the nsfs inode associated with the IPC namespace. +func (i *IPCNamespace) GetInode() *nsfs.Inode { + return i.inode +} + +// UserNamespace returns the user namespace associated with the namespace. +func (i *IPCNamespace) UserNamespace() *auth.UserNamespace { + return i.userNS +} + // MsgqueueRegistry returns the message queue registry for this namespace. func (i *IPCNamespace) MsgqueueRegistry() *msgqueue.Registry { return i.queues @@ -98,14 +126,14 @@ func (i *IPCNamespace) PosixQueues() *mq.Registry { return i.posixQueues } -// DecRef implements refs.RefCounter.DecRef. +// IncRef increments the Namespace's refcount. +func (i *IPCNamespace) IncRef() { + i.inode.IncRef() +} + +// DecRef decrements the namespace's refcount. func (i *IPCNamespace) DecRef(ctx context.Context) { - i.IPCNamespaceRefs.DecRef(func() { - i.shms.Release(ctx) - if i.posixQueues != nil { - i.posixQueues.Destroy(ctx) - } - }) + i.inode.DecRef(ctx) } // IPCNamespace returns the task's IPC namespace. @@ -114,3 +142,14 @@ func (t *Task) IPCNamespace() *IPCNamespace { defer t.mu.Unlock() return t.ipcns } + +// GetIPCNamespace takes a reference on the task IPC namespace and +// returns it. It will return nil if the task isn't alive. +func (t *Task) GetIPCNamespace() *IPCNamespace { + t.mu.Lock() + defer t.mu.Unlock() + if t.ipcns != nil { + t.ipcns.IncRef() + } + return t.ipcns +} diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 9a7b4b568..46d10fd65 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -451,6 +451,7 @@ func (k *Kernel) Init(args InitKernelArgs) error { nsfsMount := k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{}) k.nsfsMount = nsfsMount k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, nsfsMount, k.rootNetworkNamespace)) + k.rootIPCNamespace.SetInode(nsfs.NewInode(ctx, nsfsMount, k.rootIPCNamespace)) tmpfsOpts := vfs.GetFilesystemOptions{ InternalData: tmpfs.FilesystemOpts{ diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index 5a2fa27cc..c98020936 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -101,6 +101,9 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { return 0, nil, linuxerr.EPERM } + cu := cleanup.Make(func() {}) + defer cu.Clean() + utsns := t.UTSNamespace() if args.Flags&linux.CLONE_NEWUTS != 0 { // Note that this must happen after NewUserNamespace so we get @@ -108,17 +111,17 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { utsns = t.UTSNamespace().Clone(userns) } - ipcns := t.IPCNamespace() + ipcns := t.ipcns if args.Flags&linux.CLONE_NEWIPC != 0 { ipcns = NewIPCNamespace(userns) ipcns.InitPosixQueues(t, t.k.VFS(), creds) + ipcns.SetInode(nsfs.NewInode(t, t.k.nsfsMount, ipcns)) } else { ipcns.IncRef() } - cu := cleanup.Make(func() { + cu.Add(func() { ipcns.DecRef(t) }) - defer cu.Clean() netns := t.netns.Load() if args.Flags&linux.CLONE_NEWNET != 0 { @@ -437,6 +440,21 @@ func (t *Task) Setns(fd *vfs.FileDescription, flags int32) error { t.mu.Unlock() oldNS.DecRef(t) return nil + case *IPCNamespace: + if flags != 0 && flags != linux.CLONE_NEWIPC { + return linuxerr.EINVAL + } + if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, ns.UserNamespace()) || + !t.Credentials().HasCapability(linux.CAP_SYS_ADMIN) { + return linuxerr.EPERM + } + oldNS := t.IPCNamespace() + ns.IncRef() + t.mu.Lock() + t.ipcns = ns + t.mu.Unlock() + oldNS.DecRef(t) + return nil default: return linuxerr.EINVAL } @@ -538,6 +556,10 @@ func (t *Task) Unshare(flags int32) error { oldIPCNS = t.ipcns t.ipcns = NewIPCNamespace(creds.UserNamespace) t.ipcns.InitPosixQueues(t, t.k.VFS(), creds) + t.ipcns.SetInode(nsfs.NewInode(t, t.k.nsfsMount, t.ipcns)) + if oldIPCNS != nil { + oldIPCNS.DecRef(t) + } } var oldFDTable *FDTable if flags&linux.CLONE_FILES != 0 { @@ -550,9 +572,6 @@ func (t *Task) Unshare(flags int32) error { t.fsContext = oldFSContext.Fork() } t.mu.Unlock() - if oldIPCNS != nil { - oldIPCNS.DecRef(t) - } if oldFDTable != nil { oldFDTable.DecRef(t) } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index bac32de7d..7231c4091 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -668,6 +668,10 @@ syscall_test( use_tmpfs = True, ) +syscall_test( + test = "//test/syscalls/linux:setns_test", +) + syscall_test( add_overlay = True, test = "//test/syscalls/linux:splice_test", diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index f11cd874f..a2fcc8059 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -2323,6 +2323,21 @@ cc_binary( ], ) +cc_binary( + name = "setns_test", + testonly = 1, + srcs = ["setns.cc"], + linkstatic = 1, + deps = [ + "//test/util:capability_util", + "//test/util:file_descriptor", + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:test_util", + gtest, + ], +) + cc_binary( name = "splice_test", testonly = 1, diff --git a/test/syscalls/linux/setns.cc b/test/syscalls/linux/setns.cc new file mode 100644 index 000000000..91322eb5b --- /dev/null +++ b/test/syscalls/linux/setns.cc @@ -0,0 +1,51 @@ +// 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. + +#include + +#include "gtest/gtest.h" +#include "test/util/file_descriptor.h" +#include "test/util/linux_capability_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { +namespace { + +TEST(SetnsTest, ChangeIPCNamespace) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + struct stat st; + uint64_t ipcns1, ipcns2, ipcns3; + const FileDescriptor nsfd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/ipc", O_RDONLY)); + ASSERT_THAT(stat("/proc/thread-self/ns/ipc", &st), SyscallSucceeds()); + ipcns1 = st.st_ino; + + // Use unshare(CLONE_NEWIPC) to change into a new IPC namespace. + ASSERT_THAT(unshare(CLONE_NEWIPC), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/ipc", &st), SyscallSucceeds()); + ipcns2 = st.st_ino; + ASSERT_NE(ipcns1, ipcns2); + + ASSERT_THAT(setns(nsfd.get(), CLONE_NEWIPC), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/ipc", &st), SyscallSucceeds()); + ipcns3 = st.st_ino; + EXPECT_EQ(ipcns1, ipcns3); +} + +} // namespace +} // namespace testing +} // namespace gvisor