-
Notifications
You must be signed in to change notification settings - Fork 38.7k
scripts: add MACHO lazy bindings check to security-check.py #18295
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
scripts: add MACHO lazy bindings check to security-check.py #18295
Conversation
|
@fanquake and me had some discussion about this on IRC. The conclusion was that it's probably better to ignore the Also, it would make sense to get Apple to clarify the documentation on this. It happens more often that header bits simply go unused in new versions in favor of new mechanisms, but it would have avoided a lot of hassle if this had been properly documented. |
I've reached out to a few people at Apple. So hopefully we'll get some clarification. |
|
Great! |
|
Received this reply from Nick Kledzik (ld / dyld at Apple):
# regular build (has lazy binding)
[/tmp]> xcrun -sdk macosx.internal clang main.c
[/tmp]> xcrun dyldinfo -lazy_bind -bind a.out
bind information:
segment section address type addend dylib symbol
__DATA_CONST __got 0x100001000 pointer 0 libSystem dyld_stub_binder
lazy binding information (from lazy_bind part of dyld info):
segment section address index dylib symbol
__DATA __la_symbol_ptr 0x100002000 0x0000 libSystem _printf# -bind_at_load build (no lazy binding)
[/tmp]> xcrun -sdk macosx.internal clang main.c -bind_at_load
[/tmp]> xcrun dyldinfo -lazy_bind -bind a.out
bind information:
segment section address type addend dylib symbol
__DATA __la_symbol_ptr 0x100002000 pointer 0 libSystem _printf
__DATA_CONST __got 0x100001000 pointer 0 libSystem dyld_stub_binder
no compressed lazy binding info |
|
Thanks for asking for clarification. So from what I understand essentially this means that there is no point in forcing I think this means we don't need this and can close it? |
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
|
🐙 This pull request conflicts with the target branch and needs rebase. |
I've asked Cory to follow up here now that we have some more information. I think even if we didn't want to use |
e72f8da to
5884bcd
Compare
|
@fanquake Thanks for pinging someone at Apple! I think I understand now, and that makes sense. I wasn't aware of the old faux-static linkage, but the flag makes sense in that context. Sounds like it produced the same behavior that a normal link and DYLD_BIND_AT_LAUNCH at runtime would now. That confirms (to me, anyway) that we're doing the right (extra paranoid) thing by turning on bind_at_load and sacrificing startup time to avoid stub binding that obfuscates what's happening at runtime, which proved to be a real issue recently: bitcoin-core/secp256k1#674 (comment) It also explains why the dyloader doesn't bother checking for the flag at runtime, so there's no need for us to set the bit. It also confirms that your thoughts on checks in #17686 were spot on:
So, let's go for that instead, unless it's already been merged as part of another PR? Kudos to @fanquake for looking so deeply into this, this was some really great detective work. |
5884bcd to
153dba1
Compare
153dba1 to
5ca90f8
Compare
Thanks for following up. I've added a different test to security-check.py, which checks the |
|
ACK 5ca90f8 |
Gitian builds
|
…ty-check.py 5ca90f8 scripts: add MACHO lazy bindings check to security-check.py (fanquake) Pull request description: This is a slightly belated follow up to bitcoin#17686 and some discussion with Cory. It's not entirely clear if we should make this change due to the way the macOS dynamic loader appears to work. However I'm opening this for some discussion. Also related to bitcoin#17768. #### Issue: [`LD64`](https://opensource.apple.com/source/ld64/) doesn't set the [MH_BINDATLOAD](https://opensource.apple.com/source/xnu/xnu-6153.11.26/EXTERNAL_HEADERS/mach-o/loader.h.auto.html) bit in the header of MACHO executables, when building with `-bind_at_load`. This is in contradiction to the [documentation](https://opensource.apple.com/source/ld64/ld64-450.3/doc/man/man1/ld.1.auto.html): ```bash -bind_at_load Sets a bit in the mach header of the resulting binary which tells dyld to bind all symbols when the binary is loaded, rather than lazily. ``` The [`ld` in Apples cctools](https://opensource.apple.com/source/cctools/cctools-927.0.2/ld/layout.c.auto.html) does set the bit, however the [cctools-port](https://github.com/tpoechtrager/cctools-port/) that we use for release builds, bundles `LD64`. However; even if the linker hasn't set that bit, the dynamic loader ([`dyld`](https://opensource.apple.com/source/dyld/)) doesn't seem to ever check for it, and from what I understand, it looks at a different part of the header when determining whether to lazily load symbols. Note that our release binaries are currently working as expected, and no lazy loading occurs. #### Example: Using a small program, we can observe the behaviour of the dynamic loader. Conducted using: ```bash clang++ --version Apple clang version 11.0.0 (clang-1100.0.33.17) Target: x86_64-apple-darwin18.7.0 ld -v @(#)PROGRAM:ld PROJECT:ld64-530 BUILD 18:57:17 Dec 13 2019 LTO support using: LLVM version 11.0.0, (clang-1100.0.33.17) (static support for 23, runtime is 23) TAPI support using: Apple TAPI version 11.0.0 (tapi-1100.0.11) ``` ```cpp #include <iostream> int main() { std::cout << "Hello World!\n"; return 0; } ``` Compile and check the MACHO header: ```bash clang++ test.cpp -o test otool -vh test ... Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL LIB64 EXECUTE 16 1424 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE # Run and dump dynamic loader bindings: DYLD_PRINT_BINDINGS=1 DYLD_PRINT_TO_FILE=no_bind.txt ./test Hello World! ``` Recompile with `-bind_at_load`. Note still no `BINDATLOAD` flag: ```bash clang++ test.cpp -o test -Wl,-bind_at_load otool -vh test Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC_64 X86_64 ALL LIB64 EXECUTE 16 1424 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE ... DYLD_PRINT_BINDINGS=1 DYLD_PRINT_TO_FILE=bind.txt ./test Hello World! ``` If we diff the outputs, you can see that `dyld` doesn't perform any lazy bindings when the binary is compiled with `-bind_at_load`, even if the `BINDATLOAD` flag is not set: ```diff @@ -1,11 +1,27 @@ +dyld: bind: test:0x103EDF030 = libc++.1.dylib:__ZNKSt3__16locale9use_facetERNS0_2idE, *0x103EDF030 = 0x7FFF70C9FA58 +dyld: bind: test:0x103EDF038 = libc++.1.dylib:__ZNKSt3__18ios_base6getlocEv, *0x103EDF038 = 0x7FFF70CA12C2 +dyld: bind: test:0x103EDF068 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_, *0x103EDF068 = 0x7FFF70CA12B6 +dyld: bind: test:0x103EDF070 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev, *0x103EDF070 = 0x7FFF70CA1528 +dyld: bind: test:0x103EDF080 = libc++.1.dylib:__ZNSt3__16localeD1Ev, *0x103EDF080 = 0x7FFF70C9FAE6 <trim> -dyld: lazy bind: test:0x10D4AC0C8 = libsystem_platform.dylib:_strlen, *0x10D4AC0C8 = 0x7FFF73C5C6E0 -dyld: lazy bind: test:0x10D4AC068 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_, *0x10D4AC068 = 0x7FFF70CA12B6 -dyld: lazy bind: test:0x10D4AC038 = libc++.1.dylib:__ZNKSt3__18ios_base6getlocEv, *0x10D4AC038 = 0x7FFF70CA12C2 -dyld: lazy bind: test:0x10D4AC030 = libc++.1.dylib:__ZNKSt3__16locale9use_facetERNS0_2idE, *0x10D4AC030 = 0x7FFF70C9FA58 -dyld: lazy bind: test:0x10D4AC080 = libc++.1.dylib:__ZNSt3__16localeD1Ev, *0x10D4AC080 = 0x7FFF70C9FAE6 -dyld: lazy bind: test:0x10D4AC070 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev, *0x10D4AC070 = 0x7FFF70CA1528 ``` Note: `dyld` also has a `DYLD_BIND_AT_LAUNCH=1` environment variable, that when set, will force any lazy bindings to be non-lazy: ```bash dyld: forced lazy bind: test:0x10BEC8068 = libc++.1.dylib:__ZNSt3__113basic_ostream ``` #### Thoughts: After looking at the dyld source, I can't find any checks for `MH_BINDATLOAD`. You can see the flags it does check for, such as MH_PIE or MH_BIND_TO_WEAK [here](https://opensource.apple.com/source/dyld/dyld-732.8/src/ImageLoaderMachO.cpp.auto.html). It seems that the lazy binding of any symbols depends on whether or not [lazy_bind_size](https://opensource.apple.com/source/xnu/xnu-6153.11.26/EXTERNAL_HEADERS/mach-o/loader.h.auto.html) from the `LC_DYLD_INFO_ONLY` load command is > 0. Which was mentioned in [bitcoin#17686](bitcoin#17686 (comment)). #### Changes: This PR is one of [Corys commits](theuni@7b6ba26), that I've rebased and modified to make build. I've also included an addition to the `security-check.py` script to check for the flag. However, given the above, I'm not entirely sure this patch is the correct approach. If the linker no-longer inserts it, and the dynamic loader doesn't look for it, there might be little benefit to setting it. Or, maybe this is an oversight from Apple and needs some upstream discussion. Looking for some thoughts / Concept ACK/NACK. One alternate approach we could take is to drop the patch and modify security-check.py to look for `lazy_bind_size` == 0 in the `LC_DYLD_INFO_ONLY` load command, using `otool -l`. ACKs for top commit: theuni: ACK 5ca90f8 Tree-SHA512: 444022ea9d19ed74dd06dc2ab3857a9c23fbc2f6475364e8552d761b712d684b3a7114d144f20de42328d1a99403b48667ba96885121392affb2e05b834b6e1c
I didn't add the relevant test in bitcoin#18295.
8334ee3 scripts: add MACHO LAZY_BINDINGS test to test-security-check.py (fanquake) 7b99c74 scripts: add MACHO Canary check to security-check.py (fanquake) Pull request description: 7b99c74 uses `otool -Iv` to check for `___stack_chk_fail` in the macOS binaries. Similar to the [ELF check](https://github.com/bitcoin/bitcoin/blob/master/contrib/devtools/security-check.py#L105). Note that looking for a triple underscore prefixed function (as opposed to two for ELF) is correct for the macOS binaries. i.e: ```bash otool -Iv bitcoind | grep chk 0x00000001006715b8 509 ___memcpy_chk 0x00000001006715be 510 ___snprintf_chk 0x00000001006715c4 511 ___sprintf_chk 0x00000001006715ca 512 ___stack_chk_fail 0x00000001006715d6 517 ___vsnprintf_chk 0x0000000100787898 513 ___stack_chk_guard ``` 8334ee3 is a follow up to #18295 and adds test cases to `test-security-check.py` that for some reason I didn't add at the time. I'll sort out #18434 so that we can run these tests in the CI. ACKs for top commit: practicalswift: ACK 8334ee3: Mitigations are important. Important things are worth asserting :) jonasschnelli: utACK 8334ee3. Tree-SHA512: 1aa5ded34bbd187eddb112b27278deb328bfc21ac82316b20fab6ad894f223b239a76b53dab0ac1770d194c1760fcc40d4da91ec09959ba4fc8eadedb173936a
I didn't add the relevant test in bitcoin#18295.
This is a slightly belated follow up to #17686 and some discussion with Cory. It's not entirely clear if we should make this change due to the way the macOS dynamic loader appears to work. However I'm opening this for some discussion. Also related to #17768.
Issue:
LD64doesn't set the MH_BINDATLOAD bit in the header of MACHO executables, when building with-bind_at_load. This is in contradiction to the documentation:-bind_at_load Sets a bit in the mach header of the resulting binary which tells dyld to bind all symbols when the binary is loaded, rather than lazily.The
ldin Apples cctools does set the bit, however the cctools-port that we use for release builds, bundlesLD64.However; even if the linker hasn't set that bit, the dynamic loader (
dyld) doesn't seem to ever check for it, and from what I understand, it looks at a different part of the header when determining whether to lazily load symbols.Note that our release binaries are currently working as expected, and no lazy loading occurs.
Example:
Using a small program, we can observe the behaviour of the dynamic loader.
Conducted using:
Compile and check the MACHO header:
Recompile with
-bind_at_load. Note still noBINDATLOADflag:If we diff the outputs, you can see that
dylddoesn't perform any lazy bindings when the binary is compiled with-bind_at_load, even if theBINDATLOADflag is not set:Note:
dyldalso has aDYLD_BIND_AT_LAUNCH=1environment variable, that when set, will force any lazy bindings to be non-lazy:Thoughts:
After looking at the dyld source, I can't find any checks for
MH_BINDATLOAD. You can see the flags it does check for, such as MH_PIE or MH_BIND_TO_WEAK here.It seems that the lazy binding of any symbols depends on whether or not lazy_bind_size from the
LC_DYLD_INFO_ONLYload command is > 0. Which was mentioned in #17686.Changes:
This PR is one of Corys commits, that I've rebased and modified to make build. I've also included an addition to the
security-check.pyscript to check for the flag.However, given the above, I'm not entirely sure this patch is the correct approach. If the linker no-longer inserts it, and the dynamic loader doesn't look for it, there might be little benefit to setting it. Or, maybe this is an oversight from Apple and needs some upstream discussion. Looking for some thoughts / Concept ACK/NACK.
One alternate approach we could take is to drop the patch and modify security-check.py to look for
lazy_bind_size== 0 in theLC_DYLD_INFO_ONLYload command, usingotool -l.