On 13.02.2025 12:01, LIU Hao wrote:
在 2025-02-13 18:17, Jacek Caban 写道:
This isn't entirely true for other linkers. While those linkers do place .idata sections, including the IAT itself, in .rdata, they don’t use .idata sections for delay imports. Instead, they implement delay loading within the linker itself rather than relying on special delay import libraries, placing the delay IAT in the .data section.

I’ve seen a blog claim that MSVC makes them read-only, but in my testing with modern MSVC, that doesn’t appear to be the case, perhaps they realized it wasn’t a good fit and reverted the change? I’m not sure.

MSVC does make IAT read-only, as in this example: https://gcc.godbolt.org/z/Msfqhs7x3


Your example shows the regular IAT, which should indeed be read-only. That's not the case for delay load IAT.



Also my patch for binutils took LLD as a reference, as well as MSVC.


Again, that's not the case for delay load IAT, see DelayLoadContents::getDataChunks() in LLD (IAT is stored in addresses vector).



I say it’s not a good fit because changing permissions like that is generally a bad idea. Another thread might be executing code that relies on data in the same page remaining read-only or might be performing similar permission changes for other reasons. Taking a lock only protects against concurrent __delayLoadHelper2 calls.

It's actually a known issue, also referenced in the article in The Old New Thing, quoted in the commit message.

It also explains why a static lock is necessary here: The unprotect operation and write operation must happen atomically, so another thread will not make the IAT read-only in between.


Some code out of our control may do a similar unprotect+protect dance for another reason.



If you have Visual Studio installed, you can also see that Microsoft code does similar stuff in 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.43.34808\include\delayhlp.cpp'; however their code checks for image load config, which binutils does not support at the moment.


I can't look at that, if I did I wouldn't be allowed to work on relevant Wine code, we're pretty strict about that for good reasons.


Wine provides its own __delayLoadHelper2 implementation, which simply forwards the call to the public ResolveDelayLoadedAPI function. I’m attaching a hack that does the same for mingw-w64 (since it’s a Windows 8 API, it’s not something mingw-w64 could use by default). ResolveDelayLoadedAPI doesn’t seem to perform the protection change. at least in this case, though maybe there’s a way to trigger it? Either way, the test case crashes. The only workaround would be modifying Wine’s __delayLoadHelper2 to duplicate ResolveDelayLoadedAPI inside a static library and add the necessary permission changes.

A writeable IAT is a conspicuous target for code injection.

If MSVC makes it read-only, LLD makes it read-only, and there have been few issues with MSVC in practice (see the Old New Thing article), then there's no reason for us to not do that; do you agree?


It makes a lot of sense for regular IATs. Delay load IATs are a separate thing, both on MSVC and LLD.



And MSYS2 have also updated their binutils since about a week ago, and there do not seem to be issues, either.


I guess none of its binaries use delay load functionality. That's not really surprising, while we have support for generating them in mingw-w64-crt, we skip them in make install, so using them is rather tricky.



That’s unfortunate. If anyone has a better idea, please let me know. Ideally, I’d like to restore the previous behavior for delay load imports. Wine doesn’t use dlltool to create delay load import libraries; it generates them itself. In theory, this gives Wine more flexibility, but I haven’t found a way to make it work.


  SetEntryHookBypass:
+  while(InterlockedExchange(&gDelayLoadLock, 1) != 0)
+    Sleep(100);


You could just use a critical section.

The reason is that a critical section can't be initialized statically. This lock is only taken once for each symbol, so I suspect we'd better not introduce extra complexity.


static CRITICAL_SECTION cs = { .LockCount = -1 };

should do the trick.


Thanks,

Jacek



_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to