diff --git a/pkg/sentry/fsimpl/proc/task.go b/pkg/sentry/fsimpl/proc/task.go index de725d794..c15c23ca0 100644 --- a/pkg/sentry/fsimpl/proc/task.go +++ b/pkg/sentry/fsimpl/proc/task.go @@ -77,6 +77,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns "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), + "uts": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWUTS), }), "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 dff44cf78..06a7cf013 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -1283,6 +1283,11 @@ func (s *namespaceSymlink) getInode(t *kernel.Task) *nsfs.Inode { return ipcns.GetInode() } return nil + case linux.CLONE_NEWUTS: + if utsns := t.GetUTSNamespace(); utsns != nil { + return utsns.GetInode() + } + return nil case linux.CLONE_NEWNS: mntns := t.GetMountNamespace() if mntns == nil { diff --git a/pkg/sentry/fsimpl/proc/tasks_sys.go b/pkg/sentry/fsimpl/proc/tasks_sys.go index 69555ebb2..9b523ecc9 100644 --- a/pkg/sentry/fsimpl/proc/tasks_sys.go +++ b/pkg/sentry/fsimpl/proc/tasks_sys.go @@ -169,6 +169,7 @@ var _ dynamicInode = (*hostnameData)(nil) // Generate implements vfs.DynamicBytesSource.Generate. func (*hostnameData) Generate(ctx context.Context, buf *bytes.Buffer) error { utsns := kernel.UTSNamespaceFromContext(ctx) + defer utsns.DecRef(ctx) buf.WriteString(utsns.HostName()) buf.WriteString("\n") return nil diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 8455914e1..6bf133fbb 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -452,6 +452,7 @@ func (k *Kernel) Init(args InitKernelArgs) error { k.nsfsMount = k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{}) k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootNetworkNamespace)) k.rootIPCNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootIPCNamespace)) + k.rootUTSNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootUTSNamespace)) tmpfsOpts := vfs.GetFilesystemOptions{ InternalData: tmpfs.FilesystemOpts{ @@ -769,7 +770,9 @@ func (ctx *createProcessContext) Value(key any) any { case CtxPIDNamespace: return ctx.args.PIDNamespace case CtxUTSNamespace: - return ctx.args.UTSNamespace + utsns := ctx.args.UTSNamespace + utsns.IncRef() + return utsns case ipc.CtxIPCNamespace: ipcns := ctx.args.IPCNamespace ipcns.IncRef() @@ -1284,6 +1287,7 @@ func (k *Kernel) RootUserNamespace() *auth.UserNamespace { // RootUTSNamespace returns the root UTSNamespace. func (k *Kernel) RootUTSNamespace() *UTSNamespace { + k.rootUTSNamespace.IncRef() return k.rootUTSNamespace } @@ -1527,7 +1531,9 @@ func (ctx *supervisorContext) Value(key any) any { case CtxPIDNamespace: return ctx.Kernel.tasks.Root case CtxUTSNamespace: - return ctx.Kernel.rootUTSNamespace + utsns := ctx.Kernel.rootUTSNamespace + utsns.IncRef() + return utsns case ipc.CtxIPCNamespace: ipcns := ctx.Kernel.rootIPCNamespace ipcns.IncRef() diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index a8324d004..5885cfeb9 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -107,12 +107,18 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { cu := cleanup.Make(func() {}) defer cu.Clean() - utsns := t.UTSNamespace() + utsns := t.utsns if args.Flags&linux.CLONE_NEWUTS != 0 { // Note that this must happen after NewUserNamespace so we get // the new userns if there is one. - utsns = t.UTSNamespace().Clone(userns) + utsns = utsns.Clone(userns) + utsns.SetInode(nsfs.NewInode(t, t.k.nsfsMount, utsns)) + } else { + utsns.IncRef() } + cu.Add(func() { + utsns.DecRef(t) + }) ipcns := t.ipcns if args.Flags&linux.CLONE_NEWIPC != 0 { @@ -495,6 +501,21 @@ func (t *Task) Setns(fd *vfs.FileDescription, flags int32) error { oldNS.DecRef(t) oldFSContext.DecRef(t) return nil + case *UTSNamespace: + if flags != 0 && flags != linux.CLONE_NEWUTS { + return linuxerr.EINVAL + } + if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, ns.UserNamespace()) || + !t.Credentials().HasCapability(linux.CAP_SYS_ADMIN) { + return linuxerr.EPERM + } + oldNS := t.UTSNamespace() + ns.IncRef() + t.mu.Lock() + t.utsns = ns + t.mu.Unlock() + oldNS.DecRef(t) + return nil default: return linuxerr.EINVAL } @@ -588,7 +609,10 @@ func (t *Task) Unshare(flags int32) error { } // Note that this must happen after NewUserNamespace, so the // new user namespace is used if there is one. + oldUTSNS := t.utsns t.utsns = t.utsns.Clone(creds.UserNamespace) + t.utsns.SetInode(nsfs.NewInode(t, t.k.nsfsMount, t.utsns)) + cu.Add(func() { oldUTSNS.DecRef(t) }) } if flags&linux.CLONE_NEWIPC != 0 { if !haveCapSysAdmin { diff --git a/pkg/sentry/kernel/task_context.go b/pkg/sentry/kernel/task_context.go index f57d79434..99bad23f8 100644 --- a/pkg/sentry/kernel/task_context.go +++ b/pkg/sentry/kernel/task_context.go @@ -73,7 +73,9 @@ func (t *Task) contextValue(key any, isTaskGoroutine bool) any { t.mu.Lock() defer t.mu.Unlock() } - return t.utsns + utsns := t.utsns + utsns.IncRef() + return utsns case ipc.CtxIPCNamespace: if !isTaskGoroutine { t.mu.Lock() diff --git a/pkg/sentry/kernel/task_exit.go b/pkg/sentry/kernel/task_exit.go index 43561a164..0bca6a294 100644 --- a/pkg/sentry/kernel/task_exit.go +++ b/pkg/sentry/kernel/task_exit.go @@ -287,12 +287,15 @@ func (*runExitMain) execute(t *Task) taskRunState { t.mu.Lock() mntns := t.mountNamespace t.mountNamespace = nil + utsns := t.utsns + t.utsns = nil ipcns := t.ipcns t.ipcns = nil netns := t.netns t.netns = nil t.mu.Unlock() mntns.DecRef(t) + utsns.DecRef(t) ipcns.DecRef(t) netns.DecRef(t) diff --git a/pkg/sentry/kernel/task_start.go b/pkg/sentry/kernel/task_start.go index 40ca0e316..cd76149ba 100644 --- a/pkg/sentry/kernel/task_start.go +++ b/pkg/sentry/kernel/task_start.go @@ -116,6 +116,7 @@ func (ts *TaskSet) NewTask(ctx context.Context, cfg *TaskConfig) (*Task, error) cfg.TaskImage.release(ctx) cfg.FSContext.DecRef(ctx) cfg.FDTable.DecRef(ctx) + cfg.UTSNamespace.DecRef(ctx) cfg.IPCNamespace.DecRef(ctx) cfg.NetworkNamespace.DecRef(ctx) if cfg.MountNamespace != nil { diff --git a/pkg/sentry/kernel/uts_namespace.go b/pkg/sentry/kernel/uts_namespace.go index 8ccf04bd1..87f7f8703 100644 --- a/pkg/sentry/kernel/uts_namespace.go +++ b/pkg/sentry/kernel/uts_namespace.go @@ -15,6 +15,8 @@ package kernel import ( + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sync" ) @@ -35,6 +37,8 @@ type UTSNamespace struct { // // userns is immutable. userns *auth.UserNamespace + + inode *nsfs.Inode } // NewUTSNamespace creates a new UTS namespace. @@ -53,6 +57,17 @@ func (t *Task) UTSNamespace() *UTSNamespace { return t.utsns } +// GetUTSNamespace takes a reference on the task UTS namespace and +// returns it. It will return nil if the task isn't alive. +func (t *Task) GetUTSNamespace() *UTSNamespace { + t.mu.Lock() + defer t.mu.Unlock() + if t.utsns != nil { + t.utsns.IncRef() + } + return t.utsns +} + // HostName returns the host name of this UTS namespace. func (u *UTSNamespace) HostName() string { u.mu.Lock() @@ -88,6 +103,42 @@ func (u *UTSNamespace) UserNamespace() *auth.UserNamespace { return u.userns } +// Type implements nsfs.Namespace.Type. +func (u *UTSNamespace) Type() string { + return "uts" +} + +// Destroy implements nsfs.Namespace.Destroy. +func (u *UTSNamespace) Destroy(ctx context.Context) {} + +// SetInode sets the nsfs `inode` to the UTS namespace. +func (u *UTSNamespace) SetInode(inode *nsfs.Inode) { + u.mu.Lock() + defer u.mu.Unlock() + u.inode = inode +} + +// GetInode returns the nsfs inode associated with the UTS namespace. +func (u *UTSNamespace) GetInode() *nsfs.Inode { + u.mu.Lock() + defer u.mu.Unlock() + return u.inode +} + +// IncRef increments the Namespace's refcount. +func (u *UTSNamespace) IncRef() { + u.mu.Lock() + defer u.mu.Unlock() + u.inode.IncRef() +} + +// DecRef decrements the namespace's refcount. +func (u *UTSNamespace) DecRef(ctx context.Context) { + u.mu.Lock() + defer u.mu.Unlock() + u.inode.DecRef(ctx) +} + // Clone makes a copy of this UTS namespace, associating the given user // namespace. func (u *UTSNamespace) Clone(userns *auth.UserNamespace) *UTSNamespace { diff --git a/test/syscalls/linux/setns.cc b/test/syscalls/linux/setns.cc index 91322eb5b..f8cf24a0d 100644 --- a/test/syscalls/linux/setns.cc +++ b/test/syscalls/linux/setns.cc @@ -14,6 +14,9 @@ #include +#include + +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/util/file_descriptor.h" #include "test/util/linux_capability_util.h" @@ -46,6 +49,28 @@ TEST(SetnsTest, ChangeIPCNamespace) { EXPECT_EQ(ipcns1, ipcns3); } +TEST(SetnsTest, ChangeUTSNamespace) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + struct stat st; + uint64_t utsns1, utsns2, utsns3; + const FileDescriptor nsfd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/uts", O_RDONLY)); + ASSERT_THAT(stat("/proc/thread-self/ns/uts", &st), SyscallSucceeds()); + utsns1 = st.st_ino; + + // Use unshare(CLONE_NEWUTS) to change into a new UTS namespace. + ASSERT_THAT(unshare(CLONE_NEWUTS), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/uts", &st), SyscallSucceeds()); + utsns2 = st.st_ino; + ASSERT_NE(utsns1, utsns2); + + ASSERT_THAT(setns(nsfd.get(), CLONE_NEWUTS), SyscallSucceedsWithValue(0)); + ASSERT_THAT(stat("/proc/thread-self/ns/uts", &st), SyscallSucceeds()); + utsns3 = st.st_ino; + EXPECT_EQ(utsns1, utsns3); +} + } // namespace } // namespace testing } // namespace gvisor