-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
enable Control Flow Guard #49
Comments
Thanks for the heads up that cfguard is getting ready for use. After enabling the necessary options, it worked with llvm-mingw out of the box. Then I bumped LLVM to 16 on Debian, and it also successfully created the binaries (meaning no build-time errors).
This is because the mingw-w64 package is built without the I'm hesitating making llvm-mingw the default for x64 (and x86), because most users expect curl-for-win libs to work with the llvm + mingw-w64 coming with the popular package managers. I also want the keep curl-for-win itself run correctly with toolchains offered via package managers. Test build here: |
Enabled cfguard for ARM64 in live builds: Binaries: |
The PS script didn't work for me ( The docs say that this isn't enough and some other bits need to be set in the "load config" structure. This is what's missing in builds with the warning mentioned above. I don't have a suitable Windows system to make tests with Windows Defender though. |
Here are the x64 and x86 binaries with cfguard and the mentioned warning (meaning it's most probably inactive, but it'd be interesting to confirm it on a real system): And these all built with llvm-mingw + cfguard: Debian-testing, macOS+Homebrew and MSYS2 are all well-updated distros, but they rely on GCC + binutils, which is slow to pick-up with Windows features (ARM, UCRT, cfguard). |
@arm64-v9a Thanks for your tests. Could you try running the x64 This is an attempt to enable cfguard for the static libs, while keeping it disabled in |
Thank you! Based on this it seems that cfguard is an all-or-nothing setting, even a cfguard-compiled object triggers the OS, in an executable with unset CFGUARD flag. It means there is no way to ship cfguard-enabled objects and let the linker decide to enable it or not. [ Also CMake doesn't offer a way to pass an option to the C compiler only, without passing it to the linker, too. This is a problem because the compiler and linker flags have the same name ( Therefore, enabling cfguard forces all curl-for-win static lib consumers to have to enable it for all the their other objects and their executables, while also switching to llvm-mingw. This may work with ARM64 a little better because it required llvm-mingw already. For x64/x86 it reduces choices at the moment. Hard to say if this inflexibility can be improved by the tooling or something inherent to the design of cfguard. [UPDATE: I'd guess the latter.] I had expected that including a single cfguard-compiled object won't break a final executable, unless cfguard was explicitly enabled at link-time. |
One thing I find odd that this feature seems entirely the function of objects having cfguard enabled, yet it also requires a link-time option to set the GUARD_CF flag in executables. Either I'm missing something or this flag "should be" unnecessary. To summarize where we are now:
|
Hi, I just noticed this issue by chance and thought I should comment. Unfortunately some context has been lost because it seems most replies by the OP has been deleted, so I don't have the full picture.
As far as I remember this is not true. You should be able to compile objects with You can check with the test case in llvm-mingw: > clang -mguard=cf cfguard-test.c -c -o cfguard-test.o
> clang cfguard-test.o -o cfguard-test.exe
> llvm-readobj --coff-load-config cfguard-test.exe
[...]
GuardFlags [ (0x0)
]
[,,,[
> cfguard-test check_enabled
Control Flow Guard is _not_ enabled!
> cfguard-test normal_icall
Performing normal indirect call.
Normal function called.
> cfguard-test invalid_icall
Performing invalid indirect call. If CFG is enabled this should crash with exit code 0xc0000409 (-1073740791)...
Pwned!!!
I have not tried it myself, but the docs for |
It's possible I got it wrong, but after a considerable amount of time spent on testing, I positively could not make it work that way, meaning: by enabling CFG at compile-time, not enabling it at link-time, yet getting a runnable binary. (I don't have a Windows machine capable of testing this, so this involved feedback from others, which feedback is apparently now missing from this thread's history.) I'd be happy to be proven wrong as it'd allow switching to llvm-mingw and enabling this without forcing everyone downstream doing the same. [ Well, it'd still need some convincing to jump to llvm-mingw and drop the standard toolchains shipping via package managers. ] |
Here's a llvm-mingw build with CFLAGS-only: Please report which one is running or failing to run on old and new Windows versions, |
Tested on my x64 Windows 10 system, both win32 and win64 builds run fine here (can perform an https request). The CFLAGS-only ones don't have CFG enabled, while the CFLAGS+LDFLAGS ones do have CFG enabled (checked in Process Explorer), which matches my expectations. I don't have an aarch64 Windows system, nor do I have any older Intel Windows systems/VMs, but I can try to ask someone else to help. |
Both of the aarch64 builds do run just fine; I don't have Process Explorer available right now to check that CFG is enabled though. |
@alvinhochun: Thanks for your tests! This is different from previous tests, but it's possible there was an error along the way before. Both x64 binaries also run fine on a Win7 system. This tells that we could publish CFG-enabled static libs and those could still be used with non-llvm-mingw toolchains, though this might need actually testing this to confirm. |
Using this script I built an example program with clang, gcc and llvm-mingw (CFG and non-CFG) and the resulting 4 .exe also built fine and then run fine on Windows 7: #!/bin/sh
opt='-static -s -DCURL_STATICLIB connect-to.c -I../include -L.
-lbrotlicommon -lbrotlidec -lcrypto -lssl -lnghttp2 -lnghttp3 -lngtcp2 -lngtcp2_crypto_quictls
-lssh2 -lz -lzstd -lcurl -lws2_32 -lbcrypt -lcrypt32 -lwldap32'
/usr/local/opt/llvm/bin/clang -fuse-ld=lld \
-target x86_64-w64-mingw32 --sysroot /usr/local/opt/mingw-w64/toolchain-x86_64 \
-D_UCRT -lucrt ${opt} -o curltest-clang
x86_64-w64-mingw32-gcc -dumpspecs | sed 's/-lmsvcrt/-lucrt/g' > _gcc-specs-ucrt
x86_64-w64-mingw32-gcc -specs=_gcc-specs-ucrt \
-D_UCRT -lucrt -Wl,--start-group ${opt} -Wl,--end-group -o curltest-gcc
rm -f _gcc-specs-ucrt
~/llvm-mingw/bin/x86_64-w64-mingw32-clang \
${opt} -o curltest-llvm-mingw
~/llvm-mingw/bin/x86_64-w64-mingw32-clang \
${opt} -o curltest-llvm-mingw-cfg -mguard=cf
~/llvm-mingw/bin/llvm-readobj --coff-load-config ./*.exe |
One thing to note, objects compiled with CFG (if they make indirect calls) do need to reference the symbol |
Good point. I did these tests with mingw-w64 v11. In general I'm okay requiring a fresh mingw-w64, but in this case v11 is missing from the latest stable Debian/Ubuntu for example, which might be a problem for some: 🚫 https://packages.debian.org/bookworm/mingw-w64-common (since testing/trixie) https://repology.org/project/mingw-w64/versions Probably more users would use MSYS2, which always has the latest mingw-w64. Though I have no insight how (and how many) people use curl's static libs. |
Another possibly interesting bit is ASM compatibility with CFG. It is reported that ASM does not work with CFG, to which OpenSSL maintainers suggest to disable ASM for CFG builds. This should already affect curl-for-win ARM64 Windows builds, having both CFG and ASM enabled. It'd be nice to see any feedback on that binary. I haven't had a chance to ever run those myself, nor do they run in any CI. This might also affect LibreSSL x64 builds in the future. |
This issue is too vague to tell anything. openssl/openssl#1592 (comment) does give an insight into what may be happening. In particular, the CFG check fails with a pointer inside the table (Typically when C/C++ code references a function defined in assembly it would be declared as an extern function and have its address taken in C/C++ code. Any address-taken functions are marked as valid call targets, which normally would be enough to make any function pointers to assembly functions work with CFG.) One way to fix this would be to construct There isn't really a good way to find all these cases other than to run a test suite that tests all code paths calling assembly functions. I suppose one can also look for all indirect call sites (i.e. find references to |
If the issue is limited to uplink (or 'applink'), curl-for-win is not affected because it builds OpenSSL with An interesting tidbit about applink: It was a feature that was causing continuous headaches for many years downstream. While discussing this in 2016 with the OpenSSL team, it turned out that it was not working before 1.1.0 due to a local bug in the assembly code. This received a fix shortly after. This was a good opportunity IMO to switch to a different solution or to drop it completely, but the feature is still there and continues causing headaches. |
The only way this will resolve is when upstream packages enable CFG support in places necessary (mingw-w64 support, with this option enabled). On part of curl-for-win this will require an update to start using it at the right moment, or possibly implement detecting such moment, though this doesn't seem trivial or worthy the effort. Probably worth revisiting when Debian Trixie is release. Will convert this to a TODO and close. |
I don't think depending on Debian here is sensible. To support CFG, mingw-w64 has to be built with Clang+LLD while Debian and most of the other Linux distributions use GCC+Binutils. |
What are our other options? |
The fundamental issue is that CFG-enabled libs cannot be used with non-CFG-enabled toolchains. We offer an option to force using llvm-mingw for all CPU targets (by default it's used for ARM64 only). If enabled, CFG is also enabled. To offer CFG out of the box for Intel, there are these options:
It would be useful to know which if any distro mingw toolchains offer CFG compatibility. If there are other options, or there is a logic mistake in the above, let me know. |
Sorry, forgot to respond.
I doubt this is doable since GCC lacks this feature, and building Debian's mingw-w64 package with Clang would make it incompatible with other GCC-based toolchains because of the other missing features in GCC like native TLS. |
Multiple binaries would make things proportionally more complex and resource intensive, just to offer a hard-to-explain choice on the download page. It seems like a case where the solution is in the hands of toolchain authors, by either making this option more flexible or making sure it gets supported and deployed everywhere. Till then, the solution we can offer is to do a custom build with |
You can link CFG-enabled object files with non-CFG objects and it will still work fine, as long as the mingw-w64 crt is new enough (mingw-w64 v11) to include the dummy |
@alvinhochun That's where I'm heading to, but waiting for the last major distro (Debian stable) to catch up with its mingw-w64. It's at 10, whch doesn't have support. Or am I missing something? |
Since LLVM 16: Support for mstorsjo/llvm-mingw#301 (-mguard=cf compile and link flags)
May need to migrate x86_64 build to llvm-mingw
The text was updated successfully, but these errors were encountered: