-
Notifications
You must be signed in to change notification settings - Fork 37
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
Improve CrashLogMessageExtractor memory safety #909
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mallexxx I went through these changes, these looks safer programmatically but it's getting more and more difficult to process what's going on here. :)
So two questions:
- How we can make this more accessible to regular iOS/macOS dev that typically sees dyld only during WWDC highlights :)
- Can we test this handler logic?
guard symtab.indices.contains(symtabIndex) else { return nil } | ||
let strtabOffset = Int(symtab[symtabIndex].n_un.n_strx) | ||
guard strtab.indices.contains(strtabOffset), | ||
strtab.indices.contains(strtabOffset + 1) else { return nil } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see all of this guarantees the checks on indexes 0 and 1 won't crash - could be a good idea to add some docs to the function to describe how safe to use is returned pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there were crashes in swift‘s String(cString:)
initializer for some C string pointers, I‘ve changed it to match the original KSCrash
code using strcmp
(bytewise compare) that doesn‘t copy the memory and stop at the first non-matching byte, so the check is there from KSCrash
, probably strcmp
would just work…
strcmp(symbolName.advanced(by: 1), "__cxa_throw") == 0 else { continue } | ||
|
||
let sectionInfo = try Dl_info(section) | ||
os_log(.debug, log: CxaThrowSwapper.log, "found %s: %s", String(cString: symbolName), indirectSymbolBindings[i].debugDescription) | ||
|
||
cxxOriginalThrowFunctions[UnsafeRawPointer(sectionInfo.dli_fbase)] = indirectSymbolBindings[i] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One note: as this will be processed during the app life cycle, I suspect there is one out of billion chance of this being modified as some other thread is handling exception (and re-throwing it to original handler) - but I don't think we really need to worry about it much tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general this should catch all the throws, write the last exception stack trace into a Thread dictionary and pass control to the designated handler (not inevitably leading to a crash), worths adding an extra C++ test but should just work.
guard !indicesToSkip.contains(symtabIndex), | ||
let symbolName = imageMap.symbolName(at: Int(symtabIndex)), | ||
symbolName[0] != 0, symbolName[1] != 0, | ||
strcmp(symbolName.advanced(by: 1), "__cxa_throw") == 0 else { continue } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I'm quite curious why we advance by 1 here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
me too :)
it‘s skipping the first _
, that‘s the original approach in KSCrash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you check symbol table for a binary (e.g. nm /Applications/DuckDuckGo.app/Contents/MacOS/DuckDuckGo | less
) then you'll notice that all symbols have a _
prefix. Swift mangled function names are prefixed with an underscore as well, and __cxa_throw
is also prefixed with _
:)
@@ -112,7 +112,7 @@ private func processMachHeader(_ header: UnsafePointer<mach_header>?, slide: Int | |||
} | |||
|
|||
private var cxxOriginalThrowFunctions = [UnsafeRawPointer: UnsafeRawPointer]() | |||
private let indicesToSkip = [UInt32(bitPattern: INDIRECT_SYMBOL_ABS), INDIRECT_SYMBOL_LOCAL, UInt32(Int64(INDIRECT_SYMBOL_LOCAL) | Int64(INDIRECT_SYMBOL_ABS))] | |||
private let indicesToSkip = [UInt32(INDIRECT_SYMBOL_ABS), INDIRECT_SYMBOL_LOCAL, INDIRECT_SYMBOL_LOCAL | UInt32(INDIRECT_SYMBOL_ABS)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can have some tests that find these, feed them int the logic and validate we correctly skip these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, let‘s plan a follow-up task to add some tests for the __cxa_throw
hook machinery and for now release it disabled both for iOS and macOS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, great job @mallexxx! I appreciate the recent changes to sanitizing the crash log 👏
For more context to others reading it (cc @bwaresiak):
- I've spent some time playing with this last night, going through the code and trying to make sense of it and it worked quite well with the help of ChatGPT. I've synced with Alex on MM this morning and we talked about adding documentation (even though the code is a rewrite of KSCrash, it's now our code, so better be clear about what happens there and why). The docs are added now so it's good to go 💪
- I agree it's a good idea to temporarily disable __cxa_throw swapping and re-enable it after adding tests. I've also spent some time last night trying to come up with a test, with a moderate success, then talked to Alex and he confirmed my rough plan, but I'll anyway leave it with Alex to implement tests.
…ing like a file name
2a522cf
to
a440ead
Compare
Please review the release process for BrowserServicesKit here.
Required:
Task/Issue URL: https://app.asana.com/0/414709148257752/1207878606354585/f
iOS PR: duckduckgo/iOS#3133
macOS PR: duckduckgo/macos-browser#3011
What kind of version bump will this require?: Minor
Description:
withTemporaryUnprotectedMemory
earlier, matching https://github.com/kstenerud/KSCrash/blob/master/Sources/KSCrashRecordingCore/KSCxaThrowSwapper.cswapCxaThrow
parameter toCrashLogMessageExtractor.setUp
to allow disabling__cxa_throw
swappingSteps to test this PR:
swapCxaThrow: false
to CrashLogMessageExtractor.setUpOS Testing:
Internal references:
Software Engineering Expectations
Technical Design Template