1

Like libstdc++, we're checking in some places for abi::__forced_unwind, and just re-throw it instead of taking some other action. Like libstdc++, we catch it by reference:

try {
    /* ... */
} catch (abi::__forced_unwind&) {
    throw;
} catch (...) {
    /* ... */
} 

But if we actually pthread_cancel to exercise the code, ubsan complains:

runtime error: reference binding to null pointer of type 'struct __forced_unwind'

Here, it doesn't matter whether we catch by const-ref or mutable ref.

Are we (and libstdc++) actually running into UB here, or is it a False Positive in GCC's UBSan implementation?

user3840170
  • 26,597
  • 4
  • 30
  • 62
Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
  • Notice that `libstdc++` has right to use reserved identifiers, contrary to user code... – Jarod42 Dec 16 '21 at 11:39
  • 1
    @Jarod42 this technique is sometimes necessary, because swallowing a `__forced_unwind` will terminate the application. You *must* re-throw it. I don't know a way around this otherwise. That said, I'm 95% certain I will get the same ubsan error from libstc++ headers that use the type. E.g. in ``, there are several occurrences where libstdc++ swallows all exceptions, except `__forced_unwind`. – Marc Mutz - mmutz Dec 20 '21 at 21:51
  • 1
    @Jarod42, that name is reserved for the implementation to use. In this case it uses that name to define a non-standard extension, which is also available to users. Not all reserved names are unusable. There's a big difference between declaring your own things with reserved names and using an extension provided by the implementation. – Jonathan Wakely Dec 27 '21 at 16:06
  • Pedantically, libstdc++ can't run into undefined behavior. It is part of the implementation. – Jeff Garrett Dec 28 '21 at 18:18

1 Answers1

4

This is a bug in the forced unwinding implementation, which has been filed in the GCC Bugzilla as ticket #100415. (Arguably it should be fixed in GCC itself, as you’re not creating an actual reference binding, only matching on the exception type.)

While it is being fixed, you can decorate the function containing the catch clause with the [[gnu::no_sanitize("null")]] attribute, which will suppress null checking for references and nothing else:

#include <pthread.h>
#include <cxxabi.h>
#include <iostream>

int main() {
    pthread_t thrd;

    pthread_create(
        &thrd, nullptr,
        [] [[gnu::no_sanitize("null")]] (void *) -> void * {
            try {
                pthread_exit(nullptr);
            } catch (abi::__forced_unwind const &fu) {
                std::cerr << "forced unwind with " << &fu << std::endl;

                // does not trigger UBSan
                int &x = *static_cast<int *>(nullptr);
                // triggers UBSan
                1 / 0;

                throw;
            }
        }, nullptr);
    pthread_join(thrd, nullptr);

    // triggers UBSan
    int &x = *static_cast<int *>(nullptr);

    return 0;
}
user3840170
  • 26,597
  • 4
  • 30
  • 62
  • And since the attribute only applies to that particular function, if you call a function which assigns a null reference, UBSan can still catch that. In other words, you can tailor this pretty narrowly. – Jeff Garrett Dec 28 '21 at 18:16