mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
fix: preserve robust list syscall semantics
This commit is contained in:
1 parent
35a4a0750f
commit
9b91c02e07
4 files changed
+136
-5
No files matched your search
@@ -310,6 +310,15 @@ bundled into the user-facing runtime. A valid already-signaled SYNC_FD value of
|
||||
|
||||
## Remaining stages
|
||||
|
||||
Steam's ARM64 watchdog also aborts with `futex robust_list not initialized by
|
||||
pthreads`. A captured preload trace showed successful own-thread queries
|
||||
leaving `errno=EFAULT`. The preload now prefers the real kernel query, falls
|
||||
back only for `ENOSYS`, preserves errno on fallback success and safely rejects
|
||||
invalid output pointers. A host regression compares native and fallback
|
||||
results with the kernel's actual head, including a held robust mutex and a
|
||||
second thread, and preserves permission failures. This is not yet a verified
|
||||
fix for Valve's assertion: the Quest disconnected before a new device trace.
|
||||
|
||||
- Android/Linux eye-image bridge and frame ownership/synchronization.
|
||||
- OpenVR driver IPC for predicted 6DoF head, per-eye views and tracked hands.
|
||||
- Real Touch input mapping and haptics returning through the bridge.
|
||||
|
||||
@@ -98,23 +98,36 @@ long syscall(long number, ...) {
|
||||
args[i] = va_arg(ap, long);
|
||||
}
|
||||
va_end(ap);
|
||||
int saved_errno = errno;
|
||||
if (!real_syscall) {
|
||||
real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall");
|
||||
}
|
||||
if (number == SYS_get_robust_list && (args[0] == 0 || args[0] == gettid())) {
|
||||
errno = saved_errno;
|
||||
long result = real_syscall(number, args[0], args[1], args[2]);
|
||||
if (result != -1 || errno != ENOSYS) {
|
||||
return result;
|
||||
}
|
||||
struct robust_list_head *head = thread_robust_head();
|
||||
if (!head) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
*(struct robust_list_head **)args[1] = head;
|
||||
*(size_t *)args[2] = sizeof(*head);
|
||||
size_t length = sizeof(*head);
|
||||
struct iovec local[2] = {{&head, sizeof(head)}, {&length, sizeof(length)}};
|
||||
struct iovec remote[2] = {{(void *)args[1], sizeof(head)}, {(void *)args[2], sizeof(length)}};
|
||||
ssize_t written = process_vm_writev(getpid(), local, 2, remote, 2, 0);
|
||||
if (written != (ssize_t)(sizeof(head) + sizeof(length))) {
|
||||
if (written >= 0) errno = EFAULT;
|
||||
return -1;
|
||||
}
|
||||
errno = saved_errno;
|
||||
return 0;
|
||||
}
|
||||
long handled;
|
||||
if (bl_fsync_syscall(number, args, &handled)) {
|
||||
return handled;
|
||||
}
|
||||
if (!real_syscall) {
|
||||
real_syscall = (long (*)(long, ...)) dlsym(RTLD_NEXT, "syscall");
|
||||
}
|
||||
if (number == SYS_openat2) {
|
||||
if (!__atomic_load_n(&openat2_missing, __ATOMIC_RELAXED)) {
|
||||
long fd = real_syscall(number, args[0], args[1], args[2], args[3]);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <linux/futex.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int bl_fsync_syscall(long number, const long *args, long *result) {
|
||||
(void)number;
|
||||
(void)args;
|
||||
(void)result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "../linuxfs/preload/robust.c"
|
||||
|
||||
static long (*kernel_syscall)(long, ...);
|
||||
static int missing_error;
|
||||
|
||||
static long missing_robust(long number, ...) {
|
||||
assert(number == SYS_get_robust_list);
|
||||
errno = missing_error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct robust_list_head *check_head(void) {
|
||||
struct robust_list_head *expected = NULL;
|
||||
size_t expected_length = 0;
|
||||
assert(kernel_syscall(SYS_get_robust_list, 0, &expected, &expected_length) == 0);
|
||||
struct robust_list_head *head = NULL;
|
||||
size_t length = 0;
|
||||
errno = E2BIG;
|
||||
assert(syscall(SYS_get_robust_list, 0L, &head, &length, 0L, 0L, 0L) == 0);
|
||||
assert(errno == E2BIG);
|
||||
assert(head == expected && length == expected_length);
|
||||
return head;
|
||||
}
|
||||
|
||||
static void *check_thread(void *argument) {
|
||||
(void)argument;
|
||||
pthread_mutexattr_t attributes;
|
||||
pthread_mutex_t mutex;
|
||||
assert(pthread_mutexattr_init(&attributes) == 0);
|
||||
assert(pthread_mutexattr_setrobust(&attributes, PTHREAD_MUTEX_ROBUST) == 0);
|
||||
assert(pthread_mutex_init(&mutex, &attributes) == 0);
|
||||
assert(pthread_mutexattr_destroy(&attributes) == 0);
|
||||
struct robust_list_head *head = check_head();
|
||||
assert(pthread_mutex_lock(&mutex) == 0);
|
||||
struct robust_list *first = head->list.next;
|
||||
struct robust_list *pending = head->list_op_pending;
|
||||
assert(check_head() == head);
|
||||
assert(head->list.next == first && head->list_op_pending == pending);
|
||||
assert(pthread_mutex_unlock(&mutex) == 0);
|
||||
assert(pthread_mutex_destroy(&mutex) == 0);
|
||||
return head;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
kernel_syscall = dlsym(RTLD_NEXT, "syscall");
|
||||
assert(kernel_syscall);
|
||||
real_syscall = kernel_syscall;
|
||||
struct robust_list_head *main_head = check_thread(NULL);
|
||||
pthread_t thread;
|
||||
void *thread_head;
|
||||
assert(pthread_create(&thread, NULL, check_thread, NULL) == 0);
|
||||
assert(pthread_join(thread, &thread_head) == 0);
|
||||
assert(thread_head != main_head);
|
||||
real_syscall = missing_robust;
|
||||
missing_error = ENOSYS;
|
||||
check_thread(NULL);
|
||||
assert(pthread_create(&thread, NULL, check_thread, NULL) == 0);
|
||||
assert(pthread_join(thread, NULL) == 0);
|
||||
struct robust_list_head *head = NULL;
|
||||
size_t length = 0;
|
||||
assert(syscall(SYS_get_robust_list, 0L, NULL, &length, 0L, 0L, 0L) == -1);
|
||||
assert(errno == EFAULT);
|
||||
assert(syscall(SYS_get_robust_list, 0L, &head, NULL, 0L, 0L, 0L) == -1);
|
||||
assert(errno == EFAULT);
|
||||
head = NULL;
|
||||
missing_error = EPERM;
|
||||
assert(syscall(SYS_get_robust_list, 0L, &head, &length, 0L, 0L, 0L) == -1);
|
||||
assert(errno == EPERM && !head && !length);
|
||||
missing_error = EACCES;
|
||||
assert(syscall(SYS_get_robust_list, 0L, &head, &length, 0L, 0L, 0L) == -1);
|
||||
assert(errno == EACCES && !head && !length);
|
||||
puts("robust preload: native and ENOSYS fallback metadata, threads, errno and permission failures passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("gcc"), "needs gcc on a Linux host")
|
||||
class RobustPreloadTest(unittest.TestCase):
|
||||
def test_native_and_missing_syscall_contract(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
executable = Path(directory) / "robust-preload-test"
|
||||
harness = Path(__file__).with_name("robust_preload_harness.c")
|
||||
subprocess.run(["gcc", "-O2", "-Wall", "-Wextra", "-Werror", "-pthread",
|
||||
str(harness), "-ldl", "-o", str(executable)], check=True)
|
||||
result = subprocess.run([str(executable)], capture_output=True, text=True, timeout=20)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn("permission failures passed", result.stdout)
|
||||
Reference in new issue
Block a user