Add option to clear caller-used registers on function return.

The WITH_ZEROREGS option for src.conf will zero caller-used register
contents just before returning from a function, ensuring that
temporary values are not leaked beyond the function boundary. This
means that register contents are less likely to be available for side
channel attacks and information exposures.

It reduces all except 1 of the simple "write-what-where" ROP gadgets in
/lib:
    grep "Gadget found" /tmp/before_lib* | wc -l
     197
    grep "Gadget found" /tmp/after_lib* | wc -l
       1
    grep "Gadget found" /tmp/after_lib*
    /tmp/after_libbsdxml.so.4.txt:  [+] Gadget found: 0x1b3f1 mov qword ptr [rdi], rcx ; pop rbp ; ret

To reproduce:
    for lib in *.so.*; do
        echo $lib:
        ROPgadget --ropchain --binary /tmp/be_mount.Sx87/lib/$lib | sed -n '/Step 1/,/Step 2/p' >! /tmp/before_$lib.txt
        ROPgadget --ropchain --binary $lib | sed -n '/Step 1/,/Step 2/p' >!  /tmp/after_$lib.txt
    done

Additionally, in some cases this reduces the number of all ROP gadgets
(quick check with /libs only):
libalias.so.7: reduction 10.000%
libavl.so.2: reduction 13.900%
libbsdxml.so.4: reduction 37.500%
libc.so.7: reduction 10.000%
libc++.so.1: reduction 14.800%
libcam.so.7: reduction 50.700%
libcap_netdb.so.1: reduction 5.800%
libcasper.so.1: reduction 14.600%
libcrypto.so.30: reduction 7.500%
libdtrace.so.2: reduction 3.900%
libelf.so.2: reduction 15.800%
libgcc_s.so.1: reduction 32.700%
libibverbs.so.1: reduction 5.300%
libicp.so.3: reduction 2.100%
libipt.so.0: reduction 28.200%
libirdma.so.1: reduction 1.600%
libkiconv.so.4: reduction 0%
libm.so.5: reduction 21.900%
libmd.so.6: reduction 0%
libmd.so.7: reduction 3.100%
libncursesw.so.9: reduction 11.200%
libnvpair.so.2: reduction 40.200%
libpcap.so.8: reduction 11.400%
libpjdlog.so.0: reduction 27.400%
libsbuf.so.6: reduction 2.900%
libspl.so.2: reduction 42.300%
libsys.so.7: reduction 2.700%
libthr.so.3: reduction 21.000%
libuutil.so.2: reduction 13.100%
libz.so.6: reduction 5.600%
libzpool.so.2: reduction 15.100%

In some cases it adds some ROP gadgets despite removing the simple ROP
gadgets:
lib80211.so.1: reduction -32.700%
libbe.so.1: reduction -22.300%
libbegemot.so.4: reduction -20.500%
libcap_dns.so.2: reduction -58.000%
libcap_fileargs.so.1: reduction -28.200%
libcap_grp.so.1: reduction -54.000%
libcap_net.so.1: reduction -28.800%
libcap_pwd.so.1: reduction -38.800%
libcap_sysctl.so.2: reduction -71.100%
libcap_syslog.so.1: reduction -15.000%
libcrypt.so.5: reduction -14.600%
libctf.so.2: reduction -.300%
libcxxrt.so.1: reduction -14.000%
libdevstat.so.7: reduction -1.600%
libedit.so.8: reduction -4.200%
libgeom.so.5: reduction -16.500%
libicp_rescue.so.3: reduction -2.300%
libipsec.so.4: reduction -31.800%
libjail.so.1: reduction -21.700%
libkvm.so.7: reduction -5.300%
libmlx5.so.1: reduction -6.300%
libmt.so.5: reduction -23.000%
libnv.so.1: reduction -.400%
librss.so.1: reduction -3.800%
librt.so.1: reduction -24.000%
libssp.so.0: reduction -21.100%
libstats.so.0: reduction -9.000%
libtinfow.so.9: reduction -3.500%
libtpool.so.2: reduction -36.500%
libufs.so.8: reduction -11.900%
libulog.so.0: reduction -67.400%
libumem.so.2: reduction -2.000%
libutil.so.9: reduction -7.200%
libxo.so.0: reduction -9.000%
libzdb.so.2: reduction -11.700%
libzfs_core.so.2: reduction -17.700%
libzfs.so.4: reduction -.300%
libzfsbootenv.so.1: reduction -26.900%
libzutil.so.2: reduction -5.600%

