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

build: enable libc++ and libstdc++ hardening #31424

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

Conversation

vasild
Copy link
Contributor

@vasild vasild commented Dec 5, 2024

When ENABLE_HARDENING is ON (which is the default), then enable light or full hardening in libc++ or libstdc++, depending on the library used and depending on the build type:

library Bitcoin Core compiled in debug mode Bitcoin Core compiled in non-debug mode
libc++ _LIBCPP_HARDENING_MODE_DEBUG1 _LIBCPP_HARDENING_MODE_FAST1
libstdc++ _GLIBCXX_ASSERTIONS _GLIBCXX_ASSERTIONS

1 this will override the hardening mode with which libc++ is compiled, for the header-based parts of libc++ (most of it). The pre-built components of libc++ will still use whatever mode is configured when libc++ was compiled. For more info see the docs.


Remove hardening flags from depends/hosts/linux.mk in favor of the more generic way to enable them via CMake. The same would be achieved after this change by cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_HARDENING=ON.


Inspired by #31272 (comment)

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 5, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31424.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

No conflicts as of last run.

Copy link
Contributor Author

@vasild vasild left a comment

Choose a reason for hiding this comment

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

CMakeLists.txt Outdated Show resolved Hide resolved
CMakeLists.txt Outdated
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG
-D_GLIBCXX_ASSERTIONS
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How is this related to the following?

# https://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
# https://libcxx.llvm.org/Hardening.html
linux_debug_CPPFLAGS+=-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG

Copy link
Member

Choose a reason for hiding this comment

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

These are flags set for DEBUG=1 when building depends. Iirc -D_GLIBCXX_DEBUG includes -D_GLIBCXX_ASSERTIONS as well as other slower checks.

Copy link
Member

Choose a reason for hiding this comment

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

How do they interact? According to https://libcxx.llvm.org/Hardening.html#notes-for-users only one is allowed to be passed.

I don't think it is useful to possibly silently downgrade DEBUG into FAST.

Also, it can be considered to enable the ABI checks in debug depends builds, see https://libcxx.llvm.org/Hardening.html#abi-options

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How is this related to the following?

I understand that the flags in bitcoin/depends/hosts/linux.mk are (obviously) only used when building depends and only on Linux. In comparison, if added to CMakeLists.txt under ENABLE_HARDENING then the flags will be used for depends and for non-depends builds on all platforms that use libc++ or libstdc++, not only Linux. Thus I dropped the flags from bitcoin/depends/hosts/linux.mk and added (a more extensive) hardening to CMakeLists.txt.

Yes, -D_GLIBCXX_DEBUG includes -D_GLIBCXX_ASSERTIONS.

How do they interact?

I guess it is harmless to specify both _GLIBCXX_DEBUG* and _LIBCPP_HARDENING_MODE because libstdc++ will honor the first and libc++ the second.

I don't think it is useful to possibly silently downgrade DEBUG into FAST.

Indeed that would be bad. If _LIBCPP_HARDENING_MODE is provided more than once on the command line, then a warning will be raised: "macro is redefined". E.g. -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST will produce a warning.

Also, it can be considered to enable the ABI checks

Done.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it is useful to possibly silently downgrade DEBUG into FAST.

If _LIBCPP_HARDENING_MODE is provided more than once on the command line, then a warning will be raised

Just printing a warning will more than likely still result in silent downgrading.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this is what I was referring to. But:

  • It would be good to double check the behavior instead of only relying on the docs (docs can be wrong sometimes).
  • I don't think it is useful to possibly silently downgrade DEBUG into FAST. (It is now the third time I copy-pasted this, sorry, but I am not sure how to make it any more clear)

Copy link
Contributor Author

@vasild vasild Dec 18, 2024

Choose a reason for hiding this comment

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

Using an example would help. Are you worried about the following?

