Files
DroidDeck/tools/linuxfs/preload/vmwrite.c
T
MaxsTechReview 6ba159a2f2 fix: let Wine write hooks into code FEX has translated (Ubisoft "Error at hooking API") (#1163)
fix: let Wine write hooks into code FEX has translated

Wine 11's wineserver writes another process's memory with process_vm_writev,
which the kernel refuses on a page that is not writable. FEX's WoW64 layer
keeps the pages it has translated read-only to catch self-modifying code and
re-protects them right after a program unprotects them, so WriteProcessMemory
and NtWriteVirtualMemory into ntdll failed with a partial copy and Ubisoft
games stopped at "Error at hooking API NtProtectVirtualMemory". Wine only
falls back to /proc/<pid>/mem on ENOSYS or EPERM.

libblsession now completes a single-buffer write that wineserver was refused
with EFAULT through /proc/<pid>/mem, the way Wine wrote before Wine 11. Other
programs and every other error keep the kernel's answer. The iovecs are read
with process_vm_readv, so a bad pointer still returns EFAULT, and offsets are
64-bit in the i386 build.
2026-10-20 00:31:03 -04:00

56 lines
1.8 KiB
C

#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>
typedef ssize_t (*vm_writev_fn)(pid_t, const struct iovec *, unsigned long, const struct iovec *, unsigned long,
unsigned long);
static int in_wineserver(void) {
static int known = -1;
if (known < 0) {
char exe[512];
ssize_t n = readlink("/proc/self/exe", exe, sizeof exe - 1);
exe[n > 0 ? n : 0] = 0;
const char *name = strrchr(exe, '/');
known = !strcmp(name ? name + 1 : exe, "wineserver");
}
return known;
}
ssize_t process_vm_writev(pid_t pid, const struct iovec *local, unsigned long liovcnt, const struct iovec *remote,
unsigned long riovcnt, unsigned long flags) {
static vm_writev_fn real;
if (!real) real = (vm_writev_fn)dlsym(RTLD_NEXT, "process_vm_writev");
if (!real) {
errno = ENOSYS;
return -1;
}
ssize_t result = real(pid, local, liovcnt, remote, riovcnt, flags);
if (result >= 0 || errno != EFAULT || liovcnt != 1 || riovcnt != 1 || flags || !in_wineserver()) return result;
struct iovec ends[2];
struct iovec into = {ends, sizeof ends};
struct iovec from[2] = {{(void *)local, sizeof *local}, {(void *)remote, sizeof *remote}};
if (process_vm_readv(getpid(), &into, 1, from, 2, 0) != (ssize_t)sizeof ends || ends[0].iov_len != ends[1].iov_len) {
errno = EFAULT;
return -1;
}
char path[32];
snprintf(path, sizeof path, "/proc/%d/mem", (int)pid);
int fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0) {
errno = EFAULT;
return -1;
}
ssize_t written = pwrite(fd, ends[0].iov_base, ends[0].iov_len, (off_t)(uintptr_t)ends[1].iov_base);
close(fd);
if (written < 0) errno = EFAULT;
return written;
}