diff --git a/.github/scripts/big-endian-test.sh b/.github/scripts/big-endian-test.sh new file mode 100755 index 00000000..a3f8bb9e --- /dev/null +++ b/.github/scripts/big-endian-test.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# big-endian-test.sh +# +# Copyright (C) 2006-2025 wolfSSL Inc. +# +# This file is part of wolfProvider. +# +# wolfProvider 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. +# +# wolfProvider 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 wolfProvider. If not, see . +# +# Runs the standard build and unit tests on a big-endian target. Meant to +# run inside an s390x container; the test-deps image is amd64-only, so the +# toolchain is installed here instead. +# +# The unit runner already continues past a failing test, so only a signal +# stops the suite. This restarts at the next case after a crash and takes a +# backtrace, so one crash does not hide the rest of the results. + +set -euo pipefail + +LOG_DIR=${LOG_DIR:-/wolfprov/be-logs} +RUN_LOG="${LOG_DIR}/unit-run.log" +SUMMARY="${LOG_DIR}/summary.txt" + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y --no-install-recommends \ + autoconf automake build-essential ca-certificates gdb git libtool make \ + perl pkg-config + +# The workspace is bind-mounted from the runner, so its owner is not root. +git config --global --add safe.directory /wolfprov + +# Fail loudly rather than silently retesting x86-64 if the qemu platform +# selection ever regresses. +cat > /tmp/endian-check.c <<'EOF' +int main(void) +{ +#if !defined(__BYTE_ORDER__) || (__BYTE_ORDER__ != __ORDER_BIG_ENDIAN__) +#error "not a big-endian target" +#endif + return 0; +} +EOF +gcc -o /tmp/endian-check /tmp/endian-check.c +/tmp/endian-check +echo "confirmed big-endian target: $(uname -m)" + +mkdir -p "${LOG_DIR}" + +# Build only. The suite is driven below so a crash does not end the run. +WOLFPROV_SKIP_TEST=1 ./scripts/build-wolfprovider.sh + +source ./scripts/env-setup + +total=$(./test/unit.test --list | grep -cE '^ *[0-9]+: ') +echo "unit test cases: ${total}" + +crashed=() +failed=() +next=1 + +while [ "${next}" -le "${total}" ]; do + echo "=== running cases ${next}..${total} ===" + rc=0 + # shellcheck disable=SC2046 + ./test/unit.test $(seq "${next}" "${total}") 2>&1 | tee -a "${RUN_LOG}" \ + || rc=${PIPESTATUS[0]} + + # Under 128 means the runner reached the end on its own, reporting any + # failures as it went. Only a signal leaves cases unrun. + if [ "${rc}" -lt 128 ]; then + break + fi + + last=$(grep -oE '^#### Start: [0-9]+' "${RUN_LOG}" | tail -1 \ + | grep -oE '[0-9]+$') + name=$(./test/unit.test --list | grep -E "^ *${last}: " \ + | sed -E 's/^ *[0-9]+: //') + echo "=== case ${last} (${name}) died with exit ${rc}; getting backtrace ===" + crashed+=("${last} ${name}") + + ./libtool --mode=execute gdb -batch \ + -ex run -ex 'bt full' -ex 'info registers' \ + -ex 'thread apply all bt' \ + --args ./test/unit.test "${last}" \ + > "${LOG_DIR}/backtrace-${last}.log" 2>&1 || true + echo "--- backtrace for case ${last} (${name}) ---" + cat "${LOG_DIR}/backtrace-${last}.log" + + next=$((last + 1)) +done + +mapfile -t failed < <(grep -oE '^#### FAILED: [0-9]+ - [^ ]+' "${RUN_LOG}" \ + | sed -E 's/^#### FAILED: //' | sort -u || true) + +{ + echo "big-endian unit results on $(uname -m)" + echo "cases: ${total}" + echo + echo "crashed (${#crashed[@]}):" + printf ' %s\n' "${crashed[@]:-none}" + echo + echo "failed (${#failed[@]}):" + printf ' %s\n' "${failed[@]:-none}" + echo + echo "byte-order import path (the reason this job exists):" + grep -E '^#### (SUCCESS|FAILED): [0-9]+ - test_(rsa|dh|ec)_fromdata' \ + "${RUN_LOG}" || echo " never reached" +} | tee "${SUMMARY}" + +if [ "${#crashed[@]}" -gt 0 ] || [ "${#failed[@]}" -gt 0 ]; then + exit 1 +fi diff --git a/.github/workflows/README.md b/.github/workflows/README.md index b199923d..02f3514b 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -96,7 +96,7 @@ gh workflow run pr-osp-select.yml --ref -f jobs="all" ### What runs in the nightly fan-out -43 workflows total: 40 third-party OSS integrations, 2 internal +44 workflows total: 40 third-party OSS integrations, 3 internal validations, and the static-analysis suite. Every one of these patches the upstream project (where needed) via `osp/wolfProvider//*.patch` from [wolfssl/osp](https://github.com/wolfssl/osp), builds it against @@ -176,6 +176,7 @@ exercised, with and without `WOLFPROV_FORCE_FAIL=1`. | `debian-package.yml` | End-to-end check: builds the wolfprov `.deb`s and confirms they install cleanly on a fresh container and the provider loads. | | `openssl-version.yml` | Sweeps every upstream `openssl-3.X.Y` release tag — catches breakage from OpenSSL point releases before they hit our matrix defaults. | | `static-analysis.yml` | cppcheck, clang scan-build, Facebook Infer. Heavy enough that it lives in the nightly fan-out rather than per-PR. | +| `big-endian.yml` | Builds and runs the unit tests on s390x (big-endian) under qemu emulation. Guards the `#ifdef LITTLE_ENDIAN_ORDER` byte-order paths, which no other job exercises. | Sanitizers (ASan+UBSan, TSan) run on every PR/push — see the PR table above. They're fast enough with caching to gate merges, so they don't @@ -264,6 +265,35 @@ The scan-build and infer thresholds are baseline-based, not strict — they let pre-existing issues slide but flag obvious regressions. Bringing them to 0 is a future cleanup. +## Big-endian coverage + +`big-endian.yml` runs nightly (and via `ci:big-endian` / `ci:all`). Every +other job in this repo runs on x86-64, so the `#else` half of each +`#ifdef LITTLE_ENDIAN_ORDER` block in `src/wp_params.c` is otherwise never +compiled or executed. This job closes that gap. + +GitHub offers no big-endian runners, so it emulates s390x with qemu +binfmt (`docker/setup-qemu-action`) and builds inside a `debian:bookworm` +s390x container. Emulation is roughly an order of magnitude slower than +native, which is why it is nightly-only rather than a PR gate. + +`.github/scripts/big-endian-test.sh` installs a toolchain — the test-deps +image is amd64-only, so it cannot be reused here — asserts the target +really is big-endian, then hands off to `scripts/build-wolfprovider.sh` +with no arguments. That is the same entry point `simple.yml` uses, at its +default OpenSSL + wolfSSL refs, and it runs `make test` itself. The +endian assert means a qemu or platform regression fails the job instead +of silently retesting x86-64. + +Everything after the emulation setup is therefore the stock build. The +job is slow because every instruction is emulated, including the full +OpenSSL and wolfSSL source builds, not because it does anything special. + +No big-endian-specific test case exists or is needed: `test_rsa_fromdata` +already compares a wolfProvider `EVP_PKEY_fromdata` import against +OpenSSL's via `EVP_PKEY_cmp`, which is exactly the comparison a byte-order +mistake in the `OSSL_PARAM` import path breaks. + ## Overhead regression testing `perf-regression.yml` (workflow display name **Overhead Regression**) runs nightly at 07:00 UTC (and on diff --git a/.github/workflows/big-endian.yml b/.github/workflows/big-endian.yml new file mode 100644 index 00000000..e66e03f7 --- /dev/null +++ b/.github/workflows/big-endian.yml @@ -0,0 +1,64 @@ +name: Big-Endian + +# GitHub has no big-endian runners, so s390x runs under qemu binfmt +# emulation. Emulation is slow, which is why this is nightly / ci:all +# only and not a per-PR check. + +on: + workflow_call: {} + workflow_dispatch: {} + +# run-scoped: a reusable workflow's group keys off the caller, so a shared +# group would let one nightly run cancel another's big-endian job. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_id }} + cancel-in-progress: false + +jobs: + big-endian: + name: Big-Endian s390x + runs-on: ubuntu-22.04 + permissions: + contents: read + timeout-minutes: 240 + steps: + - name: Checkout wolfProvider + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Enable s390x emulation + uses: docker/setup-qemu-action@v3 + with: + platforms: s390x + + - name: Build and test on s390x + run: | + docker run --rm --platform linux/s390x \ + -v "$PWD:/wolfprov" -w /wolfprov \ + debian:bookworm \ + /bin/bash /wolfprov/.github/scripts/big-endian-test.sh + + - name: Summary + if: ${{ always() }} + run: | + if [ -f be-logs/summary.txt ] ; then + cat be-logs/summary.txt >> "$GITHUB_STEP_SUMMARY" + cat be-logs/summary.txt + fi + + - name: Print build log on failure + if: ${{ failure() }} + run: | + if [ -f scripts/build-release.log ] ; then + tail -200 scripts/build-release.log + fi + + # The full run is far too long for the console; backtraces live here. + - name: Upload logs + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: big-endian-logs + path: be-logs/ + if-no-files-found: warn diff --git a/.github/workflows/nightly-osp.yml b/.github/workflows/nightly-osp.yml index 32a423b9..b4eb0f03 100644 --- a/.github/workflows/nightly-osp.yml +++ b/.github/workflows/nightly-osp.yml @@ -167,5 +167,6 @@ jobs: xmlsec-584: { needs: wave1-done, if: always(), uses: ./.github/workflows/xmlsec.yml, with: { wolfssl_refs_json: '["v5.8.4-stable"]' } } # === Out-of-wave: not wolfssl-version-split === + big-endian: { uses: ./.github/workflows/big-endian.yml } static-analysis: { uses: ./.github/workflows/static-analysis.yml } multi-compiler: { uses: ./.github/workflows/nightly-multi-compiler.yml } diff --git a/.github/workflows/pr-osp-select.yml b/.github/workflows/pr-osp-select.yml index c98eb7c8..a4c6a0e4 100644 --- a/.github/workflows/pr-osp-select.yml +++ b/.github/workflows/pr-osp-select.yml @@ -219,6 +219,10 @@ jobs: needs: select if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' xmlsec ') uses: ./.github/workflows/xmlsec.yml + big-endian: + needs: select + if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' big-endian ') + uses: ./.github/workflows/big-endian.yml multi-compiler: needs: select if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' multi-compiler ') diff --git a/src/wp_params.c b/src/wp_params.c index 9ac19417..9b935969 100644 --- a/src/wp_params.c +++ b/src/wp_params.c @@ -27,10 +27,10 @@ /** - * Read little-endian array of bytes representing a large number. + * Read local endian array of bytes representing a large number. * * @param [in, out] mp Multi-precision number. - * @param [in] data Little-endian array of bytes. + * @param [in] data Local endian array of bytes. * @param [in] len Length of array in bytes. * @return 1 on success. * @return 0 on failure. @@ -39,12 +39,15 @@ int wp_mp_read_unsigned_bin_le(mp_int* mp, const unsigned char* data, size_t len) { int ok = 1; + int rc; +#ifdef LITTLE_ENDIAN_ORDER unsigned char rdata[1024]; size_t i; - int rc; +#endif WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_mp_read_unsigned_bin_le"); +#ifdef LITTLE_ENDIAN_ORDER if (len > sizeof(rdata)) { ok = 0; } @@ -65,6 +68,13 @@ int wp_mp_read_unsigned_bin_le(mp_int* mp, const unsigned char* data, /* rdata may hold private key material (RSA d/p/q, EC/DH private). */ OPENSSL_cleanse(rdata, sizeof(rdata)); +#else + rc = mp_read_unsigned_bin(mp, data, (word32)len); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "mp_read_unsigned_bin", rc); + ok = 0; + } +#endif WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; @@ -100,6 +110,9 @@ int wp_mp_to_unsigned_bin_le(mp_int* mp, unsigned char* data, size_t len) data[len - 1 - i] = t; } } +#else + (void)i; + (void)len; #endif WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);