A libc++ vendor, lets say "Ubuntu 123.45 LTS", compiles the libc++ they ship with LIBCXX_HARDENING_MODE=debug (see https://libcxx.llvm.org/Hardening.html#notes-for-vendors). Then a user of "Ubuntu 123.45 LTS" compiles Bitcoin Core with cmake -DCMAKE_BUILD_TYPE=Release. This results in Bitcoin Core being compiled with fast hardening mode (debug silently downgraded to fast?).

The above is how it should be - the OS default is supposed to be overridable and in Bitcoin Core -DCMAKE_BUILD_TYPE=Release is intended to use the fast mode.

There is also this note in the docs:

Since the static and shared library components of libc++ are built by the vendor, setting this macro will have no impact on the hardening mode for the pre-built components. Most libc++ code is header-based, so a user-provided value for _LIBCPP_HARDENING_MODE will be mostly respected.

I think it is no-brainer that an application (e.g. Bitcoin Core) should pass -D_LIBCPP_HARDENING_MODE=... to the compiler and that is the correct and the best thing an app can do and this PR does that.

Copy link
Member

Choose a reason for hiding this comment

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

The above is how it should be - the OS default is supposed to be overridable and in Bitcoin Core -DCMAKE_BUILD_TYPE=Release is intended to use the fast mode.

It would be good to mention this in the pull description, that this is your goal. I'd say the system vendor default should not be silently downgraded, but if your goal is to ignore the vendor default, then it should be mentioned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extended the PR description, see the 1 specifically. I wouldn't call it a "downgrade" - it is less checks, but an "upgrade" from performance point of view. All these settings provide varying tradeoffs between performance and amount of checks. Downgrade for one is upgrade for the other.

This is analogous to the -O flag. Assuming the default is -O1 (if no -O... is provided). If the user explicitly provides -O2 that will use -O2 instead of the default -O1. I wouldn't call this (silent) "downgrade" or "upgrade", but rather "intentional override of the default".

Copy link
Member

@maflcko maflcko Dec 20, 2024

Choose a reason for hiding this comment

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

I don't expect the downgrade will ever affect users. So instead of "Ubuntu 123.45 LTS" I can only imagine a test-only build of a distro that has debug hardening enabled in the system toolchain. This way, they can simply compile all system (and third-party) packages and run the tests with maximum hardening to spot any mistakes before they hit real users.

This was possible before your changes, but won't be possible after your changes (unless they pick the build type debug). Maybe it is expected that they need to specifically select the debug cmake build type in this case. Either way is fine, and the pull should be an overall improvement in any case.

CMakeLists.txt Outdated Show resolved Hide resolved
CMakeLists.txt Outdated
-D_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING
-D_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
-D_LIBCPP_ABI_BOUNDED_UNIQUE_PTR
-D_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY
Copy link
Member

Choose a reason for hiding this comment

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

Have you tested this? I expect it to break the ABI for at least bdb?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not manually, but I assume CI will fail if that happens. If it fails, then I will remove it.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think using a passing CI as an excuse to break debug builds for people using vendor libc++ is a good approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think it would be better to omit those _LIBCPP_ABI_BOUNDED*?

Copy link
Member

Choose a reason for hiding this comment

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

It would be good to have them in a CI task

Copy link
Contributor Author

@vasild vasild Dec 20, 2024

Choose a reason for hiding this comment

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

I removed the _LIBCPP_ABI_BOUNDED* options from CMake when ENABLE_HARDENING=ON because they caused linking failures for the multiprocess task because libmultiprocess was compiled without them edit: tsan task, i.e. ABI incompatibility issues. So, don't enable them on users when ENABLE_HARDENING=ON.

It would be good to have them in a CI task

Right, I changed a few CI tasks to use them, enabling them manually, outside of what CMake does by default, see the second commit and the PR description.

Copy link
Member

Choose a reason for hiding this comment

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

Why would the other tasks not break the ABI? Just because CI is green doesn't mean the change is correct. Instead of repeating what the changes are doing in the description, it would be more helpful to reviewers to explain why the changes are correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, wait, it was the tsan task, not the multiprocess task! Edited the above comment of mine. Anyway, it does not matter that much - it is an ABI incompatibility, so the conclusion is the same: don't enable _LIBCPP_ABI_BOUNDED* on users when ENABLE_HARDENING=ON.

Why would the other tasks not break the ABI?

I am not sure exactly, but it has probably something to do with the way libc++ is compiled or _LIBCPP_HARDENING_MODE_FAST not playing well with _LIBCPP_ABI_BOUNDED*. Here is the exact failure: https://api.cirrus-ci.com/v1/task/5304194619408384/logs/ci.log, see undefined reference to ... std::__1::basic_string<...>::insert(std::__1::__bounded_iter<.... This error just does not happen on the other tasks.

Just because CI is green doesn't mean the change is correct.

If there are ABI incompatibilities, then it will fail to compile with undefined reference because it cannot find __bounded_iter or std::__debug::whatever in the standard library.

Copy link
Member

Choose a reason for hiding this comment

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

This error just does not happen on the other tasks.

Why would it not happen? I am happy to review and test this myself, but I'd appreciate if you could explain, or at least test the changes yourself. The link error will absolutely happen on the tasks, if you tested it.

The only reason why the CI is passing is that the ABI checks haven't been implemented in clang-16, so if you tested your change, you could see that the addition to the CI task is a no-op (as in: It can't find any more bugs than before). Even worse, once the CI task is bumped to use a clang version that implements the ABI-breaking checks, it will fail the link step.

