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

cmake: Add hardening, reduce exports, werror and install #32

Merged
merged 9 commits into from
Oct 29, 2023
125 changes: 92 additions & 33 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ cmake_dependent_option(BUILD_WALLET_TOOL "Build bitcoin-wallet tool." ON "ENABLE

cmake_dependent_option(CXX20 "Enable compilation in C++20 mode." OFF "NOT MSVC" ON)
option(THREADLOCAL "Enable features that depend on the C++ thread_local keyword (currently just thread names in debug logs)." ON)
option(HARDENING "Attempt to harden the resulting executables." ON)
option(REDUCE_EXPORTS "Attempt to reduce exported symbols in the resulting executables." OFF)
option(WERROR "Treat compiler warnings as errors." OFF)

tristate_option(CCACHE "Use ccache for compiling." "if ccache is found." AUTO)
tristate_option(WITH_NATPMP "Enable NAT-PMP." "if libnatpmp is found." AUTO)
Expand All @@ -66,6 +69,7 @@ tristate_option(WITH_USDT

option(BUILD_TESTS "Build test_bitcoin executable." ON)
option(BUILD_BENCH "Build bench_bitcoin executable." ON)
option(INSTALL_MAN "Install man pages." ON)

if(CXX20)
set(CMAKE_CXX_STANDARD 20)
Expand Down Expand Up @@ -135,10 +139,10 @@ if(WIN32)
_WINDOWS
_MT
)
try_append_linker_flag(core "-static")
try_append_linker_flag("-static" TARGET core)
# We require Windows 7 (NT 6.1) or later.
try_append_linker_flag(core "-Wl,--major-subsystem-version,6")
try_append_linker_flag(core "-Wl,--minor-subsystem-version,1")
try_append_linker_flag("-Wl,--major-subsystem-version,6" TARGET core)
try_append_linker_flag("-Wl,--minor-subsystem-version,1" TARGET core)
endif()
endif()

Expand All @@ -157,9 +161,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# These flags are specific to ld64, and may cause issues with other linkers.
# For example: GNU ld will interpret -dead_strip as -de and then try and use
# "ad_strip" as the symbol for the entry point.
try_append_linker_flag(core "-Wl,-dead_strip")
try_append_linker_flag(core "-Wl,-dead_strip_dylibs")
try_append_linker_flag(core "-Wl,-headerpad_max_install_names")
try_append_linker_flag("-Wl,-dead_strip" TARGET core)
try_append_linker_flag("-Wl,-dead_strip_dylibs" TARGET core)
try_append_linker_flag("-Wl,-headerpad_max_install_names" TARGET core)
endif()

if(CMAKE_CROSSCOMPILING)
Expand Down Expand Up @@ -212,25 +216,89 @@ else()
replace_c_flag_in_config(Release -O3 -O2)
replace_cxx_flag_in_config(Release -O3 -O2)

set(debug_c_flags "")
set(debug_cxx_flags "")
try_append_cxx_flags(debug_cxx_flags "-O0" RESULT_VAR compiler_supports_O0)
if(compiler_supports_O0)
string(STRIP "${debug_c_flags} -O0" debug_c_flags)
set(debug_flags)
try_append_cxx_flags("-O0" VAR debug_flags)
try_append_cxx_flags("-g3" VAR debug_flags RESULT_VAR compiler_supports_g3)
if(NOT compiler_supports_g3)
try_append_cxx_flags("-g" VAR debug_flags)
endif()
try_append_cxx_flags(debug_cxx_flags "-g3" RESULT_VAR compiler_supports_g3)
if(compiler_supports_g3)
string(STRIP "${debug_c_flags} -g3" debug_c_flags)
set(CMAKE_C_FLAGS_DEBUG "${debug_flags}")
try_append_cxx_flags("-ftrapv" VAR debug_flags)
set(CMAKE_CXX_FLAGS_DEBUG "${debug_flags}")
unset(debug_flags)
endif()

include(cmake/optional.cmake)

# Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review.
try_append_cxx_flags("-fno-extended-identifiers" TARGET core)

# Currently all versions of gcc are subject to a class of bugs, see the
# gccbug_90348 test case (only reproduces on GCC 11 and earlier) and
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111843. To work around that, set
# -fstack-reuse=none for all gcc builds. (Only gcc understands this flag).
try_append_cxx_flags("-fstack-reuse=none" TARGET core)

if(HARDENING)
add_library(hardening INTERFACE)
if(MSVC)
try_append_linker_flag("/DYNAMICBASE" TARGET hardening)
try_append_linker_flag("/HIGHENTROPYVA" TARGET hardening)
try_append_linker_flag("/NXCOMPAT" TARGET hardening)
else()
try_append_cxx_flags(debug_cxx_flags "-g")
string(STRIP "${debug_c_flags} -g" debug_c_flags)
target_compile_options(hardening INTERFACE
$<$<NOT:$<CONFIG:Debug>>:-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3>
)

try_append_cxx_flags("-Wstack-protector" TARGET hardening)
try_append_cxx_flags("-fstack-protector-all" TARGET hardening)
try_append_cxx_flags("-fcf-protection=full" TARGET hardening)

if(MINGW)
# stack-clash-protection doesn't compile with GCC 10 and earlier.
# In any case, it is a no-op for Windows.
# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 for more details.
else()
try_append_cxx_flags("-fstack-clash-protection" TARGET hardening)
endif()

if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
Copy link

Choose a reason for hiding this comment

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

Could you explain why this checks for both? Do we need to do this everywhere we want aarch64?

Copy link
Owner Author

@hebasto hebasto Oct 26, 2023

Choose a reason for hiding this comment

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

Could you explain why this checks for both? Do we need to do this everywhere we want aarch64?

It is "arm64" on macOS, and "aarch64" on Linux.

We can use CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)" though.

Copy link

Choose a reason for hiding this comment

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

Got it. Nah, thanks, it's good as-is.

try_append_cxx_flags("-mbranch-protection=bti" TARGET hardening)
endif()

try_append_linker_flag("-Wl,--enable-reloc-section" TARGET hardening)
try_append_linker_flag("-Wl,--dynamicbase" TARGET hardening)
try_append_linker_flag("-Wl,--nxcompat" TARGET hardening)
try_append_linker_flag("-Wl,--high-entropy-va" TARGET hardening)
try_append_linker_flag("-Wl,-z,relro" TARGET hardening)
try_append_linker_flag("-Wl,-z,now" TARGET hardening)
try_append_linker_flag("-Wl,-z,separate-code" TARGET hardening)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
try_append_linker_flag("-Wl,-bind_at_load" TARGET hardening)
try_append_linker_flag("-Wl,-fixup_chains" TARGET hardening)
endif()
endif()
try_append_cxx_flags(debug_cxx_flags "-ftrapv")
set(CMAKE_C_FLAGS_DEBUG "${debug_c_flags}")
set(CMAKE_CXX_FLAGS_DEBUG "${debug_cxx_flags}")
target_link_libraries(core INTERFACE hardening)
endif()

include(cmake/optional.cmake)
if(REDUCE_EXPORTS)
Copy link

Choose a reason for hiding this comment

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

Note that this should probably become on by default in the future (once libbitcoinkernel is ok with it).

Historically it was only off by default because of an ancient broken boost impl that didn't properly export its exceptions.

set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
try_append_linker_flag("-Wl,--exclude-libs,ALL" TARGET core)
endif()

if(WERROR)
if(MSVC)
set(werror_flag "/WX")
else()
set(werror_flag "-Werror")
endif()
try_append_cxx_flags(${werror_flag} TARGET core RESULT_VAR compiler_supports_werror)
if(NOT compiler_supports_werror)
message(FATAL_ERROR "WERROR set but ${werror_flag} is not usable.")
endif()
unset(werror_flag)
endif()

find_package(Python3 3.9 COMPONENTS Interpreter)
set(PYTHON_COMMAND ${Python3_EXECUTABLE})
Expand Down Expand Up @@ -279,25 +347,16 @@ message("C++ compiler .......................... ${CMAKE_CXX_COMPILER}")
list(JOIN DEPENDS_CXX_COMPILER_FLAGS " " depends_cxx_flags)
string(STRIP "${CMAKE_CXX_FLAGS} ${depends_cxx_flags}" combined_cxx_flags)
message("CXXFLAGS .............................. ${combined_cxx_flags}")
get_target_property(common_compile_options core INTERFACE_COMPILE_OPTIONS)
if(common_compile_options)
list(JOIN common_compile_options " " common_compile_options)
else()
set(common_compile_options)
endif()
string(GENEX_STRIP "${common_compile_options}" common_compile_options)
get_target_interface(common_compile_options core COMPILE_OPTIONS)
message("Common compile options ................ ${common_compile_options}")
get_target_property(common_link_options core INTERFACE_LINK_OPTIONS)
if(common_link_options)
list(JOIN common_link_options " " common_link_options)
else()
set(common_link_options)
endif()
get_target_interface(common_link_options core LINK_OPTIONS)
message("Common link options ................... ${common_link_options}")
message("Linker flags for executables .......... ${CMAKE_EXE_LINKER_FLAGS}")
message("Linker flags for shared libraries ..... ${CMAKE_SHARED_LINKER_FLAGS}")
print_config_flags()
message("Use assembly routines ................. ${ASM}")
message("Attempt to harden executables ......... ${HARDENING}")
message("Treat compiler warnings as errors ..... ${WERROR}")
message("Use ccache for compiling .............. ${CCACHE}")
message("\n")
if(configure_warnings)
Expand Down
23 changes: 22 additions & 1 deletion cmake/module/ProcessConfigurations.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ endfunction()
function(separate_by_configs options)
list(JOIN ${options} " " ${options}_ALL)
string(GENEX_STRIP "${${options}_ALL}" ${options}_ALL)
string(STRIP "${${options}_ALL}" ${options}_ALL)
set(${options}_ALL "${${options}_ALL}" PARENT_SCOPE)

get_all_configs(all_configs)
foreach(config IN LISTS all_configs)
string(REGEX MATCHALL "\\$<\\$<CONFIG:${config}>[^\n]*>" match "${${options}}")
string(REGEX MATCHALL "\\$<\\$<CONFIG:${config}>:[^<>\n]*>" match "${${options}}")
list(JOIN match " " match)
string(REPLACE "\$<\$<CONFIG:${config}>:" "" match "${match}")
string(REPLACE ">" "" match "${match}")
Expand All @@ -106,6 +107,26 @@ function(separate_by_configs options)
endforeach()
endfunction()

function(get_target_interface var target property)
get_target_property(result ${target} INTERFACE_${property})
if(result)
string(GENEX_STRIP "${result}" result)
list(JOIN result " " result)
else()
set(result)
endif()

get_target_property(dependencies ${target} INTERFACE_LINK_LIBRARIES)
if(dependencies)
foreach(dependency IN LISTS dependencies)
get_target_interface(dep_result ${dependency} ${property})
string(STRIP "${result} ${dep_result}" result)
endforeach()
endif()

set(${var} "${result}" PARENT_SCOPE)
endfunction()

function(print_config_flags)
macro(print_flags config)
string(TOUPPER "${config}" config_uppercase)
Expand Down
76 changes: 49 additions & 27 deletions cmake/module/TryAppendCXXFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,28 @@ include(CheckCXXSourceCompiles)
#[=[
Usage examples:

try_append_cxx_flags(warn_cxx_flags "-Wformat -Wformat-security")
try_append_cxx_flags("-Wformat -Wformat-security" VAR warn_cxx_flags)


