Repository: randombit/botan (cpp)

Issue:
Build failure, 3.3.0 amalgamation arm64
When building using clang 16.0.6, on Ubuntu arm64, I found that I had to modify:
```
src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp
```
to specify `BOTAN_FUNC_ISA("+crypto,sha3")`, instead of just `"+crypto"`, as without doing so, the compilation failed with errors of the format:
```
botan_all.cpp:88261:30: error: always_inline function 'vsha512su0q_u64' requires target feature 'sha3', but would be inlined into function 'compress_digest_armv8' that is compiled without support for 'sha3'
```
Excessive compiler spew omitted for brevity; many such errors.

Unlikely to be an optimal solution, but it did resolve the issue for me.

Pull request description:
FIX: arm64 amalgamations + better CI coverage
This continues the work @lieser started in #3780. It aims to fix #3927 and #3774. It also incorporates #3930.

Along with fixes to the relevant `BOTAN_FUNC_ISA()` annotations, this adds CI targets for amalgamation builds on arm64 (using GCC) and Android NDK arm64 (using clang). Also, this adds builds on the `macos-14` runner [released a month ago](https://github.blog/changelog/2024-01-30-github-actions-macos-14-sonoma-is-now-available/), that is based on Apple Silicon, i.e. arm64 😅. 

**Note:** This also fixes an issue in the Python wrapper, where the `ffi_api()` declaration failed to explicitly specify a `c_int` buffer length parameter. On Apple silicon that resulted in this parameter being `0` on the C/C++ side. Not entirely sure how that ever worked on all other platforms we test in CI.

@randombit I'm somewhat worried that these inline ISA-annotations aren't necessarily in sync with whatever is defined in the `src/build-data/cc/*.txt` files. Currently, for arm64 the definitions are as such:

clang.txt
```
arm64:armv8crypto -> "-march=armv8+crypto"
arm64:armv8sha512 -> "-march=armv8.2-a+sha3"
```

gcc.txt
```
arm64:armv8crypto -> ""
arm64:armv8sm3 -> "-march=armv8.2-a+sm4"
arm64:armv8sm4 -> "-march=armv8.2-a+sm4"
arm64:armv8sha512 -> "-march=armv8.2-a+sha3"
arm64:armv8sha3 -> "-march=armv8.2-a+sha3"
```

... i.e. `clang.txt` doesn't even define all of them. I'm guessing that the inline annotations using `BOTAN_FUNC_ISA()` are saving the day for those, no?

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d5d734202..5892aebf1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -97,10 +97,18 @@ jobs:
         include:
           - target: shared
             compiler: clang
+            os: macos-13
           - target: amalgamation
             compiler: clang
+            os: macos-13
+          - target: shared
+            compiler: clang
+            os: macos-14 # uses Apple Silicon
+          - target: amalgamation
+            compiler: clang
+            os: macos-14 # uses Apple Silicon
 
-    runs-on: macos-13
+    runs-on: ${{ matrix.os }}
 
     steps:
       - uses: actions/checkout@v4
@@ -109,7 +117,7 @@ jobs:
         uses: ./.github/actions/setup-build-agent
         with:
           target: ${{ matrix.target }}
-          cache-key: macos-${{ matrix.compiler }}-x86_64-${{ matrix.target }}
+          cache-key: macos-${{ matrix.compiler }}-${{ matrix.os }}-${{ matrix.target }}
 
       - name: Build and Test Botan
         run: python3 ./src/scripts/ci_build.py --cc='${{ matrix.compiler }}' --test-results-dir=junit_results ${{ matrix.target }}
@@ -250,6 +258,9 @@ jobs:
           - target: cross-arm64
             compiler: gcc
             host_os: ubuntu-22.04
+          - target: cross-arm64-amalgamation
+            compiler: gcc
+            host_os: ubuntu-22.04
           - target: cross-ppc64
             compiler: gcc
             host_os: ubuntu-22.04
@@ -265,6 +276,9 @@ jobs:
           - target: cross-android-arm64
             compiler: clang
             host_os: ubuntu-22.04
+          - target: cross-android-arm64-amalgamation
+            compiler: clang
+            host_os: ubuntu-22.04
           - target: static
             compiler: gcc
             host_os: windows-2022
diff --git a/src/lib/block/aes/aes_armv8/aes_armv8.cpp b/src/lib/block/aes/aes_armv8/aes_armv8.cpp
index a7a834d82..3170dae2e 100644
--- a/src/lib/block/aes/aes_armv8/aes_armv8.cpp
+++ b/src/lib/block/aes/aes_armv8/aes_armv8.cpp
@@ -17,11 +17,11 @@ namespace Botan {
 
 namespace AES_AARCH64 {
 
-BOTAN_FUNC_ISA_INLINE("+crypto") void enc(uint8x16_t& B, uint8x16_t K) {
+BOTAN_FUNC_ISA_INLINE("+crypto+aes") void enc(uint8x16_t& B, uint8x16_t K) {
    B = vaesmcq_u8(vaeseq_u8(B, K));
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto")
+BOTAN_FUNC_ISA_INLINE("+crypto+aes")
 void enc4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) {
    B0 = vaesmcq_u8(vaeseq_u8(B0, K));
    B1 = vaesmcq_u8(vaeseq_u8(B1, K));
@@ -29,11 +29,11 @@ void enc4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x
    B3 = vaesmcq_u8(vaeseq_u8(B3, K));
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto") void enc_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) {
+BOTAN_FUNC_ISA_INLINE("+crypto+aes") void enc_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) {
    B = veorq_u8(vaeseq_u8(B, K), K2);
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto")
+BOTAN_FUNC_ISA_INLINE("+crypto+aes")
 void enc4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) {
    B0 = veorq_u8(vaeseq_u8(B0, K), K2);
    B1 = veorq_u8(vaeseq_u8(B1, K), K2);
@@ -41,11 +41,11 @@ void enc4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, u
    B3 = veorq_u8(vaeseq_u8(B3, K), K2);
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto") void dec(uint8x16_t& B, uint8x16_t K) {
+BOTAN_FUNC_ISA_INLINE("+crypto+aes") void dec(uint8x16_t& B, uint8x16_t K) {
    B = vaesimcq_u8(vaesdq_u8(B, K));
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto")
+BOTAN_FUNC_ISA_INLINE("+crypto+aes")
 void dec4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K) {
    B0 = vaesimcq_u8(vaesdq_u8(B0, K));
    B1 = vaesimcq_u8(vaesdq_u8(B1, K));
@@ -53,11 +53,11 @@ void dec4(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x
    B3 = vaesimcq_u8(vaesdq_u8(B3, K));
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto") void dec_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) {
+BOTAN_FUNC_ISA_INLINE("+crypto+aes") void dec_last(uint8x16_t& B, uint8x16_t K, uint8x16_t K2) {
    B = veorq_u8(vaesdq_u8(B, K), K2);
 }
 
-BOTAN_FUNC_ISA_INLINE("+crypto")
+BOTAN_FUNC_ISA_INLINE("+crypto+aes")
 void dec4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, uint8x16_t K, uint8x16_t K2) {
    B0 = veorq_u8(vaesdq_u8(B0, K), K2);
    B1 = veorq_u8(vaesdq_u8(B1, K), K2);
@@ -70,7 +70,7 @@ void dec4_last(uint8x16_t& B0, uint8x16_t& B1, uint8x16_t& B2, uint8x16_t& B3, u
 /*
 * AES-128 Encryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_EK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
@@ -133,7 +133,7 @@ BOTAN_FUNC_ISA("+crypto") void AES_128::hw_aes_encrypt_n(const uint8_t in[], uin
 /*
 * AES-128 Decryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_DK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
@@ -196,7 +196,7 @@ BOTAN_FUNC_ISA("+crypto") void AES_128::hw_aes_decrypt_n(const uint8_t in[], uin
 /*
 * AES-192 Encryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_EK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
@@ -265,7 +265,7 @@ BOTAN_FUNC_ISA("+crypto") void AES_192::hw_aes_encrypt_n(const uint8_t in[], uin
 /*
 * AES-192 Decryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_DK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
@@ -334,7 +334,7 @@ BOTAN_FUNC_ISA("+crypto") void AES_192::hw_aes_decrypt_n(const uint8_t in[], uin
 /*
 * AES-256 Encryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_EK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
@@ -411,7 +411,7 @@ BOTAN_FUNC_ISA("+crypto") void AES_256::hw_aes_encrypt_n(const uint8_t in[], uin
 /*
 * AES-256 Decryption
 */
-BOTAN_FUNC_ISA("+crypto") void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+aes") void AES_256::hw_aes_decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint8_t* skey = reinterpret_cast<const uint8_t*>(m_DK.data());
 
    const uint8x16_t K0 = vld1q_u8(skey + 0 * 16);
diff --git a/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp b/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp
index 0e1796748..a61307764 100644
--- a/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp
+++ b/src/lib/block/shacal2/shacal2_armv8/shacal2_arvm8.cpp
@@ -13,7 +13,8 @@ namespace Botan {
 Only encryption is supported since the inverse round function would
 require a different instruction
 */
-BOTAN_FUNC_ISA("+crypto") void SHACAL2::armv8_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const {
+BOTAN_FUNC_ISA("+crypto+sha2")
+void SHACAL2::armv8_encrypt_blocks(const uint8_t in[], uint8_t out[], size_t blocks) const {
    const uint32_t* input32 = reinterpret_cast<const uint32_t*>(in);
    uint32_t* output32 = reinterpret_cast<uint32_t*>(out);
 
diff --git a/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp b/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp
index df9ffbad4..861776900 100644
--- a/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp
+++ b/src/lib/block/sm4/sm4_armv8/sm4_armv8.cpp
@@ -31,7 +31,8 @@ inline uint32x4_t bqswap_32(uint32x4_t B) {
    return vreinterpretq_u32_u8(vqtbl1q_u8(vreinterpretq_u8_u32(B), vld1q_u8(bswap_tbl)));
 }
 
-inline void SM4_E(uint32x4_t& B0, uint32x4_t& B1, uint32x4_t& B2, uint32x4_t& B3, uint32x4_t K) {
+inline void BOTAN_FUNC_ISA("arch=armv8.2-a+sm4")
+   SM4_E(uint32x4_t& B0, uint32x4_t& B1, uint32x4_t& B2, uint32x4_t& B3, uint32x4_t K) {
    B0 = vsm4eq_u32(B0, K);
    B1 = vsm4eq_u32(B1, K);
    B2 = vsm4eq_u32(B2, K);
diff --git a/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp b/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp
index 913df6255..354b9babb 100644
--- a/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp
+++ b/src/lib/hash/sha1/sha1_armv8/sha1_armv8.cpp
@@ -16,7 +16,7 @@ namespace Botan {
 * SHA-1 using CPU instructions in ARMv8
 */
 //static
-BOTAN_FUNC_ISA("+crypto")
+BOTAN_FUNC_ISA("+crypto+sha2")
 void SHA_1::sha1_armv8_compress_n(digest_type& digest, std::span<const uint8_t> input8, size_t blocks) {
    uint32x4_t ABCD;
    uint32_t E0;
diff --git a/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp b/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp
index e32e3924f..18066eac8 100644
--- a/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp
+++ b/src/lib/hash/sha2_32/sha2_32_armv8/sha2_32_armv8.cpp
@@ -18,7 +18,7 @@ namespace Botan {
 * SHA-256 using CPU instructions in ARMv8
 */
 //static
-BOTAN_FUNC_ISA("+crypto")
+BOTAN_FUNC_ISA("+crypto+sha2")
 void SHA_256::compress_digest_armv8(digest_type& digest, std::span<const uint8_t> input8, size_t blocks) {
    alignas(64) static const uint32_t K[] = {
       0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
diff --git a/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp b/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp
index 845c0d8cb..bd6e9bb9f 100644
--- a/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp
+++ b/src/lib/hash/sha2_64/sha2_64_armv8/sha2_64_armv8.cpp
@@ -14,7 +14,7 @@ namespace Botan {
 /*
 * SHA-512 using CPU instructions in ARMv8
 */
-BOTAN_FUNC_ISA("+crypto")
+BOTAN_FUNC_ISA("arch=armv8.2-a+sha3")
 void SHA_512::compress_digest_armv8(digest_type& digest, std::span<const uint8_t> input8, size_t blocks) {
    alignas(128) static const uint64_t K[] = {
       0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC, 0x3956C25BF348B538,
diff --git a/src/lib/utils/simd/simd_32.h b/src/lib/utils/simd/simd_32.h
index 5f62d5645..3bfa6db5c 100644
--- a/src/lib/utils/simd/simd_32.h
+++ b/src/lib/utils/simd/simd_32.h
@@ -38,7 +38,7 @@
 #elif defined(BOTAN_SIMD_USE_NEON)
    #if defined(BOTAN_TARGET_ARCH_IS_ARM64)
       #define BOTAN_SIMD_ISA "+simd"
-      #define BOTAN_CLMUL_ISA "+crypto"
+      #define BOTAN_CLMUL_ISA "+crypto+aes"
    #else
       #define BOTAN_SIMD_ISA "fpu=neon"
    #endif
diff --git a/src/python/botan3.py b/src/python/botan3.py
index c7ac481ff..8df15da26 100755
--- a/src/python/botan3.py
+++ b/src/python/botan3.py
@@ -343,8 +343,8 @@ def _set_prototypes(dll):
     ffi_api(dll.botan_pubkey_load_x25519, [c_void_p, c_char_p])
     ffi_api(dll.botan_privkey_x25519_get_privkey, [c_void_p, c_char_p])
     ffi_api(dll.botan_pubkey_x25519_get_pubkey, [c_void_p, c_char_p])
-    ffi_api(dll.botan_privkey_load_kyber, [c_void_p, c_char_p])
-    ffi_api(dll.botan_pubkey_load_kyber, [c_void_p, c_char_p])
+    ffi_api(dll.botan_privkey_load_kyber, [c_void_p, c_char_p, c_int])
+    ffi_api(dll.botan_pubkey_load_kyber, [c_void_p, c_char_p, c_int])
     ffi_api(dll.botan_privkey_view_kyber_raw_key, [c_void_p, c_void_p, VIEW_BIN_CALLBACK])
     ffi_api(dll.botan_pubkey_view_kyber_raw_key, [c_void_p, c_void_p, VIEW_BIN_CALLBACK])
     ffi_api(dll.botan_privkey_load_ecdsa, [c_void_p, c_void_p, c_char_p])
diff --git a/src/scripts/ci/setup_gh_actions.sh b/src/scripts/ci/setup_gh_actions.sh
index c6346080f..691178286 100755
--- a/src/scripts/ci/setup_gh_actions.sh
+++ b/src/scripts/ci/setup_gh_actions.sh
@@ -45,7 +45,7 @@ if type -p "apt-get"; then
     elif [ "$TARGET" = "cross-arm32" ]; then
         sudo apt-get -qq install qemu-user g++-arm-linux-gnueabihf
 
-    elif [ "$TARGET" = "cross-arm64" ]; then
+    elif [ "$TARGET" = "cross-arm64" ] || [ "$TARGET" = "cross-arm64-amalgamation" ]; then
         sudo apt-get -qq install qemu-user g++-aarch64-linux-gnu
 
     elif [ "$TARGET" = "cross-ppc64" ]; then
@@ -57,7 +57,7 @@ if type -p "apt-get"; then
     elif [ "$TARGET" = "cross-s390x" ]; then
         sudo apt-get -qq install qemu-user g++-s390x-linux-gnu
 
-    elif [ "$TARGET" = "cross-android-arm32" ] || [ "$TARGET" = "cross-android-arm64" ]; then
+    elif [ "$TARGET" = "cross-android-arm32" ] || [ "$TARGET" = "cross-android-arm64" ] || [ "$TARGET" = "cross-android-arm64-amalgamation" ]; then
         wget -nv https://dl.google.com/android/repository/"$ANDROID_NDK"-linux.zip
         unzip -qq "$ANDROID_NDK"-linux.zip
 
@@ -102,6 +102,11 @@ else
 
     if [ "$TARGET" = "shared" ]; then
         brew install boost
+
+        # On Apple Silicon we need to specify the include directory
+        # so that the build can find the boost headers.
+        boostincdir=$(brew --prefix boost)/include
+        echo "BOOST_INCLUDEDIR=$boostincdir" >> "$GITHUB_ENV"
     elif [ "$TARGET" = "emscripten" ]; then
         brew install emscripten
     fi
diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py
index a1af0fb2d..9192c99b3 100755
--- a/src/scripts/ci_build.py
+++ b/src/scripts/ci_build.py
@@ -35,9 +35,11 @@ def known_targets():
         'coverage',
         'cross-android-arm32',
         'cross-android-arm64',
+        'cross-android-arm64-amalgamation',
         'cross-arm32',
         'cross-arm32-baremetal',
         'cross-arm64',
+        'cross-arm64-amalgamation',
         'cross-i386',
         'cross-ios-arm64',
         'cross-mips64',
@@ -129,7 +131,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
             target_os = 'ios'
         elif target == 'cross-win64':
             target_os = 'mingw'
-        elif target in ['cross-android-arm32', 'cross-android-arm64']:
+        elif target in ['cross-android-arm32', 'cross-android-arm64', 'cross-android-arm64-amalgamation']:
             target_os = 'android'
 
     if target_os == 'windows' and target_cc == 'gcc':
@@ -185,7 +187,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
     if target in ['minimized']:
         flags += ['--minimized-build', '--enable-modules=system_rng,sha2_32,sha2_64,aes']
 
-    if target in ['amalgamation']:
+    if target in ['amalgamation', 'cross-arm64-amalgamation', 'cross-android-arm64-amalgamation']:
         flags += ['--amalgamation']
 
     if target in ['bsi', 'nist']:
@@ -292,7 +294,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 cc_bin = os.path.join(toolchain_dir, 'armv7a-linux-androideabi%d-clang++' % (api_lvl))
                 flags += ['--cpu=armv7',
                           '--ar-command=%s' % (os.path.join(toolchain_dir, 'llvm-ar'))]
-            elif target == 'cross-android-arm64':
+            elif target in ['cross-android-arm64', 'cross-android-arm64-amalgamation']:
                 cc_bin = os.path.join(toolchain_dir, 'aarch64-linux-android%d-clang++' % (api_lvl))
                 flags += ['--cpu=arm64',
                           '--ar-command=%s' % (os.path.join(toolchain_dir, 'llvm-ar'))]
@@ -319,7 +321,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 test_prefix = ['qemu-arm', '-L', '/usr/arm-linux-gnueabihf/']
                 # disable a few tests that are exceptionally slow under arm32 qemu
                 disabled_tests += ['dh_invalid', 'dlies', 'xmss_sign']
-            elif target == 'cross-arm64':
+            elif target in ['cross-arm64', 'cross-arm64-amalgamation']:
                 flags += ['--cpu=aarch64']
                 cc_bin = 'aarch64-linux-gnu-g++'
                 test_prefix = ['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu/']
@@ -374,10 +376,10 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 # make sure clang ignores warnings in boost headers
                 flags += ["--extra-cxxflags=--system-header-prefix=boost/"]
 
-            if target_os in ['windows', 'mingw']:
-                # ./configure.py needs boost's location on Windows
-                if 'BOOST_INCLUDEDIR' in os.environ:
-                    flags += ['--with-external-includedir', os.environ.get('BOOST_INCLUDEDIR')]
+            if 'BOOST_INCLUDEDIR' in os.environ:
+                # ./configure.py needs boost's location on some platforms
+                # BOOST_INCLUDEDIR is set by the setup_gh_actions.* script
+                flags += ['--with-external-includedir', os.environ.get('BOOST_INCLUDEDIR')]
 
             if target_os == 'mingw':
                 # apparently mingw needs this legacy socket library version for reasons


Test patch:
diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py
index ac67fb33f..5800095f4 100755
--- a/src/scripts/test_cli.py
+++ b/src/scripts/test_cli.py
@@ -820,20 +820,22 @@ gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA
     test_cli("ec_group_info", "--pem secp256r1", secp256r1_pem)
 
 def cli_cpuid_tests(_tmp_dir):
-    cpuid_output = test_cli("cpuid", [])
+    def read_flags(cli_output):
+        if not cpuid_output.startswith('CPUID flags:'):
+            logging.error('Unexpected cpuid output "%s"', cpuid_output)
 
-    if not cpuid_output.startswith('CPUID flags:'):
-        logging.error('Unexpected cpuid output "%s"', cpuid_output)
+        return cli_output[13:].split(' ') if cli_output[13:] != '' else []
 
+    cpuid_output = test_cli("cpuid", [])
     flag_re = re.compile('[a-z0-9_]+')
-    flags = cpuid_output[13:].split(' ')
+    flags = read_flags(cpuid_output)
     for flag in flags:
         if flag != '' and flag_re.match(flag) is None:
             logging.error('Unexpected CPUID flag name "%s"', flag)
 
         env = {'BOTAN_CLEAR_CPUID': flag}
         cpuid_output = test_cli("cpuid", [], None, None, None, True, env)
-        mod_flags = cpuid_output[13:].split(' ')
+        mod_flags = read_flags(cpuid_output)
 
         for f in mod_flags:
             if f == flag: