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