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

Placing a guard for undefined behaviour that appears to trigger sanit… #235

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

TheWiseWolfy
Copy link

@TheWiseWolfy TheWiseWolfy commented Dec 20, 2024

Description

For the last few weeks we have been getting weird sanitizer hits for this exact function inside our automated testing pipeline. It seems to depend a lot on the order of the sections, if those sections have exceptions handled within them, etc, etc. It is incredibly hard to replicate and debug this anomaly. I am not even able to replicate it outside the GitLab pipeline itself. I noticed this unguarded undefined behavior and it's pretty hard for me not to blame this. So I hope you accept this change.

The solution

Added a guard against undefined behavior.
No tests or behavior should be affected.

The report in question always looks like this:

=================================================================
==31==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000bba4c0 at pc 0x55b49a938f2a bp 0x7ffdc275b010 sp 0x7ffdc275b000
READ of size 8 at 0x506000bba4c0 thread T0
    #0 0x55b49a938f29 in Catch2ApprovalListener::sectionEnded(Catch::SectionStats const&) (/xentis/bin/ConverterApprovalTestUI+0x1ccf29) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #1 0x55b49a7e14b3 in Catch::ListeningReporter::sectionEnded(Catch::SectionStats const&) (/xentis/bin/ConverterApprovalTestUI+0x754b3) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #2 0x55b49a893a65 in Catch::RunContext::sectionEnded(Catch::SectionEndInfo const&) (/xentis/bin/ConverterApprovalTestUI+0x127a65) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #3 0x55b49a7fd1f8 in Catch::RunContext::handleUnfinishedSections() (/xentis/bin/ConverterApprovalTestUI+0x911f8) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #4 0x55b49a8945f9 in Catch::RunContext::runCurrentTest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (/xentis/bin/ConverterApprovalTestUI+0x1285f9) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #5 0x55b49a8facac in Catch::RunContext::runTest(Catch::TestCase const&) (/xentis/bin/ConverterApprovalTestUI+0x18ecac) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #6 0x55b49a8fe590 in Catch::Session::runInternal() (/xentis/bin/ConverterApprovalTestUI+0x192590) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #7 0x55b49a9005b1 in Catch::Session::run() (/xentis/bin/ConverterApprovalTestUI+0x1945b1) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #8 0x55b49a921ead in main::{lambda(boost::application::basic_context&)#1}::operator()(boost::application::basic_context&) const [clone .constprop.0] (/xentis/bin/ConverterApprovalTestUI+0x1b5ead) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #9 0x7f1f960b2a50 in xentis::ServerApplication::run(std::function<int (boost::application::basic_context&)> const&) (/xentis/packages/XENTIS/lib/libAmisAppl.so+0x10eea50) (BuildId: b2f14750fec3ecb1e7764b83b87478a970f31a86)
    #10 0x7f1f95746555 in xentis::ServerMain::main(int, char**, std::function<int (boost::application::basic_context&)> const&) (/xentis/packages/XENTIS/lib/libAmisAppl.so+0x782555) (BuildId: b2f14750fec3ecb1e7764b83b87478a970f31a86)
    #11 0x55b49a7dce4f in main (/xentis/bin/ConverterApprovalTestUI+0x70e4f) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #12 0x7f1f3d0b07e4 in __libc_start_main (/lib64/libc.so.6+0x3a7e4) (BuildId: 37e4ac6a7fb96950b0e6bf72d73d94f3296c77eb)
    #13 0x55b49a7de67d in _start (/xentis/bin/ConverterApprovalTestUI+0x7267d) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
0x506000bba4c0 is located 32 bytes before 64-byte region [0x506000bba4e0,0x506000bba520)
allocated by thread T0 here:
    #0 0x7f1fc0bf5f78 in operator new(unsigned long) (/lib64/libasan.so.8+0xfaf78) (BuildId: 98bb16229beb4f63e6e54817a90d0e80fe9febf6)
    #1 0x55b49a98e2e0 in void std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::_M_realloc_insert<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>(__gnu_cxx::__normal_iterator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (/xentis/bin/ConverterApprovalTestUI+0x2222e0) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #2 0x55b49a98fd2e in Catch2ApprovalListener::sectionStarting(Catch::SectionInfo const&) (/xentis/bin/ConverterApprovalTestUI+0x223d2e) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #3 0x55b49a7e09f3 in Catch::ListeningReporter::sectionStarting(Catch::SectionInfo const&) (/xentis/bin/ConverterApprovalTestUI+0x749f3) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #4 0x55b49a8f7afc in Catch::RunContext::sectionStarted(Catch::SectionInfo const&, Catch::Counts&) (/xentis/bin/ConverterApprovalTestUI+0x18bafc) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #5 0x55b49a89568d in Catch::Section::Section(Catch::SectionInfo const&) (/xentis/bin/ConverterApprovalTestUI+0x12968d) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #6 0x55b49aa24a47 in xentis::test::C_A_T_C_H_T_E_S_T_0() (/xentis/bin/ConverterApprovalTestUI+0x2b8a47) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #7 0x55b49a801bfe in Catch::RunContext::invokeActiveTestCase() (/xentis/bin/ConverterApprovalTestUI+0x95bfe) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #8 0x55b49a8944c6 in Catch::RunContext::runCurrentTest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (/xentis/bin/ConverterApprovalTestUI+0x1284c6) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #9 0x55b49a8facac in Catch::RunContext::runTest(Catch::TestCase const&) (/xentis/bin/ConverterApprovalTestUI+0x18ecac) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #10 0x55b49a8fe590 in Catch::Session::runInternal() (/xentis/bin/ConverterApprovalTestUI+0x192590) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #11 0x55b49a9005b1 in Catch::Session::run() (/xentis/bin/ConverterApprovalTestUI+0x1945b1) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #12 0x55b49a921ead in main::{lambda(boost::application::basic_context&)#1}::operator()(boost::application::basic_context&) const [clone .constprop.0] (/xentis/bin/ConverterApprovalTestUI+0x1b5ead) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #13 0x7f1f960b2a50 in xentis::ServerApplication::run(std::function<int (boost::application::basic_context&)> const&) (/xentis/packages/XENTIS/lib/libAmisAppl.so+0x10eea50) (BuildId: b2f14750fec3ecb1e7764b83b87478a970f31a86)
    #14 0x7f1f95746555 in xentis::ServerMain::main(int, char**, std::function<int (boost::application::basic_context&)> const&) (/xentis/packages/XENTIS/lib/libAmisAppl.so+0x782555) (BuildId: b2f14750fec3ecb1e7764b83b87478a970f31a86)
    #15 0x55b49a7dce4f in main (/xentis/bin/ConverterApprovalTestUI+0x70e4f) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd)
    #16 0x7f1f3d0b07e4 in __libc_start_main (/lib64/libc.so.6+0x3a7e4) (BuildId: 37e4ac6a7fb96950b0e6bf72d73d94f3296c77eb)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/xentis/bin/ConverterApprovalTestUI+0x1ccf29) (BuildId: 10650d95495ed4db8676e7e5db47fb56797adacd) in Catch2ApprovalListener::sectionEnded(Catch::SectionStats const&)
