scripts: add MACHO lazy bindings check to security-check.py #18295

pull fanquake wants to merge 1 commits into bitcoin:master from fanquake:does_noone_care_about_MH_BINDATLOAD_anymore changing 1 files +19 −1
  1. fanquake commented at 7:03 am on March 8, 2020: member

    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:

    LD64 doesn’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:

    0-bind_at_load
    1     Sets a bit in the mach header of the resulting binary which tells dyld to
    2     bind all symbols when the binary is loaded, rather than lazily.
    

    The ld in Apples cctools does set the bit, however the cctools-port that we use for release builds, bundles LD64.

    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:

    0clang++ --version
    1Apple clang version 11.0.0 (clang-1100.0.33.17)
    2Target: x86_64-apple-darwin18.7.0
    3
    4ld -v
    5@(#)PROGRAM:ld  PROJECT:ld64-530
    6BUILD 18:57:17 Dec 13 2019
    7LTO support using: LLVM version 11.0.0, (clang-1100.0.33.17) (static support for 23, runtime is 23)
    8TAPI support using: Apple TAPI version 11.0.0 (tapi-1100.0.11)
    
    0#include <iostream>
    1int main() {
    2	std::cout << "Hello World!\n";
    3	return 0;
    4}
    

    Compile and check the MACHO header:

    0clang++ test.cpp -o test
    1otool -vh test
    2...
    3Mach header
    4      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
    5MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    16       1424   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE
    6
    7# Run and dump dynamic loader bindings:
    8DYLD_PRINT_BINDINGS=1 DYLD_PRINT_TO_FILE=no_bind.txt ./test
    9Hello World!
    

    Recompile with -bind_at_load. Note still no BINDATLOAD flag:

    0clang++ test.cpp -o test -Wl,-bind_at_load
    1otool -vh test
    2Mach header
    3      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
    4MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    16       1424   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE
    5...
    6DYLD_PRINT_BINDINGS=1 DYLD_PRINT_TO_FILE=bind.txt ./test
    7Hello 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:

     0@@ -1,11 +1,27 @@
     1+dyld: bind: test:0x103EDF030 = libc++.1.dylib:__ZNKSt3__16locale9use_facetERNS0_2idE, *0x103EDF030 = 0x7FFF70C9FA58
     2+dyld: bind: test:0x103EDF038 = libc++.1.dylib:__ZNKSt3__18ios_base6getlocEv, *0x103EDF038 = 0x7FFF70CA12C2
     3+dyld: bind: test:0x103EDF068 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_, *0x103EDF068 = 0x7FFF70CA12B6
     4+dyld: bind: test:0x103EDF070 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev, *0x103EDF070 = 0x7FFF70CA1528
     5+dyld: bind: test:0x103EDF080 = libc++.1.dylib:__ZNSt3__16localeD1Ev, *0x103EDF080 = 0x7FFF70C9FAE6
     6<trim>
     7-dyld: lazy bind: test:0x10D4AC0C8 = libsystem_platform.dylib:_strlen, *0x10D4AC0C8 = 0x7FFF73C5C6E0
     8-dyld: lazy bind: test:0x10D4AC068 = libc++.1.dylib:__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_, *0x10D4AC068 = 0x7FFF70CA12B6
     9-dyld: lazy bind: test:0x10D4AC038 = libc++.1.dylib:__ZNKSt3__18ios_base6getlocEv, *0x10D4AC038 = 0x7FFF70CA12C2
    10-dyld: lazy bind: test:0x10D4AC030 = libc++.1.dylib:__ZNKSt3__16locale9use_facetERNS0_2idE, *0x10D4AC030 = 0x7FFF70C9FA58
    11-dyld: lazy bind: test:0x10D4AC080 = libc++.1.dylib:__ZNSt3__16localeD1Ev, *0x10D4AC080 = 0x7FFF70C9FAE6
    12-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:

    0dyld: 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.

    It seems that the lazy binding of any symbols depends on whether or not lazy_bind_size from the LC_DYLD_INFO_ONLY load 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.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.

  2. fanquake added the label macOS on Mar 8, 2020
  3. fanquake added the label Build system on Mar 8, 2020
  4. fanquake added the label Needs gitian build on Mar 8, 2020
  5. fanquake added the label Needs Conceptual Review on Mar 8, 2020
  6. DrahtBot removed the label Needs gitian build on Mar 8, 2020
  7. MarcoFalke added the label Needs gitian build on Mar 8, 2020
  8. DrahtBot removed the label Needs gitian build on Mar 8, 2020
  9. MarcoFalke added the label Needs gitian build on Mar 8, 2020
  10. fanquake deleted a comment on Mar 9, 2020
  11. fanquake deleted a comment on Mar 9, 2020
  12. DrahtBot removed the label Needs gitian build on Mar 9, 2020
  13. laanwj commented at 3:07 pm on March 10, 2020: member

    @fanquake and me had some discussion about this on IRC. The conclusion was that it’s probably better to ignore the MH_BINDATLOAD flag like MacOS’ static linker does. Instead, to adopt the security check for the change(s) that -Wl,-bind_at_load actually makes and is used.

    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.

  14. fanquake commented at 11:25 pm on March 10, 2020: member

    Also, it would make sense to get Apple to clarify the documentation on this

    I’ve reached out to a few people at Apple. So hopefully we’ll get some clarification.

  15. laanwj commented at 3:16 pm on March 11, 2020: member
    Great!
  16. fanquake commented at 12:38 pm on March 12, 2020: member

    Received this reply from Nick Kledzik (ld / dyld at Apple):

    Michael,

    Traditionally, there is two kinds of binding. Binds which must be done at load time before any code is run, and binds which can be done lazily. Lazy binds are only used for procedural interfaces. That is, the symbol is an external function call. The linker tool and dyld conspire to have lazying bind work by having the actual pointer bound to a helper which on first call looks up and set the pointer to the real implementation. This lazy symbol optimization made sense if looking up symbols took a long time and the program was not going to call them immediately. With dyld3 all symbol lookups are done ahead of time and cached, so lazy bind makes no sense.

    The semantics of the -bind_at_load option is omit that optimization. That is, have all binding done at launch. Originally that was done by setting the MH_BINDATLOAD bit in the mach_header and have dyld bind all lazy pointer early.

    A few years back we changed to instead have the static linker generate the binary without lazy binding - just up front binding for all symbols. You can see that in this example:

    0# regular build (has lazy binding)
    1[/tmp]> xcrun -sdk macosx.internal clang main.c
    2[/tmp]> xcrun dyldinfo -lazy_bind -bind a.out
    3bind information:
    4segment section          address        type    addend dylib            symbol
    5__DATA_CONST __got            0x100001000    pointer      0 libSystem        dyld_stub_binder
    6lazy binding information (from lazy_bind part of dyld info):
    7segment section          address    index  dylib                        symbol
    8__DATA       __la_symbol_ptr  0x100002000 0x0000           libSystem        _printf
    
    0# -bind_at_load build (no lazy binding)
    1[/tmp]> xcrun -sdk macosx.internal clang main.c -bind_at_load
    2[/tmp]> xcrun dyldinfo -lazy_bind -bind a.out
    3bind information:
    4segment section          address        type    addend dylib              symbol
    5__DATA       __la_symbol_ptr  0x100002000    pointer      0 libSystem        _printf
    6__DATA_CONST __got            0x100001000    pointer      0 libSystem        dyld_stub_binder
    7no compressed lazy binding info
    
  17. laanwj commented at 2:07 pm on March 25, 2020: member

    Thanks for asking for clarification.

    So from what I understand essentially this means that there is no point in forcing MH_BINDATLOAD because it’s no longer necessary?

    I think this means we don’t need this and can close it?

  18. DrahtBot commented at 8:36 am on March 26, 2020: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    No conflicts as of last run.

  19. DrahtBot added the label Needs rebase on Mar 28, 2020
  20. DrahtBot commented at 12:27 pm on March 28, 2020: member

    🐙 This pull request conflicts with the target branch and needs rebase.

  21. fanquake commented at 1:35 am on March 29, 2020: member

    I think this means we don’t need this and can close it?

    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 MH_BINDATLOAD we can at least add a related check to security-check.py.

  22. fanquake force-pushed on Mar 30, 2020
  23. fanquake removed the label Needs rebase on Mar 30, 2020
  24. theuni commented at 3:58 pm on April 3, 2020: member

    @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: https://github.com/bitcoin-core/secp256k1/issues/674#issuecomment-548452744

    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:

    “You can check the binaries using otool -l, and looking for the LC_DYLD_INFO_ONLY section; lazy_bind_off and lazy_bind_size should both be 0.”

    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.

  25. fanquake removed the label Build system on Apr 4, 2020
  26. fanquake removed the label Needs Conceptual Review on Apr 4, 2020
  27. fanquake added the label Scripts and tools on Apr 4, 2020
  28. fanquake force-pushed on Apr 4, 2020
  29. fanquake renamed this:
    build: teach ld64 to actually insert MH_BINDATLOAD
    scripts: add MACHO BINDATLOAD check to security-check.py
    on Apr 4, 2020
  30. fanquake renamed this:
    scripts: add MACHO BINDATLOAD check to security-check.py
    scripts: add MACHO lazy bindings check to security-check.py
    on Apr 4, 2020
  31. scripts: add MACHO lazy bindings check to security-check.py 5ca90f8b59
  32. fanquake force-pushed on Apr 4, 2020
  33. fanquake commented at 1:55 am on April 4, 2020: member

    So, let’s go for that instead,

    Thanks for following up. I’ve added a different test to security-check.py, which checks the otool -l output, and dropped the other changes.

  34. theuni commented at 9:32 pm on April 8, 2020: member
    ACK 5ca90f8b598978437340bb8467f527b9edfb2bbf
  35. MarcoFalke deleted a comment on Apr 8, 2020
  36. MarcoFalke added the label Needs gitian build on Apr 8, 2020
  37. DrahtBot commented at 2:59 pm on April 9, 2020: member

    Gitian builds

    File commit 2392566284d84ad905d03617b07a906386f8769c(master) commit 4fe1a3c9d04b46f08f70b74a8567b728f31927fc(master and this pull)
    bitcoin-0.19.99-aarch64-linux-gnu-debug.tar.gz 828cc7165fcda6d7... f3b9690c517d9059...
    bitcoin-0.19.99-aarch64-linux-gnu.tar.gz 19f39430f6d6b932... 86ca6c57c5ec6fc1...
    bitcoin-0.19.99-arm-linux-gnueabihf-debug.tar.gz b286dc1cfdd27aba... 53ba6e0e8f9ac2cc...
    bitcoin-0.19.99-arm-linux-gnueabihf.tar.gz 2e878c97fd9f751f... 36469b72a4662acf...
    bitcoin-0.19.99-osx-unsigned.dmg ba4c6e4dfe5f1a44... eb2f85206862b352...
    bitcoin-0.19.99-osx64.tar.gz 7d187bef777061e0... 0b7d7c09764b938c...
    bitcoin-0.19.99-riscv64-linux-gnu-debug.tar.gz 9a7450aa0ee7e771... c4b9d5f0722a3ace...
    bitcoin-0.19.99-riscv64-linux-gnu.tar.gz 164a69c7fdb34e11... 09adf866d4f66fd0...
    bitcoin-0.19.99-win64-debug.zip 5d8182cff04f0b23... 073839c45a9136e1...
    bitcoin-0.19.99-win64-setup-unsigned.exe 64a56adc8f69e4c6... 107383e299f03bb5...
    bitcoin-0.19.99-win64.zip be63697d003bbb92... f8dc05a3eb0d6c5d...
    bitcoin-0.19.99-x86_64-linux-gnu-debug.tar.gz 298322ed9ef88bec... bdc52155fa70b17b...
    bitcoin-0.19.99-x86_64-linux-gnu.tar.gz b71bfcf87e8e358d... e4570cd79dc22e31...
    bitcoin-0.19.99.tar.gz 7d4b6a6e5c51268e... f27925bee4954a49...
    bitcoin-core-linux-0.20-res.yml f88a03e78fc38919... 7561725e03990ebe...
    bitcoin-core-osx-0.20-res.yml 7c8b45a96259f8b1... f81dbafcc120c4be...
    bitcoin-core-win-0.20-res.yml d16c64f2db26cf2a... f93c8a58b1923457...
    linux-build.log ff9228e47e8fb766... 8c5b55e1802f6308...
    osx-build.log f925455d7374a55b... fb1ad644fa0d6a84...
    win-build.log 4d8c4d669d323e8c... 7480254e38a93b0e...
    bitcoin-core-linux-0.20-res.yml.diff 109a108b45090280...
    bitcoin-core-osx-0.20-res.yml.diff 14e31aab16dd39a8...
    bitcoin-core-win-0.20-res.yml.diff 0ce086b52363f070...
    linux-build.log.diff a4202579b5719f74...
    osx-build.log.diff 47652bfb05a2dd88...
    win-build.log.diff 8491793429c1912e...
  38. DrahtBot removed the label Needs gitian build on Apr 9, 2020
  39. fanquake merged this on Apr 9, 2020
  40. fanquake closed this on Apr 9, 2020

  41. fanquake deleted the branch on Apr 9, 2020
  42. sidhujag referenced this in commit 985073abb5 on Apr 13, 2020
  43. fanquake referenced this in commit 8334ee31f8 on Apr 21, 2020
  44. fanquake referenced this in commit c90a9e6fff on Apr 22, 2020
  45. sidhujag referenced this in commit 48733034a4 on Apr 23, 2020
  46. pierreN referenced this in commit e91692f5cd on May 1, 2020
  47. DrahtBot locked this on Feb 15, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-19 09:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me