From 3d405634f43d39079ee93cdc59ed7fc0a5e8917a Mon Sep 17 00:00:00 2001 From: KJ Tsanaktsidis Date: Sun, 9 Jun 2024 21:15:39 +1000 Subject: [PATCH] Extract hardening CFLAGS to a special $hardenflags variable This changes the automatic detection of -fstack-protector, -D_FORTIFY_SOURCE, and -mbranch-protection to write to $hardenflags instead of $XCFLAGS. The definition of $cflags is changed to "$hardenflags $orig_cflags $optflags $debugflags $warnflags" to match. Furthermore, these flags are _prepended_ to $hardenflags, rather than appended. The implications of doing this are as follows: * If a CRuby builder specifies cflags="-mbranch-protection=foobar" at the ./configure script, and the configure script detects that -mbranch-protection=pac-ret is accepted, then GCC will be invoked as "gcc -mbranch-protection=pac-ret -mbranch-protection=foobar". Since the last flags take precedence, that means that user-supplied values of these flags in $cflags will take priority. * Likewise, if a CRuby builder explicitly specifies "hardenflags=-mbranch-protection=foobar", because we _prepend_ to $hardenflags in our autoconf script, we will still invoke GCC as "gcc -mbranch-protection=pac-ret -mbranch-protection=foobar". * If a CRuby builder specifies CFLAGS="..." at the configure line, automatic detection of hardening flags is ignored as before. * C extensions will _also_ be built with hardening flags now as well (this was not the case by default before because the detected flags went into $XCFLAGS). Additionally, as part of this work, I changed how the detection of PAC/BTI in Context.S works. Rather than appending the autodetected option to ASFLAGS, we simply compile a set of test programs with the actual CFLAGS in use to determine what PAC/BTI settings were actually chosen by the builder. Context.S is made aware of these choices through some custom macros. The result of this work is that: * Ruby will continue to choose some sensible defaults for hardening options for the C compiler * Distributors are able to specify CFLAGS that are consistent with their distribution and override these defaults * Context.S will react to whatever -mbranch-protection is actually in use, not what was autodetected * Extensions get built with hardening flags too. [Bug #20154] [Bug #20520] --- configure.ac | 81 ++++++++++++++++++++++++++++++----- coroutine/arm64/Context.S | 14 +++--- template/Makefile.in | 1 + tool/m4/ruby_append_option.m4 | 4 ++ tool/m4/ruby_try_cflags.m4 | 17 ++++++++ 5 files changed, 100 insertions(+), 17 deletions(-) diff --git a/configure.ac b/configure.ac index f35fad6a362611..0da15772d36671 100644 --- a/configure.ac +++ b/configure.ac @@ -354,7 +354,7 @@ test -z "$warnflags" || AS_IF([test -z "${CFLAGS+set}"], [ cflags=`echo " $cflags " | sed "$cflagspat;s/^ *//;s/ *$//"` orig_cflags="$cflags" - cflags="$cflags "'${optflags} ${debugflags} ${warnflags}' + cflags='${hardenflags} '"$cflags "'${optflags} ${debugflags} ${warnflags}' ]) dnl AS_IF([test -z "${CXXFLAGS+set}"], [ dnl cxxflags=`echo " $cxxflags " | sed "$cflagspat;s/^ *//;s/ *$//"` @@ -802,7 +802,7 @@ AS_IF([test "$GCC" = yes], [ [fortify_source=$enableval]) AS_IF([test "x$fortify_source" != xno], [ RUBY_TRY_CFLAGS([$optflags -D_FORTIFY_SOURCE=2], - [RUBY_APPEND_OPTION(XCFLAGS, -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2)], [], + [RUBY_PREPEND_OPTION(hardenflags, -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2)], [], [@%:@include ]) ]) @@ -823,20 +823,24 @@ AS_IF([test "$GCC" = yes], [ AC_MSG_CHECKING([for -fstack-protector]) AC_MSG_RESULT(["$stack_protector"]) AS_CASE(["$stack_protector"], [-*], [ - RUBY_APPEND_OPTION(XCFLAGS, $stack_protector) - RUBY_APPEND_OPTION(XLDFLAGS, $stack_protector) - RUBY_APPEND_OPTION(LDFLAGS, $stack_protector) + RUBY_PREPEND_OPTION(hardenflags, $stack_protector) + RUBY_APPEND_OPTION(XLDFLAGS, $stack_protector) + RUBY_APPEND_OPTION(LDFLAGS, $stack_protector) ]) # aarch64 branch protection AS_CASE(["$target_cpu"], [aarch64], [ AS_FOR(option, opt, [-mbranch-protection=pac-ret -msign-return-address=all], [ - RUBY_TRY_CFLAGS(option, [branch_protection=yes], [branch_protection=no]) + # Try these flags in the _prepended_ position - i.e. we want to try building a program + # with CFLAGS="-mbranch-protection=pac-ret $CFLAGS". If the builder has provided different + # branch protection flags in CFLAGS, we don't want to overwrite those. We just want to + # find some branch protection flags which work if none were provided. + RUBY_TRY_CFLAGS_PREPEND(option, [branch_protection=yes], [branch_protection=no]) AS_IF([test "x$branch_protection" = xyes], [ - # C compiler and assembler must be consistent for -mbranch-protection - # since they both check `__ARM_FEATURE_PAC_DEFAULT` definition. - RUBY_APPEND_OPTION(XCFLAGS, option) - RUBY_APPEND_OPTION(ASFLAGS, option) + # _prepend_ the options to CFLAGS, so that user-provided flags will overwrite them. + # These CFLAGS are used during the configure script to compile further test programs; + # however, $harden_flags is prepended separately to CFLAGS at the end of the script. + RUBY_PREPEND_OPTION(hardenflags, $opt) break ]) ]) @@ -985,6 +989,59 @@ test -z "${ac_env_CFLAGS_set}" -a -n "${cflags+set}" && eval CFLAGS="\"$cflags $ test -z "${ac_env_CXXFLAGS_set}" -a -n "${cxxflags+set}" && eval CXXFLAGS="\"$cxxflags $ARCH_FLAG\"" } +# The lines above expand out the $cflags/$optflags/$debugflags/$hardenflags variables into the +# CFLAGS variable. So, at this point, we have a $CFLAGS var with the actual compiler flags we're +# going to use. +# That means this is the right time to check what branch protection flags are going to be in use +# and define appropriate macros for use in Context.S based on this +AS_CASE(["$target_cpu"], [aarch64], [ + AC_CACHE_CHECK([whether __ARM_FEATURE_BTI_DEFAULT is defined], + rb_cv_aarch64_bti_enabled, + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + @%:@ifndef __ARM_FEATURE_BTI_DEFAULT + @%:@error "__ARM_FEATURE_BTI_DEFAULT not defined" + @%:@endif + ]])], + [rb_cv_aarch64_bti_enabled=yes], + [rb_cv_aarch64_bti_enabled=no]) + ) + AS_IF([test "$rb_cv_aarch64_bti_enabled" = yes], + AC_DEFINE(RUBY_AARCH64_BTI_ENABLED, 1)) + AC_CACHE_CHECK([whether __ARM_FEATURE_PAC_DEFAULT is defined], + rb_cv_aarch64_pac_enabled, + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + @%:@ifndef __ARM_FEATURE_PAC_DEFAULT + @%:@error "__ARM_FEATURE_PAC_DEFAULT not defined" + @%:@endif + ]])], + [rb_cv_aarch64_pac_enabled=yes], + [rb_cv_aarch64_pac_enabled=no]) + ) + AS_IF([test "$rb_cv_aarch64_pac_enabled" = yes], + AC_DEFINE(RUBY_AARCH64_PAC_ENABLED, 1)) + # Context.S will only ever sign its return address with the A-key; it doesn't support + # the B-key at the moment. + AS_IF([test "$rb_cv_aarch64_pac_enabled" = yes], [ + AC_CACHE_CHECK([whether __ARM_FEATURE_PAC_DEFAULT specifies the b-key bit 0x02], + rb_cv_aarch64_pac_b_key, + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ + @%:@ifdef __ARM_FEATURE_PAC_DEFAULT + @%:@if __ARM_FEATURE_PAC_DEFAULT & 0x02 + @%:@error "__ARM_FEATURE_PAC_DEFAULT specifies B key" + @%:@endif + @%:@endif + ]])], + [rb_cv_aarch64_pac_b_key=no], + [rb_cv_aarch64_pac_b_key=yes]) + ) + AS_IF([test "$rb_cv_aarch64_pac_b_key" = yes], + AC_MSG_ERROR(-mbranch-protection flag specified b-key but Ruby's Context.S does not support this yet.)) + ]) +]) + AC_CACHE_CHECK([whether compiler has statement and declarations in expressions], rb_cv_have_stmt_and_decl_in_expr, [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]],[[ __extension__ ({ int a = 0; a; }); ]])], @@ -4215,12 +4272,13 @@ AS_IF([test "${ARCH_FLAG}"], [ rb_cv_warnflags=`echo "$rb_cv_warnflags" | sed 's/^ *//;s/ *$//'` warnflags="$rb_cv_warnflags" AC_SUBST(cppflags)dnl -AC_SUBST(cflags, ["${orig_cflags:+$orig_cflags }"'${optflags} ${debugflags} ${warnflags}'])dnl +AC_SUBST(cflags, ['${hardenflags} '"${orig_cflags:+$orig_cflags }"' ${optflags} ${debugflags} ${warnflags}'])dnl AC_SUBST(cxxflags)dnl AC_SUBST(optflags)dnl AC_SUBST(debugflags)dnl AC_SUBST(warnflags)dnl AC_SUBST(strict_warnflags)dnl +AC_SUBST(hardenflags)dnl AC_SUBST(XCFLAGS)dnl AC_SUBST(XLDFLAGS)dnl AC_SUBST(EXTLDFLAGS)dnl @@ -4688,6 +4746,7 @@ config_summary "DLDFLAGS" "$DLDFLAGS" config_summary "optflags" "$optflags" config_summary "debugflags" "$debugflags" config_summary "warnflags" "$warnflags" +config_summary "hardenflags" "$hardenflags" config_summary "strip command" "$STRIP" config_summary "install doc" "$DOCTARGETS" config_summary "YJIT support" "$YJIT_SUPPORT" diff --git a/coroutine/arm64/Context.S b/coroutine/arm64/Context.S index 5251ab214df1f0..54611a247e2f66 100644 --- a/coroutine/arm64/Context.S +++ b/coroutine/arm64/Context.S @@ -5,6 +5,8 @@ ## Copyright, 2018, by Samuel Williams. ## +#include "ruby/config.h" + #define TOKEN_PASTE(x,y) x##y #define PREFIXED_SYMBOL(prefix,name) TOKEN_PASTE(prefix,name) @@ -27,10 +29,10 @@ .global PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer) PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): -#if defined(__ARM_FEATURE_PAC_DEFAULT) && (__ARM_FEATURE_PAC_DEFAULT != 0) +#if defined(RUBY_AARCH64_PAC_ENABLED) # paciasp (it also acts as BTI landing pad, so no need to insert BTI also) hint #25 -#elif defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT != 0) +#elif defined(RUBY_AARCH64_BTI_ENABLED) # For the the case PAC is not enabled but BTI is. # bti c hint #34 @@ -73,7 +75,7 @@ PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): # Pop stack frame add sp, sp, 0xa0 -#if defined(__ARM_FEATURE_PAC_DEFAULT) && (__ARM_FEATURE_PAC_DEFAULT != 0) +#if defined(RUBY_AARCH64_PAC_ENABLED) # autiasp: Authenticate x30 (LR) with SP and key A hint #29 #endif @@ -85,18 +87,18 @@ PREFIXED_SYMBOL(SYMBOL_PREFIX,coroutine_transfer): .section .note.GNU-stack,"",%progbits #endif -#if __ARM_FEATURE_BTI_DEFAULT != 0 || __ARM_FEATURE_PAC_DEFAULT != 0 +#if defined(RUBY_AARCH64_BTI_ENABLED) || defined(RUBY_AARCH64_PAC_ENABLED) /* See "ELF for the Arm 64-bit Architecture (AArch64)" https://github.com/ARM-software/abi-aa/blob/2023Q3/aaelf64/aaelf64.rst#program-property */ # define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1<<0) # define GNU_PROPERTY_AARCH64_FEATURE_1_PAC (1<<1) -# if __ARM_FEATURE_BTI_DEFAULT != 0 +# if defined(RUBY_AARCH64_BTI_ENABLED) # define BTI_FLAG GNU_PROPERTY_AARCH64_FEATURE_1_BTI # else # define BTI_FLAG 0 # endif -# if __ARM_FEATURE_PAC_DEFAULT != 0 +# if defined(RUBY_AARCH64_PAC_ENABLED) # define PAC_FLAG GNU_PROPERTY_AARCH64_FEATURE_1_PAC # else # define PAC_FLAG 0 diff --git a/template/Makefile.in b/template/Makefile.in index 033ac56cb38886..abb4469777ce8a 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -89,6 +89,7 @@ cflags = @cflags@ optflags = @optflags@ debugflags = @debugflags@ warnflags = @warnflags@ @strict_warnflags@ +hardenflags = @hardenflags@ cppflags = @cppflags@ incflags = @incflags@ RUBY_DEVEL = @RUBY_DEVEL@ # "yes" or empty diff --git a/tool/m4/ruby_append_option.m4 b/tool/m4/ruby_append_option.m4 index ff828d2162c22f..98359fa1f95f52 100644 --- a/tool/m4/ruby_append_option.m4 +++ b/tool/m4/ruby_append_option.m4 @@ -3,3 +3,7 @@ AC_DEFUN([RUBY_APPEND_OPTION], [# RUBY_APPEND_OPTION($1) AS_CASE([" [$]{$1-} "], [*" $2 "*], [], [' '], [ $1="$2"], [ $1="[$]$1 $2"])])dnl +AC_DEFUN([RUBY_PREPEND_OPTION], + [# RUBY_APPEND_OPTION($1) + AS_CASE([" [$]{$1-} "], + [*" $2 "*], [], [' '], [ $1="$2"], [ $1="$2 [$]$1"])])dnl diff --git a/tool/m4/ruby_try_cflags.m4 b/tool/m4/ruby_try_cflags.m4 index b74718fe5e1cef..b397642aad9ca2 100644 --- a/tool/m4/ruby_try_cflags.m4 +++ b/tool/m4/ruby_try_cflags.m4 @@ -17,3 +17,20 @@ AC_DEFUN([RUBY_TRY_CFLAGS], [ AC_MSG_RESULT(no)]) ]) ])dnl + +AC_DEFUN([_RUBY_TRY_CFLAGS_PREPEND], [ + RUBY_WERROR_FLAG([ + CFLAGS="$1 [$]CFLAGS" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$4]], [[$5]])], + [$2], [$3]) + ])dnl +])dnl +AC_DEFUN([RUBY_TRY_CFLAGS_PREPEND], [ + AC_MSG_CHECKING([whether ]$1[ is accepted as CFLAGS])dnl + _RUBY_TRY_CFLAGS_PREPEND([$1], + [$2 + AC_MSG_RESULT(yes)], + [$3 + AC_MSG_RESULT(no)], + [$4], [$5]) +])dnl