Companion to willfaust/Madeira#248 (https://github.com/willfaust/Madeira/pull/248), whose RWX-memory commits route kernel and wineserver writes into PAGE_EXECUTE_READWRITE memory through its RW alias. The two should land together; Madeira's wine pin can be bumped afterwards.
Problem
On iOS, Madeira maps anonymous PAGE_EXECUTE_READWRITE memory as an RX view with a separate RW alias (W^X). Stores into it from user space are redirected to the alias by Madeira's fault handler. The kernel cannot write such a buffer, so a system call that fills it fails with EFAULT. Three paths in this tree let the kernel write straight into a caller's buffer, and each misbehaves when that buffer is RWX memory, where Windows succeeds:
do_getsockopt(the AFD getsockopt ioctls) and theIP_DONTFRAG/IPV6_DONTFRAGqueries return WSAEFAULT.read_dev_urandom(used byNtQuerySystemInformation(SystemInterruptInformation), among others) reports success with the buffer left unfilled.NtReadFileScatterreturns STATUS_END_OF_FILE (ERROR_HANDLE_EOF) without data. The EFAULT frompread()is overwritten because nothing was read.
Cause
getsockopt(), read() and pread() are called with the caller's buffer as the destination, so the kernel does the write.
Change
do_getsockopt,IP_DONTFRAG,IPV6_DONTFRAG:getsockopt()fills a local copy, which is then copied into the caller's buffer.- The local copy is 256 bytes on the stack, or malloc'd for larger sizes.
- The DONTFRAG queries use a DWORD-sized local and the same length cap as before.
read_dev_urandom: reads 256-byte chunks and copies each one. A short read is now completed instead of leaving the rest of the buffer unfilled.NtReadFileScatter: reads throughvirtual_locked_pread/virtual_locked_read, asNtReadFiledoes. Madeira's versions of those fill RWX memory through the RW alias (companion PR).
Other buffers are unaffected, apart from the completed short read in read_dev_urandom.
Evidence
These results come from our downstream builds (Mac Catalyst and iPad), not from this branch. The three functions are byte-identical at 257f271c and in those builds before this change.
The test program is a small Windows x64 program. It calls getsockopt(SO_KEEPALIVE) on a loopback socket, NtQuerySystemInformation(SystemInterruptInformation) (48 bytes) and ReadFileScatter (one page, unbuffered file). The destination is heap, stack, image .data, an RWX image section, and four places in a 64 KB VirtualAlloc(PAGE_EXECUTE_READWRITE) region.
- Before (with the Madeira half already applied), in every anonymous-RWX case:
- getsockopt failed with WSAEFAULT (10014).
- SystemInterruptInformation returned success with 0 of 48 bytes written.
- ReadFileScatter failed with ERROR_HANDLE_EOF (38) and 0 bytes.
- After, in the same cases on Mac and iPad:
- getsockopt succeeds.
- The interrupt information is filled. The check counts bytes changed from a fill pattern, so 47 of 48 happens by chance with random data.
- ReadFileScatter reads the full page and the data matches.
- The heap, stack and image cases are unchanged.
Validation
What I ran on this branch:
clang -fsyntax-onlywith Madeira'sbuild/ntdll-unix/build.shflags (iPhoneOS 27.0 SDK, arm64), which is how Madeira compiles these files, forfile.c,socket.candsystem.c. The diagnostics are identical before and after:file.c: 2 pre-existing warnings.socket.c: 4 pre-existing warnings.system.c: none. It was compiled with an extra include path so that its../../../../build/madeira_cfg.hresolves to a Madeira checkout.
- Both
IP_DONTFRAGandIPV6_DONTFRAGare defined in the iOS SDK, so the changed branches are compiled. I confirmed this with the preprocessor. - Not run: this branch on a device, a full Wine or Madeira build, and any test that exercises the copies on the host. The redirect into the alias only exists in Madeira's iOS fault handler.
Notes / risks
- Land together with the Madeira PR.
NtReadFileScatteronly gains anything with the Madeira half. Without it,virtual_locked_pread/readbehave like plainpread/readfor RWX memory, giving the same STATUS_END_OF_FILE as today.- The getsockopt and
/dev/urandomchanges need only the store emulator already in Madeiramain. - Neither PR breaks the build without the other. Madeira's
winesubmodule pin needs a bump after this merges.
- Invalid buffer pointer (not tested). Before, the kernel returned EFAULT (WSAEFAULT for getsockopt; ignored in
read_dev_urandom). Now the copy faults in ntdll's unix code and goes through the signal handler's in-syscall fault path (handle_syscall_fault, which returns the exception status from the syscall). - Not offered to WineHQ. The motivation is Madeira's W^X mapping. The
NtReadFileScatterchange alone would also make it handle write-watched buffers asNtReadFiledoes, which could be offered upstream separately. - Out of scope. getsockopt(SO_KEEPALIVE) returns the BSD flag value (8) with length 1 rather than TRUE, on every buffer kind. This is a separate Wine-on-macOS difference.
Licence, DCO, AI assistance
- Licence. Contributed under LGPL-2.1-or-later, the licence of this branch and of upstream Wine, per
CONTRIBUTING.md. Upstream notices are unchanged. Recording the contribution inLICENSE-MADEIRA.mdis left to the maintainer, as with earlier pull requests. - DCO. The submitter certifies the Developer Certificate of Origin and signs off the commit before the PR is opened (for example
git commit --amend --signoff). The commit was prepared without aSigned-off-byline, so that the sign-off is the submitter's own. - AI assistance. This PR was prepared with AI assistance. An AI coding assistant ported the downstream commit and drafted this description.
Conflicts with madeira-lgpl now; could you rebase?