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

Fix possible infinite recursion in FMT_ASSERT #1744

Merged
merged 2 commits into from
Jul 1, 2020
Merged

Fix possible infinite recursion in FMT_ASSERT #1744

merged 2 commits into from
Jul 1, 2020

Conversation

tohammer
Copy link
Contributor

With exceptions disabled and assertions enabled FMT_ASSERT can lead to infinite recursion until stack overflows.

Reason
When a fmt assertion is triggered it tries to print a
message via assert_fail before terminating. For printing it uses
fmt::print to stderr that, when stderr output is limited leads again
to an assertion that tries to print a message (and so on).

Reproduce
Easiest way to reproduce is to close stderr to cause the recursion and to also close stdout to trigger an assertion condition.
Compile the following code with FMT_EXCEPTIONS=0:

#include "fmt/format.h"

int main() {
    std::fclose(stdout);
    std::fclose(stderr);
    fmt::print("foo");
    return 0;
}

Resulting stacktrace:

...
#35474 0x000055d68c17609f in fmt::v6::vprint (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#35475 0x000055d68c17b09c in fmt::v6::print<char [28], char const*&, int&, char const*&, char> (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=...) at ../fmt/include/fmt/core.h:1857
#35476 0x000055d68c174bf2 in fmt::v6::detail::assert_fail (file=0x55d68c1912b8 "../fmt/include/fmt/format-inl.h", line=177, message=0x55d68c191371 "") at ../fmt/include/fmt/format-inl.h:52
#35477 0x000055d68c174ff4 in fmt::v6::detail::fwrite_fully (ptr=0x7ffe387b8210, size=1, count=55, stream=0x7fc5892db680 <_IO_2_1_stderr_>) at ../fmt/include/fmt/format-inl.h:177
#35478 0x000055d68c17609f in fmt::v6::vprint (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#35479 0x000055d68c17b09c in fmt::v6::print<char [28], char const*&, int&, char const*&, char> (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=...) at ../fmt/include/fmt/core.h:1857
#35480 0x000055d68c174bf2 in fmt::v6::detail::assert_fail (file=0x55d68c1912b8 "../fmt/include/fmt/format-inl.h", line=177, message=0x55d68c191371 "") at ../fmt/include/fmt/format-inl.h:52
#35481 0x000055d68c174ff4 in fmt::v6::detail::fwrite_fully (ptr=0x7ffe387b85c0, size=1, count=55, stream=0x7fc5892db680 <_IO_2_1_stderr_>) at ../fmt/include/fmt/format-inl.h:177
#35482 0x000055d68c17609f in fmt::v6::vprint (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#35483 0x000055d68c17b09c in fmt::v6::print<char [28], char const*&, int&, char const*&, char> (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=...) at ../fmt/include/fmt/core.h:1857
#35484 0x000055d68c174bf2 in fmt::v6::detail::assert_fail (file=0x55d68c1912b8 "../fmt/include/fmt/format-inl.h", line=177, message=0x55d68c191371 "") at ../fmt/include/fmt/format-inl.h:52
#35485 0x000055d68c174ff4 in fmt::v6::detail::fwrite_fully (ptr=0x7ffe387b8970, size=1, count=55, stream=0x7fc5892db680 <_IO_2_1_stderr_>) at ../fmt/include/fmt/format-inl.h:177
#35486 0x000055d68c17609f in fmt::v6::vprint (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#35487 0x000055d68c17b09c in fmt::v6::print<char [28], char const*&, int&, char const*&, char> (f=0x7fc5892db680 <_IO_2_1_stderr_>, format_str=...) at ../fmt/include/fmt/core.h:1857
#35488 0x000055d68c174bf2 in fmt::v6::detail::assert_fail (file=0x55d68c1912b8 "../fmt/include/fmt/format-inl.h", line=177, message=0x55d68c191371 "") at ../fmt/include/fmt/format-inl.h:52
#35489 0x000055d68c174ff4 in fmt::v6::detail::fwrite_fully (ptr=0x7ffe387b8d20, size=1, count=3, stream=0x7fc5892db760 <_IO_2_1_stdout_>) at ../fmt/include/fmt/format-inl.h:177
#35490 0x000055d68c17609f in fmt::v6::vprint (f=0x7fc5892db760 <_IO_2_1_stdout_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#35491 0x000055d68c176131 in fmt::v6::vprint (format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1427
#35492 0x000055d68c174a61 in fmt::v6::print<char [4], , char>(char const (&) [4]) (format_str=...) at ../fmt/include/fmt/core.h:1876
#35493 0x000055d68c1749a3 in main () at ../main.cc:8

Proposed fix is to not rely on std::print for assertion message output but to directly use (unchecked) output via std::fprintf.
This results in the expected stacktrace with abort:

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007f483226e801 in __GI_abort () at abort.c:79
#2  0x00007f48328d1257 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007f48328dc606 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007f48328dc671 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x0000562cb8e61c4b in fmt::v6::detail::assert_fail (file=0x562cb8e7dc58 "../fmt/include/fmt/format-inl.h", line=177, message=0x562cb8e7dd11 "") at ../fmt/include/fmt/format-inl.h:55
#6  0x0000562cb8e62048 in fmt::v6::detail::fwrite_fully (ptr=0x7fffc5ac1260, size=1, count=3, stream=0x7f483261a760 <_IO_2_1_stdout_>) at ../fmt/include/fmt/format-inl.h:177
#7  0x0000562cb8e630f3 in fmt::v6::vprint (f=0x7f483261a760 <_IO_2_1_stdout_>, format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1412
#8  0x0000562cb8e63185 in fmt::v6::vprint (format_str=..., args=...) at ../fmt/include/fmt/format-inl.h:1427
#9  0x0000562cb8e61ab1 in fmt::v6::print<char [4], , char>(char const (&) [4]) (format_str=...) at ../fmt/include/fmt/core.h:1876
#10 0x0000562cb8e619f3 in main () at ../main.cc:7

I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing.

Use std::fprintf for assertion message output preventing infinite
recursion when output to stderr is limited or broken.
@tohammer tohammer changed the title Fix possible resursion in FMT_ASSERT Fix possible infinite recursion in FMT_ASSERT Jun 26, 2020
Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

@@ -49,7 +49,7 @@ FMT_BEGIN_NAMESPACE
namespace detail {

FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
print(stderr, "{}:{}: assertion failed: {}", file, line, message);
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a short comment explaining why we use printf here. It's too tempting to change it back =).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done.

@tohammer tohammer requested a review from vitaut June 30, 2020 08:39
@vitaut vitaut merged commit 5de62af into fmtlib:master Jul 1, 2020
@vitaut
Copy link
Contributor

vitaut commented Jul 1, 2020

Thank you!

sthagen added a commit to sthagen/fmtlib-fmt that referenced this pull request Jul 2, 2020
Fix possible infinite recursion in FMT_ASSERT (fmtlib#1744)
@tohammer tohammer deleted the fix-infinite-recursion-in-assert-without-exceptions branch July 2, 2020 10:56
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.

2 participants