[PATCH v5 1/1] aarch64: support PAC and BTI

Jeremy Linton jeremy.linton at arm.com
Fri Apr 18 02:56:30 CEST 2025


Hi,

First I apologize, that this mail will likely not thread properly.

Secondly, thanks for working on this!

I spun this up on an orion6 (PAC+BTI+MTE) board running Fedora 42 and it 
seems to be working correctly. The library now has appropriate gnu 
notes, and the unit tests/etc all seem to be working as expected. I 
noticed a few largly trivial things while reading the patch.


On 3/25/25 Bill Roberts,  wrote:
> 
> Enable Pointer Authentication Codes (PAC) and Branch Target
> Identification (BTI) support for ARM 64 targets.
> 
> PAC works by signing the LR with either an A key or B key and verifying
> the return address. There are quite a few instructions capable of doing
> this, however, the Linux ARM ABI is to use hint compatible instructions
This might be better worded something similar to:

"While there are several instructions that can perform this operation, 
the Linux ARM ABI uses hint-space instructions which execute as NOPs on 
older hardware."
> that can be safely NOP'd on older hardware and can be assembled and
> linked with older binutils. This limits the instruction set to paciasp,
> pacibsp, autiasp and autibsp. Instructions prefixed with pac are for
> signing and instructions prefixed with aut are for signing. Both

aut is for authentication.