To reproduce:
    for lib in *.so.*; do
        echo -n $lib:
        before="$(ROPgadget --nosys --nojop --binary /tmp/be_mount.Sx87/lib/$lib | tail -n1 | cut -d : -f 2)"
        after="$(ROPgadget --nosys --nojop --binary $lib | tail -n1 | cut -d : -f 2)"
        echo " reduction" $(bc -S 3 -e "(1-${after}/${before})*100")%
    done >/tmp/reduction.txt

Most of the time the size difference is very small (<1% for >50% of the
files and >10% for only 2 files):
lib80211.so.1: size change .100%
libalias.so.7: size change 0%
libavl.so.2: size change 0%
libbe.so.1: size change .100%
libbegemot.so.4: size change .100%
libbsdxml.so.4: size change 0%
libc.so.7: size change 1.200%
libc++.so.1: size change 1.600%
libcam.so.7: size change 1.900%
libcap_dns.so.2: size change .100%
libcap_fileargs.so.1: size change .100%
libcap_grp.so.1: size change .100%
libcap_net.so.1: size change .100%
libcap_netdb.so.1: size change .100%
libcap_pwd.so.1: size change .100%
libcap_sysctl.so.2: size change .100%
libcap_syslog.so.1: size change .100%
libcasper.so.1: size change 0%
libcrypt.so.5: size change 3.900%
libcrypto.so.30: size change 1.400%
libctf.so.2: size change .100%
libcxxrt.so.1: size change .100%
libdevstat.so.7: size change 15.400%		exceptional
libdtrace.so.2: size change .600%
libedit.so.8: size change 1.800%
libelf.so.2: size change .100%
libgcc_s.so.1: size change 3.000%
libgeom.so.5: size change 0%
libibverbs.so.1: size change .100%
libicp_rescue.so.3: size change .100%
libicp.so.3: size change 1.500%
libipsec.so.4: size change .100%
libipt.so.0: size change 3.100%
libirdma.so.1: size change .100%
libjail.so.1: size change .100%
libkiconv.so.4: size change .100%
libkvm.so.7: size change .100%
libm.so.5: size change 1.700%
libmd.so.6: size change 0%
libmd.so.7: size change .100%
libmlx5.so.1: size change 0%
libmt.so.5: size change .100%
libncursesw.so.9: size change 1.900%
libnv.so.1: size change 4.300%
libnvpair.so.2: size change 4.300%
libpcap.so.8: size change 1.200%
libpjdlog.so.0: size change .100%
librss.so.1: size change .200%
librt.so.1: size change .100%
libsbuf.so.6: size change .100%
libspl.so.2: size change 0%
libssp.so.0: size change .100%
libstats.so.0: size change .100%
libsys.so.7: size change .100%
libthr.so.3: size change 2.400%
libtinfow.so.9: size change 1.600%
libtpool.so.2: size change .100%
libufs.so.8: size change .100%
libulog.so.0: size change .100%
libumem.so.2: size change 54.300%		exceptional
libutil.so.9: size change .100%
libuutil.so.2: size change .100%
libxo.so.0: size change .100%
libz.so.6: size change .100%
libzdb.so.2: size change .300%
libzfs_core.so.2: size change .100%
libzfs.so.4: size change 2.000%
libzfsbootenv.so.1: size change .100%
libzpool.so.2: size change 1.200%
libzutil.so.2: size change 0%
This commit is contained in:
Alexander Leidinger 2025-01-11 13:48:50 +01:00 committed by Alexander Leidinger
parent 9dcb984251
commit 2a44cccd40
8 changed files with 39 additions and 2 deletions

View file

