Skip to content
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

Merged
merged 5 commits into from
Aug 2, 2024

Conversation

mallexxx
Copy link
Collaborator

@mallexxx mallexxx commented Jul 24, 2024

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:

Steps to test this PR:

  1. pass swapCxaThrow: false to CrashLogMessageExtractor.setUp
  2. detach debugger
  3. simulate c++ crash
  4. run from debugger
  5. validate uploaded reports is generated (without the exception stack trace)
  6. Simulate NSException and validate the stack trace is there

OS Testing:

  • iOS 14
  • iOS 15
  • iOS 16
  • macOS 10.15
  • macOS 11
  • macOS 12

Internal references:

Software Engineering Expectations
Technical Design Template

Copy link
Collaborator

@bwaresiak bwaresiak left a 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:

  1. How we can make this more accessible to regular iOS/macOS dev that typically sees dyld only during WWDC highlights :)
  2. 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 }
Copy link
Collaborator

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.

Copy link
Collaborator Author

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]
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 }
Copy link
Collaborator

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 :)

Copy link
Collaborator Author

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

Copy link
Contributor

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)]
Copy link
Collaborator

@bwaresiak bwaresiak Jul 25, 2024

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.

Copy link
Collaborator Author

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

Copy link
Contributor

@ayoy ayoy left a 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.

@mallexxx mallexxx force-pushed the alex/process-mach-header branch from 2a522cf to a440ead Compare August 2, 2024 08:10
@mallexxx mallexxx changed the base branch from main to hotfix/176.1.1-1 August 2, 2024 08:10
@mallexxx mallexxx merged commit 7325384 into hotfix/176.1.1-1 Aug 2, 2024
7 checks passed
@mallexxx mallexxx deleted the alex/process-mach-header branch August 2, 2024 08:16
samsymons added a commit that referenced this pull request Aug 3, 2024
* main:
  [DuckPlayer] - Create DuckPlayer target and move initially shared code (#921)
  Improve CrashLogMessageExtractor memory safety (#909)
samsymons added a commit that referenced this pull request Aug 4, 2024
* main:
  [DuckPlayer] - Create DuckPlayer target and move initially shared code (#921)
  Improve CrashLogMessageExtractor memory safety (#909)
samsymons added a commit that referenced this pull request Aug 5, 2024
* main:
  Tweaks to DB API, improved abstractions for better testability. (#913)
  Add RMF `messageShown` attribute (#923)
  [DuckPlayer] - Create DuckPlayer target and move initially shared code (#921)
  Improve CrashLogMessageExtractor memory safety (#909)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants