CMake Implementation of OpenSSF Compiler Hardening Options
C++ C++ cmake security openssf
Published: 2024-11-12
CMake Implementation of OpenSSF Compiler Hardening Options

This post describes how to implement the OpenSSF Compiler Options Hardening Guide for C and C++ in CMake.

On October 31, 2024, the Open Source Security Foundation (OpenSSF) published a compiler options hardening guide for C and C++ with the following objective:

The objective of compiler options hardening is to produce application binaries (executables) with security mechanisms against potential attacks and/or misbehavior.

I want to be able to integrate these guidelines into my CMake-based projects by including a CMake file, so I wrote the following, which I’m sharing for public use:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# openssf.cmake: Implement OpenSSF compiler-hardening options from
# https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++
#
# Only works on clang/GNU-based compilers (GCC, Clang, etc.) because OpenSSF
# does not provide compiler-hardening options for MSVC.

cmake_minimum_required(VERSION 3.19)

include(CheckCompilerFlag)
include(CheckLinkerFlag)

# Table 1: Recommended compiler options that enable strictly compile-time checks.
check_compiler_flag(CXX "-Wall" CXX_SUPPORTS_WALL)
if(CXX_SUPPORTS_WALL)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
endif()
check_compiler_flag(CXX "-Wextra" CXX_SUPPORTS_WEXTRA)
if(CXX_SUPPORTS_WEXTRA)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra")
endif()
check_compiler_flag(CXX "-Wformat" CXX_SUPPORTS_WFORMAT)
if(CXX_SUPPORTS_WFORMAT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat")
endif()
check_compiler_flag(CXX "-Wformat=2" CXX_SUPPORTS_WFORMAT2)
if(CXX_SUPPORTS_WFORMAT2)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2")
endif()
check_compiler_flag(CXX "-Wconversion" CXX_SUPPORTS_WCONVERSION)
if(CXX_SUPPORTS_WCONVERSION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion")
endif()
check_compiler_flag(CXX "-Wsign-conversion" CXX_SUPPORTS_WSIGNCONVERSION)
if(CXX_SUPPORTS_WSIGNCONVERSION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion")
endif()
check_compiler_flag(CXX "-Wtrampolines" CXX_SUPPORTS_WTRAMPOLINES)
if(CXX_SUPPORTS_WTRAMPOLINES)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wtrampolines")
endif()
check_compiler_flag(CXX "-Wimplicit-fallthrough" CXX_SUPPORTS_WIMPLICIT_FALLTHROUGH)
if(CXX_SUPPORTS_WIMPLICIT_FALLTHROUGH)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wimplicit-fallthrough")
endif()
check_compiler_flag(CXX "-Wbidi-chars=any" CXX_SUPPORTS_WBIDI_CHARS_ANY)
if(CXX_SUPPORTS_WBIDI_CHARS_ANY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wbidi-chars=any")
endif()
check_compiler_flag(CXX "-Werror=format-security" CXX_SUPPORTS_WERROR_FORMAT_SECURITY)
if(CXX_SUPPORTS_WERROR_FORMAT_SECURITY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=format-security")
endif()
check_compiler_flag(CXX "-Werror=implicit" CXX_SUPPORTS_WERROR_IMPLICIT)
if(CXX_SUPPORTS_WERROR_IMPLICIT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=implicit")
endif()
check_compiler_flag(CXX "-Werror=incompatible-pointer-types" CXX_SUPPORTS_WERROR_INCOMPATIBLE_POINTER_TYPES)
if(CXX_SUPPORTS_WERROR_INCOMPATIBLE_POINTER_TYPES)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=incompatible-pointer-types")
endif()
check_compiler_flag(CXX "-Werror=int-conversion" CXX_SUPPORTS_WERROR_INT_CONVERSION)
if(CXX_SUPPORTS_WERROR_INT_CONVERSION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=int-conversion")
endif()
# Note that the below should only be enabled during development, not source distribution
check_compiler_flag(CXX "-Werror" CXX_SUPPORTS_WERROR)
if(CXX_SUPPORTS_WERROR)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
endif()

# Table 2: Recommended compiler options that enable run-time protection mechanisms.
check_compiler_flag(CXX "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" CXX_SUPPORTS_FORTIFY_SOURCE)
if(CXX_SUPPORTS_FORTIFY_SOURCE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3")
endif()
check_compiler_flag(CXX "-D_GLIBCXX_ASSERTIONS" CXX_SUPPORTS_GLIBCXX_ASSERTIONS)
if(CXX_SUPPORTS_GLIBCXX_ASSERTIONS)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS")
endif()
check_compiler_flag(CXX "-fstrict-flex-arrays=3" CXX_SUPPORTS_FSTRICT_FLEX_ARRAYS3)
if(CXX_SUPPORTS_FSTRICT_FLEX_ARRAYS3)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstrict-flex-arrays=3")
endif()
check_compiler_flag(CXX "-fstack-clash-protection" CXX_SUPPORTS_FSTACK_CLASH_PROTECTION)
if(CXX_SUPPORTS_FSTACK_CLASH_PROTECTION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-clash-protection")
endif()
check_compiler_flag(CXX "-fstack-protector-strong" CXX_SUPPORTS_FSTACK_PROTECTOR_STRONG)
if(CXX_SUPPORTS_FSTACK_PROTECTOR_STRONG)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
endif()
check_compiler_flag(CXX "-fcf-protection=full" CXX_SUPPORTS_FCF_PROTECTION_FULL)
if(CXX_SUPPORTS_FCF_PROTECTION_FULL)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcf-protection=full")
endif()
check_compiler_flag(CXX "-mbranch-protection=standard" CXX_SUPPORTS_MBRANCH_PROTECTION_STANDARD)
if(CXX_SUPPORTS_MBRANCH_PROTECTION_STANDARD)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mbranch-protection=standard")
endif()
check_compiler_flag(CXX "-fno-delete-null-pointer-checks" CXX_SUPPORTS_FNO_DELETE_NULL_POINTER_CHECKS)
if(CXX_SUPPORTS_FNO_DELETE_NULL_POINTER_CHECKS)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-delete-null-pointer-checks")
endif()
check_compiler_flag(CXX "-fno-strict-overflow" CXX_SUPPORTS_FNO_STRICT_OVERFLOW)
if(CXX_SUPPORTS_FNO_STRICT_OVERFLOW)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-overflow")
endif()
check_compiler_flag(CXX "-fno-strict-aliasing" CXX_SUPPORTS_FNO_STRICT_ALIASING)
if(CXX_SUPPORTS_FNO_STRICT_ALIASING)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing")
endif()
check_compiler_flag(CXX "-ftrivial-auto-var-init" CXX_SUPPORTS_FTRIVIAL_AUTO_VAR_INIT)
if(CXX_SUPPORTS_FTRIVIAL_AUTO_VAR_INIT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftrivial-auto-var-init")
endif()

check_linker_flag(CXX "-Wl,-z,nodlopen" CXX_LINKER_SUPPORTS_NODLOPEN)
if(CXX_LINKER_SUPPORTS_NODLOPEN)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nodlopen")
endif()
check_linker_flag(CXX "-Wl,-z,noexecstack" CXX_LINKER_SUPPORTS_NOEXECSTACK)
if(CXX_LINKER_SUPPORTS_NOEXECSTACK)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,noexecstack")
endif()
check_linker_flag(CXX "-Wl,-z,relro" CXX_LINKER_SUPPORTS_RELRO)
if(CXX_LINKER_SUPPORTS_RELRO)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro")
endif()
check_linker_flag(CXX "-Wl,-z,now" CXX_LINKER_SUPPORTS_NOW)
if(CXX_LINKER_SUPPORTS_NOW)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro")
endif()
check_linker_flag(CXX "-Wl,--as-needed" CXX_LINKER_SUPPORTS_AS_NEEDED)
if(CXX_LINKER_SUPPORTS_AS_NEEDED)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
endif()
check_linker_flag(CXX "-Wl,--no-copy-dt-needed-entries" CXX_LINKER_SUPPORTS_NO_COPY_DT_NEEDED_ENTRIES)
if(CXX_LINKER_SUPPORTS_NO_COPY_DT_NEEDED_ENTRIES)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-copy-dt-needed-entries")
endif()

To use this, copy the above to a file named CMake/openssf.cmake and then add include(CMake/openssf.cmake) to your CMakeLists.txt file.