> instructions are then followed with an a or b to indicate which signing
> key they are using. The keys can be controlled using
> -mbranch-protection=pac-ret for the A key and
> -mbranch-protection=pac-ret+b-key for the B key.
> 
> BTI works by marking all call and jump positions with bti c and bti
> j instructions. If execution control transfers to an instruction other
> than a BTI instruction, the execution is killed via SIGILL. Note that
> to remove one instruction, the aforementioned pac instructions will
> also work as a BTI landing pad for bti c usages.
> 
> For BTI to work, all object files linked for a unit of execution,
> whether an executable or a library must have the GNU Notes section of
> the ELF file marked to indicate BTI support. This is so loader/linkers
> can apply the proper permission bits (PROT_BRI) on the memory region.
> 
> PAC can also be annotated in the GNU ELF notes section, but it's not
> required for enablement, as interleaved PAC and non-pac code works as
> expected since it's the callee that performs all the checking. The
> linker follows the same rules as BTI for discarding the PAC flag from
> the GNU Notes section.
> 
> Testing was done under the following CFLAGS and CXXFLAGS for all
> combinations:
> 1. -mbranch-protection=none
> 2. -mbranch-protection=standard
> 3. -mbranch-protection=pac-ret
> 4. -mbranch-protection=pac-ret+b-key
> 5. -mbranch-protection=bti
> 
> Add tests that get skipped on non-pac and bti enabled systems,
> so this safely limits the tests to aarch64 platforms with support.
> One test dynamically tests that an mpn assembly routine supports
> BTI when the binary is enabled AND the system has support by
> calling that routine and verifying it's functionality and then
> by calling it one instruction past the correct entry point, and
> thus missing the landing pad.
> 
> The other test added, tests that the ELF binary has the proper
> GNU Notes section for the set of build flags.
> 
> Signed-off-by: Bill Roberts <bill.roberts at arm.com>
> ---
>   acinclude.m4                   |  33 +++++++++++
>   configure.ac                   |  22 +++++++-
>   mpn/Makeasm.am                 |   3 +-
>   mpn/arm64/arm64-defs.m4        | 100 +++++++++++++++++++++++++++++++++
>   mpn/arm64/divrem_1.asm         |   8 ++-
>   tests/mpn/Makefile.am          |  43 +++++++++-----
>   tests/mpn/log-compiler.sh      |  21 +++++++
>   tests/mpn/t-arm64_bti.c        |  86 ++++++++++++++++++++++++++++
>   tests/mpn/t-arm64_elf_check.sh |  96 +++++++++++++++++++++++++++++++
>   9 files changed, 394 insertions(+), 18 deletions(-)
>   create mode 100755 tests/mpn/log-compiler.sh
>   create mode 100644 tests/mpn/t-arm64_bti.c
>   create mode 100755 tests/mpn/t-arm64_elf_check.sh
> 
> diff --git a/acinclude.m4 b/acinclude.m4
> index 4fca12de2..4b9a579b1 100644
> --- a/acinclude.m4
> +++ b/acinclude.m4
> @@ -3992,3 +3992,36 @@ case $gmp_cv_check_libm_for_build in
>     *)   LIBM_FOR_BUILD=$gmp_cv_check_libm_for_build ;;
>   esac
>   ])
> +
> +# Define GMP_GET_MACRO_VALUE to capture the value of a C preprocessor symbol via compilation.
> +# This is useful when something like AC_EGREP_CPP doesn't have the correct environment.
> +# Arg 1 - The name of the macro to check in the compiled program.
> +# Arg 2 - The variable name to define the value of the macro to.
> +# Arg 3 - The default value if not defined.
> +#
> +# Example: GMP_GET_MACRO_VALUE([FOO], [BAR], [0])
> +# This will check for macro FOO and define in a new variable BAR the value
> +# of FOO as derived from invoking the C pre-processor or the default value
> +# as specified by the caller.
> +#
> +AC_DEFUN([GMP_GET_MACRO_VALUE], [
> +  AC_MSG_CHECKING([value of $1])
> +
> +  $2=$(printf "#ifdef $1\n$1_VALUE=$1\n#else\n$1_VALUE=$3\n#endif\n" | ${CC} ${CFLAGS} -E - | grep "$1_VALUE" | cut -d'=' -f2-)
> +  AC_MSG_RESULT([$$2])
> +])
> +
> +# Define GMP_CHECK_PROG to find a host program using AC_CHECK_PROG and fail if not found.
> +#
> +# Arg 1 - The name of the variable to define if found.
> +# Arg 2 - The program to check for, and the value of the variable named in argument 1.
> +#
> +# Example: GMP_CHECK_PROG([GREP], [grep])
> +# This will check for program grep and define GREP equal to "grep"
> +#
> +AC_DEFUN([GMP_CHECK_PROG], [
> +  AC_CHECK_PROG([$1], [$2], [$2])
> +  if test "$$1" != "$2"; then
> +    AC_MSG_FAILURE([Could not find $2! Ensure it's on PATH and/or installed."])

Is there an extranious quote at the end?


> +  fi
> +])
> diff --git a/configure.ac b/configure.ac
> index edee25fae..bd3524cb9 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -82,6 +82,8 @@ AM_INIT_AUTOMAKE([1.8 gnu no-dependencies subdir-objects])
>   AC_CONFIG_HEADERS(config.h:config.in)
>   AM_MAINTAINER_MODE
>   
> +GMP_CHECK_PROG([GREP], [grep])
> +GMP_CHECK_PROG([CUT], [cut])
>   
>   AC_ARG_ENABLE(assert,
>   AS_HELP_STRING([--enable-assert],[enable ASSERT checking [default=no]]),
> @@ -3767,7 +3769,17 @@ if test "$gmp_asm_syntax_testing" != no; then
>   	    *-*-darwin*)
>   	      GMP_INCLUDE_MPN(arm64/darwin.m4) ;;
>   	    *)
> -	      GMP_INCLUDE_MPN(arm64/arm64-defs.m4) ;;
> +	      GMP_INCLUDE_MPN(arm64/arm64-defs.m4)
> +	      GMP_GET_MACRO_VALUE([__ARM_FEATURE_BTI_DEFAULT], [ARM64_FEATURE_BTI_DEFAULT], [0])
> +		  GMP_DEFINE_RAW(["define(<ARM64_FEATURE_BTI_DEFAULT>,<$ARM64_FEATURE_BTI_DEFAULT>)"])
> +	      AC_SUBST([ARM64_FEATURE_BTI_DEFAULT])
> +
> +	      GMP_GET_MACRO_VALUE([__ARM_FEATURE_PAC_DEFAULT], [ARM64_FEATURE_PAC_DEFAULT], [0])
> +		  GMP_DEFINE_RAW(["define(<ARM64_FEATURE_PAC_DEFAULT>,<$ARM64_FEATURE_PAC_DEFAULT>)"])
> +	      AC_SUBST([ARM64_FEATURE_PAC_DEFAULT])
> +
> +	      GMP_GET_MACRO_VALUE([__ELF__], [ARM64_ELF], [0])
> +		  GMP_DEFINE_RAW(["define(<ARM64_ELF>,<$ARM64_ELF>)"])
>             esac
>   	  ;;
>         esac
> @@ -4058,6 +4070,12 @@ fi
>   AC_PROG_YACC
>   AM_PROG_LEX
>   
> +AC_CHECK_TOOL([HAVE_BASH], [bash], [no])
> +AM_CONDITIONAL([HAVE_BASH], [test "$HAVE_BASH" != "no"])
> +
> +AC_CHECK_TOOL([HAVE_READELF], [readelf], [no])
> +AM_CONDITIONAL([HAVE_READELF], [test "$HAVE_READELF" != "no"])
> +
>   # Create config.m4.
>   GMP_FINISH
>   
> @@ -4069,7 +4087,7 @@ AC_CONFIG_FILES([Makefile						\
>     tests/Makefile tests/devel/Makefile					\
>     tests/mpf/Makefile tests/mpn/Makefile tests/mpq/Makefile		\
>     tests/mpz/Makefile tests/rand/Makefile tests/misc/Makefile		\
> -  tests/cxx/Makefile							\
> +  tests/cxx/Makefile						\

Whitespace?

>     doc/Makefile tune/Makefile						\
>     demos/Makefile demos/calc/Makefile demos/expr/Makefile		\
>     gmp.h:gmp-h.in gmp.pc:gmp.pc.in gmpxx.pc:gmpxx.pc.in])
> diff --git a/mpn/Makeasm.am b/mpn/Makeasm.am
> index 5d7306c22..527bf41cf 100644
> --- a/mpn/Makeasm.am
> +++ b/mpn/Makeasm.am
> @@ -115,4 +115,5 @@ RM_TMP = rm -f
>   	$(CCAS) $(COMPILE_FLAGS) tmp-$*.s -o $@
>   	$(RM_TMP) tmp-$*.s
>   .asm.lo:
> -	$(LIBTOOL) --mode=compile --tag=CC $(top_srcdir)/mpn/m4-ccas --m4="$(M4)" $(CCAS) $(COMPILE_FLAGS) `test -f '$<' || echo '$(srcdir)/'`$<
> +	$(LIBTOOL) --mode=compile --tag=CC $(top_srcdir)/mpn/m4-ccas --m4="$(M4)" \
> +		$(CCAS) $(COMPILE_FLAGS) `test -f '$<' || echo '$(srcdir)/'`$<
> diff --git a/mpn/arm64/arm64-defs.m4 b/mpn/arm64/arm64-defs.m4
> index 46149f7bf..c717e5ebd 100644
> --- a/mpn/arm64/arm64-defs.m4
> +++ b/mpn/arm64/arm64-defs.m4
> @@ -36,6 +36,101 @@ dnl  don't want to disable macro expansions in or after them.
>   
>   changecom
>   
> +dnl use the hint instructions so they NOP on older machines.
> +dnl Add comments so the assembly is notated with the instruction
> +
> +
> +define(`PACIASP', `hint #25  /* paciasp */')
> +define(`AUTIASP', `hint #29  /* autiasp */')
> +define(`PACIBSP', `hint #27  /* pacibsp */')
> +define(`AUTIBSP', `hint #31  /* autibsp */')
> +
> +dnl if BTI is enabled we want the SIGN_LR to be a valid
> +dnl landing pad, we don't need VERIFY_LR and we need to
> +dnl indicate the valid BTI support for gnu notes.
> +
> +
> +ifelse(ARM64_FEATURE_BTI_DEFAULT, `1',
> +  `define(`BTI_C',   `hint #34  /* bti c */')
> +   define(`SIGN_LR', `BTI_C')
> +   define(`GNU_PROPERTY_AARCH64_BTI', `1')
> +   define(`PAC_OR_BTI')', `
> +   define(`BTI_C', `')
> +   define(`GNU_PROPERTY_AARCH64_BTI', `0')'
> +')
> +
> +dnl define instructions for PAC, which can use the A
> +dnl or the B key. PAC instructions are also valid BTI
> +dnl landing pads, so we re-define SIGN_LR if BTI is
> +dnl enabled.
> +
> +
> +ifelse(ARM64_FEATURE_PAC_DEFAULT, `1',
> +    `define(`SIGN_LR', `PACIASP')
> +     define(`VERIFY_LR', `AUTIASP')
> +     define(`GNU_PROPERTY_AARCH64_POINTER_AUTH', `2')
> +     define(`PAC_OR_BTI')',
> +   ARM64_FEATURE_PAC_DEFAULT, `2',
> +    `define(`SIGN_LR', `PACIBSP')
> +     define(`VERIFY_LR', `AUTIBSP')
> +     define(`GNU_PROPERTY_AARCH64_POINTER_AUTH', `2')
> +     define(`PAC_OR_BTI')',
> +    `ifdef(`SIGN_LR', , `define(`SIGN_LR', `')')
> +     define(`VERIFY_LR', `')
> +     define(`GNU_PROPERTY_AARCH64_POINTER_AUTH', `0')'
> +')
> +
> +dnl NOTE OVERRIDES asm-defs.m4 definition for arch specific functionality
> +dnl
> +dnl Usage: PROLOGUE_cpu(GSYM_PREFIX`'foo[,param])
> +dnl         EPILOGUE_cpu(GSYM_PREFIX`'foo)
> +dnl
> +dnl  These macros hold the CPU-specific parts of PROLOGUE and is called
> +dnl  with the function name, with GSYM_PREFIX already prepended.
> +dnl
> +dnl  By default, it marks entry points with a bti c instruction unless
> +dnl  the second argument is true and it marks it using SIGN_LR which expands
> +dnl  to the proper paci instruction OR bti c instruction depending on
> +dnl  compilation flags. In the case of an instruction that uses paci, this
> +dnl  provides a one instruction advantage over having a bti c followed by
> +dnl  a paci instruction.
> +
> +define(`PROLOGUE_cpu',
> +m4_assert_numargs_range(1,2)
> +`	TEXT
> +	ALIGN(8)
> +	GLOBL	`$1' GLOBL_ATTR
> +	TYPE(`$1',`function')
> +`$1'LABEL_SUFFIX
> +	ifelse(`$2',`true',
> +		`SIGN_LR',
> +		`BTI_C')
> +')
> +
> +dnl ADD_GNU_NOTES_IF_NEEDED
> +dnl
> +dnl Conditionally add into ELF assembly files the GNU notes indicating if
> +dnl BTI or PAC is support. BTI is required by the linkers and loaders, however
> +dnl PAC is a nice to have for auditing. Use readelf -n to display.
> +
> +
> +define(`ADD_GNU_NOTES_IF_NEEDED', `
> +  ifdef(`ARM64_ELF', `
> +    ifdef(`PAC_OR_BTI', `
> +      .pushsection .note.gnu.property, "a";
> +      .balign 8;
> +      .long 4;
> +      .long 0x10;
> +      .long 0x5;
> +      .asciz "GNU";
> +      .long 0xc0000000; /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */
> +      .long 4;
> +      .long eval(indir(`GNU_PROPERTY_AARCH64_POINTER_AUTH') + indir(`GNU_PROPERTY_AARCH64_BTI'));
> +      .long 0;
> +      .popsection;
> +    ')
> +  ')
> +')
>   
>   dnl  LEA_HI(reg,gmp_symbol), LEA_LO(reg,gmp_symbol)
>   dnl
> @@ -50,4 +145,9 @@ define(`LEA_HI', `adrp	$1, $2')dnl
>   define(`LEA_LO', `add	$1, $1, :lo12:$2')dnl
>   ')dnl
>   
> +dnl divert output to the following m4 file to shove the GNU Notes section into subsequent
> +dnl files implicitly.
> +divert(1)
> +ADD_GNU_NOTES_IF_NEEDED
> +
>   divert`'dnl
> diff --git a/mpn/arm64/divrem_1.asm b/mpn/arm64/divrem_1.asm
> index 9d5bb5959..2c5265780 100644
> --- a/mpn/arm64/divrem_1.asm
> +++ b/mpn/arm64/divrem_1.asm
> @@ -65,7 +65,7 @@ dnl                      mp_limb_t d_unnorm, mp_limb_t dinv, int cnt)
>   
>   ASM_START()
>   
> -PROLOGUE(mpn_preinv_divrem_1)
> +PROLOGUE(mpn_preinv_divrem_1, true)
>   	cbz	n_arg, L(fz)
>   	stp	x29, x30, [sp, #-80]!
>   	mov	x29, sp
> @@ -85,7 +85,7 @@ PROLOGUE(mpn_preinv_divrem_1)
>   	b	L(uentry)
>   EPILOGUE()
>   
> -PROLOGUE(mpn_divrem_1)
> +PROLOGUE(mpn_divrem_1, true)
>   	cbz	n_arg, L(fz)
>   	stp	x29, x30, [sp, #-80]!
>   	mov	x29, sp
> @@ -154,6 +154,7 @@ L(uend):add	x2, x11, #1
>   	ldp	x21, x22, [sp, #32]
>   	ldp	x23, x24, [sp, #48]
>   	ldp	x29, x30, [sp], #80
> +	VERIFY_LR
>   	ret
>   
>   L(ufx):	add	x2, x2, #1
> @@ -194,6 +195,7 @@ L(nend):cbnz	fn, L(frac)
>   	ldp	x21, x22, [sp, #32]
>   	ldp	x23, x24, [sp, #48]
>   	ldp	x29, x30, [sp], #80
> +	VERIFY_LR
>   	ret
>   
>   L(nfx):	add	x2, x2, #1
> @@ -219,6 +221,7 @@ L(ftop):add	x2, x11, #1
>   	ldp	x21, x22, [sp, #32]
>   	ldp	x23, x24, [sp, #48]
>   	ldp	x29, x30, [sp], #80
> +	VERIFY_LR
>   	ret
>   
>   C Block zero. We need this for the degenerated case of n = 0, fn != 0.
> @@ -227,5 +230,6 @@ L(ztop):str	xzr, [qp_arg], #8
>   	sub	fn_arg, fn_arg, #1
>   	cbnz	fn_arg, L(ztop)
>   L(zend):mov	x0, #0
> +	VERIFY_LR
>   	ret
>   EPILOGUE()
> diff --git a/tests/mpn/Makefile.am b/tests/mpn/Makefile.am
> index 0e979a3ad..16d4d2dc6 100644
> --- a/tests/mpn/Makefile.am
> +++ b/tests/mpn/Makefile.am
> @@ -22,19 +22,36 @@ AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/tests
>   AM_LDFLAGS = -no-install
>   LDADD = $(top_builddir)/tests/libtests.la $(top_builddir)/libgmp.la
>   
> -check_PROGRAMS = t-asmtype t-aors_1 t-divrem_1 t-mod_1 t-fat t-get_d	\
> -  t-instrument t-iord_u t-mp_bases t-perfsqr t-scan logic		\
> -  t-toom22 t-toom32 t-toom33 t-toom42 t-toom43 t-toom44			\
> -  t-toom52 t-toom53 t-toom54 t-toom62 t-toom63 t-toom6h t-toom8h	\
> -  t-toom2-sqr t-toom3-sqr t-toom4-sqr t-toom6-sqr t-toom8-sqr		\
> -  t-div t-mul t-mullo t-sqrlo t-mulmod_bnm1 t-sqrmod_bnm1 t-mulmid	\
> -  t-mulmod_bknp1 t-sqrmod_bknp1						\
> -  t-addaddmul t-hgcd t-hgcd_appr t-matrix22 t-invert t-bdiv t-fib2m	\
> -  t-broot t-brootinv t-minvert t-sizeinbase t-gcd_11 t-gcd_22 t-gcdext_1
> -
> -EXTRA_DIST = toom-shared.h toom-sqr-shared.h
> -
> -TESTS = $(check_PROGRAMS)
> +TEST_EXTENSIONS = .sh
> +AM_SH_LOG_FLAGS = --enable-pac=@ARM64_FEATURE_PAC_DEFAULT@ \
> +  --enable-bti=@ARM64_FEATURE_BTI_DEFAULT@ \
> +  $(top_builddir)/.libs/libgmp.so
> +SH_LOG_COMPILER = $(srcdir)/log-compiler.sh
> +
> +check_PROGRAMS = t-asmtype t-aors_1 t-divrem_1 t-mod_1 t-fat t-get_d	 \
> +  t-instrument t-iord_u t-mp_bases t-perfsqr t-scan logic		 \
> +  t-toom22 t-toom32 t-toom33 t-toom42 t-toom43 t-toom44			 \
> +  t-toom52 t-toom53 t-toom54 t-toom62 t-toom63 t-toom6h t-toom8h	 \
> +  t-toom2-sqr t-toom3-sqr t-toom4-sqr t-toom6-sqr t-toom8-sqr		 \
> +  t-div t-mul t-mullo t-sqrlo t-mulmod_bnm1 t-sqrmod_bnm1 t-mulmid	 \
> +  t-mulmod_bknp1 t-sqrmod_bknp1						 \
> +  t-addaddmul t-hgcd t-hgcd_appr t-matrix22 t-invert t-bdiv t-fib2m	 \
> +  t-broot t-brootinv t-minvert t-sizeinbase t-gcd_11 t-gcd_22 t-gcdext_1 \
> +  t-arm64_bti
> +
> +test_scripts =
> +if HAVE_BASH
> +if HAVE_READELF
> +  test_scripts += t-arm64_elf_check.sh
> +endif
> +endif
> +check_SCRIPTS = $(test_scripts)
> +
> +EXTRA_DIST = toom-shared.h toom-sqr-shared.h t-arm64_elf_check.sh
> +
> +TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
> +
> +XFAIL_TESTS = t-arm64_bti
>   
>   $(top_builddir)/tests/libtests.la:
>   	cd $(top_builddir)/tests; $(MAKE) $(AM_MAKEFLAGS) libtests.la
> diff --git a/tests/mpn/log-compiler.sh b/tests/mpn/log-compiler.sh
> new file mode 100755
> index 000000000..092b21b33
> --- /dev/null
> +++ b/tests/mpn/log-compiler.sh
> @@ -0,0 +1,21 @@
> +#!/usr/bin/env bash
> +
> +echo "Log Compiler: $@"
> +
> +# Flip <options> command to command <options> by swaping the
> +# first and last elements of the argv array
> +# Convert "$@" to an array for easy manipulation
> +args=("$@")
> +
> +# Get the indices for the first and last elements
> +first=0
> +last=$((${#args[@]} - 1))
> +
> +# Swap the first and last elements
> +temp="${args[$first]}"
> +args[$first]="${args[$last]}"
> +args[$last]="$temp"
> +
> +# Run the script
> +./${args[@]}
> +exit $?
> diff --git a/tests/mpn/t-arm64_bti.c b/tests/mpn/t-arm64_bti.c
> new file mode 100644
> index 000000000..6c36da2d5
> --- /dev/null
> +++ b/tests/mpn/t-arm64_bti.c
> @@ -0,0 +1,86 @@
> +/*
> +Copyright 2024 Free Software Foundation, Inc.
> +
> +This file is part of the GNU MP Library test suite.
> +
> +The GNU MP Library test suite is free software; you can redistribute it
> +and/or modify it under the terms of the GNU General Public License as
> +published by the Free Software Foundation; either version 3 of the License,
> +or (at your option) any later version.
> +
> +The GNU MP Library test suite is distributed in the hope that it will be
> +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
> +Public License for more details.
> +
> +You should have received a copy of the GNU General Public License along with
> +the GNU MP Library test suite.  If not, see https://www.gnu.org/licenses/.  */
> +
> +/*
> + * Test if if BTI is working within the GMP assembly stubs for AArch64 aka arm64
> + * within GMP. This test gets a function pointer to mpn_lshift avoiding the PLT
> + * using dlsym and calls the function and checks for a valid return. It then
> + * advances the function pointer by 2, which points us to the next instruction,
> + * and calls. The following scenarios are possible:
> + * | Binary BTI Enabled | Hardware BTI Enabled | Executable Outcome  | Test Outcome |
> + * | 0                  | 0                    | Works returning 77  | SKIP          |
> + * | 0                  | 1                    | Works returning 77  | SKIP          |
> + * | 1                  | 0                    | Works returning 77  | SKIP          |
> + * | 1                  | 1                    | BTI Exception       | PASS          |
> + * Note: 77 is the magic value for autotools to indicate to skip a test.
> + * Note: You MUST run this test when enabled on a BTI enabled hardware setup.
> + * Note: That for non-aarch64 platforms, this also just skips.
> + */
> +
> +#define SKIP 77
> +
> +/* AArch64 BTI Binary enabled code ONLY */
> +#ifdef __ARM_FEATURE_BTI_DEFAULT
> +
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +
> +#include <dlfcn.h>
> +#include <sys/auxv.h>
> +#include <asm/hwcap.h>
> +
> +#include "gmp-impl.h"
> +#include "tests.h"
> +
> +typedef mp_limb_t (*fn_mpn_lshift)(mp_ptr, mp_srcptr, mp_size_t, unsigned int);
> +
> +int
> +main (int argc, char **argv)
> +{
> +	unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +	if (!(hwcap2 & HWCAP2_BTI)) {
> +		fprintf(stderr, "Hardware does not support BTI\n");
> +		return SKIP;
> +	}
> +
> +	mp_limb_t xp = 0x1001, wp;
> +
> +	fn_mpn_lshift fn = dlsym(RTLD_DEFAULT, "__gmpn_lshift");
> +	if (!fn) {
> +	  fprintf(stderr, "Could not find the symbol __gmpn_lshift\n");
> +	  return 0;

Right so we return 'success', when the harness is expecting failure. 
Took me a bit to understand what was going on. Might be worth a comment.

> +	}
> +
> +	/* should work as this will land on a BTI landing pad as expected */
> +	fn (&wp, &xp, (mp_size_t) 1, 1);
> +	ASSERT_ALWAYS (wp == 0x2002);
> +
> +	/* this should fail as it's off 1 instruction */
> +	fn = (fn_mpn_lshift)((uintptr_t)fn + 4);

Caveat emptor here, the function casting UB might result in a diffrent 
kind of crash on future compilers. Inline assembly might be able to pin 
that down, but its going to result portability issues on mac/windows?

> +	fn(&wp, &xp, (mp_size_t) 1, 1);
> +	fprintf(stderr, "This should cause an exception, does your system support BTI?\n");
> +	return 0;
> +}
> +#else
> +/* No binary support for BTI or another arch, just skips */
> +int
> +main (int argc, char **argv) {
> +	return SKIP;
> +}
> +#endif
> diff --git a/tests/mpn/t-arm64_elf_check.sh b/tests/mpn/t-arm64_elf_check.sh
> new file mode 100755
> index 000000000..b0d294692
> --- /dev/null
> +++ b/tests/mpn/t-arm64_elf_check.sh
> @@ -0,0 +1,96 @@
> +#!/usr/bin/env bash
> +
> +set -e -o pipefail
> +
> +check_val() {
> +
> +  local grep_flags="-qi"
> +  local not_msg=""
> +  # invert the grep match if it SHOULDN'T be found in the flags.
> +  # ie BTI 0 means BTI should not be in the notes.
> +  if [ "${2}" -eq 0 ]; then
> +    grep_flags+="v"
> +    not_msg="Not "
> +  fi
> +
> +  printf 'Checking for %s in "%s". Expecting "%sPresent", ' "${1}" "${ELF_BINARY}" "${not_msg}"
> +
> +  set +e
> +  readelf -n "${ELF_BINARY}" | grep $grep_flags -- "${1}"
> +  local r="${?}"
> +  set -e
> +  # Possible states we care about, which grep will fail under:
> +  #   - State 1: Not expecting and Found
> +  #   - State 2: Expecting and not Found
> +  if [[ "${r}" -ne 0 ]]; then
> +    # Flip the not message
> +    if [ -z "${not_msg}" ]; then
> +      not_msg="Not "
> +    else
> +      not_msg=""
> +    fi
> +  fi
> +
> +  # print found or not found
> +  printf 'got "%sPresent."\n' "${not_msg}"
> +
> +  # The grep result means we return the rc through the named variable
> +  # this way consumers can just add all the values to determine if its
> +  # a failure.
> +  eval "${1}=\"${r}\""
This is just setting the global BTI and PAC variables right? 'define -g' 
is generally safer, right?

> +}
> +
> +# Initialize variables
> +BTI="0"
> +PAC="0"
> +ELF_BINARY=""
> +
> +# Loop through the arguments
> +while [[ "${#}" -gt 0 ]]; do
> +  case "${1}" in
> +  --enable-bti=*)
> +    BTI="${1#*=}"
> +    shift
> +    ;;
> +  --enable-pac=*)
> +    PAC="${1#*=}"
> +    shift
> +    ;;
> +  --enable-bti | --enable-pac)
> +    # If the argument is in the form --enable-bti value (without =)
> +    printf 'Error: Option %s requires a value, like --enable-bti=value' "${1}"
> +    exit 1
> +    ;;
> +  *)
> +    # Handle the non-option argument
> +    if [[ -z "${ELF_BINARY}" ]]; then
> +      ELF_BINARY="${1}"
> +    else
> +      printf 'Error: More than one non-option argument provided: %s\n' "${1}"
> +      exit 1
> +    fi
> +    shift
> +    ;;
> +  esac
> +done
> +
> +if [ -z "${ELF_BINARY}" ]; then
> +  printf "Must specify the ELF binary ast he ONLY script argument\n"

"binary as the ONLY"

> +  exit 1
> +fi
> +
> +# Skip if nothing is enabled, 77 is automake magic for SKIP this test.
> +# For non-supporting architectures and ABIs both of these will be 0
> +# and thus skip.
> +if [[ "${BTI}" -eq 0 && "${PAC}" -eq 0 ]]; then
> +  printf "PAC and BTI disabled...skipping\n"
> +  exit 77
> +fi
> +
> +check_val "BTI" "${BTI}"
> +check_val "PAC" "${PAC}"
> +
> +# don't use expr as it returns non-zero when the addition result is non-zero
> +# and causes the set -e script to fail.
> +rc=$((BTI + PAC))
> +exit ${rc}


Otherwise generally looks good. Are people using this library on arm 
mac/windows machines? If so, was it validated there?


Thanks again,





More information about the gmp-devel mailing list