diff --git a/pkg/abi/linux/BUILD b/pkg/abi/linux/BUILD index b34f6fe9e5..55940e0607 100644 --- a/pkg/abi/linux/BUILD +++ b/pkg/abi/linux/BUILD @@ -58,6 +58,7 @@ go_library( "netlink.go", "netlink_route.go", "nf_tables.go", + "personality.go", "poll.go", "prctl.go", "ptrace.go", diff --git a/pkg/abi/linux/personality.go b/pkg/abi/linux/personality.go new file mode 100644 index 0000000000..63c2d0bddf --- /dev/null +++ b/pkg/abi/linux/personality.go @@ -0,0 +1,29 @@ +// Copyright 2024 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 linux + +// Personality flags, used by personality(2), +// from include/uapi/linux/personality.h. +const ( + SHORT_INODE = 0x1000000 + WHOLE_SECONDS = 0x2000000 + PER_LINUX = 0x0000 + PER_BSD = 0x0006 +) + +// NOTE: All of the above flags are non-security-sensitive and may be copied +// from parent task to child task. However, this is not the case for all +// personality bits. If adding more, check PER_CLEAR_ON_SETID and ensure that +// these are cleared on suid/sgid execs. diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 964730384f..7422e3112b 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -1157,6 +1157,7 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, InitialCgroups: args.InitialCgroups, UserCounters: k.GetUserCounters(args.Credentials.RealKUID), Origin: args.Origin, + Personality: linux.PER_LINUX, // A task with no parent starts out with no session keyring. SessionKeyring: nil, } diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 2df0144470..4343ab7cb4 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -608,6 +608,9 @@ type Task struct { // +checklocks:mu sessionKeyring *auth.Key + // personality is the task's personality(2) bits. + personality atomicbitops.Uint32 + // Origin is the origin of the task. Origin TaskOrigin } @@ -869,3 +872,14 @@ func (t *Task) ResetKcov() { t.kcov = nil } } + +// Personality returns the task's personality. +func (t *Task) Personality() uint32 { + return t.personality.Load() +} + +// SetPersonality sets the task's personality. +// It returns the task's former personality. +func (t *Task) SetPersonality(personality uint32) uint32 { + return t.personality.Swap(personality) +} diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index 635b4f9951..f6d331e8af 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -265,6 +265,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { ContainerID: t.ContainerID(), UserCounters: uc, SessionKeyring: sessionKeyring, + Personality: t.personality.Load(), Origin: t.Origin, } if args.Flags&linux.CLONE_THREAD == 0 { diff --git a/pkg/sentry/kernel/task_start.go b/pkg/sentry/kernel/task_start.go index ded77133f7..985ec2e3db 100644 --- a/pkg/sentry/kernel/task_start.go +++ b/pkg/sentry/kernel/task_start.go @@ -104,6 +104,9 @@ type TaskConfig struct { // It may be nil. SessionKeyring *auth.Key + // Personality is the personality of the parent task. + Personality uint32 + Origin TaskOrigin } @@ -174,6 +177,7 @@ func (ts *TaskSet) newTask(ctx context.Context, cfg *TaskConfig) (*Task, error) cgroups: make(map[Cgroup]struct{}), userCounters: cfg.UserCounters, sessionKeyring: cfg.SessionKeyring, + personality: atomicbitops.FromUint32(cfg.Personality), Origin: cfg.Origin, } t.netns = cfg.NetworkNamespace diff --git a/pkg/sentry/syscalls/linux/BUILD b/pkg/sentry/syscalls/linux/BUILD index 86117d91f2..22d7a35c55 100644 --- a/pkg/sentry/syscalls/linux/BUILD +++ b/pkg/sentry/syscalls/linux/BUILD @@ -33,6 +33,7 @@ go_library( "sys_mount.go", "sys_mq.go", "sys_msgqueue.go", + "sys_personality.go", "sys_pipe.go", "sys_poll.go", "sys_prctl.go", diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index e95411f566..8d999a4d48 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -187,7 +187,7 @@ var AMD64 = &kernel.SyscallTable{ 132: syscalls.Supported("utime", Utime), 133: syscalls.Supported("mknod", Mknod), 134: syscalls.Error("uselib", linuxerr.ENOSYS, "Obsolete", nil), - 135: syscalls.ErrorWithEvent("personality", linuxerr.EINVAL, "Unable to change personality.", nil), + 135: syscalls.PartiallySupported("personality", Personality, "Only setting no-op personality bits and retrieving them are supported.", nil), 136: syscalls.ErrorWithEvent("ustat", linuxerr.ENOSYS, "Needs filesystem support.", nil), 137: syscalls.Supported("statfs", Statfs), 138: syscalls.Supported("fstatfs", Fstatfs), @@ -523,7 +523,7 @@ var ARM64 = &kernel.SyscallTable{ 89: syscalls.CapError("acct", linux.CAP_SYS_PACCT, "", nil), 90: syscalls.Supported("capget", Capget), 91: syscalls.Supported("capset", Capset), - 92: syscalls.ErrorWithEvent("personality", linuxerr.EINVAL, "Unable to change personality.", nil), + 92: syscalls.PartiallySupported("personality", Personality, "Only setting no-op personality bits and retrieving them are supported.", nil), 93: syscalls.Supported("exit", Exit), 94: syscalls.Supported("exit_group", ExitGroup), 95: syscalls.Supported("waitid", Waitid), diff --git a/pkg/sentry/syscalls/linux/sys_personality.go b/pkg/sentry/syscalls/linux/sys_personality.go new file mode 100644 index 0000000000..88674c0bab --- /dev/null +++ b/pkg/sentry/syscalls/linux/sys_personality.go @@ -0,0 +1,43 @@ +// Copyright 2024 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 linux + +import ( + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/kernel" +) + +const ( + // getPersonality may be passed to `personality(2)` to get the current + // personality bits without modifying them. + getPersonality = 0xffffffff +) + +// Personality implements Linux syscall personality(2). +func Personality(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + // allowedPersonalityBits are the personality bits that are allowed to be set. + const allowedPersonalityBits = linux.PER_LINUX | linux.PER_BSD | linux.SHORT_INODE | linux.WHOLE_SECONDS + + personality := args[0].Uint() + if personality == getPersonality { + return uintptr(t.Personality()), nil, nil + } + if personality&allowedPersonalityBits != personality { + return 0, nil, linuxerr.EINVAL + } + return uintptr(t.SetPersonality(personality)), nil, nil +} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index e9eba54a9d..712b0c2f2d 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -479,6 +479,10 @@ syscall_test( test = "//test/syscalls/linux:pause_test", ) +syscall_test( + test = "//test/syscalls/linux:personality_test", +) + syscall_test( size = "medium", add_hostinet = True, diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 4cf7ef6293..4b1107a9bb 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1670,6 +1670,20 @@ cc_binary( ], ) +cc_binary( + name = "personality_test", + testonly = 1, + srcs = ["personality.cc"], + linkstatic = 1, + malloc = "//test/util:errno_safe_allocator", + deps = select_gtest() + [ + "//test/util:multiprocess_util", + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:test_util", + ], +) + cc_binary( name = "ping_socket_test", testonly = 1, diff --git a/test/syscalls/linux/personality.cc b/test/syscalls/linux/personality.cc new file mode 100644 index 0000000000..f847dbf256 --- /dev/null +++ b/test/syscalls/linux/personality.cc @@ -0,0 +1,141 @@ +// Copyright 2024 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/multiprocess_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +constexpr uint64_t kGetPersonality = 0xffffffff; +constexpr uint64_t kUndefinedPersonalityBit = 0x00001000; + +TEST(PersonalityTest, DefaultPersonality) { + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); +} + +TEST(PersonalityTest, SetLinux) { + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_LINUX) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == PER_LINUX); + }), IsPosixErrorOkAndHolds(0)); + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); +} + +TEST(PersonalityTest, SetBSD) { + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_BSD) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, PersonalityIsInheritable) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_BSD) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + TEST_CHECK(InForkedProcess([&] { + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + TEST_CHECK(InForkedProcess([&] { + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }).ok()); + TEST_CHECK(personality(PER_SOLARIS) == PER_BSD); + TEST_CHECK(InForkedProcess([&] { + TEST_CHECK(personality(kGetPersonality) == PER_SOLARIS); + }).ok()); + TEST_CHECK(personality(PER_BSD) == PER_SOLARIS); + TEST_CHECK(InForkedProcess([&] { + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }).ok()); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }).ok()); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, ChildPersonalityDoesNotAffectParent) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_BSD) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + TEST_CHECK(InForkedProcess([&] { + TEST_CHECK(personality(PER_SOLARIS) == PER_BSD); + TEST_CHECK(personality(kGetPersonality) == PER_SOLARIS); + }).ok()); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }), IsPosixErrorOkAndHolds(0)); + TEST_CHECK(personality(kGetPersonality) == PER_LINUX); +} + +TEST(PersonalityTest, SetShortInode) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(SHORT_INODE) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == SHORT_INODE); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, SetWholeSeconds) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(WHOLE_SECONDS) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == WHOLE_SECONDS); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, SetMultiple) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_BSD | SHORT_INODE | WHOLE_SECONDS) == PER_LINUX); + TEST_CHECK(personality(kGetPersonality) == (PER_BSD | SHORT_INODE | WHOLE_SECONDS)); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, SetThenUnset) { + EXPECT_THAT(InForkedProcess([&] { + TEST_CHECK(personality(PER_BSD | SHORT_INODE) == PER_LINUX); + TEST_CHECK(personality(WHOLE_SECONDS | SHORT_INODE) == (PER_BSD | SHORT_INODE)); + TEST_CHECK(personality(PER_BSD) == (WHOLE_SECONDS | SHORT_INODE)); + TEST_CHECK(personality(kGetPersonality) == PER_BSD); + }), IsPosixErrorOkAndHolds(0)); +} + +TEST(PersonalityTest, UnsupportedPersonalityBitsAreRejected) { + SKIP_IF(!IsRunningOnGvisor()); + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); + EXPECT_THAT(personality(MMAP_PAGE_ZERO), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(ADDR_LIMIT_3GB), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(ADDR_LIMIT_32BIT), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(ADDR_NO_RANDOMIZE), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(ADDR_COMPAT_LAYOUT), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(READ_IMPLIES_EXEC), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(STICKY_TIMEOUTS), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(UNAME26), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(FDPIC_FUNCPTRS), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_LINUX32), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_LINUX32_3GB), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_LINUX_32BIT), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_LINUX_FDPIC), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_RISCOS), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(PER_SOLARIS), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(personality(kGetPersonality), SyscallSucceedsWithValue(PER_LINUX)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor