diff --git a/.github/actions/analysis/coverity/action.yml b/.github/actions/analysis/coverity/action.yml deleted file mode 100644 index 6c6b670..0000000 --- a/.github/actions/analysis/coverity/action.yml +++ /dev/null @@ -1,111 +0,0 @@ -# -# BSD 3-Clause License -# Copyright (C) 2026 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -# -name: 'Coverity Scan' -description: 'Install (if needed) and run Coverity static analysis' - -inputs: - coverity-url: - description: 'URL to download the Coverity analysis tarball' - required: true - coverity-user: - description: 'Artifactory username for Coverity download' - required: true - coverity-password: - description: 'Artifactory password for Coverity download' - required: true - -runs: - using: composite - steps: - - name: Install Coverity - shell: bash - env: - COVERITY_URL: ${{ inputs.coverity-url }} - COVERITY_USER: ${{ inputs.coverity-user }} - COVERITY_PASSWORD: ${{ inputs.coverity-password }} - run: | - echo "===== Coverity Setup =====" - COVERITY_DIR="$HOME/coverity" - if [ -x "$COVERITY_DIR/bin/cov-build" ]; then - echo " [OK] Coverity already installed at $COVERITY_DIR" - "$COVERITY_DIR/bin/cov-build" --ident | head -1 || true - exit 0 - fi - echo " Downloading Coverity..." - mkdir -p "$COVERITY_DIR" - wget --no-proxy -q --user="$COVERITY_USER" --password="$COVERITY_PASSWORD" \ - -O /tmp/coverity.tar.gz "$COVERITY_URL" - echo " Extracting Coverity..." - tar xzf /tmp/coverity.tar.gz --strip-components=1 -C "$COVERITY_DIR" - rm -f /tmp/coverity.tar.gz - echo " Coverity installed:" - "$COVERITY_DIR/bin/cov-build" --ident | head -1 || true - - - name: Coverity Scan - shell: bash - run: | - # Resolve MTL pkg-config path - export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/x86_64-linux-gnu/pkgconfig:${PKG_CONFIG_PATH:-} - if ! pkg-config --exists mtl 2>/dev/null; then - MTL_PC=$(find /usr /home /opt -name "mtl.pc" 2>/dev/null | head -1) - if [ -z "$MTL_PC" ]; then - echo "ERROR: MTL pkg-config file not found." - exit 1 - fi - MTL_PC_DIR=$(dirname "$MTL_PC") - echo "Found MTL pkgconfig at: $MTL_PC_DIR" - export PKG_CONFIG_PATH="${MTL_PC_DIR}:${PKG_CONFIG_PATH}" - fi - - cd "$GITHUB_WORKSPACE" - REPORT_DIR="$GITHUB_WORKSPACE/reports" - mkdir -p "$REPORT_DIR" - - { - # Configure Coverity for cc (meson uses cc which is gcc) - $HOME/coverity/bin/cov-configure --compiler cc --comptype gcc --template - - # Clean and setup meson build directory - rm -rf build coverity_output - meson setup build - - # Run cov-build wrapping the ninja compilation - $HOME/coverity/bin/cov-build --dir coverity_output/ ninja -C build - - # Analyze captured build - $HOME/coverity/bin/cov-analyze --dir coverity_output/ \ - --concurrency --enable-constraint-fpp --enable-fnptr --enable-virtual \ - --disable ASSERT_SIDE_EFFECT \ - --disable AUTO_CAUSES_COPY \ - --disable BAD_CHECK_OF_WAIT_COND \ - --disable BAD_SHIFT \ - --disable COPY_INSTEAD_OF_MOVE \ - --disable CUDA.COLLECTIVE_WARP_SHUFFLE_WIDTH \ - --disable CUDA.CUDEVICE_HANDLES \ - --disable CUDA.DEVICE_DEPENDENT \ - --disable CUDA.DEVICE_DEPENDENT_CALLBACKS \ - --disable CUDA.DIVERGENCE_AT_COLLECTIVE_OPERATION \ - --disable CUDA.ERROR_INTERFACE \ - --disable CUDA.ERROR_KERNEL_LAUNCH \ - --disable CUDA.FORK \ - --disable CUDA.INACTIVE_THREAD_AT_COLLECTIVE_WARP \ - --disable CUDA.INITIATION_OBJECT_DEVICE_THREAD_BLOCK \ - --disable CUDA.INVALID_MEMORY_ACCESS \ - --disable CUDA.SHARE_FUNCTION \ - --disable CUDA.SHARE_OBJECT_STREAM_ASSOCIATED \ - --disable CUDA.SPECIFIERS_INCONSISTENCY \ - --disable CUDA.SYNCHRONIZE_TERMINATION \ - --disable INEFFICIENT_RESERVE \ - --disable MISSING_COMMA \ - --disable MISSING_MOVE_ASSIGNMENT \ - --disable OVERLAPPING_COPY \ - --disable STREAM_FORMAT_STATE \ - --disable UNINTENDED_INTEGER_DIVISION - - # Generate JSON report - $HOME/coverity/bin/cov-format-errors --dir coverity_output/ \ - --json-output-v8 "$REPORT_DIR/coverity-report.json" - } 2>&1 | tee "$REPORT_DIR/coverity_scan.txt" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c80660c..7531b56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,13 +61,6 @@ jobs: - name: cppcheck uses: ./.github/actions/analysis/cppcheck - # - name: Coverity Scan - # uses: ./.github/actions/analysis/coverity - # with: - # coverity-url: ${{ secrets.COVERITY_URL }} - # coverity-user: ${{ secrets.COVERITY_ARTIFACTORY_USER }} - # coverity-password: ${{ secrets.COVERITY_ARTIFACTORY_PASSWORD }} - - name: Trivy Scan uses: ./.github/actions/analysis/trivy diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000..f853158 --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,207 @@ +# +# BSD 3-Clause License +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +name: Coverity Scan + +on: + push: + branches: + - main + schedule: + # Weekly on Tuesday at 05:00 UTC + - cron: '0 5 * * 2' + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverity: + name: Coverity Static Analysis + runs-on: ubuntu-latest + permissions: + contents: read # checkout repository + security-events: write # upload SARIF results + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Environment check + uses: ./.github/actions/environment-check + + - name: Install Coverity + env: + COVERITY_URL: ${{ secrets.COVERITY_URL }} + COVERITY_USER: ${{ secrets.COVERITY_ARTIFACTORY_USER }} + COVERITY_PASSWORD: ${{ secrets.COVERITY_ARTIFACTORY_PASSWORD }} + run: | + echo "===== Coverity Setup =====" + COVERITY_DIR="$HOME/coverity" + if [ -x "$COVERITY_DIR/bin/cov-build" ]; then + echo " [OK] Coverity already installed at $COVERITY_DIR" + "$COVERITY_DIR/bin/cov-build" --ident | head -1 || true + exit 0 + fi + echo " Downloading Coverity..." + mkdir -p "$COVERITY_DIR" + wget --no-proxy -q --user="$COVERITY_USER" --password="$COVERITY_PASSWORD" \ + -O /tmp/coverity.tar.gz "$COVERITY_URL" + echo " Extracting Coverity..." + tar xzf /tmp/coverity.tar.gz --strip-components=1 -C "$COVERITY_DIR" + rm -f /tmp/coverity.tar.gz + echo " Coverity installed:" + "$COVERITY_DIR/bin/cov-build" --ident | head -1 || true + + - name: Run Coverity Analysis + run: | + # Resolve MTL pkg-config path + export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/x86_64-linux-gnu/pkgconfig:${PKG_CONFIG_PATH:-} + if ! pkg-config --exists mtl 2>/dev/null; then + MTL_PC=$(find /usr /home /opt -name "mtl.pc" 2>/dev/null | head -1) + if [ -z "$MTL_PC" ]; then + echo "ERROR: MTL pkg-config file not found." + exit 1 + fi + MTL_PC_DIR=$(dirname "$MTL_PC") + echo "Found MTL pkgconfig at: $MTL_PC_DIR" + export PKG_CONFIG_PATH="${MTL_PC_DIR}:${PKG_CONFIG_PATH}" + fi + + REPORT_DIR="$GITHUB_WORKSPACE/reports" + mkdir -p "$REPORT_DIR" + + # Configure Coverity for cc (meson uses cc which is gcc) + $HOME/coverity/bin/cov-configure --compiler cc --comptype gcc --template + + # Clean and setup meson build directory + rm -rf build coverity_output + meson setup build + + # Run cov-build wrapping the ninja compilation + $HOME/coverity/bin/cov-build --dir coverity_output/ ninja -C build + + # Analyze captured build + $HOME/coverity/bin/cov-analyze --dir coverity_output/ \ + --concurrency --enable-constraint-fpp --enable-fnptr --enable-virtual \ + --disable ASSERT_SIDE_EFFECT \ + --disable AUTO_CAUSES_COPY \ + --disable BAD_CHECK_OF_WAIT_COND \ + --disable BAD_SHIFT \ + --disable COPY_INSTEAD_OF_MOVE \ + --disable CUDA.COLLECTIVE_WARP_SHUFFLE_WIDTH \ + --disable CUDA.CUDEVICE_HANDLES \ + --disable CUDA.DEVICE_DEPENDENT \ + --disable CUDA.DEVICE_DEPENDENT_CALLBACKS \ + --disable CUDA.DIVERGENCE_AT_COLLECTIVE_OPERATION \ + --disable CUDA.ERROR_INTERFACE \ + --disable CUDA.ERROR_KERNEL_LAUNCH \ + --disable CUDA.FORK \ + --disable CUDA.INACTIVE_THREAD_AT_COLLECTIVE_WARP \ + --disable CUDA.INITIATION_OBJECT_DEVICE_THREAD_BLOCK \ + --disable CUDA.INVALID_MEMORY_ACCESS \ + --disable CUDA.SHARE_FUNCTION \ + --disable CUDA.SHARE_OBJECT_STREAM_ASSOCIATED \ + --disable CUDA.SPECIFIERS_INCONSISTENCY \ + --disable CUDA.SYNCHRONIZE_TERMINATION \ + --disable INEFFICIENT_RESERVE \ + --disable MISSING_COMMA \ + --disable MISSING_MOVE_ASSIGNMENT \ + --disable OVERLAPPING_COPY \ + --disable STREAM_FORMAT_STATE \ + --disable UNINTENDED_INTEGER_DIVISION + + # Generate JSON report + $HOME/coverity/bin/cov-format-errors --dir coverity_output/ \ + --json-output-v8 "$REPORT_DIR/coverity-report.json" + + - name: Convert Coverity JSON to SARIF + run: | + REPORT_DIR="$GITHUB_WORKSPACE/reports" + python3 - <<'EOF' + import json, sys + + sarif = { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{ + "tool": { + "driver": { + "name": "Coverity", + "informationUri": "https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html", + "rules": [] + } + }, + "results": [] + }] + } + + try: + with open("reports/coverity-report.json") as f: + cov = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + with open("reports/coverity-results.sarif", "w") as f: + json.dump(sarif, f, indent=2) + sys.exit(0) + + rules_map = {} + results = [] + + for issue in cov.get("issues", []): + checker = issue.get("checkerName", "unknown") + if checker not in rules_map: + rule_idx = len(rules_map) + rules_map[checker] = rule_idx + sarif["runs"][0]["tool"]["driver"]["rules"].append({ + "id": checker, + "shortDescription": {"text": issue.get("checkerProperties", {}).get("subcategoryShortDescription", checker)}, + "helpUri": f"https://community.synopsys.com/s/article/{checker}" + }) + + events = issue.get("events", []) + main_event = events[0] if events else {} + file_path = main_event.get("strippedFilePathname", main_event.get("filePathname", "unknown")) + line = main_event.get("lineNumber", 1) + + results.append({ + "ruleId": checker, + "ruleIndex": rules_map[checker], + "level": "warning", + "message": {"text": issue.get("checkerProperties", {}).get("subcategoryLongDescription", checker)}, + "locations": [{ + "physicalLocation": { + "artifactLocation": {"uri": file_path, "uriBaseId": "%SRCROOT%"}, + "region": {"startLine": line} + } + }] + }) + + sarif["runs"][0]["results"] = results + + with open("reports/coverity-results.sarif", "w") as f: + json.dump(sarif, f, indent=2) + + print(f"Converted {len(results)} Coverity issues to SARIF") + EOF + + - name: Upload SARIF to Security tab + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + if: always() + with: + sarif_file: reports/coverity-results.sarif + category: coverity + + - name: Upload reports + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: coverity-report-${{ github.run_id }} + path: reports/ + retention-days: 30 diff --git a/.github/workflows/daily_build.yml b/.github/workflows/daily_build.yml index e49ad33..682d748 100644 --- a/.github/workflows/daily_build.yml +++ b/.github/workflows/daily_build.yml @@ -64,13 +64,6 @@ jobs: - name: cppcheck uses: ./.github/actions/analysis/cppcheck - # - name: Coverity Scan - # uses: ./.github/actions/analysis/coverity - # with: - # coverity-url: ${{ secrets.COVERITY_URL }} - # coverity-user: ${{ secrets.COVERITY_ARTIFACTORY_USER }} - # coverity-password: ${{ secrets.COVERITY_ARTIFACTORY_PASSWORD }} - - name: Trivy Scan uses: ./.github/actions/analysis/trivy diff --git a/.github/workflows/libfuzzer.yml b/.github/workflows/libfuzzer.yml new file mode 100644 index 0000000..aaea7ec --- /dev/null +++ b/.github/workflows/libfuzzer.yml @@ -0,0 +1,94 @@ +# +# BSD 3-Clause License +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +name: Fuzz Testing (libFuzzer) + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Weekly on Thursday at 03:00 UTC + - cron: '0 3 * * 4' + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + libfuzzer: + name: libFuzzer - Config Parser + runs-on: ubuntu-latest + permissions: + contents: read # checkout repository + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang libavutil-dev libavformat-dev libavcodec-dev libswscale-dev pkg-config + + - name: Build fuzz harness (AddressSanitizer + libFuzzer) + run: | + cd fuzz + export CC=clang + $CC -fsanitize=fuzzer,address -g -O1 -fno-omit-frame-pointer -I../include \ + fuzz_config_reader_libfuzzer.c \ + ../src/util/config_reader.c \ + ../src/util/logger.c \ + -o fuzz_config_reader_libfuzzer \ + $(pkg-config --cflags --libs libavutil) -lm + echo "Build successful: $(file fuzz_config_reader_libfuzzer)" + + - name: Run libFuzzer (timed) + run: | + cd fuzz + mkdir -p libfuzzer_corpus + # Seed from existing corpus, run for 5 minutes + ./fuzz_config_reader_libfuzzer \ + libfuzzer_corpus/ corpus/ \ + -max_total_time=300 \ + -print_final_stats=1 || FUZZ_EXIT=$? + if [ "${FUZZ_EXIT:-0}" -ne 0 ]; then + echo "::error::libFuzzer found a crash (exit code $FUZZ_EXIT)!" + exit 1 + fi + echo "No crashes found — libFuzzer passed." + + - name: Build fuzz harness (UndefinedBehaviorSanitizer + libFuzzer) + run: | + cd fuzz + export CC=clang + $CC -fsanitize=fuzzer,undefined -g -O1 -fno-omit-frame-pointer -I../include \ + fuzz_config_reader_libfuzzer.c \ + ../src/util/config_reader.c \ + ../src/util/logger.c \ + -o fuzz_config_reader_ubsan \ + $(pkg-config --cflags --libs libavutil) -lm + + - name: Run libFuzzer with UBSan (timed) + run: | + cd fuzz + mkdir -p ubsan_corpus + ./fuzz_config_reader_ubsan \ + ubsan_corpus/ corpus/ \ + -max_total_time=300 \ + -print_final_stats=1 || FUZZ_EXIT=$? + if [ "${FUZZ_EXIT:-0}" -ne 0 ]; then + echo "::error::libFuzzer+UBSan found an issue (exit code $FUZZ_EXIT)!" + exit 1 + fi + echo "No issues found — UBSan fuzzing passed." diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index af6319f..56da48c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -65,13 +65,6 @@ jobs: - name: cppcheck uses: ./.github/actions/analysis/cppcheck - # - name: Coverity Scan - # uses: ./.github/actions/analysis/coverity - # with: - # coverity-url: ${{ secrets.COVERITY_URL }} - # coverity-user: ${{ secrets.COVERITY_ARTIFACTORY_USER }} - # coverity-password: ${{ secrets.COVERITY_ARTIFACTORY_PASSWORD }} - - name: Trivy Scan uses: ./.github/actions/analysis/trivy diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e9a27de..161c239 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -31,18 +31,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Run OpenSSF Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: scorecard-results.sarif results_format: sarif publish_results: true + repo_token: ${{ secrets.SCORECARD_TOKEN }} - name: Upload SARIF to Security tab - uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: scorecard-results.sarif diff --git a/fuzz/fuzz_config_reader_libfuzzer.c b/fuzz/fuzz_config_reader_libfuzzer.c new file mode 100644 index 0000000..8d236e4 --- /dev/null +++ b/fuzz/fuzz_config_reader_libfuzzer.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2026 Intel Corporation + * + * libFuzzer harness for the JSON config parser. + * Used by ClusterFuzzLite for continuous fuzzing (detected by OpenSSF Scorecard). + * + * Build: clang -fsanitize=fuzzer,address -g -O1 -I../include \ + * fuzz_config_reader_libfuzzer.c ../src/util/config_reader.c \ + * ../src/util/logger.c -lavutil -lm -o fuzz_config_reader_libfuzzer + */ + +#include +#include +#include +#include +#include +#include +#include "util/config_reader.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 2 || size > (1 << 20)) { + return 0; + } + + /* Write fuzz input to a temporary file for parse_tx_config */ + char tmpfile[] = "/tmp/fuzz_cfg_XXXXXX"; + int fd = mkstemp(tmpfile); + if (fd < 0) { + return 0; + } + + if (write(fd, data, size) != (ssize_t)size) { + close(fd); + unlink(tmpfile); + return 0; + } + close(fd); + + struct dvledtx_config config; + memset(&config, 0, sizeof(config)); + parse_tx_config(tmpfile, &config); + validate_tx_config(&config); + + unlink(tmpfile); + return 0; +}