[Chrome] arm64: Copying a new Mach-O executable to an inode that previously contained a different Mach-O executable produces something that incorrectly fails code signature verification and won’t run
Originator: | mark | ||
Number: | rdar://FB8914243 | Date Originated: | 2020-11-23 |
Status: | Open | Resolved: | |
Product: | macOS | Product Version: | 11.0.1 20B29 |
Classification: | Application Crash | Reproducible: | Always |
If a vnode contains a Mach-O executable, replacing the contents with a different Mach-O executable produces a file that incorrectly fails code signature verification in xnu and won’t run. This happens when “kill” semantics are enforced, which is the default on arm64. Simplified test case: mark@arm-and-hammer zsh% rm -f /tmp/test mark@arm-and-hammer zsh% cp /usr/bin/false /tmp/test; echo $? mark@arm-and-hammer zsh% /tmp/test 1 mark@arm-and-hammer zsh% cp /usr/bin/true /tmp/test; echo $? mark@arm-and-hammer zsh% /tmp/test zsh: killed /tmp/test 137 Detailed test case: 1. Create a Mach-O executable and make sure that xnu is aware of it and its code signature by running it. Note its inode number, modification timestamp, and code directory hash. In this example, I’m using my own executables for “true” and “false” in place of the ones that ship in /usr/bin, because the kernel produces a more valuable log in this case. However, per the simplified test case above, the same behavior (minus the log messages) occurs when using the prebuilt /usr/bin/true and /usr/bin/false in place of the compiled /tmp/true and /tmp/false. mark@arm-and-hammer zsh% rm -f /tmp/test mark@arm-and-hammer zsh% clang -x c - -o /tmp/false <<< 'int main() { return 1; }' mark@arm-and-hammer zsh% cp /tmp/false /tmp/test mark@arm-and-hammer zsh% stat -f '%i %Fm' /tmp/test 6951173 1606151966.467082817 mark@arm-and-hammer zsh% codesign --display --verbose=5 /tmp/test 2>&1 | grep ^CDHash= CDHash=a66099928a3c71e1e76865b010bacd46b5c53358 mark@arm-and-hammer zsh% /tmp/test; echo $? 1 2. Replace the contents of the file with a different executable. Verify that the inode has remained the same. mark@arm-and-hammer zsh% clang -x c - -o /tmp/true <<< 'int main() { return 0; }' mark@arm-and-hammer zsh% cp /tmp/true /tmp/test mark@arm-and-hammer zsh% stat -f '%i %Fm' /tmp/test 6951173 1606152024.837585424 mark@arm-and-hammer zsh% codesign --display --verbose=5 /tmp/test 2>&1 | grep ^CDHash= CDHash=485dec78a904ee3c8301d10037caebe65feec83d mark@arm-and-hammer zsh% /tmp/test; echo $? Expected behavior: The executable should run. mark@arm-and-hammer zsh% /tmp/test; echo $? 0 Observed behavior: The executable does not run. It’s killed by the kernel with a SIGKILL. mark@arm-and-hammer zsh% /tmp/test; echo $? zsh: killed /tmp/test 137 Observing the system log during the above test, these messages are visible: admin@arm-and-hammer zsh% sudo log stream --predicate 'sender = "kernel"' Filtering the log data using "sender == "kernel"" Timestamp Thread Type Activity PID TTL 2020-11-23 12:20:36.817603-0500 0x1077 Default 0x0 0 0 kernel: ignoring detached code signature on 'test' with cdhash 'a66099928a3c71e1e76865b010bacd46b5c53358' because it is invalid, or not a simple adhoc signature. 2020-11-23 12:20:36.825652-0500 0x1078 Default 0x0 0 0 kernel: CODE SIGNING: process 398[test]: rejecting invalid page at address 0x104b44000 from offset 0x0 in file "/private/tmp/test" (cs_mtime:1606151966.467082817 != mtime:1606152024.837585424) (signed:1 validated:1 tainted:1 nx:0 wpmapped:0 dirty:0 depth:0) Of note: a66099928a3c71e1e76865b010bacd46b5c53358 is the CDHash from /tmp/false and the original version of /tmp/test. It is not the CDHash of the version of /tmp/test that I was expecting to run the second time (485dec78a904ee3c8301d10037caebe65feec83d) 1606151966.467082817 is the mtime of the original version of /tmp/test, but not the mtime of the version of /tmp/test that I was expecting to run the second time (1606152024.837585424). System information: mark@arm-and-hammer zsh% sw_vers ProductName: macOS ProductVersion: 11.0.1 BuildVersion: 20B29 mark@arm-and-hammer zsh% xcodebuild -version Xcode 12.2 Build version 12B45b mark@arm-and-hammer zsh% uname -m arm64 mark@arm-and-hammer zsh% system_profiler SPHardwareDataType | grep 'Model Identifier' Model Identifier: ADP3,2 This occurs on shipping M1-based hardware as well. To reproduce on x86_64, set the “kill” flag in the code signature. mark@sweet16 zsh% rm -f /tmp/test mark@sweet16 zsh% clang -x c - -o /tmp/false <<< 'int main() { return 1; }' mark@sweet16 zsh% codesign --sign=- --options=kill /tmp/false mark@sweet16 zsh% cp /tmp/false /tmp/test mark@sweet16 zsh% /tmp/test; echo $? 1 mark@sweet16 zsh% clang -x c - -o /tmp/true <<< 'int main() { return 0; }' mark@sweet16 zsh% codesign --sign=- --options=kill /tmp/true mark@sweet16 zsh% cp /tmp/true /tmp/test mark@sweet16 zsh% /tmp/test; echo $? zsh: killed /tmp/test 137 mark@sweet16 zsh% sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H15 mark@sweet16 zsh% xcodebuild -version Xcode 12.2 Build version 12B45b mark@sweet16 zsh% uname -m x86_64 mark@sweet16 zsh% system_profiler SPHardwareDataType | grep 'Model Identifier' Model Identifier: MacBookPro16,1 Additional information: Feedback FB8914231 is related. Unlike in that report, purge and msync(…, MS_INVALIDATE) are not viable workarounds here. This bug was discovered during the course of building and testing open-source llvm/clang on mac-arm64, tracked at https://bugs.llvm.org/show_bug.cgi?id=46644#c7.
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!