mirror of
https://github.com/speed47/spectre-meltdown-checker.git
synced 2026-04-27 19:13:19 +02:00
Compare commits
74 Commits
master
...
50845adbfb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50845adbfb | ||
|
|
7eaa794980 | ||
|
|
7e5eee74ac | ||
|
|
9bef6ec533 | ||
|
|
f587d9355e | ||
|
|
83be8fd544 | ||
|
|
9383287fc6 | ||
|
|
a2823830a6 | ||
|
|
6212de226a | ||
|
|
f8873048fc | ||
|
|
463e33d61c | ||
|
|
4d1af90420 | ||
|
|
e8a3c7d7f5 | ||
|
|
8ae598802c | ||
|
|
48a4c0e49c | ||
|
|
1557bbee42 | ||
|
|
4530f39fae | ||
|
|
d247733496 | ||
|
|
fc66ee567a | ||
|
|
072b98cefd | ||
|
|
bceb62f982 | ||
|
|
aacdd35c57 | ||
|
|
c0a389b086 | ||
|
|
726f9e54f5 | ||
|
|
11210ab772 | ||
|
|
624aef4a46 | ||
|
|
b6a7ee2345 | ||
|
|
5698711b3d | ||
|
|
e0f9aeab81 | ||
|
|
2f550ba8cd | ||
|
|
3f60773ec4 | ||
|
|
acaf3b684f | ||
|
|
0ec51090ae | ||
|
|
e9cb988409 | ||
|
|
c147f3f7d4 | ||
|
|
065f19e313 | ||
|
|
1214e63687 | ||
|
|
67be7eb116 | ||
|
|
b4db134e49 | ||
|
|
d7cd9e8b6b | ||
|
|
a4c3900ef0 | ||
|
|
1d00acbc9a | ||
|
|
90a8a3057c | ||
|
|
40b7ae9098 | ||
|
|
27ac93dd39 | ||
|
|
dab7bebd3c | ||
|
|
8f76537159 | ||
|
|
fd7083cb08 | ||
|
|
8ef4c71d36 | ||
|
|
240d6db210 | ||
|
|
fbfdb89e7a | ||
|
|
5c571bacc6 | ||
|
|
6f8112c700 | ||
|
|
f46c743cad | ||
|
|
33bdd0688d | ||
|
|
7f87ade3fe | ||
|
|
e2d4d14e14 | ||
|
|
ddf2f2c723 | ||
|
|
fe376887ab | ||
|
|
7b41bcca2b | ||
|
|
151dd12e3e | ||
|
|
15ea90f312 | ||
|
|
5fd6a20ebb | ||
|
|
e7df6a3e30 | ||
|
|
ba24551c56 | ||
|
|
7c2699c01a | ||
|
|
6663b6422e | ||
|
|
fe55c70658 | ||
|
|
d0822e1f9d | ||
|
|
10e5b5749e | ||
|
|
4f7f83a40e | ||
|
|
4bbbd71564 | ||
|
|
c174a8b754 | ||
|
|
0f36203b5f |
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
@@ -25,21 +25,81 @@ jobs:
|
|||||||
mv spectre-meltdown-checker.sh dist/
|
mv spectre-meltdown-checker.sh dist/
|
||||||
- name: check direct execution
|
- name: check direct execution
|
||||||
run: |
|
run: |
|
||||||
|
set -x
|
||||||
expected=$(cat .github/workflows/expected_cve_count)
|
expected=$(cat .github/workflows/expected_cve_count)
|
||||||
cd dist
|
cd dist
|
||||||
nb=$(sudo ./spectre-meltdown-checker.sh --batch json | jq '.[]|.CVE' | wc -l)
|
|
||||||
|
json=$(sudo ./spectre-meltdown-checker.sh --batch json || true)
|
||||||
|
|
||||||
|
# Validate JSON is well-formed (and show it if not)
|
||||||
|
echo "$json" | jq . >/dev/null || {
|
||||||
|
echo "Invalid JSON produced by spectre-meltdown-checker.sh"
|
||||||
|
echo "$json"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required keys exist
|
||||||
|
for key in meta system cpu cpu_microcode vulnerabilities; do
|
||||||
|
echo "$json" | jq -e ".$key" >/dev/null || {
|
||||||
|
echo "Missing top-level key: $key"
|
||||||
|
echo "$json" | jq .
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
# Use -r to get raw scalars (no quotes)
|
||||||
|
fmtver=$(echo "$json" | jq -r '.meta.format_version // empty')
|
||||||
|
if [ "$fmtver" != "1" ]; then
|
||||||
|
echo "Unexpected format_version: $fmtver"
|
||||||
|
echo "$json" | jq .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_as_root=$(echo "$json" | jq -r '.meta.run_as_root // empty')
|
||||||
|
if [ "$run_as_root" != "true" ]; then
|
||||||
|
echo "Expected run_as_root=true, got: $run_as_root"
|
||||||
|
echo "$json" | jq .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mocked=$(echo "$json" | jq -r '.meta.mocked // "false"')
|
||||||
|
if [ "$mocked" = "true" ]; then
|
||||||
|
echo "mocked=true must never appear in production"
|
||||||
|
echo "$json" | jq .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Count CVEs robustly (as a number)
|
||||||
|
nb=$(echo "$json" | jq -r '[.vulnerabilities[].cve] | length')
|
||||||
if [ "$nb" -ne "$expected" ]; then
|
if [ "$nb" -ne "$expected" ]; then
|
||||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||||
|
echo "$json" | jq '.vulnerabilities[].cve'
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "OK $nb CVEs reported"
|
echo "OK $nb CVEs reported"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Validate json-terse backward compatibility
|
||||||
|
nb_terse=$(sudo ./spectre-meltdown-checker.sh --batch json-terse | jq -r 'map(.CVE) | length')
|
||||||
|
if [ "$nb_terse" -ne "$expected" ]; then
|
||||||
|
echo "json-terse backward compat broken: $nb_terse CVEs instead of $expected"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK json-terse backward compat: $nb_terse CVEs"
|
||||||
|
fi
|
||||||
- name: check docker compose run execution
|
- name: check docker compose run execution
|
||||||
run: |
|
run: |
|
||||||
expected=$(cat .github/workflows/expected_cve_count)
|
expected=$(cat .github/workflows/expected_cve_count)
|
||||||
cd dist
|
cd dist
|
||||||
docker compose build
|
docker compose build
|
||||||
nb=$(docker compose run --rm spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l)
|
json=$(docker compose run --rm spectre-meltdown-checker --batch json || true)
|
||||||
|
echo "$json" | jq . > /dev/null
|
||||||
|
fmtver=$(echo "$json" | jq '.meta.format_version')
|
||||||
|
if [ "$fmtver" != "1" ]; then
|
||||||
|
echo "Unexpected format_version: $fmtver"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
nb=$(echo "$json" | jq '.vulnerabilities[].cve' | wc -l)
|
||||||
if [ "$nb" -ne "$expected" ]; then
|
if [ "$nb" -ne "$expected" ]; then
|
||||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -51,7 +111,14 @@ jobs:
|
|||||||
expected=$(cat .github/workflows/expected_cve_count)
|
expected=$(cat .github/workflows/expected_cve_count)
|
||||||
cd dist
|
cd dist
|
||||||
docker build -t spectre-meltdown-checker .
|
docker build -t spectre-meltdown-checker .
|
||||||
nb=$(docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker --batch json | jq '.[]|.CVE' | wc -l)
|
json=$(docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker --batch json || true)
|
||||||
|
echo "$json" | jq . > /dev/null
|
||||||
|
fmtver=$(echo "$json" | jq '.meta.format_version')
|
||||||
|
if [ "$fmtver" != "1" ]; then
|
||||||
|
echo "Unexpected format_version: $fmtver"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
nb=$(echo "$json" | jq '.vulnerabilities[].cve' | wc -l)
|
||||||
if [ "$nb" -ne "$expected" ]; then
|
if [ "$nb" -ne "$expected" ]; then
|
||||||
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
echo "Invalid number of CVEs reported: $nb instead of $expected"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -92,15 +159,19 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: create a pull request to ${{ github.ref_name }}-build
|
- name: create a pull request to ${{ github.ref_name }}-build
|
||||||
run: |
|
run: |
|
||||||
|
# all the files in dist/* and .github/* must be moved as is to the -build branch root, move them out for now:
|
||||||
tmpdir=$(mktemp -d)
|
tmpdir=$(mktemp -d)
|
||||||
mv ./dist/* .github $tmpdir/
|
mv ./dist/* .github $tmpdir/
|
||||||
rm -rf ./dist
|
rm -rf ./dist
|
||||||
|
|
||||||
git fetch origin ${{ github.ref_name }}-build
|
git fetch origin ${{ github.ref_name }}-build
|
||||||
git checkout -f ${{ github.ref_name }}-build
|
git checkout -f ${{ github.ref_name }}-build
|
||||||
|
rm -rf doc/
|
||||||
mv $tmpdir/* .
|
mv $tmpdir/* .
|
||||||
rm -rf src/
|
rm -rf src/ scripts/ img/
|
||||||
mkdir -p .github
|
mkdir -p .github
|
||||||
rsync -vaP --delete $tmpdir/.github/ .github/
|
rsync -vaP --delete $tmpdir/.github/ .github/
|
||||||
|
|
||||||
git add --all
|
git add --all
|
||||||
echo =#=#= DIFF CACHED
|
echo =#=#= DIFF CACHED
|
||||||
git diff --cached
|
git diff --cached
|
||||||
|
|||||||
199
.github/workflows/daily_vuln_scan_prompt.md
vendored
Normal file
199
.github/workflows/daily_vuln_scan_prompt.md
vendored
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Daily transient-execution vulnerability scan
|
||||||
|
|
||||||
|
You are a scheduled agent running inside a GitHub Actions job. Your job
|
||||||
|
is to audit public news/advisory sources for **transient-execution and
|
||||||
|
CPU side-channel vulnerabilities** that may need to be added to
|
||||||
|
**spectre-meltdown-checker** (this repository).
|
||||||
|
|
||||||
|
## What counts as "relevant"
|
||||||
|
|
||||||
|
spectre-meltdown-checker detects, reports, and suggests mitigations for
|
||||||
|
CPU vulnerabilities such as: Spectre v1/v2/v4, Meltdown, Foreshadow/L1TF,
|
||||||
|
MDS (ZombieLoad/RIDL/Fallout), TAA, SRBDS, iTLB Multihit, Zenbleed,
|
||||||
|
Downfall (GDS), Retbleed, Inception, SRSO, BHI, RFDS, Reptar, FP-DSS,
|
||||||
|
and any similar microarchitectural side-channel or speculative-execution
|
||||||
|
issue on x86 (Intel/AMD) or ARM CPUs. It also surfaces related hardware
|
||||||
|
mitigation features (SMAP/SMEP/UMIP/IBPB/eIBRS/STIBP…) when they gate
|
||||||
|
the remediation for a tracked CVE.
|
||||||
|
|
||||||
|
It does **not** track generic software CVEs, GPU driver bugs, networking
|
||||||
|
stacks, filesystem bugs, userspace crypto issues, or unrelated kernel
|
||||||
|
subsystems.
|
||||||
|
|
||||||
|
## Inputs handed to you by the workflow
|
||||||
|
|
||||||
|
- Working directory: the repo root (`/github/workspace` in Actions, or
|
||||||
|
wherever `actions/checkout` placed it). You may `grep` the repo to
|
||||||
|
check whether a CVE or codename is already covered.
|
||||||
|
- `state/seen.json` — memory carried over from the previous run, with
|
||||||
|
shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"last_run": "2026-04-17T08:00:12Z",
|
||||||
|
"seen": {
|
||||||
|
"<stable-id-1>": { "bucket": "unrelated", "seen_at": "2026-04-17T08:00:12Z", "source": "phoronix" },
|
||||||
|
"<stable-id-2>": { "bucket": "tocheck", "seen_at": "2026-04-17T08:00:12Z", "source": "oss-sec", "cve": "CVE-2026-1234" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On the very first run, or when the prior artifact has expired,
|
||||||
|
the file exists but `seen` is empty and `last_run` is `null`.
|
||||||
|
|
||||||
|
- Environment: `SCAN_DATE` (ISO-8601 timestamp of the run start, set by
|
||||||
|
the workflow). Treat this as "now" for all time-window decisions.
|
||||||
|
|
||||||
|
## Time window
|
||||||
|
|
||||||
|
This is a belt-and-suspenders design — use **both** mechanisms:
|
||||||
|
|
||||||
|
1. **Primary: stable-id dedup.** If an item's stable identifier (see
|
||||||
|
below) is already present in `state.seen`, skip it entirely — it
|
||||||
|
was classified on a previous day.
|
||||||
|
2. **Secondary: 25-hour window.** Among *new* items, prefer those whose
|
||||||
|
publication/update timestamp is within the last 25 h relative to
|
||||||
|
`SCAN_DATE`. This bounds work when the prior artifact expired
|
||||||
|
(90-day retention) or when `last_run` is stale (missed runs).
|
||||||
|
If `last_run` is older than 25 h, widen the window to
|
||||||
|
`now - last_run + 1h` so no items are lost across missed runs.
|
||||||
|
3. Items without a parseable timestamp: include them (fail-safe).
|
||||||
|
|
||||||
|
## Sources to poll
|
||||||
|
|
||||||
|
Fetch each URL with
|
||||||
|
`curl -sS -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" -L --max-time 20`.
|
||||||
|
On non-2xx or timeout, record the failure in the run summary and
|
||||||
|
continue — do not abort.
|
||||||
|
|
||||||
|
### RSS / Atom feeds (primary — parse feed timestamps)
|
||||||
|
|
||||||
|
| Short name | URL |
|
||||||
|
|-----------------|-----|
|
||||||
|
| phoronix | https://www.phoronix.com/rss.php |
|
||||||
|
| oss-sec | https://seclists.org/rss/oss-sec.rss |
|
||||||
|
| lwn | https://lwn.net/headlines/newrss |
|
||||||
|
| project-zero | https://googleprojectzero.blogspot.com/feeds/posts/default |
|
||||||
|
| vusec | https://www.vusec.net/feed/ |
|
||||||
|
| comsec-eth | https://comsec.ethz.ch/category/news/feed/ |
|
||||||
|
| msrc | https://msrc.microsoft.com/update-guide/rss |
|
||||||
|
| cisa | https://www.cisa.gov/cybersecurity-advisories/all.xml |
|
||||||
|
| cert-cc | https://www.kb.cert.org/vuls/atomfeed/ |
|
||||||
|
|
||||||
|
### HTML pages (no RSS — fetch, extract dated entries)
|
||||||
|
|
||||||
|
| Short name | URL |
|
||||||
|
|-----------------|-----|
|
||||||
|
| intel-psirt | https://www.intel.com/content/www/us/en/security-center/default.html |
|
||||||
|
| amd-psirt | https://www.amd.com/en/resources/product-security.html |
|
||||||
|
| arm-spec | https://developer.arm.com/Arm%20Security%20Center/Speculative%20Processor%20Vulnerability |
|
||||||
|
| transient-fail | https://transient.fail/ |
|
||||||
|
|
||||||
|
For HTML pages: look for advisory tables or listings with dates. Extract
|
||||||
|
the advisory title, permalink, and date. If a page has no dates at all,
|
||||||
|
compare its content against `state.seen` — any new advisory IDs not yet
|
||||||
|
classified count as "new this run".
|
||||||
|
|
||||||
|
## Stable identifier per source
|
||||||
|
|
||||||
|
Use the first available of these, in order, as the dedup key:
|
||||||
|
|
||||||
|
1. Vendor advisory ID (`INTEL-SA-01234`, `AMD-SB-7001`, `ARM-2024-0042`,
|
||||||
|
`VU#123456`, `CVE-YYYY-NNNNN`)
|
||||||
|
2. RSS `<guid>` / Atom `<id>`
|
||||||
|
3. Permalink URL (`<link>`)
|
||||||
|
|
||||||
|
Always also record the permalink URL in the output file so a human can
|
||||||
|
click through.
|
||||||
|
|
||||||
|
## Classification rules
|
||||||
|
|
||||||
|
For each **new** item (not in `state.seen`) that passes the time window,
|
||||||
|
pick exactly one bucket:
|
||||||
|
|
||||||
|
- **toimplement** — a clearly-identified new transient-execution / CPU
|
||||||
|
side-channel vulnerability in scope, **and not already covered by
|
||||||
|
this repo**. Verify the second half by grepping the repo for the CVE
|
||||||
|
ID *and* the codename before classifying; if either matches existing
|
||||||
|
code, demote to `tocheck`.
|
||||||
|
- **tocheck** — plausibly in-scope but ambiguous: mitigation-only
|
||||||
|
feature (LASS, IBT, APIC-virt, etc.); item seemingly already
|
||||||
|
implemented but worth confirming scope; unclear applicability
|
||||||
|
(e.g. embedded-only ARM SKU); CVE-ID pending; contradictory info
|
||||||
|
across sources. State clearly what would resolve the ambiguity.
|
||||||
|
- **unrelated** — everything else.
|
||||||
|
|
||||||
|
Tie-breakers: prefer `tocheck` over `unrelated` when uncertain. Prefer
|
||||||
|
`tocheck` over `toimplement` when the CVE ID is still "reserved" /
|
||||||
|
"pending" — false positives in `toimplement` waste human time more than
|
||||||
|
false positives in `tocheck`.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
Compute `TODAY=$(date -u -d "$SCAN_DATE" +%F)`. Write these files under
|
||||||
|
the repo root, overwriting if they already exist (they shouldn't unless
|
||||||
|
the workflow re-ran the same day):
|
||||||
|
|
||||||
|
- `rss_${TODAY}_toimplement.md`
|
||||||
|
- `rss_${TODAY}_tocheck.md`
|
||||||
|
- `rss_${TODAY}_unrelated.md`
|
||||||
|
|
||||||
|
Each file uses level-2 headers per source short-name, then one bullet
|
||||||
|
per item: the stable ID (if any), the permalink URL, and 1–2 sentences.
|
||||||
|
Keep entries terse — a human skims these daily.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## oss-sec
|
||||||
|
- **CVE-2026-1234** — https://www.openwall.com/lists/oss-security/2026/04/18/3
|
||||||
|
New Intel transient-execution bug "Foo" disclosed today; affects
|
||||||
|
Redwood Cove cores, microcode fix pending. Not yet covered by this
|
||||||
|
repo (grepped for CVE-2026-1234 and "Foo" — no matches).
|
||||||
|
|
||||||
|
## phoronix
|
||||||
|
- https://www.phoronix.com/news/Some-Article
|
||||||
|
Linux 7.2 drops a compiler-target flag; unrelated to CPU side channels.
|
||||||
|
```
|
||||||
|
|
||||||
|
If a bucket has no items, write the file with a single line
|
||||||
|
`(no new items in this window)` so it is obvious the job ran.
|
||||||
|
|
||||||
|
### Run summary
|
||||||
|
|
||||||
|
Append this block to the **tocheck** file (creating it if empty):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Run summary
|
||||||
|
- SCAN_DATE: <value>
|
||||||
|
- window cutoff: <computed cutoff>
|
||||||
|
- prior state size: <N> entries, last_run=<value>
|
||||||
|
- per-source new item counts: phoronix=<n>, oss-sec=<n>, lwn=<n>, ...
|
||||||
|
- fetch failures: <list, or "none">
|
||||||
|
- total classified this run: toimplement=<n>, tocheck=<n>, unrelated=<n>
|
||||||
|
```
|
||||||
|
|
||||||
|
### State update
|
||||||
|
|
||||||
|
Rewrite `state/seen.json` with:
|
||||||
|
|
||||||
|
- `last_run` = `SCAN_DATE`
|
||||||
|
- `seen` = union of (pruned prior `seen`) ∪ (all items classified this
|
||||||
|
run, keyed by stable ID, with `{bucket, seen_at=SCAN_DATE, source, cve?}`)
|
||||||
|
|
||||||
|
Pruning (keep state bounded): drop any entry whose `seen_at` is older
|
||||||
|
than 30 days before `SCAN_DATE`. The workflow step also does this as
|
||||||
|
a safety net, but do it here too so the in-memory view is consistent.
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Do NOT modify any repo source code. Only write the three markdown
|
||||||
|
output files and `state/seen.json`.
|
||||||
|
- Do NOT create commits, branches, or PRs.
|
||||||
|
- Do NOT call any tool that posts externally (Slack, GitHub comments,
|
||||||
|
issues, email, etc.).
|
||||||
|
- Do NOT follow links off-site for deeper investigation unless strictly
|
||||||
|
needed to resolve a `tocheck` ambiguity — budget of at most 5 such
|
||||||
|
follow-ups per run.
|
||||||
|
- If a source returns unexpectedly large content, truncate to the first
|
||||||
|
~200 items before parsing.
|
||||||
|
- If total runtime exceeds 15 minutes, finish whatever you can,
|
||||||
|
write partial outputs, and note it in the run summary.
|
||||||
2
.github/workflows/expected_cve_count
vendored
2
.github/workflows/expected_cve_count
vendored
@@ -1 +1 @@
|
|||||||
26
|
32
|
||||||
|
|||||||
129
.github/workflows/vuln-scan.yml
vendored
Normal file
129
.github/workflows/vuln-scan.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
name: Online search for vulns
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '42 8 * * *'
|
||||||
|
workflow_dispatch: {} # allow manual trigger
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read # needed to list/download previous run artifacts
|
||||||
|
id-token: write # needed to mint OIDC token
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: vuln-scan
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository (for grep-based dedup against existing checks)
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
# ---- Load previous state ---------------------------------------------
|
||||||
|
# Find the most recent successful run of THIS workflow (other than the
|
||||||
|
# current one) and pull its `vuln-scan-state` artifact. On the very
|
||||||
|
# first run there will be none — that's fine, we start empty.
|
||||||
|
- name: Find previous successful run id
|
||||||
|
id: prev
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
run_id=$(gh run list \
|
||||||
|
--workflow="${{ github.workflow }}" \
|
||||||
|
--status=success \
|
||||||
|
--limit 1 \
|
||||||
|
--json databaseId \
|
||||||
|
--jq '.[0].databaseId // empty')
|
||||||
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||||
|
if [ -n "$run_id" ]; then
|
||||||
|
echo "Found previous successful run: $run_id"
|
||||||
|
else
|
||||||
|
echo "No previous successful run — starting from empty state."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Download previous state artifact
|
||||||
|
if: steps.prev.outputs.run_id != ''
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
continue-on-error: true # tolerate retention expiry
|
||||||
|
with:
|
||||||
|
name: vuln-scan-state
|
||||||
|
path: state/
|
||||||
|
run-id: ${{ steps.prev.outputs.run_id }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Ensure state file exists
|
||||||
|
run: |
|
||||||
|
mkdir -p state
|
||||||
|
if [ ! -f state/seen.json ]; then
|
||||||
|
echo '{"last_run": null, "seen": {}}' > state/seen.json
|
||||||
|
echo "Initialized empty state."
|
||||||
|
fi
|
||||||
|
echo "State size: $(wc -c < state/seen.json) bytes"
|
||||||
|
|
||||||
|
# ---- Run the scan ----------------------------------------------------
|
||||||
|
# Runs Claude Code against daily_vuln_scan_prompt.md.
|
||||||
|
# That prompt file fully specifies: sources to poll, how to read
|
||||||
|
# state/seen.json, the 25-hour window, the output files to write,
|
||||||
|
# and how to rewrite state/seen.json at the end of the run.
|
||||||
|
- name: Research for online mentions of new vulns
|
||||||
|
id: scan
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
env:
|
||||||
|
SCAN_DATE: ${{ github.run_started_at }}
|
||||||
|
with:
|
||||||
|
claude_args: |
|
||||||
|
--model claude-opus-4-7 --allowedTools "Read,Write,Edit,Bash,Grep,Glob,WebFetch"
|
||||||
|
prompt: |
|
||||||
|
Read the full task instructions from .github/workflows/daily_vuln_scan_prompt.md and execute them end-to-end. That file fully specifies: sources to poll, how to read and update state/seen.json, the 25-hour window, which rss_YYYY-MM-DD_*.md files to write, and the run guardrails. Use $SCAN_DATE (env var) as "now" for time-window decisions.
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload Claude execution log
|
||||||
|
if: ${{ always() && steps.scan.outputs.execution_file != '' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: claude-execution-log-${{ github.run_id }}
|
||||||
|
path: ${{ steps.scan.outputs.execution_file }}
|
||||||
|
retention-days: 30
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
|
# ---- Persist outputs -------------------------------------------------
|
||||||
|
- name: Prune state (keep only entries from the last 30 days)
|
||||||
|
run: |
|
||||||
|
python3 - <<'PY'
|
||||||
|
import json, datetime, pathlib
|
||||||
|
p = pathlib.Path("state/seen.json")
|
||||||
|
data = json.loads(p.read_text())
|
||||||
|
cutoff = (datetime.datetime.utcnow() - datetime.timedelta(days=30)).isoformat()
|
||||||
|
before = len(data.get("seen", {}))
|
||||||
|
data["seen"] = {
|
||||||
|
k: v for k, v in data.get("seen", {}).items()
|
||||||
|
if v.get("seen_at", "9999") >= cutoff
|
||||||
|
}
|
||||||
|
after = len(data["seen"])
|
||||||
|
p.write_text(json.dumps(data, indent=2, sort_keys=True))
|
||||||
|
print(f"Pruned state: {before} -> {after} entries")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Upload new state artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: vuln-scan-state
|
||||||
|
path: state/seen.json
|
||||||
|
retention-days: 90
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload daily report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: vuln-scan-report-${{ github.run_id }}
|
||||||
|
path: rss_*.md
|
||||||
|
retention-days: 90
|
||||||
|
if-no-files-found: warn
|
||||||
190
.github/workflows/vuln-watch.yml
vendored
190
.github/workflows/vuln-watch.yml
vendored
@@ -1,190 +0,0 @@
|
|||||||
name: Online search for vulns
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '42 8 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
model:
|
|
||||||
description: 'Claude model to use (cron runs default to Sonnet)'
|
|
||||||
required: false
|
|
||||||
type: choice
|
|
||||||
default: claude-sonnet-4-6
|
|
||||||
options:
|
|
||||||
- claude-sonnet-4-6
|
|
||||||
- claude-opus-4-7
|
|
||||||
- claude-haiku-4-5-20251001
|
|
||||||
window_hours:
|
|
||||||
description: 'Lookback window in hours (cron runs use 25)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: '25'
|
|
||||||
reconsider_age_days:
|
|
||||||
description: 'Only reconsider backlog entries last reviewed ≥ N days ago (0 = all, default 7)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: '7'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
actions: read # needed to list/download previous run artifacts
|
|
||||||
id-token: write # needed by claude-code-action for OIDC auth
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: vuln-watch
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
watch:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 20
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# The scripts driving this workflow live on the `vuln-watch` branch so
|
|
||||||
# they don't clutter master (which is what ships to production). The
|
|
||||||
# workflow file itself MUST stay on the default branch, as GitHub only
|
|
||||||
# honors `schedule:` triggers on the default branch.
|
|
||||||
- name: Checkout vuln-watch branch (scripts + prompt)
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: vuln-watch
|
|
||||||
fetch-depth: 1
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install Python dependencies
|
|
||||||
run: python -m pip install --quiet feedparser
|
|
||||||
|
|
||||||
# ---- Load previous state ---------------------------------------------
|
|
||||||
# Find the most recent successful run of THIS workflow (other than the
|
|
||||||
# current one) and pull its `vuln-watch-state` artifact. On the very
|
|
||||||
# first run there will be none — that's fine, we start empty.
|
|
||||||
- name: Find previous successful run id
|
|
||||||
id: prev
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
run_id=$(gh run list \
|
|
||||||
--workflow="${{ github.workflow }}" \
|
|
||||||
--status=success \
|
|
||||||
--limit 1 \
|
|
||||||
--json databaseId \
|
|
||||||
--jq '.[0].databaseId // empty')
|
|
||||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
||||||
if [ -n "$run_id" ]; then
|
|
||||||
echo "Found previous successful run: $run_id"
|
|
||||||
else
|
|
||||||
echo "No previous successful run — starting from empty state."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Download previous state artifact
|
|
||||||
if: steps.prev.outputs.run_id != ''
|
|
||||||
uses: actions/download-artifact@v5
|
|
||||||
continue-on-error: true # tolerate retention expiry
|
|
||||||
with:
|
|
||||||
name: vuln-watch-state
|
|
||||||
path: state/
|
|
||||||
run-id: ${{ steps.prev.outputs.run_id }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# ---- Fetch + diff (token-free; runs every time) ---------------------
|
|
||||||
# Performs conditional GETs (ETag / If-Modified-Since) against every
|
|
||||||
# source, parses RSS/Atom/HTML, dedups against state.seen + state.aliases,
|
|
||||||
# applies the time-window filter, and emits new_items.json.
|
|
||||||
# Updates state.sources (HTTP cache metadata + per-source high-water
|
|
||||||
# marks) in place so the cache survives even when Claude doesn't run.
|
|
||||||
- name: Fetch + diff all sources
|
|
||||||
id: diff
|
|
||||||
env:
|
|
||||||
SCAN_DATE: ${{ github.run_started_at }}
|
|
||||||
# Cron runs have no `inputs` context, so the fallback kicks in.
|
|
||||||
WINDOW_HOURS: ${{ inputs.window_hours || '25' }}
|
|
||||||
RECONSIDER_AGE_DAYS: ${{ inputs.reconsider_age_days || '7' }}
|
|
||||||
run: python -m scripts.vuln_watch.fetch_and_diff
|
|
||||||
|
|
||||||
# ---- Fetch checker code so Claude can grep it for coverage ---------
|
|
||||||
# The orphan vuln-watch branch has none of the actual checker code,
|
|
||||||
# so we pull the `test` branch (the dev branch where coded-but-
|
|
||||||
# unreleased CVE checks live) into ./checker/. The prompt tells
|
|
||||||
# Claude this is the canonical source of truth for "is CVE-X already
|
|
||||||
# implemented?". Only fetched on days with something to classify.
|
|
||||||
- name: Checkout checker code (test branch) for coverage grep
|
|
||||||
if: steps.diff.outputs.new_count != '0' || steps.diff.outputs.reconsider_count != '0'
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: test
|
|
||||||
path: checker
|
|
||||||
fetch-depth: 1
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
# ---- Classify new items with Claude (skipped when nothing is new) ---
|
|
||||||
# Model selection: a manual workflow_dispatch run picks from a dropdown
|
|
||||||
# (defaulting to Sonnet). Scheduled cron runs have no `inputs` context,
|
|
||||||
# so the `|| 'claude-sonnet-4-6'` fallback kicks in — cron always uses
|
|
||||||
# Sonnet to keep the daily cost floor low.
|
|
||||||
- name: Run classifier with Claude
|
|
||||||
id: classify
|
|
||||||
if: steps.diff.outputs.new_count != '0' || steps.diff.outputs.reconsider_count != '0'
|
|
||||||
uses: anthropics/claude-code-action@v1
|
|
||||||
env:
|
|
||||||
SCAN_DATE: ${{ github.run_started_at }}
|
|
||||||
with:
|
|
||||||
prompt: |
|
|
||||||
Read the full task instructions from scripts/daily_vuln_watch_prompt.md
|
|
||||||
and execute them end-to-end. Your input is new_items.json (already
|
|
||||||
deduped, windowed, and pre-filtered — do NOT re-fetch sources).
|
|
||||||
Write the three watch_${TODAY}_*.md files and classifications.json.
|
|
||||||
Use $SCAN_DATE as the canonical timestamp.
|
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
||||||
# model + tool allowlist pass through claude_args (v1 dropped the
|
|
||||||
# dedicated `model:` and `allowed_tools:` inputs). Job-level
|
|
||||||
# `timeout-minutes: 20` above bounds total runtime.
|
|
||||||
claude_args: |
|
|
||||||
--model ${{ inputs.model || 'claude-sonnet-4-6' }}
|
|
||||||
--allowedTools "Read,Write,Edit,Bash,Grep,Glob,WebFetch"
|
|
||||||
|
|
||||||
- name: Upload Claude execution log
|
|
||||||
if: ${{ always() && steps.classify.outputs.execution_file != '' }}
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: claude-execution-log-${{ github.run_id }}
|
|
||||||
path: ${{ steps.classify.outputs.execution_file }}
|
|
||||||
retention-days: 30
|
|
||||||
if-no-files-found: warn
|
|
||||||
|
|
||||||
# ---- Merge classifications back into state --------------------------
|
|
||||||
# Also writes stub watch_*.md files if the classify step was skipped, so
|
|
||||||
# the report artifact is consistent across runs.
|
|
||||||
- name: Merge classifications into state
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
SCAN_DATE: ${{ github.run_started_at }}
|
|
||||||
run: python -m scripts.vuln_watch.merge_state
|
|
||||||
|
|
||||||
- name: Upload new state artifact
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: vuln-watch-state
|
|
||||||
path: state/seen.json
|
|
||||||
retention-days: 90
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload daily report
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: vuln-watch-report-${{ github.run_id }}
|
|
||||||
path: |
|
|
||||||
watch_*.md
|
|
||||||
current_toimplement.md
|
|
||||||
current_tocheck.md
|
|
||||||
new_items.json
|
|
||||||
classifications.json
|
|
||||||
retention-days: 90
|
|
||||||
if-no-files-found: warn
|
|
||||||
22
README.md
22
README.md
@@ -272,23 +272,23 @@ In **Hardware-only** mode, the script only reports CPU information and per-CVE h
|
|||||||
|
|
||||||
- Get the latest version of the script using `curl` *or* `wget`
|
- Get the latest version of the script using `curl` *or* `wget`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -L https://meltdown.ovh -o spectre-meltdown-checker.sh
|
curl -L https://meltdown.ovh -o spectre-meltdown-checker.sh
|
||||||
wget https://meltdown.ovh -O spectre-meltdown-checker.sh
|
wget https://meltdown.ovh -O spectre-meltdown-checker.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
- Inspect the script. You never blindly run scripts you downloaded from the Internet, do you?
|
- Inspect the script. You never blindly run scripts you downloaded from the Internet, do you?
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vim spectre-meltdown-checker.sh
|
vim spectre-meltdown-checker.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
- When you're ready, run the script as root
|
- When you're ready, run the script as root
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x spectre-meltdown-checker.sh
|
chmod +x spectre-meltdown-checker.sh
|
||||||
sudo ./spectre-meltdown-checker.sh
|
sudo ./spectre-meltdown-checker.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using a docker container
|
### Using a docker container
|
||||||
|
|
||||||
|
|||||||
@@ -102,9 +102,7 @@ boundaries by a malicious guest. Prioritise remediation where
|
|||||||
|
|
||||||
### `cpu`
|
### `cpu`
|
||||||
|
|
||||||
CPU hardware identification. `null` when `--no-hw` is active, or when
|
CPU hardware identification. `null` when `--no-hw` is active.
|
||||||
`--arch-prefix` is set (host CPU info is then suppressed to avoid mixing
|
|
||||||
with a different-arch target kernel).
|
|
||||||
|
|
||||||
The object uses `arch` as a discriminator: `"x86"` for Intel/AMD/Hygon CPUs,
|
The object uses `arch` as a discriminator: `"x86"` for Intel/AMD/Hygon CPUs,
|
||||||
`"arm"` for ARM/Cavium/Phytium. Arch-specific fields live under a matching
|
`"arm"` for ARM/Cavium/Phytium. Arch-specific fields live under a matching
|
||||||
@@ -142,7 +140,7 @@ fields from the other architecture.
|
|||||||
|
|
||||||
#### `cpu.x86.capabilities`
|
#### `cpu.x86.capabilities`
|
||||||
|
|
||||||
Every capability is a **tri-state**: `true` (present), `false` (absent), or
|
Each capability is a **tri-state**: `true` (present), `false` (absent), or
|
||||||
`null` (not applicable or could not be read, e.g. when not root or on AMD for
|
`null` (not applicable or could not be read, e.g. when not root or on AMD for
|
||||||
Intel-specific features).
|
Intel-specific features).
|
||||||
|
|
||||||
@@ -240,7 +238,7 @@ with an unknown CVE ID).
|
|||||||
| `status` | string | `"OK"` / `"VULN"` / `"UNK"` | Check outcome (see below) |
|
| `status` | string | `"OK"` / `"VULN"` / `"UNK"` | Check outcome (see below) |
|
||||||
| `vulnerable` | boolean \| null | `false` / `true` / `null` | `false`=OK, `true`=VULN, `null`=UNK |
|
| `vulnerable` | boolean \| null | `false` / `true` / `null` | `false`=OK, `true`=VULN, `null`=UNK |
|
||||||
| `info` | string | | Human-readable description of the specific mitigation state or reason |
|
| `info` | string | | Human-readable description of the specific mitigation state or reason |
|
||||||
| `sysfs_status` | string \| null | `"OK"` / `"VULN"` / `"UNK"` / null | Status as reported by the kernel via `/sys/devices/system/cpu/vulnerabilities/`; null if sysfs was not consulted for this CVE, or if the CVE's check read sysfs in silent/quiet mode (raw message is still captured in `sysfs_message`) |
|
| `sysfs_status` | string \| null | `"OK"` / `"VULN"` / `"UNK"` / null | Status as reported by the kernel via `/sys/devices/system/cpu/vulnerabilities/`; null if sysfs was not consulted for this CVE |
|
||||||
| `sysfs_message` | string \| null | | Raw text from the sysfs file (e.g. `"Mitigation: PTI"`); null if sysfs was not consulted |
|
| `sysfs_message` | string \| null | | Raw text from the sysfs file (e.g. `"Mitigation: PTI"`); null if sysfs was not consulted |
|
||||||
|
|
||||||
#### Status values
|
#### Status values
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"description": "CPU hardware identification. Null when --no-hw is active or when --arch-prefix is set (host CPU info is then suppressed to avoid mixing with a different-arch target kernel). Contains an 'arch' discriminator ('x86' or 'arm') and a matching arch-specific sub-object with identification fields and capabilities.",
|
"description": "CPU hardware identification. Null when --no-hw is active. Contains an 'arch' discriminator ('x86' or 'arm') and a matching arch-specific sub-object with identification fields and capabilities.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "type": "null" },
|
{ "type": "null" },
|
||||||
{
|
{
|
||||||
@@ -180,16 +180,16 @@
|
|||||||
"type": ["string", "null"]
|
"type": ["string", "null"]
|
||||||
},
|
},
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"description": "CPU feature flags detected via CPUID and MSR reads. Every value is tri-state: true=present, false=absent, null=not applicable or unreadable.",
|
"description": "CPU feature flags detected via CPUID and MSR reads. Each value is true (present), false (absent), or null (not applicable or could not be read).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"spec_ctrl": { "type": ["boolean", "null"], "description": "SPEC_CTRL MSR present (Intel; enables IBRS + IBPB via WRMSR)" },
|
"spec_ctrl": { "type": ["boolean", "null"], "description": "SPEC_CTRL MSR present (Intel; enables IBRS + IBPB via WRMSR)" },
|
||||||
"ibrs": { "type": ["boolean", "null"], "description": "IBRS supported (via SPEC_CTRL, IBRS_SUPPORT, or cpuinfo fallback)" },
|
"ibrs": { "type": ["boolean", "null"], "description": "Indirect Branch Restricted Speculation" },
|
||||||
"ibpb": { "type": ["boolean", "null"], "description": "IBPB supported (via SPEC_CTRL, IBPB_SUPPORT, or cpuinfo fallback)" },
|
"ibpb": { "type": ["boolean", "null"], "description": "Indirect Branch Prediction Barrier" },
|
||||||
"ibpb_ret": { "type": ["boolean", "null"], "description": "IBPB on return (enhanced form)" },
|
"ibpb_ret": { "type": ["boolean", "null"], "description": "IBPB on return (enhanced form)" },
|
||||||
"stibp": { "type": ["boolean", "null"], "description": "STIBP supported (Intel/AMD/HYGON or cpuinfo fallback)" },
|
"stibp": { "type": ["boolean", "null"], "description": "Single Thread Indirect Branch Predictors" },
|
||||||
"ssbd": { "type": ["boolean", "null"], "description": "SSBD supported (SPEC_CTRL, VIRT_SPEC_CTRL, non-architectural MSR, or cpuinfo fallback)" },
|
"ssbd": { "type": ["boolean", "null"], "description": "Speculative Store Bypass Disable" },
|
||||||
"l1d_flush": { "type": ["boolean", "null"], "description": "L1D cache flush instruction" },
|
"l1d_flush": { "type": ["boolean", "null"], "description": "L1D cache flush instruction" },
|
||||||
"md_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers (MDS mitigation)" },
|
"md_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers (MDS mitigation)" },
|
||||||
"arch_capabilities": { "type": ["boolean", "null"], "description": "IA32_ARCH_CAPABILITIES MSR is present" },
|
"arch_capabilities": { "type": ["boolean", "null"], "description": "IA32_ARCH_CAPABILITIES MSR is present" },
|
||||||
@@ -231,7 +231,7 @@
|
|||||||
"tsa_l1_no": { "type": ["boolean", "null"], "description": "Not susceptible to TSA-L1" },
|
"tsa_l1_no": { "type": ["boolean", "null"], "description": "Not susceptible to TSA-L1" },
|
||||||
"verw_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers" },
|
"verw_clear": { "type": ["boolean", "null"], "description": "VERW clears CPU buffers" },
|
||||||
"autoibrs": { "type": ["boolean", "null"], "description": "AMD AutoIBRS (equivalent to enhanced IBRS on Intel)" },
|
"autoibrs": { "type": ["boolean", "null"], "description": "AMD AutoIBRS (equivalent to enhanced IBRS on Intel)" },
|
||||||
"sbpb": { "type": ["boolean", "null"], "description": "Selective Branch Predictor Barrier (AMD Inception mitigation): true if PRED_CMD MSR SBPB bit write succeeded; false if write failed; null if not verifiable (non-root, CPUID error, or CPU does not report SBPB support)" },
|
"sbpb": { "type": ["boolean", "null"], "description": "Selective Branch Predictor Barrier (AMD Inception mitigation)" },
|
||||||
"avx2": { "type": ["boolean", "null"], "description": "AVX2 supported (relevant to Downfall / GDS)" },
|
"avx2": { "type": ["boolean", "null"], "description": "AVX2 supported (relevant to Downfall / GDS)" },
|
||||||
"avx512": { "type": ["boolean", "null"], "description": "AVX-512 supported (relevant to Downfall / GDS)" }
|
"avx512": { "type": ["boolean", "null"], "description": "AVX-512 supported (relevant to Downfall / GDS)" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ STATUS: summary | perfdata
|
|||||||
| VULN + UNK | `N/T CVE(s) vulnerable: CVE-A CVE-B ..., M inconclusive` |
|
| VULN + UNK | `N/T CVE(s) vulnerable: CVE-A CVE-B ..., M inconclusive` |
|
||||||
| UNK only | `N/T CVE checks inconclusive` |
|
| UNK only | `N/T CVE checks inconclusive` |
|
||||||
| Non-root + VULN | `N/T CVE(s) appear vulnerable (unconfirmed, not root): CVE-A ...` |
|
| Non-root + VULN | `N/T CVE(s) appear vulnerable (unconfirmed, not root): CVE-A ...` |
|
||||||
| Non-root + VULN + UNK | `N/T CVE(s) appear vulnerable (unconfirmed, not root): CVE-A ..., M inconclusive` |
|
|
||||||
|
|
||||||
### Lines 2+ (long output)
|
### Lines 2+ (long output)
|
||||||
|
|
||||||
@@ -60,19 +59,15 @@ Never parsed by the monitoring core; safe to add or reorder.
|
|||||||
|
|
||||||
#### Context notes
|
#### Context notes
|
||||||
|
|
||||||
Printed before per-CVE details when applicable. Notes are emitted in this
|
Printed before per-CVE details when applicable:
|
||||||
order when more than one applies:
|
|
||||||
|
|
||||||
| Note | Condition |
|
| Note | Condition |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `NOTE: paranoid mode active, stricter mitigation requirements applied` | `--paranoid` was used |
|
| `NOTE: paranoid mode active, stricter mitigation requirements applied` | `--paranoid` was used |
|
||||||
| `NOTE: hypervisor host detected (reason); L1TF/MDS severity is elevated` | System is detected as a VM host (KVM, Xen, VMware…) |
|
| `NOTE: hypervisor host detected (reason); L1TF/MDS severity is elevated` | System is a VM host (KVM, Xen, VMware…) |
|
||||||
| `NOTE: not a hypervisor host` | System is confirmed not a VM host |
|
| `NOTE: not a hypervisor host` | System is confirmed not a VM host |
|
||||||
| `NOTE: not running as root; MSR reads skipped, results may be incomplete` | Script ran without root privileges |
|
| `NOTE: not running as root; MSR reads skipped, results may be incomplete` | Script ran without root privileges |
|
||||||
|
|
||||||
When VMM detection did not run (e.g. `--no-hw`), neither the
|
|
||||||
`hypervisor host detected` nor the `not a hypervisor host` note is printed.
|
|
||||||
|
|
||||||
#### Per-CVE detail lines
|
#### Per-CVE detail lines
|
||||||
|
|
||||||
One line per non-OK CVE. VULN entries (`[CRITICAL]`) appear before UNK
|
One line per non-OK CVE. VULN entries (`[CRITICAL]`) appear before UNK
|
||||||
|
|||||||
@@ -90,16 +90,13 @@ smc_build_info{version="25.30.0250400123",mode="live",run_as_root="true",paranoi
|
|||||||
|
|
||||||
Operating system and kernel metadata. Always value `1`.
|
Operating system and kernel metadata. Always value `1`.
|
||||||
|
|
||||||
Absent entirely when none of `kernel_release`, `kernel_arch`, or
|
Absent in offline mode when neither `uname -r` nor `uname -m` is available.
|
||||||
`hypervisor_host` can be determined (e.g. non-live mode with no VMM detection).
|
|
||||||
Each label is emitted only when its value is known; missing labels are
|
|
||||||
omitted rather than set to an empty string.
|
|
||||||
|
|
||||||
| Label | Values | Meaning |
|
| Label | Values | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `kernel_release` | string | Output of `uname -r`; emitted only in live mode |
|
| `kernel_release` | string | Output of `uname -r` (live mode only) |
|
||||||
| `kernel_arch` | string | Output of `uname -m`; emitted only in live mode |
|
| `kernel_arch` | string | Output of `uname -m` (live mode only) |
|
||||||
| `hypervisor_host` | `true` / `false` | Whether this machine is detected as a hypervisor host (running KVM, Xen, VMware, etc.); absent when VMM detection did not run (e.g. `--no-hw`) |
|
| `hypervisor_host` | `true` / `false` | Whether this machine is detected as a hypervisor host (running KVM, Xen, VMware, etc.) |
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```
|
```
|
||||||
@@ -117,47 +114,26 @@ a malicious guest. Always prioritise remediation on hosts where
|
|||||||
### `smc_cpu_info`
|
### `smc_cpu_info`
|
||||||
|
|
||||||
CPU hardware and microcode metadata. Always value `1`. Absent when `--no-hw`
|
CPU hardware and microcode metadata. Always value `1`. Absent when `--no-hw`
|
||||||
is used or when `--arch-prefix` is set (host CPU info is suppressed to avoid
|
is used.
|
||||||
mixing with a different-arch target kernel).
|
|
||||||
|
|
||||||
Common labels (always emitted when the data is available):
|
|
||||||
|
|
||||||
| Label | Values | Meaning |
|
| Label | Values | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `vendor` | string | CPU vendor (e.g. `GenuineIntel`, `AuthenticAMD`, `HygonGenuine`, `ARM`) |
|
| `vendor` | string | CPU vendor (e.g. `Intel`, `AuthenticAMD`) |
|
||||||
| `model` | string | CPU friendly name from `/proc/cpuinfo` |
|
| `model` | string | CPU friendly name from `/proc/cpuinfo` |
|
||||||
| `arch` | `x86` / `arm` | Architecture family; determines which arch-specific labels follow |
|
|
||||||
| `smt` | `true` / `false` | Whether SMT (HyperThreading) is currently enabled; absent if undeterminable |
|
|
||||||
| `microcode` | hex string | Installed microcode version (e.g. `0xf4`); absent if unreadable |
|
|
||||||
| `microcode_latest` | hex string | Latest known-good microcode version from the firmware database; absent if the CPU is not in the database |
|
|
||||||
| `microcode_up_to_date` | `true` / `false` | Whether `microcode == microcode_latest`; absent if either is unavailable |
|
|
||||||
| `microcode_blacklisted` | `true` / `false` | Whether the installed microcode is known to cause problems and should be rolled back; emitted whenever `microcode` is emitted |
|
|
||||||
|
|
||||||
x86-only labels (emitted when `arch="x86"`):
|
|
||||||
|
|
||||||
| Label | Values | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `family` | integer string | CPU family number |
|
| `family` | integer string | CPU family number |
|
||||||
| `model_id` | integer string | CPU model number |
|
| `model_id` | integer string | CPU model number |
|
||||||
| `stepping` | integer string | CPU stepping number |
|
| `stepping` | integer string | CPU stepping number |
|
||||||
| `cpuid` | hex string | Full CPUID value (e.g. `0x000906ed`) |
|
| `cpuid` | hex string | Full CPUID value (e.g. `0x000906ed`); absent on some ARM CPUs |
|
||||||
| `codename` | string | Intel CPU codename (e.g. `Coffee Lake`); absent on AMD/Hygon |
|
| `codename` | string | Intel CPU codename (e.g. `Coffee Lake`); absent on AMD and ARM |
|
||||||
|
| `smt` | `true` / `false` | Whether SMT (HyperThreading) is currently enabled |
|
||||||
|
| `microcode` | hex string | Installed microcode version (e.g. `0xf4`) |
|
||||||
|
| `microcode_latest` | hex string | Latest known-good microcode version from the firmware database |
|
||||||
|
| `microcode_up_to_date` | `true` / `false` | Whether `microcode == microcode_latest` |
|
||||||
|
| `microcode_blacklisted` | `true` / `false` | Whether the installed microcode is known to cause problems and should be rolled back |
|
||||||
|
|
||||||
ARM-only labels (emitted when `arch="arm"`):
|
**Example:**
|
||||||
|
|
||||||
| Label | Values | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `part_list` | string | Space-separated list of ARM part numbers across cores (e.g. `0xd0b 0xd05` on big.LITTLE) |
|
|
||||||
| `arch_list` | string | Space-separated list of ARM architecture levels across cores (e.g. `8 8`) |
|
|
||||||
|
|
||||||
**x86 example:**
|
|
||||||
```
|
```
|
||||||
smc_cpu_info{vendor="GenuineIntel",model="Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz",arch="x86",family="6",model_id="158",stepping="13",cpuid="0x000906ed",codename="Coffee Lake",smt="true",microcode="0xf4",microcode_latest="0xf4",microcode_up_to_date="true",microcode_blacklisted="false"} 1
|
smc_cpu_info{vendor="Intel",model="Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz",family="6",model_id="158",stepping="13",cpuid="0x000906ed",codename="Coffee Lake",smt="true",microcode="0xf4",microcode_latest="0xf4",microcode_up_to_date="true",microcode_blacklisted="false"} 1
|
||||||
```
|
|
||||||
|
|
||||||
**ARM example:**
|
|
||||||
```
|
|
||||||
smc_cpu_info{vendor="ARM",model="ARM v8 model 0xd0b",arch="arm",part_list="0xd0b 0xd05",arch_list="8 8",smt="false"} 1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Microcode labels:**
|
**Microcode labels:**
|
||||||
@@ -376,15 +352,9 @@ queries. CVE checks that rely on hardware capability detection (`cap_*` flags,
|
|||||||
MSR reads) will report `unknown` status. `mode="no-hw"` in `smc_build_info`
|
MSR reads) will report `unknown` status. `mode="no-hw"` in `smc_build_info`
|
||||||
signals this.
|
signals this.
|
||||||
|
|
||||||
**Cross-arch inspection (`--arch-prefix`)**
|
|
||||||
When a cross-arch toolchain prefix is passed, the script suppresses the host
|
|
||||||
CPU metadata so it does not get mixed with data from a different-arch target
|
|
||||||
kernel: `smc_cpu_info` is not emitted, the same as under `--no-hw`.
|
|
||||||
|
|
||||||
**Hardware-only mode (`--hw-only`)**
|
**Hardware-only mode (`--hw-only`)**
|
||||||
Only hardware detection is performed; CVE checks are skipped. `smc_cpu_info`
|
Only hardware detection is performed; CVE checks are skipped. `smc_cpu_info`
|
||||||
is emitted but no `smc_vulnerability_status` metrics appear (and
|
is emitted but no `smc_vuln` metrics appear. `mode="hw-only"` in
|
||||||
`smc_vulnerable_count` / `smc_unknown_count` are `0`). `mode="hw-only"` in
|
|
||||||
`smc_build_info` signals this.
|
`smc_build_info` signals this.
|
||||||
|
|
||||||
**`--sysfs-only`**
|
**`--sysfs-only`**
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#
|
#
|
||||||
# Stephane Lesimple
|
# Stephane Lesimple
|
||||||
#
|
#
|
||||||
VERSION='26.33.0420460'
|
VERSION='26.33.0419451'
|
||||||
|
|
||||||
# --- Common paths and basedirs ---
|
# --- Common paths and basedirs ---
|
||||||
readonly VULN_SYSFS_BASE="/sys/devices/system/cpu/vulnerabilities"
|
readonly VULN_SYSFS_BASE="/sys/devices/system/cpu/vulnerabilities"
|
||||||
@@ -2402,17 +2402,15 @@ _prom_escape() {
|
|||||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' '
|
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
# Convert a shell capability value to a JSON boolean token
|
# Convert a shell capability value to a JSON token
|
||||||
# Args: $1=value (1=true, 0=false, -1/empty=null, any other non-empty string=true)
|
# Args: $1=value (1=true, 0=false, -1/empty=null, other string=quoted string)
|
||||||
# Prints: JSON token (true/false/null)
|
# Prints: JSON token
|
||||||
# Note: capability variables can be set to arbitrary strings internally to carry
|
|
||||||
# detection-path context (e.g. cap_ssbd='Intel SSBD'); for the JSON output those
|
|
||||||
# are normalized to true so consumers see a clean boolean | null type.
|
|
||||||
_json_cap() {
|
_json_cap() {
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
|
1) printf 'true' ;;
|
||||||
0) printf 'false' ;;
|
0) printf 'false' ;;
|
||||||
-1 | '') printf 'null' ;;
|
-1 | '') printf 'null' ;;
|
||||||
*) printf 'true' ;;
|
*) printf '"%s"' "$(_json_escape "$1")" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2515,7 +2513,7 @@ _build_json_system() {
|
|||||||
# Sets: g_json_cpu
|
# Sets: g_json_cpu
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
_build_json_cpu() {
|
_build_json_cpu() {
|
||||||
local cpuid_hex codename caps arch_sub arch_type sbpb_norm
|
local cpuid_hex codename caps arch_sub arch_type
|
||||||
if [ -n "${cpu_cpuid:-}" ]; then
|
if [ -n "${cpu_cpuid:-}" ]; then
|
||||||
cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
|
cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
|
||||||
else
|
else
|
||||||
@@ -2526,15 +2524,6 @@ _build_json_cpu() {
|
|||||||
codename=$(get_intel_codename 2>/dev/null || true)
|
codename=$(get_intel_codename 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# cap_sbpb uses non-standard encoding (1=YES, 2=NO, 3=UNKNOWN) because the
|
|
||||||
# CVE-2023-20569 check distinguishes the unknown case. Normalize for JSON.
|
|
||||||
case "${cap_sbpb:-}" in
|
|
||||||
1) sbpb_norm=1 ;;
|
|
||||||
2) sbpb_norm=0 ;;
|
|
||||||
3) sbpb_norm=-1 ;;
|
|
||||||
*) sbpb_norm='' ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Determine architecture type and build the arch-specific sub-object
|
# Determine architecture type and build the arch-specific sub-object
|
||||||
case "${cpu_vendor:-}" in
|
case "${cpu_vendor:-}" in
|
||||||
GenuineIntel | AuthenticAMD | HygonGenuine)
|
GenuineIntel | AuthenticAMD | HygonGenuine)
|
||||||
@@ -2588,7 +2577,7 @@ _build_json_cpu() {
|
|||||||
"$(_json_cap "${cap_tsa_l1_no:-}")" \
|
"$(_json_cap "${cap_tsa_l1_no:-}")" \
|
||||||
"$(_json_cap "${cap_verw_clear:-}")" \
|
"$(_json_cap "${cap_verw_clear:-}")" \
|
||||||
"$(_json_cap "${cap_autoibrs:-}")" \
|
"$(_json_cap "${cap_autoibrs:-}")" \
|
||||||
"$(_json_cap "$sbpb_norm")" \
|
"$(_json_cap "${cap_sbpb:-}")" \
|
||||||
"$(_json_cap "${cap_avx2:-}")" \
|
"$(_json_cap "${cap_avx2:-}")" \
|
||||||
"$(_json_cap "${cap_avx512:-}")")
|
"$(_json_cap "${cap_avx512:-}")")
|
||||||
arch_sub=$(printf '{"family":%s,"model":%s,"stepping":%s,"cpuid":%s,"platform_id":%s,"hybrid":%s,"codename":%s,"capabilities":%s}' \
|
arch_sub=$(printf '{"family":%s,"model":%s,"stepping":%s,"cpuid":%s,"platform_id":%s,"hybrid":%s,"codename":%s,"capabilities":%s}' \
|
||||||
|
|||||||
Reference in New Issue
Block a user