@ -1,5 +1,5 @@
.\" DO NOT EDIT-- this file is @generated by tools/build/options/makeman.
.Dd November 22, 2024
.Dd January 22, 2025
.Dt SRC.CONF 5
.Os
.Sh NAME
@ -1856,6 +1856,11 @@ Build
without support for the IEEE 802.1X protocol and without
support for EAP-PEAP, EAP-TLS, EAP-LEAP, and EAP-TTLS
protocols (usable only via 802.1X).
.It Va WITH_ZEROREGS
Build the basesystem with code to zero caller-used register contents
on function return.
This prevents leaking temporary values for side channel attacks.
Additionally this reduces the number of usable ROP gadgets for attackers.
.It Va WITHOUT_ZFS
Do not build the ZFS file system kernel module, libraries such as
.Xr libbe 3 ,

View file

@ -24,6 +24,7 @@
# - retpoline: supports the retpoline speculative execution vulnerability
# mitigation.
# - init-all: supports stack variable initialization.
# - zeroregs: supports zeroing used registers on return
# - aarch64-sha512: supports the AArch64 sha512 intrinsic functions.
#
# When bootstrapping on macOS, 'apple-clang' will be set in COMPILER_FEATURES
@ -263,6 +264,11 @@ ${X_}COMPILER_FEATURES+= compressed-debug
${X_}COMPILER_FEATURES+= fileprefixmap
.endif
.if (${${X_}COMPILER_TYPE} == "clang" && ${${X_}COMPILER_VERSION} >= 150000) || \
(${${X_}COMPILER_TYPE} == "gcc" && ${${X_}COMPILER_VERSION} >= 110000)
${X_}COMPILER_FEATURES+= zeroregs
.endif
.if (${${X_}COMPILER_TYPE} == "clang" && ${${X_}COMPILER_VERSION} >= 130000) || \
(${${X_}COMPILER_TYPE} == "gcc" && ${${X_}COMPILER_VERSION} >= 90000)
# AArch64 sha512 intrinsics are supported (and have been tested) in

View file

@ -118,6 +118,15 @@ CXXFLAGS+= -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-cl
.endif
.endif
# Zero used registers on return (mitigate some ROP)
.if ${MK_ZEROREGS} != "no"
.if ${COMPILER_FEATURES:Mzeroregs}
ZEROREG_TYPE?= used
CFLAGS+= -fzero-call-used-regs=${ZEROREG_TYPE}
CXXFLAGS+= -fzero-call-used-regs=${ZEROREG_TYPE}
.endif
.endif
# bsd.sanitizer.mk is not installed, so don't require it (e.g. for ports).
.sinclude "bsd.sanitizer.mk"

View file

@ -81,7 +81,8 @@ __DEFAULT_NO_OPTIONS = \
RETPOLINE \
STALE_STAGED \
UBSAN \
UNDEFINED_VERSION
UNDEFINED_VERSION \
ZEROREGS
__DEFAULT_DEPENDENT_OPTIONS = \
MAKE_CHECK_USE_SANDBOX/TESTS \

View file

@ -90,6 +90,15 @@ CXXFLAGS+= -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-cl
.endif
.endif
# Zero used registers on return (mitigate some ROP)
.if ${MK_ZEROREGS} != "no"
.if ${COMPILER_FEATURES:Mzeroregs}
ZEROREG_TYPE?= used
CFLAGS+= -fzero-call-used-regs=${ZEROREG_TYPE}
CXXFLAGS+= -fzero-call-used-regs=${ZEROREG_TYPE}
.endif
.endif
# bsd.sanitizer.mk is not installed, so don't require it (e.g. for ports).
.sinclude "bsd.sanitizer.mk"

View file

@ -11,6 +11,7 @@ FORTIFY_SOURCE= 0
MK_CTF= no
MK_SSP= no
MK_PIE= no
MK_ZEROREGS= no
MAN=
.if !defined(PIC)
NO_PIC=

View file

@ -0,0 +1,2 @@
Do not build build the basesystem with code to zero caller-used register
contents on function return.

View file

@ -0,0 +1,4 @@
Build the basesystem with code to zero caller-used register contents
on function return.
This prevents leaking temporary values for side channel attacks.
Additionally this reduces the number of usable ROP gadgets for attackers.