try_append_cxx_flags(warn_cxx_flags "-Wsuggest-override"
try_append_cxx_flags("-Wsuggest-override" VAR warn_cxx_flags
SOURCE "struct A { virtual void f(); }; struct B : A { void f() final; };"
)


try_append_cxx_flags(sanitizers_cxx_flags "-fsanitize=${SANITIZERS}" RESULT_VAR cxx_supports_sanitizers)
try_append_cxx_flags("-fsanitize=${SANITIZERS}" TARGET core
RESULT_VAR cxx_supports_sanitizers
)
if(NOT cxx_supports_sanitizers)
message(FATAL_ERROR "Compiler did not accept requested flags.")
endif()


try_append_cxx_flags(warn_cxx_flags "-Wunused-parameter" IF_CHECK_PASSED "-Wno-unused-parameter")
try_append_cxx_flags("-Wunused-parameter" TARGET core
IF_CHECK_PASSED "-Wno-unused-parameter"
)


try_append_cxx_flags(error_cxx_flags "-Werror=return-type"
try_append_cxx_flags("-Werror=return-type" TARGET core
IF_CHECK_FAILED "-Wno-error=return-type"
SOURCE "#include <cassert>\nint f(){ assert(false); }"
)
Expand All @@ -37,49 +41,67 @@ In configuration output, this function prints a string by the following pattern:
-- Performing Test CXX_SUPPORTS_[flags] - Success

]=]
function(try_append_cxx_flags flags_var flags)
cmake_parse_arguments(PARSE_ARGV 2
function(try_append_cxx_flags flags)
cmake_parse_arguments(PARSE_ARGV 1
TACXXF # prefix
"" # options
"SOURCE;RESULT_VAR" # one_value_keywords
"TARGET;VAR;SOURCE;RESULT_VAR" # one_value_keywords
"IF_CHECK_PASSED;IF_CHECK_FAILED" # multi_value_keywords
)

string(MAKE_C_IDENTIFIER "${flags}" result)
string(TOUPPER "${result}" result)
set(result "CXX_SUPPORTS_${result}")

# Every subsequent check_cxx_source_compiles((<code> <resultVar>) run will re-use
# the cached result rather than performing the check again, even if the <code> changes.
# Removing the cached result in order to force the check to be re-evaluated.
unset(${result} CACHE)

if(NOT DEFINED TACXXF_SOURCE)
set(TACXXF_SOURCE "int main() { return 0; }")
string(PREPEND result CXX_SUPPORTS_)

set(source "int main() { return 0; }")
if(DEFINED TACXXF_SOURCE AND NOT TACXXF_SOURCE STREQUAL source)
set(source "${TACXXF_SOURCE}")
string(SHA256 source_hash "${source}")
string(SUBSTRING ${source_hash} 0 4 source_hash_head)
string(APPEND result _${source_hash_head})
endif()

# This avoids running a linker.
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_REQUIRED_FLAGS "${flags} ${working_compiler_werror_flag}")
check_cxx_source_compiles("${TACXXF_SOURCE}" ${result})
check_cxx_source_compiles("${source}" ${result})

if(${result})
if(DEFINED TACXXF_IF_CHECK_PASSED)
string(STRIP "${${flags_var}} ${TACXXF_IF_CHECK_PASSED}" ${flags_var})
if(DEFINED TACXXF_TARGET)
target_compile_options(${TACXXF_TARGET} INTERFACE ${TACXXF_IF_CHECK_PASSED})
endif()
if(DEFINED TACXXF_VAR)
string(STRIP "${${TACXXF_VAR}} ${TACXXF_IF_CHECK_PASSED}" ${TACXXF_VAR})
endif()
else()
string(STRIP "${${flags_var}} ${flags}" ${flags_var})
if(DEFINED TACXXF_TARGET)
target_compile_options(${TACXXF_TARGET} INTERFACE ${flags})
endif()
if(DEFINED TACXXF_VAR)
string(STRIP "${${TACXXF_VAR}} ${flags}" ${TACXXF_VAR})
endif()
endif()
elseif(DEFINED TACXXF_IF_CHECK_FAILED)
string(STRIP "${${flags_var}} ${TACXXF_IF_CHECK_FAILED}" ${flags_var})
if(DEFINED TACXXF_TARGET)
target_compile_options(${TACXXF_TARGET} INTERFACE ${TACXXF_IF_CHECK_FAILED})
endif()
if(DEFINED TACXXF_VAR)
string(STRIP "${${TACXXF_VAR}} ${TACXXF_IF_CHECK_FAILED}" ${TACXXF_VAR})
endif()
endif()

if(DEFINED TACXXF_VAR)
set(${TACXXF_VAR} "${${TACXXF_VAR}}" PARENT_SCOPE)
endif()

if(DEFINED TACXXF_RESULT_VAR)
set(${TACXXF_RESULT_VAR} "${${result}}" PARENT_SCOPE)
endif()
set(${flags_var} "${${flags_var}}" PARENT_SCOPE)
set(${TACXXF_RESULT_VAR} "${${result}}" PARENT_SCOPE)
endfunction()

if(MSVC)
set(warning_as_error_flag /WX)
try_append_cxx_flags("/WX /options:strict" VAR working_compiler_werror_flag)
else()
set(warning_as_error_flag -Werror)
try_append_cxx_flags("-Werror" VAR working_compiler_werror_flag)
endif()
try_append_cxx_flags(working_compiler_werror_flag ${warning_as_error_flag})
unset(warning_as_error_flag)
Loading