From f979f8fb3d5a00b993b0b75205d185b1b19a00e9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 5 Jan 2026 14:02:14 -0800 Subject: [PATCH] Add Static Analysis CI tools --- .github/workflows/static-analysis.yml | 281 ++++++++++++++++++++++++++ src/wp_aes_aead.c | 2 +- src/wp_dec_epki2pki.c | 4 +- src/wp_dec_pem2der.c | 8 +- src/wp_ecc_kmgmt.c | 4 +- src/wp_mac_sig.c | 8 +- src/wp_rsa_kem.c | 4 +- test/test_rsa.c | 33 ++- 8 files changed, 318 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/static-analysis.yml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000..96344933 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,281 @@ +name: Static Analysis + +# START OF COMMON SECTION +on: + schedule: + # Run nightly at 2 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cppcheck: + name: cppcheck Static Analysis + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - name: Checkout wolfProvider + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cppcheck + + - name: Build dependencies (OpenSSL and wolfSSL) + run: | + OPENSSL_TAG=openssl-3.5.4 WOLFSSL_TAG=master ./scripts/build-wolfprovider.sh 2>&1 | tail -100 || true + # We only need the build to succeed enough to have headers available + + - name: Generate configure script + run: | + ./autogen.sh || true + + - name: Configure to generate config.h + run: | + OPENSSL_INSTALL_DIR="$PWD/openssl-install" + WOLFSSL_INSTALL_DIR="$PWD/wolfssl-install" + + # Configure to generate config.h (needed for cppcheck) + ./configure \ + --with-openssl="$OPENSSL_INSTALL_DIR" \ + --with-wolfssl="$WOLFSSL_INSTALL_DIR" \ + --prefix="$PWD/wolfprov-install" \ + >/dev/null 2>&1 || true + + - name: Run cppcheck + run: | + # Configure include paths for cppcheck + OPENSSL_INC="$PWD/openssl-install/include" + WOLFSSL_INC="$PWD/wolfssl-install/include" + WOLFPROV_INC="$PWD/include" + + # Run cppcheck on source files only (tests can be analyzed separately if needed) + # Use --force and suppress noValidConfiguration to proceed even with incomplete config + cppcheck \ + --enable=all \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --suppress=unmatchedSuppression \ + --suppress=noValidConfiguration \ + --inline-suppr \ + --force \ + --error-exitcode=0 \ + -I "$OPENSSL_INC" \ + -I "$WOLFSSL_INC" \ + -I "$WOLFPROV_INC" \ + --platform=unix64 \ + src/ 2>&1 | tee cppcheck-output.txt || true + + # Display output (filter out noValidConfiguration messages and progress) + grep -v "noValidConfiguration" cppcheck-output.txt | \ + grep -v "^Checking " | \ + grep -v "^[0-9]*/[0-9]* files checked" | \ + grep -v "^nofile:0:0: information:" || true + + # Count errors and warnings (count lines with error:/warning: that are actual issues) + # Use wc -l for more reliable counting, and strip whitespace + ERROR_COUNT=$(grep -E "^src/.*:[0-9]+:[0-9]+:.*error:" cppcheck-output.txt 2>/dev/null | wc -l | tr -d '[:space:]' || echo "0") + WARNING_COUNT=$(grep -E "^src/.*:[0-9]+:[0-9]+:.*warning:" cppcheck-output.txt 2>/dev/null | wc -l | tr -d '[:space:]' || echo "0") + + # Ensure we have valid integers (default to 0 if empty) + ERROR_COUNT=${ERROR_COUNT:-0} + WARNING_COUNT=${WARNING_COUNT:-0} + + echo "cppcheck found $ERROR_COUNT errors and $WARNING_COUNT warnings" + + # Fail only if critical errors found (adjust threshold as needed) + if [ "${ERROR_COUNT}" -gt 0 ] 2>/dev/null; then + echo "cppcheck found errors" + exit 1 + fi + + - name: Upload cppcheck results + if: always() + uses: actions/upload-artifact@v4 + with: + name: cppcheck-results + path: cppcheck-output.txt + retention-days: 7 + + scan-build: + name: clang Static Analyzer (scan-build) + runs-on: ubuntu-22.04 + timeout-minutes: 45 + steps: + - name: Checkout wolfProvider + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang clang-tools build-essential autoconf automake libtool pkg-config + + - name: Build dependencies (OpenSSL and wolfSSL) + run: | + OPENSSL_TAG=openssl-3.5.4 WOLFSSL_TAG=master ./scripts/build-wolfprovider.sh 2>&1 | tail -100 || true + + - name: Generate configure script + run: | + ./autogen.sh + + - name: Configure project for scan-build + run: | + OPENSSL_INSTALL_DIR="$PWD/openssl-install" + WOLFSSL_INSTALL_DIR="$PWD/wolfssl-install" + + # Configure with clang (scan-build will wrap it later) + CC=clang ./configure \ + --with-openssl="$OPENSSL_INSTALL_DIR" \ + --with-wolfssl="$WOLFSSL_INSTALL_DIR" \ + --prefix="$PWD/wolfprov-install" + + - name: Build library with scan-build + run: | + # Clean any previous build artifacts + make clean || true + + # Build only the library with scan-build wrapping clang + # scan-build intercepts compiler calls, so we need to ensure CC is set to clang + # Use --use-cc and --use-c++ to explicitly tell scan-build which compiler to wrap + scan-build -o scan-build-output \ + --use-cc=clang \ + --use-c++=clang++ \ + make -j$(nproc) CC=clang libwolfprov.la 2>&1 | tee scan-build-log.txt || true + + # scan-build returns 0 even if build fails, so we check exit status explicitly + echo "Build completed (scan-build may have reports even if build partially failed)" + + - name: Check scan-build results + run: | + # Find the latest scan-build report directory + REPORT_DIR=$(find scan-build-output -maxdepth 1 -type d -name "scan-build-*" | sort -r | head -1) + + if [ -z "$REPORT_DIR" ] || [ ! -d "$REPORT_DIR" ]; then + echo "No scan-build report directory found" + exit 0 + fi + + # Count bugs found + BUG_COUNT=$(find "$REPORT_DIR" -name "*.html" | wc -l) + echo "scan-build found $BUG_COUNT potential issues" + + # Display summary + if [ -f "$REPORT_DIR/index.html" ]; then + echo "View detailed report in scan-build-output/index.html" + # Extract text summary if possible + grep -o '.*' "$REPORT_DIR/index.html" || true + fi + + # Fail if critical bugs found (adjust threshold as needed) + if [ "$BUG_COUNT" -gt 50 ]; then + echo "Too many issues found by scan-build" + exit 1 + fi + + - name: Upload scan-build results + if: always() + uses: actions/upload-artifact@v4 + with: + name: scan-build-results + path: scan-build-output/ + retention-days: 7 + + infer: + name: Facebook Infer Static Analysis + runs-on: ubuntu-22.04 + timeout-minutes: 60 + steps: + - name: Checkout wolfProvider + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential autoconf automake libtool pkg-config python3 opam + + # Install Infer + VERSION=1.1.0 + cd /tmp + wget https://github.com/facebook/infer/releases/download/v${VERSION}/infer-linux64-v${VERSION}.tar.xz + tar xf infer-linux64-v${VERSION}.tar.xz + sudo mv infer-linux64-v${VERSION} /opt/infer + sudo ln -sf /opt/infer/bin/infer /usr/local/bin/infer + + - name: Build dependencies (OpenSSL and wolfSSL) + run: | + OPENSSL_TAG=openssl-3.5.4 WOLFSSL_TAG=master ./scripts/build-wolfprovider.sh 2>&1 | tail -100 || true + + - name: Generate configure script + run: | + ./autogen.sh + + - name: Configure project + run: | + OPENSSL_INSTALL_DIR="$PWD/openssl-install" + WOLFSSL_INSTALL_DIR="$PWD/wolfssl-install" + + ./configure \ + --with-openssl="$OPENSSL_INSTALL_DIR" \ + --with-wolfssl="$WOLFSSL_INSTALL_DIR" \ + --prefix="$PWD/wolfprov-install" \ + CC=clang + + - name: Clean build for Infer + run: | + make clean || true + rm -rf infer-out + + - name: Run Infer analysis + run: | + # Run infer on the build (it wraps the compilation) + # Build only the library to avoid test compilation issues + # Explicitly set CC=clang for make + infer run -- make -j$(nproc) CC=clang libwolfprov.la 2>&1 | tee infer-log.txt || true + + # Generate text report + if [ -d infer-out ]; then + infer report --issues-csv infer-report.csv 2>&1 || true + infer report --issues-txt infer-report.txt 2>&1 || true + + # Display summary + if [ -f infer-report.txt ]; then + echo "=== Infer Analysis Summary ===" + cat infer-report.txt + + # Count issues + ISSUE_COUNT=$(grep -c "Found.*issue" infer-report.txt || echo "0") + echo "Infer found issues (check infer-report.txt for details)" + + # Fail if too many critical issues (adjust threshold as needed) + if [ "$ISSUE_COUNT" -gt 100 ]; then + echo "Too many issues found by Infer" + exit 1 + fi + fi + else + echo "Infer did not produce output directory" + fi + + - name: Upload Infer results + if: always() + uses: actions/upload-artifact@v4 + with: + name: infer-results + path: | + infer-out/ + infer-report.txt + infer-report.csv + infer-log.txt + retention-days: 7 + if-no-files-found: ignore diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index 3908d637..a8d0dc12 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -223,7 +223,7 @@ static int wp_aead_tls_init(wp_AeadCtx* ctx, unsigned char* aad, size_t aadLen) { int ok = 1; unsigned char *buf = ctx->buf; - size_t len; + size_t len = 0; /* CCM will have a tag length set. */ size_t tagLen = (ctx->tagLen != UNINITIALISED_SIZET) ? ctx->tagLen : EVP_GCM_TLS_TAG_LEN; diff --git a/src/wp_dec_epki2pki.c b/src/wp_dec_epki2pki.c index c58fb656..7284a254 100644 --- a/src/wp_dec_epki2pki.c +++ b/src/wp_dec_epki2pki.c @@ -187,11 +187,11 @@ static int wp_epki2pki_decode(wp_Epki2Pki* ctx, OSSL_CORE_BIO* coreBio, { int ok = 1; int done = 0; - int rc; + int rc = 0; unsigned char* data = NULL; word32 len = 0; char password[1024]; - size_t passwordLen; + size_t passwordLen = 0; word32 tradIdx = 0; WOLFPROV_ENTER(WP_LOG_COMP_PK, "wp_epki2pki_decode"); diff --git a/src/wp_dec_pem2der.c b/src/wp_dec_pem2der.c index c0b114f0..2000fb74 100644 --- a/src/wp_dec_pem2der.c +++ b/src/wp_dec_pem2der.c @@ -207,12 +207,12 @@ static int wp_pem2der_decode_data(const unsigned char* data, word32 len, { int ok = 1; int done = 0; - int rc; - int algoId; - int type; + int rc = 0; + int algoId = 0; + int type = 0; const char* dataType = NULL; const char* dataFormat = NULL; - int obj; + int obj = 0; EncryptedInfo info; DerBuffer* der = NULL; OSSL_PARAM params[5]; diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index ca616222..95137a84 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -2222,9 +2222,9 @@ static int wp_ecc_decode_pki(wp_Ecc* ecc, unsigned char* data, word32 len) /* Keys decoded from pki should always have public key */ if (ecc->key.type == ECC_PRIVATEKEY_ONLY) { #ifdef ECC_TIMING_RESISTANT - rc = wc_ecc_make_pub_ex(&ecc->key, NULL, &ecc->rng); + (void)wc_ecc_make_pub_ex(&ecc->key, NULL, &ecc->rng); #else - rc = wc_ecc_make_pub_ex(&ecc->key, NULL, NULL); + (void)wc_ecc_make_pub_ex(&ecc->key, NULL, NULL); #endif } ecc->hasPub = 1; diff --git a/src/wp_mac_sig.c b/src/wp_mac_sig.c index fcd30ef3..c1ef17d9 100644 --- a/src/wp_mac_sig.c +++ b/src/wp_mac_sig.c @@ -187,10 +187,10 @@ static int wp_mac_digest_sign_init(wp_MacSigCtx *ctx, const char *mdName, wp_Mac *mac, const OSSL_PARAM params[]) { int ok = 1; - unsigned char* priv; - size_t privLen; - const char* cipherName; - const char* properties; + unsigned char* priv = NULL; + size_t privLen = 0; + const char* cipherName = NULL; + const char* properties = NULL; OSSL_PARAM lParams[4]; int lParamSz = 0; diff --git a/src/wp_rsa_kem.c b/src/wp_rsa_kem.c index a8744fd4..7ee541bd 100644 --- a/src/wp_rsa_kem.c +++ b/src/wp_rsa_kem.c @@ -294,8 +294,8 @@ static int wp_rsasve_generate(wp_RsaKemCtx* ctx, unsigned char* out, size_t* outLen, unsigned char* secret, size_t* secretLen) { int ok = 1; - word32 nLen; - word32 oLen; + word32 nLen = 0; + word32 oLen = 0; RsaKey* rsa = NULL; WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsasve_generate"); diff --git a/test/test_rsa.c b/test/test_rsa.c index c7042121..df3256cf 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -1569,12 +1569,17 @@ static int test_rsa_decode_pkcs8(void) int pnum = RSA_get_multi_prime_extra_count(rsakey1); BIGNUM **primes1 = malloc(sizeof(BIGNUM *) * pnum); BIGNUM **primes2 = malloc(sizeof(BIGNUM *) * pnum); - for (int i = 0; i < pnum; i++) { - primes1[i] = BN_new(); - primes2[i] = BN_new(); - if (primes1[i] == NULL || primes2[i] == NULL) { - err = 1; - break; + if (primes1 == NULL || primes2 == NULL) { + err = 1; + } + if (err == 0) { + for (int i = 0; i < pnum; i++) { + primes1[i] = BN_new(); + primes2[i] = BN_new(); + if (primes1[i] == NULL || primes2[i] == NULL) { + err = 1; + break; + } } } if (err == 0) { @@ -1587,12 +1592,18 @@ static int test_rsa_decode_pkcs8(void) } } } - for (int i = 0; i < pnum; i++) { - BN_free(primes1[i]); - BN_free(primes2[i]); + if (primes1 != NULL && primes2 != NULL) { + for (int i = 0; i < pnum; i++) { + if (primes1[i] != NULL) { + BN_free(primes1[i]); + } + if (primes2[i] != NULL) { + BN_free(primes2[i]); + } + } + free(primes1); + free(primes2); } - free(primes1); - free(primes2); } if (err == 0) {