fix: preserve robust list syscall semantics

This commit is contained in:
XxJSONDeruloxX committed 2026-10-14 21:49:23 -04:00
1 parent 35a4a0750f
commit 9b91c02e07
4 files changed
+136 -5

No files matched your search

+9
View File
@@ -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.
+18 -5
View File
@@ -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]);
+91
View File
@@ -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;
}
+18
View File
@@ -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)