Implement get/set_robust_list.

PiperOrigin-RevId: 322904430
This commit is contained in:
Nicolas Lacasse
2020-07-23 17:42:50 -07:00
committed by gVisor bot
parent 5e34ee68c9
commit 4ec3516332
9 changed files with 298 additions and 7 deletions
+18
View File
@@ -60,3 +60,21 @@ const (
FUTEX_WAITERS = 0x80000000
FUTEX_OWNER_DIED = 0x40000000
)
// FUTEX_BITSET_MATCH_ANY has all bits set.
const FUTEX_BITSET_MATCH_ANY = 0xffffffff
// ROBUST_LIST_LIMIT protects against a deliberately circular list.
const ROBUST_LIST_LIMIT = 2048
// RobustListHead corresponds to Linux's struct robust_list_head.
//
// +marshal
type RobustListHead struct {
List uint64
FutexOffset uint64
ListOpPending uint64
}
// SizeOfRobustListHead is the size of a RobustListHead struct.
var SizeOfRobustListHead = (*RobustListHead)(nil).SizeBytes()
+4 -4
View File
@@ -717,10 +717,10 @@ func (m *Manager) lockPILocked(w *Waiter, t Target, addr usermem.Addr, tid uint3
}
}
// UnlockPI unlock the futex following the Priority-inheritance futex
// rules. The address provided must contain the caller's TID. If there are
// waiters, TID of the next waiter (FIFO) is set to the given address, and the
// waiter woken up. If there are no waiters, 0 is set to the address.
// UnlockPI unlocks the futex following the Priority-inheritance futex rules.
// The address provided must contain the caller's TID. If there are waiters,
// TID of the next waiter (FIFO) is set to the given address, and the waiter
// woken up. If there are no waiters, 0 is set to the address.
func (m *Manager) UnlockPI(t Target, addr usermem.Addr, tid uint32, private bool) error {
k, err := getKey(t, addr, private)
if err != nil {
+4
View File
@@ -565,6 +565,10 @@ type Task struct {
// futexWaiter is exclusive to the task goroutine.
futexWaiter *futex.Waiter `state:"nosave"`
// robustList is a pointer to the head of the tasks's robust futex
// list.
robustList usermem.Addr
// startTime is the real time at which the task started. It is set when
// a Task is created or invokes execve(2).
//
+3
View File
@@ -207,6 +207,9 @@ func (r *runSyscallAfterExecStop) execute(t *Task) taskRunState {
return flags.CloseOnExec
})
// Handle the robust futex list.
t.exitRobustList()
// NOTE(b/30815691): We currently do not implement privileged
// executables (set-user/group-ID bits and file capabilities). This
// allows us to unconditionally enable user dumpability on the new mm.
+3
View File
@@ -253,6 +253,9 @@ func (*runExitMain) execute(t *Task) taskRunState {
}
}
// Handle the robust futex list.
t.exitRobustList()
// Deactivate the address space and update max RSS before releasing the
// task's MM.
t.Deactivate()
+125
View File
@@ -15,6 +15,7 @@
package kernel
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/kernel/futex"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -52,3 +53,127 @@ func (t *Task) LoadUint32(addr usermem.Addr) (uint32, error) {
func (t *Task) GetSharedKey(addr usermem.Addr) (futex.Key, error) {
return t.MemoryManager().GetSharedFutexKey(t, addr)
}
// GetRobustList sets the robust futex list for the task.
func (t *Task) GetRobustList() usermem.Addr {
t.mu.Lock()
addr := t.robustList
t.mu.Unlock()
return addr
}
// SetRobustList sets the robust futex list for the task.
func (t *Task) SetRobustList(addr usermem.Addr) {
t.mu.Lock()
t.robustList = addr
t.mu.Unlock()
}
// exitRobustList walks the robust futex list, marking locks dead and notifying
// wakers. It corresponds to Linux's exit_robust_list(). Following Linux,
// errors are silently ignored.
func (t *Task) exitRobustList() {
t.mu.Lock()
addr := t.robustList
t.robustList = 0
t.mu.Unlock()
if addr == 0 {
return
}
var rl linux.RobustListHead
if _, err := rl.CopyIn(t, usermem.Addr(addr)); err != nil {
return
}
next := rl.List
done := 0
var pendingLockAddr usermem.Addr
if rl.ListOpPending != 0 {
pendingLockAddr = usermem.Addr(rl.ListOpPending + rl.FutexOffset)
}
// Wake up normal elements.
for usermem.Addr(next) != addr {
// We traverse to the next element of the list before we
// actually wake anything. This prevents the race where waking
// this futex causes a modification of the list.
thisLockAddr := usermem.Addr(next + rl.FutexOffset)
// Try to decode the next element in the list before waking the
// current futex. But don't check the error until after we've
// woken the current futex. Linux does it in this order too
_, nextErr := t.CopyIn(usermem.Addr(next), &next)
// Wakeup the current futex if it's not pending.
if thisLockAddr != pendingLockAddr {
t.wakeRobustListOne(thisLockAddr)
}
// If there was an error copying the next futex, we must bail.
if nextErr != nil {
break
}
// This is a user structure, so it could be a massive list, or
// even contain a loop if they are trying to mess with us. We
// cap traversal to prevent that.
done++
if done >= linux.ROBUST_LIST_LIMIT {
break
}
}
// Is there a pending entry to wake?
if pendingLockAddr != 0 {
t.wakeRobustListOne(pendingLockAddr)
}
}
// wakeRobustListOne wakes a single futex from the robust list.
func (t *Task) wakeRobustListOne(addr usermem.Addr) {
// Bit 0 in address signals PI futex.
pi := addr&1 == 1
addr = addr &^ 1
// Load the futex.
f, err := t.LoadUint32(addr)
if err != nil {
// Can't read this single value? Ignore the problem.
// We can wake the other futexes in the list.
return
}
tid := uint32(t.ThreadID())
for {
// Is this held by someone else?
if f&linux.FUTEX_TID_MASK != tid {
return
}
// This thread is dying and it's holding this futex. We need to
// set the owner died bit and wake up any waiters.
newF := (f & linux.FUTEX_WAITERS) | linux.FUTEX_OWNER_DIED
if curF, err := t.CompareAndSwapUint32(addr, f, newF); err != nil {
return
} else if curF != f {
// Futex changed out from under us. Try again...
f = curF
continue
}
// Wake waiters if there are any.
if f&linux.FUTEX_WAITERS != 0 {
private := f&linux.FUTEX_PRIVATE_FLAG != 0
if pi {
t.Futex().UnlockPI(t, addr, tid, private)
return
}
t.Futex().Wake(t, addr, private, linux.FUTEX_BITSET_MATCH_ANY, 1)
}
// Done.
return
}
}
+2 -2
View File
@@ -325,8 +325,8 @@ var AMD64 = &kernel.SyscallTable{
270: syscalls.Supported("pselect", Pselect),
271: syscalls.Supported("ppoll", Ppoll),
272: syscalls.PartiallySupported("unshare", Unshare, "Mount, cgroup namespaces not supported. Network namespaces supported but must be empty.", nil),
273: syscalls.Error("set_robust_list", syserror.ENOSYS, "Obsolete.", nil),
274: syscalls.Error("get_robust_list", syserror.ENOSYS, "Obsolete.", nil),
273: syscalls.Supported("set_robust_list", SetRobustList),
274: syscalls.Supported("get_robust_list", GetRobustList),
275: syscalls.Supported("splice", Splice),
276: syscalls.Supported("tee", Tee),
277: syscalls.PartiallySupported("sync_file_range", SyncFileRange, "Full data flush is not guaranteed at this time.", nil),
+47 -1
View File
@@ -198,7 +198,7 @@ func Futex(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
switch cmd {
case linux.FUTEX_WAIT:
// WAIT uses a relative timeout.
mask = ^uint32(0)
mask = linux.FUTEX_BITSET_MATCH_ANY
var timeoutDur time.Duration
if !forever {
timeoutDur = time.Duration(timespec.ToNsecCapped()) * time.Nanosecond
@@ -286,3 +286,49 @@ func Futex(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
return 0, nil, syserror.ENOSYS
}
}
// SetRobustList implements linux syscall set_robust_list(2).
func SetRobustList(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
// Despite the syscall using the name 'pid' for this variable, it is
// very much a tid.
head := args[0].Pointer()
length := args[1].SizeT()
if length != uint(linux.SizeOfRobustListHead) {
return 0, nil, syserror.EINVAL
}
t.SetRobustList(head)
return 0, nil, nil
}
// GetRobustList implements linux syscall get_robust_list(2).
func GetRobustList(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
// Despite the syscall using the name 'pid' for this variable, it is
// very much a tid.
tid := args[0].Int()
head := args[1].Pointer()
size := args[2].Pointer()
if tid < 0 {
return 0, nil, syserror.EINVAL
}
ot := t
if tid != 0 {
if ot = t.PIDNamespace().TaskWithID(kernel.ThreadID(tid)); ot == nil {
return 0, nil, syserror.ESRCH
}
}
// Copy out head pointer.
if _, err := t.CopyOut(head, uint64(ot.GetRobustList())); err != nil {
return 0, nil, err
}
// Copy out size, which is a constant.
if _, err := t.CopyOut(size, uint64(linux.SizeOfRobustListHead)); err != nil {
return 0, nil, err
}
return 0, nil, nil
}
+92
View File
@@ -18,6 +18,7 @@
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>
#include <algorithm>
@@ -737,6 +738,97 @@ TEST_P(PrivateAndSharedFutexTest, PITryLockConcurrency_NoRandomSave) {
}
}
int get_robust_list(int pid, struct robust_list_head** head_ptr,
size_t* len_ptr) {
return syscall(__NR_get_robust_list, pid, head_ptr, len_ptr);
}
int set_robust_list(struct robust_list_head* head, size_t len) {
return syscall(__NR_set_robust_list, head, len);
}
TEST(RobustFutexTest, BasicSetGet) {
struct robust_list_head hd = {};
struct robust_list_head* hd_ptr = &hd;
// Set!
EXPECT_THAT(set_robust_list(hd_ptr, sizeof(hd)), SyscallSucceedsWithValue(0));
// Get!
struct robust_list_head* new_hd_ptr = hd_ptr;
size_t len;
EXPECT_THAT(get_robust_list(0, &new_hd_ptr, &len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(new_hd_ptr, hd_ptr);
EXPECT_EQ(len, sizeof(hd));
}
TEST(RobustFutexTest, GetFromOtherTid) {
// Get the current tid and list head.
pid_t tid = gettid();
struct robust_list_head* hd_ptr = {};
size_t len;
EXPECT_THAT(get_robust_list(0, &hd_ptr, &len), SyscallSucceedsWithValue(0));
// Create a new thread.
ScopedThread t([&] {
// Current tid list head should be different from parent tid.
struct robust_list_head* got_hd_ptr = {};
EXPECT_THAT(get_robust_list(0, &got_hd_ptr, &len),
SyscallSucceedsWithValue(0));
EXPECT_NE(hd_ptr, got_hd_ptr);
// Get the parent list head by passing its tid.
EXPECT_THAT(get_robust_list(tid, &got_hd_ptr, &len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(hd_ptr, got_hd_ptr);
});
// Wait for thread.
t.Join();
}
TEST(RobustFutexTest, InvalidSize) {
struct robust_list_head* hd = {};
EXPECT_THAT(set_robust_list(hd, sizeof(*hd) + 1),
SyscallFailsWithErrno(EINVAL));
}
TEST(RobustFutexTest, PthreadMutexAttr) {
constexpr int kNumMutexes = 3;
// Create a bunch of robust mutexes.
pthread_mutexattr_t attrs[kNumMutexes];
pthread_mutex_t mtxs[kNumMutexes];
for (int i = 0; i < kNumMutexes; i++) {
TEST_PCHECK(pthread_mutexattr_init(&attrs[i]) == 0);
TEST_PCHECK(pthread_mutexattr_setrobust(&attrs[i], PTHREAD_MUTEX_ROBUST) ==
0);
TEST_PCHECK(pthread_mutex_init(&mtxs[i], &attrs[i]) == 0);
}
// Start thread to lock the mutexes and then exit.
ScopedThread t([&] {
for (int i = 0; i < kNumMutexes; i++) {
TEST_PCHECK(pthread_mutex_lock(&mtxs[i]) == 0);
}
pthread_exit(NULL);
});
// Wait for thread.
t.Join();
// Now try to take the mutexes.
for (int i = 0; i < kNumMutexes; i++) {
// Should get EOWNERDEAD.
EXPECT_EQ(pthread_mutex_lock(&mtxs[i]), EOWNERDEAD);
// Make the mutex consistent.
EXPECT_EQ(pthread_mutex_consistent(&mtxs[i]), 0);
// Unlock.
EXPECT_EQ(pthread_mutex_unlock(&mtxs[i]), 0);
}
}
} // namespace
} // namespace testing
} // namespace gvisor