mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement get/set_robust_list.
PiperOrigin-RevId: 322904430
This commit is contained in:
committed by
gVisor bot
parent
5e34ee68c9
commit
4ec3516332
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user