From 87c458a39a8cd23c9a6b5afc9a7b2ec57ebbc05d Mon Sep 17 00:00:00 2001 From: Wenqiang Wei Date: Tue, 4 Aug 2026 16:57:05 +0800 Subject: [PATCH] linux-user, fix: Limit private mmap shadows to SQLite Wine maps SQLite database files privately. The host-page fragment fallback copies data with pread into anonymous memory, so later writes through the file descriptor are not visible. WXWork can then reopen a document approval as a white view. The initial opt-in shadow path covered every writable file descriptor. On UOS this also shadowed writable PE cache files. WXWork could then fault in over_page_write during startup. Require MAP_PRIVATE and a writable descriptor whose first 16 bytes match the SQLite database header. Other writable files keep the normal fragment fallback, while SQLite fragments remain file-backed when LATX_PRIVATE_MMAP_SHADOW=1. Tests: - UOS WXWork 5.0.0.6008: launch and reopen one document approval - Linux and Wine SQLite regression: 10,000 mmap query/update iterations Signed-off-by: Wenqiang Wei --- linux-user/main.c | 8 +++++ linux-user/mmap.c | 42 ++++++++++++++++++++++++- target/i386/latx/include/latx-options.h | 2 ++ target/i386/latx/latx-options.c | 2 ++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/linux-user/main.c b/linux-user/main.c index 5e61dcee5c4..f54c96f0401 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -761,6 +761,11 @@ static void handle_arg_latx_monitor_shared_mem(const char *arg) option_monitor_shared_mem = strtol(arg, NULL, 0); } +static void handle_arg_latx_private_mmap_shadow(const char *arg) +{ + option_private_mmap_shadow = strtol(arg, NULL, 0); +} + static void handle_arg_smc_reload(const char *arg) { long value = 0; @@ -899,6 +904,9 @@ static const struct qemu_argument arg_table[] = { "", "enable get real self maps"}, {"latx-monitor-shared-mem", "LATX_MONITOR_SHARED_MEM", true, handle_arg_latx_monitor_shared_mem, "", "monitor shared memory, retranslate self modifying page"}, + {"latx-private-mmap-shadow", "LATX_PRIVATE_MMAP_SHADOW", true, + handle_arg_latx_private_mmap_shadow, "", + "shadow writable private SQLite file mapping fragments"}, {"smc_reload", "LATX_SMC_RELOAD", true, handle_arg_smc_reload, "", "reload SMC TB"}, {"latx-wine-pe-fixed-base", "LATX_WINE_PE_FIXED_BASE", true, diff --git a/linux-user/mmap.c b/linux-user/mmap.c index e66ad83b4a8..2505a8499f3 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -282,6 +282,35 @@ int target_mprotect(abi_ulong start, abi_ulong len, int target_prot) return ret; } +static bool fd_is_writable_sqlite_file(int fd) +{ + static const char sqlite_header[] = "SQLite format 3"; + char header[sizeof(sqlite_header)]; + int fd_flags, saved_errno; + ssize_t bytes_read; + bool result = false; + + saved_errno = errno; + if (fd < 0) { + goto out; + } + + fd_flags = fcntl(fd, F_GETFL); + if (fd_flags < 0 || (fd_flags & O_ACCMODE) == O_RDONLY) { + goto out; + } + + do { + bytes_read = pread(fd, header, sizeof(header), 0); + } while (bytes_read < 0 && errno == EINTR); + + result = bytes_read == sizeof(header) && + memcmp(header, sqlite_header, sizeof(header)) == 0; +out: + errno = saved_errno; + return result; +} + /* map an incomplete host page */ static int mmap_frag(abi_ulong real_start, abi_ulong start, abi_ulong end, @@ -290,6 +319,7 @@ static int mmap_frag(abi_ulong real_start, abi_ulong real_end, addr; void *host_start; int prot1, prot2, prot_new; + bool writable_sqlite_mapping; real_end = real_start + qemu_host_page_size; host_start = g2h_untagged(real_start); @@ -324,6 +354,12 @@ static int mmap_frag(abi_ulong real_start, } prot1 &= PAGE_BITS; + /* A pread-backed SQLite fragment cannot observe later writes via fd. */ + writable_sqlite_mapping = option_private_mmap_shadow && + !(flags & MAP_ANONYMOUS) && + (flags & MAP_TYPE) == MAP_PRIVATE && + fd_is_writable_sqlite_file(fd); + int shadow_mask = hostpage_exist_shadow_page(start); int page_mask = 0; for (addr = start; addr < end; addr += TARGET_PAGE_SIZE) { @@ -343,8 +379,12 @@ static int mmap_frag(abi_ulong real_start, prot_new = prot | prot1; if (!(flags & MAP_ANONYMOUS)) { - if ((flags & MAP_TYPE) == MAP_SHARED) { + if (writable_sqlite_mapping) { + create_shadow_page_chunk(start, end, prot, flags, fd, offset); + return 1; + } else if ((flags & MAP_TYPE) == MAP_SHARED) { struct stat stat_info; + fstat(fd, &stat_info); if (!stat_info.st_nlink || (prot & PROT_WRITE)) { diff --git a/target/i386/latx/include/latx-options.h b/target/i386/latx/include/latx-options.h index 98634f06065..e9a7021042e 100644 --- a/target/i386/latx/include/latx-options.h +++ b/target/i386/latx/include/latx-options.h @@ -91,6 +91,7 @@ extern uint64_t imm_skip_pc; extern int option_mem_test; extern int option_real_maps; extern int option_monitor_shared_mem; +extern int option_private_mmap_shadow; extern int option_shadow_file; extern int option_smc_opt; #define FORK_UNLINK_MAGIC 0x88888889 @@ -129,6 +130,7 @@ extern unsigned long long counter_mips_tr; ENVFUN(LATX_MT, handle_arg_latx_mem_test) \ ENVFUN(LATX_REAL_MAPS, handle_arg_latx_real_maps) \ ENVFUN(LATX_MONITOR_SHARED_MEM, handle_arg_latx_monitor_shared_mem) \ + ENVFUN(LATX_PRIVATE_MMAP_SHADOW, handle_arg_latx_private_mmap_shadow) \ ENVFUN(LATX_FORK_UNLINK, handle_arg_latx_fork_unlink) \ ENVFUN(LATX_ANONYM, handle_arg_latx_anonym) \ ENVFUN(LATX_MMAP_START, handle_arg_mmap_start) \ diff --git a/target/i386/latx/latx-options.c b/target/i386/latx/latx-options.c index 87c18c335e0..d569632291b 100644 --- a/target/i386/latx/latx-options.c +++ b/target/i386/latx/latx-options.c @@ -95,6 +95,7 @@ int option_anonym; int option_mem_test; int option_real_maps; int option_monitor_shared_mem; +int option_private_mmap_shadow; int option_shadow_file; int option_smc_opt; /* Disabled by default; LATX_FORK_UNLINK enables the internal signal. */ @@ -254,6 +255,7 @@ void options_init(void) option_mem_test = 0; option_real_maps = 0; option_monitor_shared_mem = 0; + option_private_mmap_shadow = 0; #ifdef LOW_MEM_MODE_0 option_shadow_file = 1; #endif