Shadow bytes around the buggy address:
  0x506000bba200: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 07 fa
  0x506000bba280: fa fa fa fa 00 00 00 00 00 00 00 04 fa fa fa fa
  0x506000bba300: 00 00 00 00 00 00 07 fa fa fa fa fa 00 00 00 00
  0x506000bba380: 00 00 00 04 fa fa fa fa fd fd fd fd fd fd fd fd
  0x506000bba400: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
=>0x506000bba480: fd fd fd fd fd fd fd fd[fa]fa fa fa 00 00 00 00
  0x506000bba500: 00 00 00 00 fa fa fa fa fd fd fd fd fd fd fd fd
  0x506000bba580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x506000bba600: fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa
  0x506000bba680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x506000bba700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==31==ABORTING

Summary by Sourcery

Bug Fixes:

  • Add a guard to prevent undefined behavior in the Catch2ApprovalListener::sectionEnded function by ensuring the sections vector is not empty before popping elements.

Summary by Sourcery

Bug Fixes:

  • Prevent undefined behavior when calling Catch2ApprovalListener::sectionEnded with an empty sections vector.

Copy link

sourcery-ai bot commented Dec 20, 2024

Reviewer's Guide by Sourcery

This pull request introduces a guard against undefined behavior in the Catch2ApprovalListener::sectionEnded function. The guard checks if the sections vector is empty before attempting to pop an element, preventing a potential heap-buffer-overflow error.

Sequence diagram for Catch2ApprovalListener section handling with guard

sequenceDiagram
    participant Test as Test Case
    participant Listener as Catch2ApprovalListener
    participant Sections as sections vector

    Test->>Listener: sectionEnded()
    Listener->>Sections: check if empty()
    alt sections is not empty
        Listener->>Sections: pop_back()
    else sections is empty
        Note over Listener: Skip pop_back()
        Note over Listener: Prevent undefined behavior
    end
Loading

State diagram for sections vector safety check

stateDiagram-v2
    [*] --> CheckSections
    CheckSections --> Empty: sections.empty()
    CheckSections --> NotEmpty: !sections.empty()
    Empty --> [*]: Skip pop_back
    NotEmpty --> PopSection: Safe to pop
    PopSection --> [*]: sections.pop_back()
Loading

File-Level Changes

Change Details Files
Added a guard against undefined behavior when popping elements from the sections vector.
  • A conditional check is added to ensure that the sections vector is not empty before calling pop_back().
ApprovalTests/integrations/catch/Catch2Approvals.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@TheWiseWolfy
Copy link
Author

Hi there and thanks for your PR. I fully agree that the call to pop_back should be guarded to avoid undefined behavior. Any specific reason for the while instead of a plain if (that would mimic the original behavior of popping one element (istead of all)?
We clearly have undefined behavior here, that should be guarded, and programs with UB are free to do whatever they want, including causing sanitizer issues.
But just to make sure, as sanitizers can be quite tricky: It would be great to have a reproducible minimal example that reproduces the issue. I have seen a unrelated change in a codebase (like printing a value, changing the order of local variables or simple refactorings) causing a sanitizer warning to seemingly go away, without the underlying issue being fixed and the same sanitizer issue coming back some days or weeks later.

We did eventually discover a problem with our assert() implementation on release builds (some gcc optimization managed to break the weird legacy implementation we used), witch let to null pointers and signals/ threads that die unpredictably.

I am not exactly sure why our stack of Catch2, Approval Tests, and the GitLab continuous integration pipelines ended up reporting this later undefined behavior in Catch2 instead of reporting the stack overflow in the software itself. (and why the execution of the test continued in spite of the signal). But now it feel like things could have gone completely unnoticed if not for this error.

As for replicating this, it's hard to identify the exact conditions needed. I would start by creating a test program with a thread that ends by stack overflow. Then that signal should be suppressed in such a way that allows the test software to continue running, thus arriving at this situation when it's trying to pop a section that no longer exists. Regardless, it's all very circumstantial. I might try my hand at this, but I will see. And thanks for the advice as well, it was very helpful in getting the right mindset to find this bug.

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.

1 participant