chromiumembedded / cef

Chromium Embedded Framework (CEF). A simple framework for embedding Chromium-based browsers in other applications.
https://bitbucket.org/chromiumembedded/cef/
Other
3.41k stars 468 forks source link

CEF Initialization Corrupts Stack for Linux Interop #3493

Closed djrecipe closed 1 year ago

djrecipe commented 1 year ago

Describe the bug Calling CefInitialize() on Linux machines prevents usage of .NET interop. While the application will appear to continue to function normally, user-thrown exceptions (including SIGTRAP/breakpoints) cannot be caught, due to stack corruption/broken stack-unwinding mechanism.

To Reproduce https://github.com/iron-software/CefStackUnwindingBug

I have created a repo which exposes CefInitialize via interop, then invokes the interop method. This repo is designed to be as concise as possible for the convenience of those who'd like to try and reproduce this issue.

The main demonstration is the inability to catch a NullReferenceException in .NET after calling CefInitialize() via interop on Linux machines.

Expected behavior Users should be able to continue to catch user-thrown exceptions and/or breakpoints, even after calling CefInitialize() via interop on Linux machines.

Screenshots Please see the README and comments in https://github.com/iron-software/CefStackUnwindingBug

Versions (please complete the following information):

Additional context It's a bit of a unique use case, but not that far in left field:

I've been using CEF for a few years now as part of a commercial software component, and finally got around to creating a repo which demonstrates this issue.

My hunch is that CefInitialize() is calling some jmp instruction or something that subsequently breaks stack-unwinding after returning to managed code. Thus you will only ever see an issue when trying to catch a user-thrown exception or by trying to set a breakpoint.

Please refer to https://github.com/iron-software/CefStackUnwindingBug for more information. Thank you for your time!

dmitry-azaraev commented 1 year ago

Is not a bug, chromium code setup own signal handlers like it should. As workaround you can store current (.net) handler and restore it back after CefInitialize. Also keep in mind what CEF/Chromium doesn't use exceptions and will not throw them, and you must not throw exceptions from any CEF's handler.

djrecipe commented 1 year ago

@dmitry-azaraev Thanks for the prompt response.

you must not throw exceptions from any CEF's handler

Of course there are no uncaught exceptions thrown within any of our CefApp or CefProxy implementations, if that is what you mean.

djrecipe commented 1 year ago

@dmitry-azaraev Regarding the first item, would you be able to point me to any resource that might explain how to get started with this (store and restore a signal handler)?

.NET Interop works on Windows machines without the aformentioned storing & receiving of a signal handler, and I couldn't find anything about this requirement in the docs for CEF. I know that Unix .NET/native interop is not as well implemented as it is for Windows, and I am happy to oblige to any further requirements needed to be able to catch exceptions in .NET after invoking CefInitialize in native code - I could just use help getting pointed in the right direction.

dmitry-azaraev commented 1 year ago

I guess you need try to use sigaction call (SIGSEGV), or similar calls.

Similar topic from past https://gitlab.com/xiliumhq/chromiumembedded/cefglue/-/issues/29 which I remember.

Also topic probably was discussed at ceforum few times. I'm personally done something similar far earlier but don't remember details.

djrecipe commented 1 year ago

@dmitry-azaraev Thanks mate, I'll give it a try and see if I can post the results, much appreciated.

djrecipe commented 1 year ago

@dmitry-azaraev lo-and-behold, you were right

It's not pretty, but at a glance it's something like

    struct sigaction sigabrt;
    struct sigaction sigfpe;
    struct sigaction sigill;
    struct sigaction sigint;
    struct sigaction sigsegv;
    struct sigaction sigterm;
    memset(&sigabrt, 0, sizeof(sigabrt));
    memset(&sigfpe, 0, sizeof(sigfpe));
    memset(&sigill, 0, sizeof(sigill));
    memset(&sigint, 0, sizeof(sigint));
    memset(&sigsegv, 0, sizeof(sigsegv));
    memset(&sigterm, 0, sizeof(sigterm));
    sigaction(SIGABRT, 0, &sigabrt);
    sigaction(SIGFPE, 0, &sigfpe);
    sigaction(SIGILL, 0, &sigill);
    sigaction(SIGINT, 0, &sigint);
    sigaction(SIGSEGV, 0, &sigsegv);
    sigaction(SIGTERM, 0, &sigterm);
        ...
        CefInitialize(args, cef_settings, &proxy, NULL);
        ...
    // restore handlers
    struct sigaction sigabrtcef;
    struct sigaction sigfpecef;
    struct sigaction sigillcef;
    struct sigaction sigintcef;
    struct sigaction sigsegvcef;
    struct sigaction sigtermcef;
    memset(&sigabrtcef, 0, sizeof(sigabrtcef));
    memset(&sigfpecef, 0, sizeof(sigfpecef));
    memset(&sigillcef, 0, sizeof(sigillcef));
    memset(&sigintcef, 0, sizeof(sigintcef));
    memset(&sigsegvcef, 0, sizeof(sigsegvcef));
    memset(&sigtermcef, 0, sizeof(sigtermcef));
    sigaction(SIGABRT, &sigabrt, &sigabrtcef);
    sigaction(SIGFPE, &sigfpe, &sigfpecef);
    sigaction(SIGILL, &sigill, &sigillcef);
    sigaction(SIGINT, &sigint, &sigintcef);
    sigaction(SIGSEGV, &sigsegv, &sigsegvcef);
    sigaction(SIGTERM, &sigterm, &sigtermcef);

Not sure if it's necessary for all the signals, but I'll play around with it.

In addition to this, based on information from the issue you ilnked, I might need to restore the CEF handlers each time I need to invoke native code, but that shouldn't be an issue for me.

Thanks again.

dmitry-azaraev commented 1 year ago

No, is misunderstanding probably. Mostly cef/chromium setup own handlers to perform diagnostics (e.g. collect crashdumps), so it generally should be one-shot action from his side, and by so it doesn't need to restore/store them for every call. However, eventually you might want to have both: native/chromium dumps and .net exceptions and for this i have no ready to use answer, and to make it work both together i guess would require extending .net runtime by providing stack decoder/prober so you can identify if you should chain to previous (chromium) handler, for example.

dmitry-azaraev commented 1 year ago

Keep in mind what signals are process-wide, so in general case you can't just swap handlers without affecting all threads. One notable threads is .net thread pool and cef's threadpool. However, modern .net handler probably good enough for generating native minidumps, but it too like convert crash to exception during pinvoke, instead of just crash, like it should be. So it not always actually handy. But if you doesnt focus on cef internals or don't use cef crashdumps, then is probably not very important.

djrecipe commented 1 year ago

I've restored the dotnet signal handlers as discussed, and now I can once again happily de-reference null in .NET try/catch clause, even after initializing CEF in native Linux.

I will mention that breakpoints still don't seem to work, even though they should be covered under SIGTRAP. However, this is not a big concern for me at this time.

Anyways, thanks again for your insight! Feel free to close this issue if you'd like.