Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ CI has a fast per-PR host lane, the tiered M33MU emulator matrix, and static
analysis. M33MU runs a per-port smoke set on every pull request and the full
matrix on labels, main pushes, and nightly.

`codeql.yml` runs C security queries on pull requests, pushes to `main` and
release branches, and weekly. `coverity.yml` scans on pushes to `main`, daily,
or on manual dispatch from `wolfSSL/wolfTrust` only. Both trace host suite
builds and secure-image builds for STM32H563 and MIMXRT700 with both crypto
engines via `tools/ci-static-analysis-build.sh`.
`codeql.yml` runs C security queries. `coverity.yml` runs on Sundays at
00:00 UTC or by manual dispatch. Both build the host suites and secure images
for STM32H563 and MIMXRT700 with both crypto engines.

Coverity scans wolfTrust runtime, ports, tests, and generated policy code.
`tools/ci-coverity-filter.py` removes dependency source units before upload
and rejects an invalid capture. Keep submodules under `lib/` and enable
**Ignore component in analysis** for the `Third party dependencies` component
(`.*/lib/.*`) in Coverity Scan to exclude dependency headers too.

An accepted upload queues analysis; results appear after Coverity processes it.

## At a glance

Expand Down
37 changes: 33 additions & 4 deletions .github/workflows/coverity.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
name: Coverity Scan

on:
push:
branches: [main]
schedule:
- cron: '0 0 * * *'
- cron: '0 0 * * 0' # Sundays at 00:00 UTC
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -43,10 +41,41 @@ jobs:
echo 'Register wolfTrust on Coverity Scan and configure COVERITY_SCAN_TOKEN and COVERITY_SCAN_EMAIL.' >&2
exit 1
fi
- name: Run Coverity Scan
- name: Capture Coverity build
uses: vapier/coverity-scan-action@1b6fd4eaba6651aa354c9ea7f48caa1710b4e12e # v1
with:
project: wolfTrust
token: ${{ secrets.COVERITY_SCAN_TOKEN }}
email: ${{ secrets.COVERITY_SCAN_EMAIL }}
command: sh tools/ci-static-analysis-build.sh
dry_run: 'true'
- name: Restrict capture to wolfTrust sources
shell: bash
run: |
export PATH="$PWD/cov-analysis/bin:$PATH"
python3 tools/ci-coverity-filter.py
tar -czf cov-int.tgz cov-int
- name: Submit build to Coverity Scan
shell: bash
env:
COVERITY_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
COVERITY_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}
run: |
response_file="$RUNNER_TEMP/coverity-upload-response.txt"
http_status=$(curl \
--silent --show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--form-string "token=$COVERITY_TOKEN" \
--form-string "email=$COVERITY_EMAIL" \
--form 'file=@cov-int.tgz' \
--form-string "version=$GITHUB_SHA" \
--form-string "description=wolfTrust $GITHUB_REF" \
'https://scan.coverity.com/builds?project=wolfTrust')
cat "$response_file"
# Coverity's official upload script requires 201 for a new submission.
if [ "$http_status" != '201' ]; then
echo "::error::Coverity did not accept the build submission (HTTP $http_status)."
exit 1
fi
printf '%s\n' 'Coverity accepted the build for analysis. Results are published after server-side processing.' >> "$GITHUB_STEP_SUMMARY"
64 changes: 64 additions & 0 deletions tools/ci-coverity-filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Remove dependency translation units before a Coverity Scan upload."""

import re
import subprocess
from pathlib import Path


def filter_capture():
root = Path.cwd().resolve()
dependencies = {root / "lib"}
index = subprocess.check_output(["git", "ls-files", "--stage", "-z"])
for entry in index.split(b"\0"):
if entry.startswith(b"160000 "):
dependencies.add(root / entry.split(b"\t", 1)[1].decode())
# Coverity Scan's ignored component also covers included headers in lib/.
# Refuse a new submodule elsewhere rather than upload unexcluded headers.
if any(path != root / "lib" and root / "lib" not in path.parents
for path in dependencies):
raise ValueError("Coverity dependencies must reside under lib/ to "
"match the project's ignored component")

command = ["cov-manage-emit", "--dir", "cov-int"]

def owned(source):
path = Path(source).resolve()
return root in path.parents and not any(
dependency in path.parents for dependency in dependencies
)

def list_units():
output = subprocess.check_output(command + ["list"], text=True)
print(output, end="")
units = []
for line in output.splitlines():
if "->" not in line:
continue
match = re.fullmatch(
r"\s*([0-9]+)\s+->\s+(/.+?)(?: \(recoverable errors\))?", line
)
if match is None:
raise ValueError("Unrecognized Coverity translation unit listing")
units.append((match[1], match[2]))
if not units:
raise ValueError("Coverity capture is empty")
return units

units = list_units()
excluded = [unit for unit, source in units if not owned(source)]
if excluded:
subprocess.run(command + ["--tu", ",".join(excluded), "delete"],
check=True)
retained = list_units()
if any(not owned(source) for _, source in retained):
raise ValueError("Coverity capture still contains dependency sources")
print(f"Coverity scope: excluded {len(excluded)} dependency units; "
f"retained {len(retained)} wolfTrust units")


if __name__ == "__main__":
try:
filter_capture()
except (OSError, ValueError, subprocess.CalledProcessError) as error:
raise SystemExit(f"::error::{error}") from error
Loading