If I wanted to look at code, where the author simply claims that it is working, without an explanation or tests, I could just ask an LLM. However, I am thinking that the changes in this pull request are important enough to get right. Simply dismissing my comments and resolving the discussion threads doesn't seem helpful nor productive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All _LIBCPP_ABI_BOUNDED* additions have been removed from this PR.

@vasild vasild force-pushed the libc_hardening branch 3 times, most recently from 9a024f7 to 3d3f9b3 Compare December 10, 2024 09:35
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

There will need to be a workaround for gcc12-14, see #31436

Edit: Fixed in #31493


if(HAVE_LIBCPP)
# https://libcxx.llvm.org/Hardening.html
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
Copy link
Member

Choose a reason for hiding this comment

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

See here for a more robust approach to the if(debug) test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I considered core_interface_debug, but I couldn't fit it into this pattern:

if debug then
    add compile-definitions-A to hardening_interface
else
    add compile-definitions-B to hardening_interface

because

  1. the surrounding code adds stuff to hardening_interface and I wanted to do the same, not add to core_interface_debug and

  2. if I add to core_interface_debug then in the non-debug case (the else branch), where should I add compile-definitions-B to? core_interface will be used for both release and debug. I think we don't have a symmetric core_interface_release which will only be used in release, like core_interface_debug is used only in debug.

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

concept ack, but it would be good to get the (CI) changes right

When `ENABLE_HARDENING` is `ON` (which is the default), then enable
light or full hardening in libc++ or libstdc++, depending on the
library used and depending on the build type.

Remove hardening flags from `depends/hosts/linux.mk` in favor of the
more generic way to enable them via CMake. The same would be achieved
after this change by
`cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_HARDENING=ON`.

Inspired by
bitcoin#31272 (comment)
@vasild
Copy link
Contributor Author

vasild commented Jan 6, 2025

7daae2cf96...39a1326488: drop some changes and move them into a separate PR: #31612, suggested by @maflcko

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 6, 2025

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/35202310502

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@vasild
Copy link
Contributor Author

vasild commented Jan 7, 2025

39a1326488...1ef9f7ca4a: drop the additions of _LIBCPP_ABI_BOUNDED* when compiling Bitcoin Core. See #31612 (comment) for reasoning.

@@ -13,12 +13,6 @@ linux_release_CXXFLAGS=$(linux_release_CFLAGS)
linux_debug_CFLAGS=-O1 -g
linux_debug_CXXFLAGS=$(linux_debug_CFLAGS)

# https://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
Copy link
Member

Choose a reason for hiding this comment

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

Not sure. This just seems to be removing an option (that otherwise doesn't conflict with the other changes)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this because the same options were enabled by CMake's HARDERNING option when in Debug mode, so this became redundant. However, later I changed HARDENING+Debug to only use _GLIBCXX_ASSERTIONS instead of -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC because the latter produced: undefined reference to mp::Connection::removeSyncCleanup(__gnu_debug::_Safe_iterator....

Maybe this should stay. I wonder why using -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC from here does not produce an error...

# normal mode which could cause linkage failures like:
# undefined reference to `mp::Connection::removeSyncCleanup(__gnu_debug::_Safe_iterator...`
# when an external library is not compiled with _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC,
# so we use _GLIBCXX_ASSERTIONS in both Debug and non-Debug builds.
Copy link
Member

@fanquake fanquake Jan 10, 2025

Choose a reason for hiding this comment

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

so we use _GLIBCXX_ASSERTIONS in both Debug and non-Debug builds.

How did you test the performance impacts of this? For example master (non-Debug build, using GCC 14.2.0):

./master/src/bench/bench_bitcoin -filter=LinearizeOptimallyExample11
|               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|      495,996,518.00 |                2.02 |    0.3% |5,431,375,728.00 |1,800,295,241.00 |  3.017 | 551,650,338.00 |    0.2% |      5.44 | `LinearizeOptimallyExample11`

vs this PR:

./build_assertions/src/bench/bench_bitcoin -filter=LinearizeOptimallyExample11
|               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|      678,578,986.00 |                1.47 |    0.2% |8,178,898,994.00 |2,465,218,869.00 |  3.318 |1,114,065,676.00 |    0.1% |      7.47 | `LinearizeOptimallyExample11`

Also see: https://corecheck.dev/bitcoin/bitcoin/pulls/31424.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting. Is that using libstdc++ or libc++?

Copy link
Member

Choose a reason for hiding this comment

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

libstdc++

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants