diff --git a/.github/actions/veristat_baseline_compare/action.yml b/.github/actions/veristat_baseline_compare/action.yml new file mode 100644 index 0000000000000..5da17a4149aaf --- /dev/null +++ b/.github/actions/veristat_baseline_compare/action.yml @@ -0,0 +1,49 @@ +name: 'run-veristat' +description: 'Run veristat benchmark' +inputs: + veristat_output: + description: 'Veristat output filepath' + required: true + baseline_name: + description: 'Veristat baseline cache name' + required: true +runs: + using: "composite" + steps: + - uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.baseline_name }} + if-no-files-found: error + path: ${{ github.workspace }}/${{ inputs.veristat_output }} + + # For pull request: + # - get baseline log from cache + # - compare it to current run + - if: ${{ github.event_name == 'pull_request' }} + uses: actions/cache/restore@v5 + with: + key: ${{ github.base_ref }}-${{ inputs.baseline_name }}- + restore-keys: | + ${{ github.base_ref }}-${{ inputs.baseline_name }} + path: '${{ github.workspace }}/${{ inputs.baseline_name }}' + + - if: ${{ github.event_name == 'pull_request' }} + name: Show veristat comparison + shell: bash + run: ./.github/scripts/compare-veristat-results.sh + env: + BASELINE_PATH: ${{ github.workspace }}/${{ inputs.baseline_name }} + VERISTAT_OUTPUT: ${{ inputs.veristat_output }} + + # For push: just put baseline log to cache + - if: ${{ github.event_name == 'push' }} + shell: bash + run: | + mv "${{ github.workspace }}/${{ inputs.veristat_output }}" \ + "${{ github.workspace }}/${{ inputs.baseline_name }}" + + - if: ${{ github.event_name == 'push' }} + uses: actions/cache/save@v5 + with: + key: ${{ github.ref_name }}-${{ inputs.baseline_name }}-${{ github.run_id }} + path: '${{ github.workspace }}/${{ inputs.baseline_name }}' diff --git a/.github/scripts/compare-veristat-results.sh b/.github/scripts/compare-veristat-results.sh new file mode 100755 index 0000000000000..21e8e311c8616 --- /dev/null +++ b/.github/scripts/compare-veristat-results.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +veristat=$(realpath selftests/bpf/veristat) + +# Dump verifier logs for a list of programs +# Usage: dump_failed_logs +# - progs_file: file with lines of format "file_name,prog_name" +dump_failed_logs() { + local progs_file="$1" + local objects_dir="${VERISTAT_OBJECTS_DIR:-$(pwd)}" + + while read -r line; do + local file prog + file=$(echo "$line" | cut -d',' -f1) + prog=$(echo "$line" | cut -d',' -f2) + echo "VERIFIER LOG FOR $file/$prog:" + echo "==================================================================" + $veristat -v "$objects_dir/$file" -f "$prog" + echo "==================================================================" + done < "$progs_file" +} + +if [[ ! -f "${BASELINE_PATH}" ]]; then + echo "# No ${BASELINE_PATH} available" >> "${GITHUB_STEP_SUMMARY}" + + echo "No ${BASELINE_PATH} available" + echo "Printing veristat results" + cat "${VERISTAT_OUTPUT}" + + if [[ -n "$VERISTAT_DUMP_LOG_ON_FAILURE" ]]; then + failed_progs=$(mktemp failed_progs_XXXXXX.txt) + awk -F',' '$3 == "failure" { print $1","$2 }' "${VERISTAT_OUTPUT}" > "$failed_progs" + if [[ -s "$failed_progs" ]]; then + echo && dump_failed_logs "$failed_progs" + fi + rm -f "$failed_progs" + fi + + echo "$(basename "$0"): no baseline provided for veristat output" + echo "VERISTAT JOB PASSED" + exit 0 +fi + +cmp_out=$(mktemp veristate_compare_out_XXXXXX.csv) + +$veristat \ + --output-format csv \ + --emit file,prog,verdict,states \ + --compare "${BASELINE_PATH}" "${VERISTAT_OUTPUT}" > $cmp_out + +python3 ./.github/scripts/veristat_compare.py $cmp_out +exit_code=$? + +# print verifier log for progs that failed to load +if [[ -n "$VERISTAT_DUMP_LOG_ON_FAILURE" ]]; then + failed_progs=$(mktemp failed_progs_XXXXXX.txt) + awk -F',' '$4 == "failure" { print $1","$2 }' "$cmp_out" > "$failed_progs" + if [[ -s "$failed_progs" ]]; then + echo && dump_failed_logs "$failed_progs" + fi + rm -f "$failed_progs" +fi + +if [[ $exit_code -eq 0 ]]; then + echo "$(basename "$0"): veristat output matches the baseline" + echo "VERISTAT JOB PASSED" +else + echo "$(basename "$0"): veristat output does not match the baseline" + echo "VERISTAT JOB FAILED" +fi + +exit $exit_code diff --git a/.github/scripts/download-gh-release.sh b/.github/scripts/download-gh-release.sh new file mode 100755 index 0000000000000..9e528ab233f8a --- /dev/null +++ b/.github/scripts/download-gh-release.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +GH_REPO=$1 +INSTALL_DIR=$(realpath $2) + +cd /tmp + +bash "$SCRIPT_DIR/install-github-cli.sh" + +tag=$(gh release list -L 1 -R ${GH_REPO} --json tagName -q .[].tagName) +if [[ -z "$tag" ]]; then + echo "Could not find latest release at ${GH_REPO}" + exit 1 +fi + +url="https://github.com/${GH_REPO}/releases/download/${tag}/${tag}.tar.zst" +echo "Downloading $url" +wget -q "$url" + +tarball=${tag}.tar.zst +dir=$(tar tf $tarball | head -1 || true) + +echo "Extracting $tarball ..." +tar -I zstd -xf $tarball && rm -f $tarball + +rm -rf $INSTALL_DIR +mv -v $dir $INSTALL_DIR + +cd - diff --git a/.github/scripts/install-github-cli.sh b/.github/scripts/install-github-cli.sh new file mode 100755 index 0000000000000..6008d88f924bf --- /dev/null +++ b/.github/scripts/install-github-cli.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +if ! command -v gh &> /dev/null; then + # https://github.com/cli/cli/blob/trunk/docs/install_linux.md + (type -p wget >/dev/null || (sudo apt update && sudo apt install wget -y)) \ + && sudo mkdir -p -m 755 /etc/apt/keyrings \ + && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && sudo mkdir -p -m 755 /etc/apt/sources.list.d \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && sudo apt update \ + && sudo apt install gh -y +fi diff --git a/.github/scripts/matrix.py b/.github/scripts/matrix.py new file mode 100644 index 0000000000000..826a2f6000f51 --- /dev/null +++ b/.github/scripts/matrix.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 + +import dataclasses +import json +import os + +from enum import Enum +from typing import Any, Dict, Final, List, Optional, Set, Union + +import requests +import requests.utils + +MANAGED_OWNER: Final[str] = "kernel-patches" +MANAGED_REPOS: Final[Set[str]] = { + f"{MANAGED_OWNER}/bpf", + f"{MANAGED_OWNER}/vmtest", +} + +DEFAULT_SELF_HOSTED_RUNNER_TAGS: Final[List[str]] = ["self-hosted", "docker-noble-main"] +DEFAULT_GITHUB_HOSTED_RUNNER: Final[str] = "ubuntu-24.04" +DEFAULT_GCC_VERSION: Final[int] = 15 +DEFAULT_LLVM_VERSION: Final[int] = 21 + +RUNNERS_BUSY_THRESHOLD: Final[float] = 0.8 + + +class Arch(str, Enum): + """ + CPU architecture supported by CI. + """ + + AARCH64 = "aarch64" + S390X = "s390x" + X86_64 = "x86_64" + + +class Compiler(str, Enum): + GCC = "gcc" + LLVM = "llvm" + + +def query_runners_from_github() -> List[Dict[str, Any]]: + if "GITHUB_TOKEN" not in os.environ: + return [] + token = os.environ["GITHUB_TOKEN"] + headers = { + "Authorization": f"token {token}", + "Accept": "application/vnd.github.v3+json", + } + owner = os.environ["GITHUB_REPOSITORY_OWNER"] + url: Optional[str] = f"https://api.github.com/orgs/{owner}/actions/runners" + # GitHub returns 30 runners per page, fetch all + all_runners = [] + try: + while url is not None: + response = requests.get(url, headers=headers) + if response.status_code != 200: + print(f"Failed to query runners: {response.status_code}") + print(f"response: {response.text}") + return [] + data = response.json() + all_runners.extend(data.get("runners", [])) + # Check for next page URL in Link header + url = None + if "Link" in response.headers: + links = requests.utils.parse_header_links(response.headers["Link"]) + for link in links: + if link["rel"] == "next": + url = link["url"] + break + return all_runners + except Exception as e: + print(f"Warning: Failed to query runner status due to exception: {e}") + return [] + + +all_runners_cached: Optional[List[Dict[str, Any]]] = None + + +def all_runners() -> List[Dict[str, Any]]: + global all_runners_cached + if all_runners_cached is None: + print("Querying runners from GitHub...") + all_runners_cached = query_runners_from_github() + print(f"Github returned {len(all_runners_cached)} runners") + counts = count_by_status(all_runners_cached) + print( + f"Busy: {counts['busy']}, Idle: {counts['idle']}, Offline: {counts['offline']}" + ) + return all_runners_cached + + +def runner_labels(runner: Dict[str, Any]) -> List[str]: + return [label["name"] for label in runner["labels"]] + + +def is_self_hosted_runner(runner: Dict[str, Any]) -> bool: + labels = runner_labels(runner) + for label in DEFAULT_SELF_HOSTED_RUNNER_TAGS: + if label not in labels: + return False + return True + + +def self_hosted_runners() -> List[Dict[str, Any]]: + runners = all_runners() + return [r for r in runners if is_self_hosted_runner(r)] + + +def runners_by_arch(arch: Arch) -> List[Dict[str, Any]]: + runners = self_hosted_runners() + return [r for r in runners if arch.value in runner_labels(r)] + + +def count_by_status(runners: List[Dict[str, Any]]) -> Dict[str, int]: + result = {"busy": 0, "idle": 0, "offline": 0} + for runner in runners: + if runner["status"] == "online": + if runner["busy"]: + result["busy"] += 1 + else: + result["idle"] += 1 + else: + result["offline"] += 1 + return result + + +@dataclasses.dataclass +class BuildConfig: + arch: Arch + kernel_compiler: Compiler = Compiler.GCC + gcc_version: int = DEFAULT_GCC_VERSION + llvm_version: int = DEFAULT_LLVM_VERSION + kernel: str = "LATEST" + run_veristat: bool = False + parallel_tests: bool = False + build_release: bool = False + is_netdev: bool = False + + @property + def runs_on(self) -> List[str]: + if is_managed_repo(): + return DEFAULT_SELF_HOSTED_RUNNER_TAGS + [self.arch.value] + else: + return [DEFAULT_GITHUB_HOSTED_RUNNER] + + @property + def build_runs_on(self) -> List[str]: + if not is_managed_repo(): + return [DEFAULT_GITHUB_HOSTED_RUNNER] + + # @Temporary: disable codebuild runners for cross-compilation jobs + match self.arch: + case Arch.S390X: + return DEFAULT_SELF_HOSTED_RUNNER_TAGS + [Arch.X86_64.value] + case Arch.AARCH64: + return DEFAULT_SELF_HOSTED_RUNNER_TAGS + [Arch.X86_64.value] + + # For managed repos, check the busyness of relevant self-hosted runners + # If they are too busy, use codebuild + runner_arch = self.arch + runners = runners_by_arch(runner_arch) + counts = count_by_status(runners) + online = counts["idle"] + counts["busy"] + busy = counts["busy"] + # if online <= 0, then something is wrong, don't use codebuild + if online > 0 and busy / online > RUNNERS_BUSY_THRESHOLD: + return ["codebuild"] + else: + return DEFAULT_SELF_HOSTED_RUNNER_TAGS + [runner_arch.value] + + @property + def tests(self) -> Dict[str, Any]: + tests_list = [ + "test_progs", + "test_progs_parallel", + "test_progs_no_alu32", + "test_progs_no_alu32_parallel", + "test_verifier", + ] + + if self.arch.value != "s390x": + tests_list.append("test_maps") + + if self.llvm_version >= 18: + tests_list.append("test_progs_cpuv4") + + # if self.arch in [Arch.X86_64, Arch.AARCH64] and not self.is_netdev: + # tests_list.append("sched_ext") + + if not self.parallel_tests: + tests_list = [test for test in tests_list if not test.endswith("parallel")] + + return {"include": [generate_test_config(test) for test in tests_list]} + + def to_dict(self) -> Dict[str, Any]: + return { + "arch": self.arch.value, + "kernel_compiler": self.kernel_compiler.value, + "gcc_version": DEFAULT_GCC_VERSION, + "llvm_version": DEFAULT_LLVM_VERSION, + "kernel": self.kernel, + "run_veristat": self.run_veristat, + "parallel_tests": self.parallel_tests, + "build_release": self.build_release, + "is_netdev": self.is_netdev, + "runs_on": self.runs_on, + "tests": self.tests, + "build_runs_on": self.build_runs_on, + } + + +def is_managed_repo() -> bool: + return ( + os.environ["GITHUB_REPOSITORY_OWNER"] == MANAGED_OWNER + and os.environ["GITHUB_REPOSITORY"] in MANAGED_REPOS + ) + + +def set_output(name, value): + """Write an output variable to the GitHub output file.""" + with open(os.getenv("GITHUB_OUTPUT"), "a", encoding="utf-8") as file: + file.write(f"{name}={value}\n") + + +def generate_test_config(test: str) -> Dict[str, Union[str, int]]: + """Create the configuration for the provided test.""" + is_parallel = test.endswith("_parallel") + config = { + "test": test, + "continue_on_error": is_parallel, + # While in experimental mode, parallel jobs may get stuck + # anywhere, including in user space where the kernel won't detect + # a problem and panic. We add a second layer of (smaller) timeouts + # here such that if we get stuck in a parallel run, we hit this + # timeout and fail without affecting the overall job success (as + # would be the case if we hit the job-wide timeout). For + # non-experimental jobs, 360 is the default which will be + # superseded by the overall workflow timeout (but we need to + # specify something). + "timeout_minutes": 30 if is_parallel else 360, + } + return config + + +if __name__ == "__main__": + matrix = [ + BuildConfig( + arch=Arch.X86_64, + run_veristat=True, + parallel_tests=True, + ), + BuildConfig( + arch=Arch.X86_64, + kernel_compiler=Compiler.LLVM, + build_release=True, + ), + BuildConfig( + arch=Arch.AARCH64, + ), + BuildConfig( + arch=Arch.S390X, + ), + ] + + # Outside of managed repositories only run on x86_64 + if not is_managed_repo(): + matrix = [config for config in matrix if config.arch == Arch.X86_64] + + # Detect netdev PRs: head repo is linux-netdev/testing-bpf-ci and branch is to-test + pr_head_repo = os.environ.get("PR_HEAD_REPO", "") + head_ref = os.environ.get("GITHUB_HEAD_REF", "") + is_netdev = pr_head_repo == "linux-netdev/testing-bpf-ci" and head_ref == "to-test" + if is_netdev: + print("Netdev PR detected, disabling BPF-specific jobs") + for config in matrix: + config.run_veristat = False + config.build_release = False + config.is_netdev = True + + json_matrix = json.dumps({"include": [config.to_dict() for config in matrix]}) + print(json.dumps(json.loads(json_matrix), indent=4)) + set_output("build_matrix", json_matrix) diff --git a/.github/scripts/stagger.py b/.github/scripts/stagger.py new file mode 100644 index 0000000000000..060a8ca282323 --- /dev/null +++ b/.github/scripts/stagger.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Stagger CI runs during KPD rebase storms. + +When KPD rebases all PR branches after an upstream commit, hundreds of +workflow runs fire at once. This script detects the storm and sleeps +for a random delay to spread the load. + +Storm = all of: + 1. PR synchronize event (force-push rebase, not a new PR) + 2. Base branch updated within the last 30 minutes (KPD just mirrored) + 3. More than 5 active workflow runs (queued + in-progress) + 4. Active runs >= 20% of open PRs + +If detected, sleeps a random 1-10 minutes then proceeds. +cancel-in-progress on the concurrency group kills sleeping runs on new pushes. +""" + +import os +import random +import time +from datetime import datetime, timezone + +import requests + +BASE_BRANCH_RECENCY_S = 1800 # base branch "just updated" threshold +STORM_RATIO = 0.2 # active runs / open PRs threshold +STORM_MIN_ACTIVE = 5 # minimum active runs to consider a storm +WAIT_MIN_S = 60 # min delay +WAIT_MAX_S = 600 # max delay + + +def gh_api(endpoint): + token = os.environ.get("GITHUB_TOKEN", "") + resp = requests.get( + f"https://api.github.com{endpoint}", + headers={ + "Authorization": f"token {token}", + "Accept": "application/vnd.github.v3+json", + }, + ) + resp.raise_for_status() + return resp.json() + + +def base_branch_age_s(repo, base_branch): + """Seconds since last commit on the base branch, or None on error.""" + try: + sha = gh_api(f"/repos/{repo}/branches/{base_branch}")["commit"]["sha"] + date_str = gh_api(f"/repos/{repo}/commits/{sha}")["commit"]["committer"]["date"] + commit_time = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + return (datetime.now(timezone.utc) - commit_time).total_seconds() + except Exception as e: + print(f"Warning: could not get base branch age: {e}") + return None + + +def active_run_count(repo): + """Number of queued + in-progress workflow runs.""" + total = 0 + for status in ("queued", "in_progress"): + try: + data = gh_api(f"/repos/{repo}/actions/runs?status={status}&per_page=1") + total += data.get("total_count", 0) + except Exception as e: + print(f"Warning: could not query {status} runs: {e}") + return total + + +def open_pr_count(repo): + """Number of open pull requests.""" + try: + data = gh_api(f"/search/issues?q=repo:{repo}+type:pr+state:open&per_page=1") + return data.get("total_count", 0) + except Exception as e: + print(f"Warning: could not query open PRs: {e}") + return 0 + + +def main(): + action = os.environ.get("GITHUB_EVENT_ACTION", "") + repo = os.environ.get("GITHUB_REPOSITORY", "") + base = os.environ.get("PR_BASE_BRANCH", "") + + if action != "synchronize": + return + + if not repo or not base: + return + + age = base_branch_age_s(repo, base) + if age is None or age > BASE_BRANCH_RECENCY_S: + print(f"Base branch {base} updated {age}s ago — no storm.") + return + + active = active_run_count(repo) + if active <= STORM_MIN_ACTIVE: + print(f"Only {active} active runs — no storm.") + return + + open_prs = open_pr_count(repo) + if open_prs == 0: + return + + ratio = active / open_prs + if ratio < STORM_RATIO: + print(f"{active} active / {open_prs} PRs ({ratio:.0%}) — no storm.") + return + + delay = random.randint(WAIT_MIN_S, WAIT_MAX_S) + print( + f"Storm detected: base {base} updated {age:.0f}s ago, " + f"{active} active / {open_prs} PRs ({ratio:.0%}). " + f"Waiting {delay}s." + ) + time.sleep(delay) + print("Proceeding.") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/tests/test_veristat_compare.py b/.github/scripts/tests/test_veristat_compare.py new file mode 100644 index 0000000000000..b65b69295235d --- /dev/null +++ b/.github/scripts/tests/test_veristat_compare.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import unittest +from typing import Iterable, List + +from ..veristat_compare import parse_table, VeristatFields + + +def gen_csv_table(records: Iterable[str]) -> List[str]: + return [ + ",".join(VeristatFields.headers()), + *records, + ] + + +class TestVeristatCompare(unittest.TestCase): + def test_parse_table_ignore_new_prog(self): + table = gen_csv_table( + [ + "prog_file.bpf.o,prog_name,N/A,success,N/A,N/A,1,N/A", + ] + ) + veristat_info = parse_table(table) + self.assertEqual(veristat_info.table, []) + self.assertFalse(veristat_info.changes) + self.assertFalse(veristat_info.new_failures) + + def test_parse_table_ignore_removed_prog(self): + table = gen_csv_table( + [ + "prog_file.bpf.o,prog_name,success,N/A,N/A,1,N/A,N/A", + ] + ) + veristat_info = parse_table(table) + self.assertEqual(veristat_info.table, []) + self.assertFalse(veristat_info.changes) + self.assertFalse(veristat_info.new_failures) + + def test_parse_table_new_failure(self): + table = gen_csv_table( + [ + "prog_file.bpf.o,prog_name,success,failure,MISMATCH,1,1,+0 (+0.00%)", + ] + ) + veristat_info = parse_table(table) + self.assertEqual( + veristat_info.table, + [["prog_file.bpf.o", "prog_name", "success -> failure (!!)", "+0.00 %"]], + ) + self.assertTrue(veristat_info.changes) + self.assertTrue(veristat_info.new_failures) + + def test_parse_table_new_changes(self): + table = gen_csv_table( + [ + "prog_file.bpf.o,prog_name,failure,success,MISMATCH,0,0,+0 (+0.00%)", + "prog_file.bpf.o,prog_name_increase,failure,failure,MATCH,1,2,+1 (+100.00%)", + "prog_file.bpf.o,prog_name_decrease,success,success,MATCH,1,1,-1 (-100.00%)", + ] + ) + veristat_info = parse_table(table) + self.assertEqual( + veristat_info.table, + [ + ["prog_file.bpf.o", "prog_name", "failure -> success", "+0.00 %"], + ["prog_file.bpf.o", "prog_name_increase", "failure", "+100.00 %"], + ["prog_file.bpf.o", "prog_name_decrease", "success", "-100.00 %"], + ], + ) + self.assertTrue(veristat_info.changes) + self.assertFalse(veristat_info.new_failures) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/tmpfsify-workspace.sh b/.github/scripts/tmpfsify-workspace.sh new file mode 100755 index 0000000000000..6fd62b4ad2a49 --- /dev/null +++ b/.github/scripts/tmpfsify-workspace.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -x -euo pipefail + +TMPFS_SIZE=20 # GB +MEM_TOTAL=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) + +# sanity check: total mem is at least double TMPFS_SIZE +if [ $MEM_TOTAL -lt $(($TMPFS_SIZE*1024*2)) ]; then + echo "tmpfsify-workspace.sh: will not allocate tmpfs, total memory is too low (${MEM_TOTAL}MB)" + exit 0 +fi + +dir="$(basename "$GITHUB_WORKSPACE")" +cd "$(dirname "$GITHUB_WORKSPACE")" +mv "${dir}" "${dir}.backup" +mkdir "${dir}" +sudo mount -t tmpfs -o size=${TMPFS_SIZE}G tmpfs "${dir}" +rsync -a "${dir}.backup/" "${dir}" +cd - + diff --git a/.github/scripts/veristat_compare.py b/.github/scripts/veristat_compare.py new file mode 100644 index 0000000000000..85dc6f4fecbb6 --- /dev/null +++ b/.github/scripts/veristat_compare.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 + +# This script reads a CSV file produced by the following invocation: +# +# veristat --emit file,prog,verdict,states \ +# --output-format csv \ +# --compare ... +# +# And produces a markdown summary for the file. +# The summary is printed to standard output and appended to a file +# pointed to by GITHUB_STEP_SUMMARY variable. +# +# Script exits with return code 1 if there are new failures in the +# veristat results. +# +# For testing purposes invoke as follows: +# +# GITHUB_STEP_SUMMARY=/dev/null python3 veristat-compare.py test.csv +# +# File format (columns): +# 0. file_name +# 1. prog_name +# 2. verdict_base +# 3. verdict_comp +# 4. verdict_diff +# 5. total_states_base +# 6. total_states_comp +# 7. total_states_diff +# +# Records sample: +# file-a,a,success,failure,MISMATCH,12,12,+0 (+0.00%) +# file-b,b,success,success,MATCH,67,67,+0 (+0.00%) +# +# For better readability suffixes '_OLD' and '_NEW' +# are used instead of '_base' and '_comp' for variable +# names etc. + +import io +import os +import sys +import re +import csv +import logging +import argparse +import enum +from dataclasses import dataclass +from typing import Dict, Iterable, List, Final + +TRESHOLD_PCT: Final[int] = 0 + +SUMMARY_HEADERS = ["File", "Program", "Verdict", "States Diff (%)"] + +# expected format: +0 (+0.00%) / -0 (-0.00%) +TOTAL_STATES_DIFF_REGEX = ( + r"(?P[+-]\d+) \((?P[+-]\d+\.\d+)\%\)" +) + + +TEXT_SUMMARY_TEMPLATE: Final[str] = """ +# {title} + +{table} +""".strip() + +HTML_SUMMARY_TEMPLATE: Final[str] = """ +# {title} + +
+Click to expand + +{table} +
+""".strip() + +GITHUB_MARKUP_REPLACEMENTS: Final[Dict[str, str]] = { + "->": "→", + "(!!)": ":bangbang:", +} + +NEW_FAILURE_SUFFIX: Final[str] = "(!!)" + + +class VeristatFields(str, enum.Enum): + FILE_NAME = "file_name" + PROG_NAME = "prog_name" + VERDICT_OLD = "verdict_base" + VERDICT_NEW = "verdict_comp" + VERDICT_DIFF = "verdict_diff" + TOTAL_STATES_OLD = "total_states_base" + TOTAL_STATES_NEW = "total_states_comp" + TOTAL_STATES_DIFF = "total_states_diff" + + @classmethod + def headers(cls) -> List[str]: + return [ + cls.FILE_NAME, + cls.PROG_NAME, + cls.VERDICT_OLD, + cls.VERDICT_NEW, + cls.VERDICT_DIFF, + cls.TOTAL_STATES_OLD, + cls.TOTAL_STATES_NEW, + cls.TOTAL_STATES_DIFF, + ] + + +@dataclass +class VeristatInfo: + table: list + changes: bool + new_failures: bool + + def get_results_title(self) -> str: + if self.new_failures: + return "There are new veristat failures" + + if self.changes: + return "There are changes in verification performance" + + return "No changes in verification performance" + + def get_results_summary(self, markup: bool = False) -> str: + title = self.get_results_title() + if not self.table: + return f"# {title}\n" + + template = TEXT_SUMMARY_TEMPLATE + table = format_table(headers=SUMMARY_HEADERS, rows=self.table) + + if markup: + template = HTML_SUMMARY_TEMPLATE + table = github_markup_decorate(table) + + return template.format(title=title, table=table) + + +def get_state_diff(value: str) -> float: + if value == "N/A": + return 0.0 + + matches = re.match(TOTAL_STATES_DIFF_REGEX, value) + if not matches: + raise ValueError(f"Failed to parse total states diff field value '{value}'") + + if percentage_diff := matches.group("percentage_diff"): + return float(percentage_diff) + + raise ValueError(f"Invalid {VeristatFields.TOTAL_STATES_DIFF} field value: {value}") + + +def parse_table(csv_file: Iterable[str]) -> VeristatInfo: + reader = csv.DictReader(csv_file) + assert reader.fieldnames == VeristatFields.headers() + + new_failures = False + changes = False + table = [] + + for record in reader: + add = False + + verdict_old, verdict_new = ( + record[VeristatFields.VERDICT_OLD], + record[VeristatFields.VERDICT_NEW], + ) + + # Ignore results from completely new and removed programs + if "N/A" in [verdict_new, verdict_old]: + continue + + if record[VeristatFields.VERDICT_DIFF] == "MISMATCH": + changes = True + add = True + verdict = f"{verdict_old} -> {verdict_new}" + if verdict_new == "failure": + new_failures = True + verdict += f" {NEW_FAILURE_SUFFIX}" + else: + verdict = record[VeristatFields.VERDICT_NEW] + + diff = get_state_diff(record[VeristatFields.TOTAL_STATES_DIFF]) + if abs(diff) > TRESHOLD_PCT: + changes = True + add = True + + if not add: + continue + + table.append( + [ + record[VeristatFields.FILE_NAME], + record[VeristatFields.PROG_NAME], + verdict, + f"{diff:+.2f} %", + ] + ) + + return VeristatInfo(table=table, changes=changes, new_failures=new_failures) + + +def github_markup_decorate(input_str: str) -> str: + for text, markup in GITHUB_MARKUP_REPLACEMENTS.items(): + input_str = input_str.replace(text, markup) + return input_str + + +def format_table(headers: List[str], rows: List[List[str]]) -> str: + column_width = [ + max(len(row[column_idx]) for row in [headers] + rows) + for column_idx in range(len(headers)) + ] + + # Row template string in the following format: + # "{0:8}|{1:10}|{2:15}|{3:7}|{4:10}" + row_template = "|".join( + f"{{{idx}:{width}}}" for idx, width in enumerate(column_width) + ) + row_template_nl = f"|{row_template}|\n" + + with io.StringIO() as out: + out.write(row_template_nl.format(*headers)) + + separator_row = ["-" * width for width in column_width] + out.write(row_template_nl.format(*separator_row)) + + for row in rows: + row_str = row_template_nl.format(*row) + out.write(row_str) + + return out.getvalue() + + +def main(compare_csv_filename: os.PathLike, output_filename: os.PathLike) -> None: + with open(compare_csv_filename, newline="", encoding="utf-8") as csv_file: + veristat_results = parse_table(csv_file) + + sys.stdout.write(veristat_results.get_results_summary()) + + with open(output_filename, encoding="utf-8", mode="a") as file: + file.write(veristat_results.get_results_summary(markup=True)) + + if veristat_results.new_failures: + return 1 + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Print veristat comparison output as markdown step summary" + ) + parser.add_argument("filename") + args = parser.parse_args() + summary_filename = os.getenv("GITHUB_STEP_SUMMARY") + if not summary_filename: + logging.error("GITHUB_STEP_SUMMARY environment variable is not set") + sys.exit(1) + sys.exit(main(args.filename, summary_filename)) diff --git a/.github/workflows/ai-agent.yml b/.github/workflows/ai-agent.yml new file mode 100644 index 0000000000000..4d95297952c58 --- /dev/null +++ b/.github/workflows/ai-agent.yml @@ -0,0 +1,242 @@ +name: BPF CI Bot + +permissions: + contents: read + id-token: write + issues: write + pull-requests: write + actions: read + +on: + schedule: + - cron: '0 12 * * 1' # Monday at ~4am Pacific Time + workflow_dispatch: + pull_request: + paths: + - .github/workflows/ai-agent.yml + - ci/claude/bpf-ci-agent.md + +concurrency: + group: bpf-ci-bot + cancel-in-progress: false + +jobs: + agent-run: + if: ${{ github.repository == 'kernel-patches/vmtest' && vars.AWS_REGION }} + runs-on: + - ${{ format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + - image:custom-linux-ghcr.io/kernel-patches/runner:ai-review + - instance-size:large + env: + AWS_REGION: us-west-2 + steps: + + - name: Checkout CI code + uses: actions/checkout@v6 + with: + sparse-checkout: | + .github/scripts + ci/claude + + - name: Set up .claude/settings.json and the prompt + shell: bash + run: | + mkdir -p ~/.claude + cp ci/claude/settings.json ~/.claude/settings.json + cp ci/claude/bpf-ci-agent.md agent.md + + - name: Checkout review-prompts + uses: actions/checkout@v6 + with: + repository: 'masoncl/review-prompts' + path: 'github/masoncl/review-prompts' + ref: main + + - name: Checkout libbpf/ci + uses: actions/checkout@v6 + with: + repository: 'libbpf/ci' + path: 'github/libbpf/ci' + ref: main + + - name: Checkout kernel-patches/vmtest + uses: actions/checkout@v6 + with: + repository: 'kernel-patches/vmtest' + path: 'github/kernel-patches/vmtest' + ref: master + + - name: Checkout kernel-patches/runner + uses: actions/checkout@v6 + with: + repository: 'kernel-patches/runner' + path: 'github/kernel-patches/runner' + ref: main + + - name: Checkout kernel-patches/kernel-patches-daemon + uses: actions/checkout@v6 + with: + repository: 'kernel-patches/kernel-patches-daemon' + path: 'github/kernel-patches/kernel-patches-daemon' + ref: main + + - name: Checkout danobi/vmtest + uses: actions/checkout@v6 + with: + repository: 'danobi/vmtest' + path: 'github/danobi/vmtest' + ref: master + + - name: Checkout facebookexperimental/semcode + uses: actions/checkout@v6 + with: + repository: 'facebookexperimental/semcode' + path: 'github/facebookexperimental/semcode' + ref: main + + - name: Checkout nojb/public-inbox + uses: actions/checkout@v6 + with: + repository: 'nojb/public-inbox' + path: 'github/nojb/public-inbox' + ref: master + + - name: Install misc tools + shell: bash + env: + GCC_VERSION: 14 + LLVM_VERSION: 19 + run: | + sudo apt-get update -y + ${{ github.workspace }}/.github/scripts/install-github-cli.sh + ${{ github.workspace }}/github/kernel-patches/runner/install-dependencies.sh all + sudo apt-get install -y python3 jq lei + + - name: Download Linux source tree + uses: libbpf/ci/get-linux-source@v4 + with: + repo: 'https://github.com/kernel-patches/bpf.git' + rev: 'bpf-next' + dest: linux + env: + REFERENCE_REPO_PATH: /libbpfci/mirrors/linux + FETCH_DEPTH: 0 # full clone + + # This manipulation is necessary to make sure that + # ${{ github.workspace }} is the root of the Linux git repo + - name: Move linux source in place + shell: bash + run: | + rm -rf .git .github + cd linux + mv -t .. $(ls -A) + cd .. + rmdir linux + + - name: semcode-index + shell: bash + run: | + git remote add torvalds https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + git fetch torvalds + MERGE_BASE=$(git merge-base torvalds/master HEAD) + rm -rf /ci/.semcode.db/lore + ln -s /ci/.semcode.db .semcode.db + semcode-index --git "${MERGE_BASE}..HEAD" + semcode-index --lore bpf + + - name: Restore NOTES.md + uses: actions/cache/restore@v5 + with: + path: NOTES.md + key: notes-md-${{ github.repository }} + + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.KP_REVIEW_BOT_APP_ID }} + private-key: ${{ secrets.KP_REVIEW_BOT_APP_PRIVATE_KEY }} + + - name: Configure AWS Credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_BEDROCK_ROLE }} + aws-region: us-west-2 + + - uses: anthropics/claude-code-action@v1 + with: + show_full_output: true + github_token: ${{ steps.app-token.outputs.token }} + use_bedrock: "true" + claude_args: | + --max-turns 200 + --mcp-config ci/claude/mcp.json + --model us.anthropic.claude-opus-4-6-v1 + allowed_bots: "kernel-patches-daemon-bpf,kernel-patches-review-bot" + additional_permissions: | + actions: read + prompt: | + Read agent.md and follow the directions + + - name: Copy NOTES.md to the output + shell: bash + run: | + mkdir -p output + cp NOTES.md output/NOTES.md + + - name: Upload output artifacts + if: always() + uses: actions/upload-artifact@v7 + with: + name: output + path: output/ + + - name: Save NOTES.md to cache + if: always() + uses: actions/cache/save@v5 + with: + path: NOTES.md + key: notes-md-${{ github.repository }} + + post-output: + needs: agent-run + if: always() + runs-on: ubuntu-slim + steps: + - name: Download output artifact + id: download + uses: actions/download-artifact@v7 + continue-on-error: true + with: + name: output + path: output/ + + - name: Generate GitHub App token + if: steps.download.outcome == 'success' && hashFiles('output/summary.md') != '' + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.KP_REVIEW_BOT_APP_ID }} + private-key: ${{ secrets.KP_REVIEW_BOT_APP_PRIVATE_KEY }} + + - name: Post an issue + if: steps.download.outcome == 'success' && hashFiles('output/summary.md') != '' + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + # Create issue from summary.md + TITLE="[bpf-ci-bot] $(head -n 1 output/summary.md | sed 's/^#\+ *//')" + tail -n +2 output/summary.md > body.md + ISSUE_URL=$(gh issue create \ + --repo "${{ github.repository }}" \ + --title "$TITLE" \ + --body-file body.md) + + # Post each .patch as a separate comment + for patch in output/*.patch; do + [ -f "$patch" ] || continue + FILENAME=$(basename "$patch") + printf '## %s\n\n```\n%s\n```' "$FILENAME" "$(cat "$patch")" > comment.md + gh issue comment "$ISSUE_URL" --body-file comment.md + done diff --git a/.github/workflows/ai-code-review.yml b/.github/workflows/ai-code-review.yml new file mode 100644 index 0000000000000..5855fa6fbbb56 --- /dev/null +++ b/.github/workflows/ai-code-review.yml @@ -0,0 +1,200 @@ +name: AI Code Review + +permissions: + contents: read + id-token: write + issues: write + pull-requests: write + +on: + pull_request: + types: [opened, review_requested] + +jobs: + get-commits: + # This codition is an indicator that we are running in a context of PR owned by kernel-patches org + if: ${{ github.repository == 'kernel-patches/bpf' && vars.AWS_REGION }} + runs-on: + - ${{ format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + - image:custom-linux-ghcr.io/kernel-patches/runner:kbuilder-debian-x86_64 + - instance-size:small + continue-on-error: true + outputs: + commits: ${{ steps.get-commits.outputs.commits }} + steps: + + - name: Download Linux source tree + uses: libbpf/ci/get-linux-source@v4 + with: + repo: ${{ github.event.pull_request.head.repo.clone_url }} + rev: ${{ github.event.pull_request.head.sha }} + dest: .kernel + env: + REFERENCE_REPO_PATH: /libbpfci/mirrors/linux + FETCH_DEPTH: 100 + + # Get the list of commits and trigger a review job for each separate commit + # As a safeguard, check no more than the first 50 commits + - name: Get PR commits + id: get-commits + run: | + cd .kernel + tmp=$(mktemp) + git rev-list ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | head -n 50 > pr_commits.txt + cat pr_commits.txt | jq -R -s -c 'split("\n")[:-1]' > $tmp + echo "commits=$(cat $tmp)" >> $GITHUB_OUTPUT + + ai-review: + needs: get-commits + runs-on: + - ${{ format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + - image:custom-linux-ghcr.io/kernel-patches/runner:ai-review + - instance-size:large + strategy: + matrix: + commit: ${{ fromJson(needs.get-commits.outputs.commits) }} + fail-fast: false + env: + AWS_REGION: us-west-2 + steps: + - name: Checkout CI code + uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.KP_REVIEW_BOT_APP_ID }} + private-key: ${{ secrets.KP_REVIEW_BOT_APP_PRIVATE_KEY }} + + - name: Configure AWS Credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_BEDROCK_ROLE }} + aws-region: us-west-2 + + - name: Set up .claude/settings.json + shell: bash + run: | + mkdir -p ~/.claude + cp ci/claude/settings.json ~/.claude/settings.json + + - name: Download Linux source tree + uses: libbpf/ci/get-linux-source@v4 + with: + repo: ${{ github.event.pull_request.head.repo.clone_url }} + rev: ${{ github.event.pull_request.head.sha }} + dest: .kernel + env: + REFERENCE_REPO_PATH: /libbpfci/mirrors/linux + FETCH_DEPTH: 100 + + # This manipulation is necessary to make sure that + # ${{ github.workspace }} is the root of the Linux git repo. + # + # The AI review config lives in ci/, which is present on every *_base + # branch and thus in this checkout (the pull_request merge ref), but not + # necessarily in the PR head tree fetched above (get-linux-source pulls + # head.sha only). Preserve the checked-out ci/ across the move instead of + # deleting it, dropping only the head tree's own ci/ to avoid a collision, + # so the config is available regardless of whether the head carries it. + - name: Move linux source in place + shell: bash + run: | + rm -rf .git .github + cd .kernel + rm -rf ci + mv -t .. $(ls -A) + cd .. + rmdir .kernel + + - name: semcode-index + shell: bash + run: | + git remote add bpf-next https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git + git fetch bpf-next + git checkout ${{ matrix.commit }} -b patch-series.local + MERGE_BASE=$(git merge-base bpf-next/master HEAD) + rm -rf /ci/.semcode.db/lore + ln -s /ci/.semcode.db ${{ github.workspace }}/.semcode.db + semcode-index --git "${MERGE_BASE}..HEAD" + semcode-index --lore bpf + + - name: Get patch subject + id: get-patch-subject + shell: bash + run: | + subject=$(git log -1 --pretty=format:"%s" ${{ matrix.commit }}) + echo "subject=$subject" >> $GITHUB_OUTPUT + + - name: Checkout prompts repo + uses: actions/checkout@v6 + with: + repository: 'masoncl/review-prompts' + path: 'review-prompts' + ref: main + + - name: Set up review prompts + shell: bash + run: | + mv review-prompts/kernel ${{ github.workspace }}/review + rm -rf review-prompts + + - uses: anthropics/claude-code-action@v1 + with: + show_full_output: true + github_token: ${{ steps.app-token.outputs.token }} + use_bedrock: "true" + claude_args: | + --max-turns 100 + --mcp-config ci/claude/mcp.json + --model us.anthropic.claude-opus-5 + allowed_bots: "kernel-patches-daemon-bpf,kernel-patches-review-bot" + prompt: | + Current directory is the root of a Linux Kernel git repository. + + Read the prompt review/agent/orc.md + + Analyze commit HEAD using prompts from review/ + + This commit is part of a series with git range ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} + + # If Claude produced review-inline.txt then it found something + # Post a comment on PR and fail the job + - name: Check review-inline.txt and review-metadata.json + id: check_review + shell: bash + run: | + review_file=$(find ${{ github.workspace }} -name review-inline.txt) + if [ -s "$review_file" ]; then + cat $review_file || true + echo "review_file=$review_file" >> $GITHUB_OUTPUT + fi + review_metadata=$(find ${{ github.workspace }} -name review-metadata.json) + if [ -s "$review_metadata" ]; then + cat $review_metadata || true + echo "review_metadata=$review_metadata" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR + if: steps.check_review.outputs.review_file != '' + uses: actions/github-script@v8 + env: + REVIEW_FILE: ${{ steps.check_review.outputs.review_file }} + REVIEW_METADATA: ${{ steps.check_review.outputs.review_metadata }} + PATCH_SUBJECT: ${{ steps.get-patch-subject.outputs.subject }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const commentScript = require('./ci/claude/post-pr-comment.js'); + await commentScript({github, context}); + + - name: Fail CI job if review file exists + if: steps.check_review.outputs.review_file != '' + run: | + echo "Review file found - failing the CI job" + exit 42 diff --git a/.github/workflows/gcc-bpf.yml b/.github/workflows/gcc-bpf.yml new file mode 100644 index 0000000000000..175cabe8085cc --- /dev/null +++ b/.github/workflows/gcc-bpf.yml @@ -0,0 +1,181 @@ +name: Testing GCC BPF compiler + +on: + workflow_call: + inputs: + runs_on: + required: true + type: string + arch: + required: true + type: string + gcc_version: + required: true + type: string + llvm_version: + required: true + type: string + toolchain: + required: true + type: string + toolchain_full: + required: true + type: string + download_sources: + required: true + type: boolean + run_tests: + required: true + type: boolean + description: Whether or not to run the test job. + +jobs: + build: + name: GCC BPF build + runs-on: + - ${{ format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + - image:custom-linux-ghcr.io/kernel-patches/runner:kbuilder-debian-x86_64 + env: + ARCH: ${{ inputs.arch }} + ARTIFACTS_ARCHIVE: ${{ github.workspace }}/selftests-bpf-gcc-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst + BPF_NEXT_BASE_BRANCH: 'master' + GCC_BPF_INSTALL_DIR: ${{ github.workspace }}/gcc-bpf + GCC_BPF_RELEASE_REPO: 'theihor/gcc-bpf' + KBUILD_OUTPUT: ${{ github.workspace }}/src/kbuild-output + REPO_ROOT: ${{ github.workspace }}/src + + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - if: ${{ inputs.download_sources }} + name: Download bpf-next tree + uses: libbpf/ci/get-linux-source@v4 + with: + dest: ${{ env.REPO_ROOT }} + rev: ${{ env.BPF_NEXT_BASE_BRANCH }} + + - if: ${{ ! inputs.download_sources }} + name: Checkout ${{ github.repository }} to ./src + uses: actions/checkout@v6 + with: + path: 'src' + + - uses: libbpf/ci/patch-kernel@v4 + with: + patches-root: '${{ github.workspace }}/ci/diffs' + repo-root: ${{ env.REPO_ROOT }} + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar artifacts + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - name: Setup build environment + uses: libbpf/ci/setup-build-env@v4 + with: + arch: ${{ inputs.arch }} + gcc-version: ${{ inputs.gcc_version }} + llvm-version: ${{ inputs.llvm_version }} + + - name: Download GCC BPF compiler + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: .github/scripts/download-gh-release.sh ${{ env.GCC_BPF_RELEASE_REPO }} ${{ env.GCC_BPF_INSTALL_DIR }} + + - name: Build selftests/bpf/test_progs-bpf_gcc + uses: libbpf/ci/build-selftests@v4 + env: + BPF_GCC: ${{ env.GCC_BPF_INSTALL_DIR }} + MAX_MAKE_JOBS: 32 + SELFTESTS_BPF_TARGETS: 'test_progs-bpf_gcc' + with: + arch: ${{ inputs.arch }} + kernel-root: ${{ env.REPO_ROOT }} + llvm-version: ${{ inputs.llvm_version }} + toolchain: ${{ inputs.toolchain }} + + - name: Tar artifacts + uses: libbpf/ci/tar-artifacts@v4 + env: + ARCHIVE_BPF_SELFTESTS: 'true' + ARCHIVE_KBUILD_OUTPUT: '' # emptystring means false + with: + arch: ${{ inputs.arch }} + archive: ${{ env.ARTIFACTS_ARCHIVE }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + repo-root: ${{ env.REPO_ROOT }} + + - uses: actions/upload-artifact@v7 + with: + name: selftests-bpf-gcc-${{ inputs.arch }}-${{ inputs.toolchain_full }} + if-no-files-found: error + path: ${{ env.ARTIFACTS_ARCHIVE }} + + test: + name: GCC BPF + if: ${{ inputs.run_tests }} + runs-on: ${{ fromJSON(inputs.runs_on) }} + needs: [build] + timeout-minutes: 100 + env: + ARCH: ${{ inputs.arch }} + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + DEPLOYMENT: ${{ github.repository == 'kernel-patches/bpf' && 'prod' || 'rc' }} + KERNEL_TEST: test_progs-bpf_gcc + ALLOWLIST_FILE: /tmp/allowlist + DENYLIST_FILE: /tmp/denylist + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar vmlinux + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + # Overlays test_progs-bpf_gcc onto the selftests directory unpacked above. + - uses: actions/download-artifact@v7 + with: + name: selftests-bpf-gcc-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar test_progs-bpf_gcc + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 selftests-bpf-gcc-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - name: Run selftests + uses: libbpf/ci/run-vmtest@v4 + env: + ARCH: ${{ inputs.arch }} + DEPLOYMENT: ${{ env.DEPLOYMENT }} + LLVM_VERSION: ${{ inputs.llvm_version }} + SELFTESTS_BPF: ${{ github.workspace }}/selftests/bpf + VMTEST_CONFIGS: ${{ github.workspace }}/ci/vmtest/configs + VMTEST_MEMORY: 5G + VMTEST_NUM_CPUS: 4 + TEST_PROGS_WATCHDOG_TIMEOUT: 600 + with: + arch: ${{ inputs.arch }} + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: ${{ env.REPO_ROOT }} + max-cpu: 8 + kernel-test: ${{ env.KERNEL_TEST }} + kbuild-output: ${{ env.REPO_ROOT }}/kbuild-output diff --git a/.github/workflows/kernel-build-test.yml b/.github/workflows/kernel-build-test.yml new file mode 100644 index 0000000000000..99abd1b9b34c6 --- /dev/null +++ b/.github/workflows/kernel-build-test.yml @@ -0,0 +1,200 @@ +name: Reusable Build/Test/Veristat workflow + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: The toolchain and for llvm, its version, e.g gcc, llvm-15 + toolchain: + required: true + type: string + description: The toolchain, e.g gcc, llvm + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + build_runs_on: + required: true + type: string + description: The runners to run the builds on. This is a json string representing an array of labels. + gcc_version: + required: true + type: string + description: GCC version to install + llvm_version: + required: true + type: string + description: LLVM version to install + kernel: + required: true + type: string + description: The kernel to run the test against. For KPD this is always LATEST, which runs against a newly built kernel. + tests: + required: true + type: string + description: A serialized json array with the tests to be running, it must follow the json-matrix format, https://www.jitsejan.com/use-github-actions-with-json-file-as-matrix + run_veristat: + required: true + type: boolean + description: Whether or not to run the veristat job. + run_tests: + required: true + type: boolean + description: Whether or not to run the test job. + download_sources: + required: true + type: boolean + description: Whether to download the linux sources into the working directory. + default: false + build_release: + required: true + type: boolean + description: Build selftests with -O2 optimization in addition to non-optimized build. + default: false + is_netdev: + required: true + type: boolean + description: Whether this is a netdev PR. When true, BPF-specific jobs like GCC BPF are skipped. + default: false + secrets: + AWS_ROLE_ARN: + required: true + +jobs: + + # Build kernel and selftest + build: + uses: ./.github/workflows/kernel-build.yml + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + toolchain: ${{ inputs.toolchain }} + runs_on: ${{ inputs.build_runs_on }} + gcc_version: ${{ inputs.gcc_version }} + llvm_version: ${{ inputs.llvm_version }} + kernel: ${{ inputs.kernel }} + download_sources: ${{ inputs.download_sources }} + + build-release: + if: ${{ inputs.build_release }} + uses: ./.github/workflows/kernel-build.yml + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + toolchain: ${{ inputs.toolchain }} + runs_on: ${{ inputs.build_runs_on }} + gcc_version: ${{ inputs.gcc_version }} + llvm_version: ${{ inputs.llvm_version }} + kernel: ${{ inputs.kernel }} + download_sources: ${{ inputs.download_sources }} + release: true + + test: + if: ${{ inputs.run_tests }} + uses: ./.github/workflows/kernel-test.yml + # Setting name to test here to avoid lengthy autogenerated names due to matrix + # e.g build-and-test x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc + name: "test" + needs: [build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(inputs.tests) }} + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + runs_on: ${{ inputs.runs_on }} + kernel: ${{ inputs.kernel }} + test: ${{ matrix.test }} + continue_on_error: ${{ toJSON(matrix.continue_on_error) }} + timeout_minutes: ${{ matrix.timeout_minutes }} + llvm_version: ${{ inputs.llvm_version }} + + test-progs-asan: + name: 'test_progs with ASAN' + if: ${{ inputs.arch != 's390x' }} + uses: ./.github/workflows/test-progs-asan.yml + needs: [build] + with: + runs_on: ${{ inputs.runs_on }} + arch: ${{ inputs.arch }} + gcc_version: ${{ inputs.gcc_version }} + llvm_version: ${{ inputs.llvm_version }} + toolchain: ${{ inputs.toolchain }} + toolchain_full: ${{ inputs.toolchain_full }} + download_sources: ${{ inputs.download_sources }} + + veristat-kernel: + if: ${{ inputs.run_veristat }} + uses: ./.github/workflows/veristat-kernel.yml + needs: [build] + permissions: + id-token: write + contents: read + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + runs_on: ${{ inputs.runs_on }} + + veristat-meta: + # Check for vars.AWS_REGION is necessary to skip this job in case of a PR from a fork. + if: ${{ inputs.run_veristat && github.repository_owner == 'kernel-patches' && vars.AWS_REGION }} + uses: ./.github/workflows/veristat-meta.yml + needs: [build] + permissions: + id-token: write + contents: read + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + aws_region: ${{ vars.AWS_REGION }} + runs_on: ${{ inputs.runs_on }} + secrets: + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} + + veristat-scx: + if: ${{ inputs.run_veristat }} + uses: ./.github/workflows/veristat-scx.yml + needs: [build] + permissions: + id-token: write + contents: read + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + runs_on: ${{ inputs.runs_on }} + llvm_version: ${{ inputs.llvm_version }} + + veristat-cilium: + if: ${{ inputs.run_veristat }} + uses: ./.github/workflows/veristat-cilium.yml + needs: [build] + permissions: + id-token: write + contents: read + with: + arch: ${{ inputs.arch }} + toolchain_full: ${{ inputs.toolchain_full }} + runs_on: ${{ inputs.runs_on }} + + gcc-bpf: + name: 'GCC BPF' + if: ${{ inputs.arch == 'x86_64' && !inputs.is_netdev }} + uses: ./.github/workflows/gcc-bpf.yml + needs: [build] + with: + # The tests run in a VM, so these must be the /dev/kvm capable runners. + # The build half of the workflow picks its own runner. + runs_on: ${{ inputs.runs_on }} + arch: ${{ inputs.arch }} + gcc_version: ${{ inputs.gcc_version }} + llvm_version: ${{ inputs.llvm_version }} + toolchain: ${{ inputs.toolchain }} + toolchain_full: ${{ inputs.toolchain_full }} + download_sources: ${{ inputs.download_sources }} + run_tests: ${{ inputs.run_tests }} diff --git a/.github/workflows/kernel-build.yml b/.github/workflows/kernel-build.yml new file mode 100644 index 0000000000000..f13ce0182c5d6 --- /dev/null +++ b/.github/workflows/kernel-build.yml @@ -0,0 +1,178 @@ + +name: Reusable build workflow + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: The toolchain and for llvm, its version, e.g gcc, llvm-15 + toolchain: + required: true + type: string + description: The toolchain, e.g gcc, llvm + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + gcc_version: + required: true + type: string + description: GCC version to install + llvm_version: + required: true + type: string + description: LLVM version to install + kernel: + required: true + type: string + description: The kernel to run the test against. For KPD this is always LATEST, which runs against a newly built kernel. + download_sources: + required: true + type: boolean + description: Whether to download the linux sources into the working directory. + default: false + release: + required: false + type: boolean + description: Build selftest with -O2 optimization + default: false + +jobs: + build: + name: build kernel and selftests ${{ inputs.release && '-O2' || '' }} + runs-on: + - ${{ github.repository == 'kernel-patches/bpf-rc' && 'ubuntu-latest' + || format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + # AWS docs about image override: https://docs.aws.amazon.com/codebuild/latest/userguide/sample-github-action-runners-update-labels.html + - image:${{ inputs.arch == 'aarch64' && 'custom-arm-ghcr.io/kernel-patches/runner:kbuilder-debian-aarch64' + || 'custom-linux-ghcr.io/kernel-patches/runner:kbuilder-debian-x86_64' }} + env: + ARTIFACTS_ARCHIVE: "vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst" + BPF_NEXT_FETCH_DEPTH: 64 # A bit of history is needed to facilitate incremental builds + CROSS_COMPILE: ${{ inputs.arch == 's390x' && 'true' || '' }} + BUILD_SCHED_EXT_SELFTESTS: ${{ inputs.arch == 'x86_64' || inputs.arch == 'aarch64' && 'true' || '' }} + KBUILD_OUTPUT: ${{ github.workspace }}/kbuild-output + KERNEL: ${{ inputs.kernel }} + KERNEL_ROOT: ${{ github.workspace }} + KERNEL_ORIGIN: ${{ github.repository == 'kernel-patches/bpf-rc' && 'https://github.com/kernel-patches/bpf-rc.git' + || 'https://github.com/kernel-patches/bpf.git' + }} + KERNEL_REVISION: ${{ inputs.download_sources && 'bpf-next' || github.sha }} + REFERENCE_REPO_PATH: /libbpfci/mirrors/linux + REPO_PATH: "" + REPO_ROOT: ${{ github.workspace }} + RUNNER_TYPE: codebuild + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - if: ${{ env.RUNNER_TYPE == 'codebuild' }} + shell: bash + run: .github/scripts/tmpfsify-workspace.sh + + - name: Download bpf-next tree @ ${{ env.KERNEL_REVISION }} + uses: libbpf/ci/get-linux-source@v4 + env: + FETCH_DEPTH: ${{ env.BPF_NEXT_FETCH_DEPTH }} + with: + dest: '.kernel' + repo: ${{ env.KERNEL_ORIGIN }} + rev: ${{ env.KERNEL_REVISION }} + + - name: Move linux source in place + shell: bash + run: | + cd .kernel + rm -rf .git .github ci + mv -t .. $(ls -A) + cd .. + rmdir .kernel + + - uses: libbpf/ci/patch-kernel@v4 + with: + patches-root: '${{ github.workspace }}/ci/diffs' + repo-root: ${{ env.REPO_ROOT }} + + - name: Setup build environment + uses: libbpf/ci/setup-build-env@v4 + with: + arch: ${{ inputs.arch }} + gcc-version: ${{ inputs.gcc_version }} + llvm-version: ${{ inputs.llvm_version }} + pahole: master + + - name: Build kernel image + uses: libbpf/ci/build-linux@v4 + with: + arch: ${{ inputs.arch }} + toolchain: ${{ inputs.toolchain }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + max-make-jobs: 32 + llvm-version: ${{ inputs.llvm_version }} + + - name: Build selftests/bpf + uses: libbpf/ci/build-selftests@v4 + env: + MAX_MAKE_JOBS: 32 + RELEASE: ${{ inputs.release && '1' || '' }} + with: + arch: ${{ inputs.arch }} + kernel-root: ${{ env.KERNEL_ROOT }} + llvm-version: ${{ inputs.llvm_version }} + toolchain: ${{ inputs.toolchain }} + + - if: ${{ env.BUILD_SCHED_EXT_SELFTESTS }} + name: Build selftests/sched_ext + uses: libbpf/ci/build-scx-selftests@v4 + with: + kbuild-output: ${{ env.KBUILD_OUTPUT }} + repo-root: ${{ env.REPO_ROOT }} + arch: ${{ inputs.arch }} + toolchain: ${{ inputs.toolchain }} + llvm-version: ${{ inputs.llvm_version }} + max-make-jobs: 32 + + - if: ${{ github.event_name != 'push' }} + name: Build samples + uses: libbpf/ci/build-samples@v4 + with: + arch: ${{ inputs.arch }} + toolchain: ${{ inputs.toolchain }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + max-make-jobs: 32 + llvm-version: ${{ inputs.llvm_version }} + - name: Tar artifacts + id: tar-artifacts + uses: libbpf/ci/tar-artifacts@v4 + env: + ARCHIVE_BPF_SELFTESTS: 'true' + ARCHIVE_MAKE_HELPERS: 'true' + ARCHIVE_SCHED_EXT_SELFTESTS: ${{ env.BUILD_SCHED_EXT_SELFTESTS }} + with: + arch: ${{ inputs.arch }} + archive: ${{ env.ARTIFACTS_ARCHIVE }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + repo-root: ${{ env.REPO_ROOT }} + - if: ${{ github.event_name != 'push' }} + name: Remove KBUILD_OUTPUT content + shell: bash + run: | + # Remove $KBUILD_OUTPUT to prevent cache creation for pull requests. + # Only on pushed changes are build artifacts actually cached, because + # of github.com/actions/cache's cache isolation logic. + rm -rf "${KBUILD_OUTPUT}" + - uses: actions/upload-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}${{ inputs.release && '-release' || '' }} + if-no-files-found: error + path: ${{ env.ARTIFACTS_ARCHIVE }} diff --git a/.github/workflows/kernel-test.yml b/.github/workflows/kernel-test.yml new file mode 100644 index 0000000000000..be39b01e73dcc --- /dev/null +++ b/.github/workflows/kernel-test.yml @@ -0,0 +1,111 @@ +name: Reusable test workflow + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: The toolchain and for llvm, its version, e.g gcc, llvm-15 + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + kernel: + required: true + type: string + description: The kernel to run the test against. For KPD this is always LATEST, which runs against a newly built kernel. + test: + required: true + type: string + description: The test to run in the vm, e.g test_progs, test_maps, test_progs_no_alu32... + continue_on_error: + required: true + type: string + description: Whether to continue on error. This is typically set to true for parallel tests which are currently known to fail, but we don't want to fail the whole CI because of that. + timeout_minutes: + required: true + type: number + description: In case a test runs for too long, after how many seconds shall we timeout and error. + llvm_version: + required: true + type: string + +jobs: + test: + name: ${{ inputs.test }} on ${{ inputs.arch }} with ${{ inputs.toolchain_full }} + runs-on: ${{ fromJSON(inputs.runs_on) }} + timeout-minutes: 100 + env: + ARCH: ${{ inputs.arch }} + KERNEL: ${{ inputs.kernel }} + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + # https://github.com/actions/runner/issues/1483#issuecomment-1031671517 + # booleans are weird in GH. + CONTINUE_ON_ERROR: ${{ inputs.continue_on_error }} + DEPLOYMENT: ${{ github.repository == 'kernel-patches/bpf' && 'prod' || 'rc' }} + ALLOWLIST_FILE: /tmp/allowlist + DENYLIST_FILE: /tmp/denylist + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: . + + - name: Untar artifacts + # zstd is installed by default in the runner images. + run: zstd -d -T0 vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - name: Run selftests + uses: libbpf/ci/run-vmtest@v4 + # https://github.com/actions/runner/issues/1483#issuecomment-1031671517 + # booleans are weird in GH. + continue-on-error: ${{ fromJSON(env.CONTINUE_ON_ERROR) }} + timeout-minutes: ${{ inputs.timeout_minutes }} + env: + ARCH: ${{ inputs.arch }} + DEPLOYMENT: ${{ env.DEPLOYMENT }} + KERNEL_TEST: ${{ inputs.test }} + LLVM_VERSION: ${{ inputs.llvm_version }} + SELFTESTS_BPF: ${{ github.workspace }}/selftests/bpf + VMTEST_CONFIGS: ${{ github.workspace }}/ci/vmtest/configs + VMTEST_MEMORY: 5G + VMTEST_NUM_CPUS: 4 + TEST_PROGS_TRAFFIC_MONITOR: ${{ inputs.arch == 'x86_64' && 'true' || '' }} + TEST_PROGS_WATCHDOG_TIMEOUT: 600 + with: + arch: ${{ inputs.arch }} + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: ${{ env.REPO_ROOT }} + max-cpu: 8 + kernel-test: ${{ inputs.test }} + # Here we must use kbuild-output local to the repo, because + # it was extracted from the artifacts. + kbuild-output: ${{ env.REPO_ROOT }}/kbuild-output + + - if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: tmon-logs-${{ inputs.arch }}-${{ inputs.toolchain_full }}-${{ inputs.test }} + if-no-files-found: ignore + path: /tmp/tmon_pcap/* + + # Written by check-kernel-splats.sh inside the VM, into the bind-mounted + # workspace. Needed to triage a splat, and to audit the scan itself. + - if: ${{ always() }} + uses: actions/upload-artifact@v7 + with: + name: kernel-log-${{ inputs.arch }}-${{ inputs.toolchain_full }}-${{ inputs.test }} + if-no-files-found: ignore + path: dmesg.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000000..b6de3101d61ea --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,65 @@ +name: "lint" + +on: + pull_request: + push: + branches: + - master + +jobs: + shellcheck: + # This workflow gets injected into other Linux repositories, but we don't + # want it to run there. + if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: ShellCheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + env: + SHELLCHECK_OPTS: --severity=warning --exclude=SC1091 + + # Ensure some consistency in the formatting. + lint: + if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Run black + uses: psf/black@stable + with: + src: ./.github/scripts + + validate_matrix: + if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: Validate matrix.py + runs-on: ubuntu-latest + env: + GITHUB_REPOSITORY_OWNER: ${{ matrix.owner }} + GITHUB_REPOSITORY: ${{ matrix.repository }} + GITHUB_OUTPUT: /dev/stdout + strategy: + matrix: + owner: ['kernel-patches', 'foo'] + repository: ['bpf', 'vmtest', 'bar'] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: run script + run: | + python3 .github/scripts/matrix.py + + unittests: + if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: Unittests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Run unittests + run: python3 -m unittest scripts/tests/*.py + working-directory: .github diff --git a/.github/workflows/linux-next-sync.yml b/.github/workflows/linux-next-sync.yml new file mode 100644 index 0000000000000..2a5d57ab41ff7 --- /dev/null +++ b/.github/workflows/linux-next-sync.yml @@ -0,0 +1,98 @@ +name: linux-next sync + +on: + push: + branches: + - linux-next-sync + schedule: + - cron: '17 */6 * * *' + workflow_dispatch: + +concurrency: + group: linux-next-sync + cancel-in-progress: false + +permissions: + contents: write + +jobs: + sync-linux-next: + if: ${{ github.repository == 'kernel-patches/bpf' }} + runs-on: ubuntu-slim + steps: + - name: Clone kernel-patches/bpf (blobless) + shell: bash + run: | + set -euo pipefail + + git clone \ + --filter=blob:none \ + --no-checkout \ + --single-branch \ + https://github.com/kernel-patches/bpf.git \ + repo + + - name: Configure git identity + working-directory: repo + shell: bash + run: | + git config user.name 'bpf-ci[bot]' + git config user.email 'bot+bpf-ci@kernel.org' + + - name: Sync linux-next branch + working-directory: repo + shell: bash + run: | + set -euo pipefail + + BASE_BRANCH='bpf_base' + HEAD_BRANCH='linux-next' + UPSTREAM_REMOTE='linux-next-upstream' + UPSTREAM_URL='https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' + UPSTREAM_BRANCH='master' + + git remote add "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}" + git config "remote.${UPSTREAM_REMOTE}.promisor" true + git config "remote.${UPSTREAM_REMOTE}.partialclonefilter" blob:none + + git fetch --no-tags origin \ + "+refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH}" + git fetch --no-tags --filter=blob:none "${UPSTREAM_REMOTE}" \ + "+refs/heads/${UPSTREAM_BRANCH}:refs/remotes/${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" + + if git ls-remote --exit-code --heads origin "${HEAD_BRANCH}" >/dev/null 2>&1; then + git fetch --no-tags origin \ + "+refs/heads/${HEAD_BRANCH}:refs/remotes/origin/${HEAD_BRANCH}" + fi + + git checkout -B "${HEAD_BRANCH}" "refs/remotes/origin/${BASE_BRANCH}" + git merge --no-ff --no-edit "refs/remotes/${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" + + - name: Generate GitHub App token + if: ${{ github.repository == 'kernel-patches/bpf' }} + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.KPD_BOT_APP_ID }} + private-key: ${{ secrets.KPD_BOT_PRIVATE_KEY }} + + - name: Push linux-next branch + if: ${{ github.repository == 'kernel-patches/bpf' }} + working-directory: repo + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + shell: bash + run: | + set -euo pipefail + + HEAD_BRANCH='linux-next' + PUSH_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/kernel-patches/bpf.git" + + if git show-ref --verify --quiet "refs/remotes/origin/${HEAD_BRANCH}"; then + old_head=$(git rev-parse "refs/remotes/origin/${HEAD_BRANCH}") + git push \ + --force-with-lease="refs/heads/${HEAD_BRANCH}:${old_head}" \ + "${PUSH_URL}" HEAD:"refs/heads/${HEAD_BRANCH}" + else + git push "${PUSH_URL}" HEAD:"refs/heads/${HEAD_BRANCH}" + fi diff --git a/.github/workflows/test-progs-asan.yml b/.github/workflows/test-progs-asan.yml new file mode 100644 index 0000000000000..094d6bc379206 --- /dev/null +++ b/.github/workflows/test-progs-asan.yml @@ -0,0 +1,170 @@ +name: test_progs with ASAN + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: The toolchain and for llvm, its version, e.g gcc, llvm-15 + toolchain: + required: true + type: string + description: The toolchain, e.g gcc, llvm + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + gcc_version: + required: true + type: string + description: GCC version to install + llvm_version: + required: true + type: string + description: LLVM version to install + download_sources: + required: true + type: boolean + + +jobs: + build: + name: build test_progs with ASAN + runs-on: + - ${{ format('codebuild-bpf-ci-{0}-{1}', github.run_id, github.run_attempt) }} + - image:${{ inputs.arch == 'aarch64' && 'custom-arm-ghcr.io/kernel-patches/runner:kbuilder-debian-aarch64' + || 'custom-linux-ghcr.io/kernel-patches/runner:kbuilder-debian-x86_64' }} + env: + ARCH: ${{ inputs.arch }} + ARTIFACTS_ARCHIVE: ${{ github.workspace }}/selftests-bpf-asan-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst + KERNEL_ORIGIN: ${{ github.repository == 'kernel-patches/bpf-rc' && 'https://github.com/kernel-patches/bpf-rc.git' + || 'https://github.com/kernel-patches/bpf.git' + }} + KERNEL_REVISION: ${{ inputs.download_sources && 'bpf-next' || github.sha }} + REPO_ROOT: ${{ github.workspace }}/src + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - name: Download bpf-next tree @ ${{ env.KERNEL_REVISION }} + uses: libbpf/ci/get-linux-source@v4 + with: + dest: ${{ env.REPO_ROOT }} + repo: ${{ env.KERNEL_ORIGIN }} + rev: ${{ env.KERNEL_REVISION }} + + - uses: libbpf/ci/patch-kernel@v4 + with: + patches-root: '${{ github.workspace }}/ci/diffs' + repo-root: ${{ env.REPO_ROOT }} + + - name: Setup build environment + uses: libbpf/ci/setup-build-env@v4 + with: + arch: ${{ inputs.arch }} + gcc-version: ${{ inputs.gcc_version }} + llvm-version: ${{ inputs.llvm_version }} + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar artifacts + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - name: Build selftests/bpf/test_progs with ASAN + uses: libbpf/ci/build-selftests@v4 + env: + KBUILD_OUTPUT: ${{ env.REPO_ROOT }}/kbuild-output + SELFTESTS_BPF_ASAN: 'true' + SELFTESTS_BPF_TARGETS: 'test_progs' + with: + arch: ${{ inputs.arch }} + kernel-root: ${{ env.REPO_ROOT }} + llvm-version: ${{ inputs.llvm_version }} + toolchain: ${{ inputs.toolchain }} + + - name: Tar artifacts + id: tar-artifacts + uses: libbpf/ci/tar-artifacts@v4 + env: + ARCHIVE_BPF_SELFTESTS: 'true' + ARCHIVE_KBUILD_OUTPUT: '' # emptystring means false + with: + arch: ${{ inputs.arch }} + archive: ${{ env.ARTIFACTS_ARCHIVE }} + kbuild-output: ${{ env.REPO_ROOT }}/kbuild-output + repo-root: ${{ env.REPO_ROOT }} + + - uses: actions/upload-artifact@v7 + with: + name: selftests-bpf-asan-${{ inputs.arch }}-${{ inputs.toolchain_full }} + if-no-files-found: error + path: ${{ env.ARTIFACTS_ARCHIVE }} + + test: + name: test_progs ASAN + runs-on: ${{ fromJSON(inputs.runs_on) }} + needs: [build] + timeout-minutes: 100 + env: + ARCH: ${{ inputs.arch }} + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + DEPLOYMENT: ${{ github.repository == 'kernel-patches/bpf' && 'prod' || 'rc' }} + ALLOWLIST_FILE: /tmp/allowlist + DENYLIST_FILE: /tmp/denylist + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar vmlinux + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 vmlinux-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - uses: actions/download-artifact@v7 + with: + name: selftests-bpf-asan-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.REPO_ROOT }} + + - name: Untar test_progs ASAN + working-directory: ${{ env.REPO_ROOT }} + run: zstd -d -T0 selftests-bpf-asan-${{ inputs.arch }}-${{ inputs.toolchain_full }}.tar.zst --stdout | tar -xf - + + - name: Run selftests + uses: libbpf/ci/run-vmtest@v4 + env: + ARCH: ${{ inputs.arch }} + DEPLOYMENT: ${{ env.DEPLOYMENT }} + LLVM_VERSION: ${{ inputs.llvm_version }} + SELFTESTS_BPF: ${{ github.workspace }}/selftests/bpf + SELFTESTS_BPF_ASAN: 'true' + VMTEST_CONFIGS: ${{ github.workspace }}/ci/vmtest/configs + VMTEST_MEMORY: 5G + VMTEST_NUM_CPUS: 4 + TEST_PROGS_WATCHDOG_TIMEOUT: 600 + with: + arch: ${{ inputs.arch }} + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: ${{ env.REPO_ROOT }} + kernel-test: test_progs + kbuild-output: ${{ env.REPO_ROOT }}/kbuild-output diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000..60b64b43235e4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,78 @@ +name: bpf-ci + +on: + pull_request: + push: + branches: + - bpf_base + - bpf-next_base + - bpf-net_base + - for-next_base + - linux-next + +concurrency: + group: ci-test-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + set-matrix: + runs-on: ubuntu-slim + permissions: read-all + outputs: + build-matrix: ${{ steps.set-matrix-impl.outputs.build_matrix }} + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + - name: Install script dependencies + shell: bash + run: | + sudo apt-get -y update + sudo apt-get -y install python3-requests + - name: Stagger if runners are busy + if: ${{ github.event.action == 'synchronize' && github.repository == 'kernel-patches/bpf' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_EVENT_ACTION: ${{ github.event.action }} + PR_BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + run: python3 .github/scripts/stagger.py + - id: set-matrix-impl + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT_READ_RUNNERS }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + python3 .github/scripts/matrix.py + + build-and-test: + # Setting name to arch-compiler here to avoid lengthy autogenerated names due to matrix + # e.g build-and-test x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc + name: ${{ matrix.arch }} ${{ matrix.kernel_compiler }}-${{ matrix.kernel_compiler == 'gcc' && matrix.gcc_version || matrix.llvm_version }} + uses: ./.github/workflows/kernel-build-test.yml + needs: [set-matrix] + permissions: + id-token: write + contents: read + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.set-matrix.outputs.build-matrix) }} + with: + arch: ${{ matrix.arch }} + toolchain: ${{ matrix.kernel_compiler }} + toolchain_full: ${{ matrix.kernel_compiler }}-${{ matrix.kernel_compiler == 'gcc' && matrix.gcc_version || matrix.llvm_version }} + runs_on: ${{ toJSON(matrix.runs_on) }} + build_runs_on: ${{ toJSON(matrix.build_runs_on) }} + gcc_version: ${{ matrix.gcc_version }} + llvm_version: ${{ matrix.llvm_version }} + kernel: ${{ matrix.kernel }} + tests: ${{ toJSON(matrix.tests) }} + run_veristat: ${{ matrix.run_veristat }} + # Pushes normally build only, except linux-next syncs which should run tests too. + run_tests: ${{ github.event_name != 'push' || github.ref_name == 'linux-next' }} + # Download sources + download_sources: ${{ github.repository == 'kernel-patches/vmtest' }} + build_release: ${{ matrix.build_release }} + is_netdev: ${{ matrix.is_netdev }} + secrets: + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} diff --git a/.github/workflows/veristat-cilium.yml b/.github/workflows/veristat-cilium.yml new file mode 100644 index 0000000000000..b5dccd533709a --- /dev/null +++ b/.github/workflows/veristat-cilium.yml @@ -0,0 +1,74 @@ +name: veristat_cilium + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: Toolchain identifier, such as llvm-20 + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + +jobs: + + veristat: + name: veristat-cilium + runs-on: ${{ fromJSON(inputs.runs_on) }} + permissions: + id-token: write + contents: read + env: + KERNEL: LATEST + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + ARCH_AND_TOOL: ${{ inputs.arch }}-${{ inputs.toolchain_full }} + VERISTAT_DUMP_LOG_ON_FAILURE: 'true' + VERISTAT_TARGET: cilium + CILIUM_BUILD_OUTPUT: ${{ github.workspace }}/cilium-build-output + CILIUM_BPF_RELEASE_REPO: 'puranjaymohan/cilium-bpf-progs' + + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - name: Download kernel build artifacts + uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ env.ARCH_AND_TOOL }} + path: . + + - name: Untar kernel build artifacts + run: zstd -d -T0 vmlinux-${{ env.ARCH_AND_TOOL }}.tar.zst --stdout | tar -xf - + + - name: Download cilium BPF programs + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: .github/scripts/download-gh-release.sh ${{ env.CILIUM_BPF_RELEASE_REPO }} ${{ env.CILIUM_BUILD_OUTPUT }} + + - name: Run veristat + uses: libbpf/ci/run-vmtest@v4 + with: + arch: x86_64 + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + kernel-test: 'run_veristat' + output-dir: '${{ github.workspace }}' + + - name: Compare and save veristat.cilium.csv + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-cilium + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-cilium diff --git a/.github/workflows/veristat-kernel.yml b/.github/workflows/veristat-kernel.yml new file mode 100644 index 0000000000000..c3b66c76f05fc --- /dev/null +++ b/.github/workflows/veristat-kernel.yml @@ -0,0 +1,65 @@ +name: veristat_kernel + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: Toolchain identifier, such as llvm-20 + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + +jobs: + veristat: + name: veristat-kernel + runs-on: ${{ fromJSON(inputs.runs_on) }} + timeout-minutes: 100 + permissions: + id-token: write + contents: read + env: + KERNEL: LATEST + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + ARCH_AND_TOOL: ${{ inputs.arch }}-${{ inputs.toolchain_full }} + VERISTAT_TARGET: kernel + + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ env.ARCH_AND_TOOL }} + path: . + + - name: Untar artifacts + run: zstd -d -T0 vmlinux-${{ env.ARCH_AND_TOOL }}.tar.zst --stdout | tar -xf - + + - name: Run veristat + uses: libbpf/ci/run-vmtest@v4 + with: + arch: x86_64 + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + max-cpu: 8 + kernel-test: 'run_veristat' + output-dir: '${{ github.workspace }}' + + - name: Compare and save veristat.kernel.csv + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-kernel + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-kernel diff --git a/.github/workflows/veristat-meta.yml b/.github/workflows/veristat-meta.yml new file mode 100644 index 0000000000000..3b97c77628b3b --- /dev/null +++ b/.github/workflows/veristat-meta.yml @@ -0,0 +1,88 @@ +name: veristat_meta + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: Toolchain identifier, such as llvm-20 + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + aws_region: + required: true + type: string + description: The AWS region where we pull bpf objects to run against veristat. + secrets: + AWS_ROLE_ARN: + required: true + description: The AWS role used by GH to pull BPF objects from AWS. + +jobs: + veristat: + name: veristat-meta + runs-on: ${{ fromJSON(inputs.runs_on) }} + timeout-minutes: 100 + permissions: + id-token: write + contents: read + env: + KERNEL: LATEST + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + ARCH_AND_TOOL: ${{ inputs.arch }}-${{ inputs.toolchain_full }} + VERISTAT_TARGET: meta + + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ env.ARCH_AND_TOOL }} + path: . + + - name: Untar artifacts + run: zstd -d -T0 vmlinux-${{ env.ARCH_AND_TOOL }}.tar.zst --stdout | tar -xf - + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-region: ${{ inputs.aws_region }} + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + role-session-name: github-action-bpf-ci + + - name: Download BPF objects + run: | + mkdir ./bpf_objects + aws s3 sync s3://veristat-bpf-binaries ./bpf_objects + env: + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} + + - name: Run veristat + uses: libbpf/ci/run-vmtest@v4 + with: + arch: x86_64 + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + max-cpu: 8 + kernel-test: 'run_veristat' + output-dir: '${{ github.workspace }}' + + - name: Compare and save veristat.meta.csv + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-meta + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-meta + diff --git a/.github/workflows/veristat-scx.yml b/.github/workflows/veristat-scx.yml new file mode 100644 index 0000000000000..f8ccc3fb593f8 --- /dev/null +++ b/.github/workflows/veristat-scx.yml @@ -0,0 +1,101 @@ +name: veristat_kernel + +on: + workflow_call: + inputs: + arch: + required: true + type: string + description: The architecture to build against, e.g x86_64, aarch64, s390x... + toolchain_full: + required: true + type: string + description: Toolchain identifier, such as llvm-20 + runs_on: + required: true + type: string + description: The runners to run the test on. This is a json string representing an array of labels. + llvm_version: + required: true + type: string + +jobs: + + build-scheds: + name: build sched-ext/scx + runs-on: ${{ fromJSON(inputs.runs_on) }} + env: + LLVM_VERSION: ${{ inputs.llvm_version }} + SCX_BUILD_OUTPUT: ${{ github.workspace }}/scx-build-output + SCX_REVISION: main + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - uses: libbpf/ci/build-scx-scheds@v4 + with: + output-dir: ${{ env.SCX_BUILD_OUTPUT }} + + - name: Upload sched-ext build output + uses: actions/upload-artifact@v7 + with: + name: sched-ext-${{ inputs.arch }}-${{ inputs.toolchain_full }} + if-no-files-found: error + path: ${{ env.SCX_BUILD_OUTPUT }} + + veristat: + name: veristat-scx + runs-on: ${{ fromJSON(inputs.runs_on) }} + needs: [build-scheds] + permissions: + id-token: write + contents: read + env: + KERNEL: LATEST + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + ARCH_AND_TOOL: ${{ inputs.arch }}-${{ inputs.toolchain_full }} + VERISTAT_TARGET: scx + SCX_BUILD_OUTPUT: ${{ github.workspace }}/scx-build-output + + steps: + + - uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + ci + + - name: Download kernel build artifacts + uses: actions/download-artifact@v7 + with: + name: vmlinux-${{ env.ARCH_AND_TOOL }} + path: . + + - name: Untar kernel build artifacts + run: zstd -d -T0 vmlinux-${{ env.ARCH_AND_TOOL }}.tar.zst --stdout | tar -xf - + + - name: Download sched-ext build output + uses: actions/download-artifact@v7 + with: + name: sched-ext-${{ inputs.arch }}-${{ inputs.toolchain_full }} + path: ${{ env.SCX_BUILD_OUTPUT }} + + - name: Run veristat + uses: libbpf/ci/run-vmtest@v4 + with: + arch: x86_64 + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + kernel-test: 'run_veristat' + output-dir: '${{ github.workspace }}' + + - name: Compare and save veristat.scx.csv + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-scx + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-scx diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..81a0c1a644b0f --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# BPF CI GitHub Actions worfklows + +This repository contains GitHub Actions workflow definitions, scripts and configuration files used by those workflows. + +You can check the workflow runs on [kernel-patches/bpf actions page](https://github.com/kernel-patches/bpf/actions/workflows/test.yml). + +**"BPF CI"** refers to a continuous integration testing system targeting [BPF subsystem of the Linux Kernel](https://ebpf.io/what-is-ebpf/). + +BPF CI consists of a number of components: +- [kernel-patches/bpf](https://github.com/kernel-patches/bpf) - a copy of Linux Kernel source repository tracking [upstream bpf trees](https://web.git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/) +- [Kernel Patches Daemon](https://github.com/kernel-patches/kernel-patches-daemon) instance - a service connecting [Patchwork](https://patchwork.kernel.org/project/netdevbpf/list/) with the GitHub repository +- [kernel-patches/vmtest](https://github.com/kernel-patches/vmtest) (this repository) - GitHub Actions workflows +- [libbpf/ci](https://github.com/libbpf/ci) - custom reusable GitHub Actions +- [kernel-patches/runner](https://github.com/kernel-patches/runner) - self-hosted GitHub Actions runners + +Of course BPF CI also has important dependencies such as: +- [selftests/bpf](https://web.git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf) - the main test suite of BPF CI +- [selftests/sched_ext](https://web.git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/sched_ext) - in-kernel sched_ext test suite +- [veristat](https://web.git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/veristat.c) - used to catch performance and BPF verification regressions on a suite of complex BPF programs +- [vmtest](https://github.com/danobi/vmtest) - a QEMU wrapper, used to execute tests in a VM +- [GCC BPF backend](https://gcc.gnu.org/wiki/BPFBackEnd) +- Above-mentioned [Patchwork](https://patchwork.kernel.org/) instance, maintained by the Linux Foundation diff --git a/ci/claude/README.md b/ci/claude/README.md new file mode 100644 index 0000000000000..669b942d0c15e --- /dev/null +++ b/ci/claude/README.md @@ -0,0 +1,67 @@ +# AI Code Reviews in BPF CI + +## TL;DR +- **Please make sure AI is actually wrong before dismissing the review** + - An email response explaining why AI is wrong would be very helpful +- BPF CI includes [a workflow](https://github.com/kernel-patches/vmtest/blob/master/.github/workflows/ai-code-review.yml) running AI code review +- The reviews are posted as comments on [kernel-patches/bpf PRs](https://github.com/kernel-patches/bpf/pulls) +- The review comments are forwarded to the patch recipients via email by [KPD](https://github.com/kernel-patches/kernel-patches-daemon) +- Prompts are here: https://github.com/masoncl/review-prompts + +If you received an AI review for your patch submission, please try to evaluate it in the same way you would if it was written by a person, and respond. +Your response is for humans, not for AI. + +## How does it work? + +BPF CI is processing every patch series submitted to the [Linux Kernel BPF mailing list](https://lore.kernel.org/bpf/). +Against each patch the system executes various tests, such as [selftests/bpf](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf), and since recently it also executes automated code reviews performed by LLM-based AI. + +BPF CI runs on [Github Actions](https://docs.github.com/en/actions) workflows orchestrated by [KPD](https://github.com/kernel-patches/kernel-patches-daemon). + +The AI review is implemented with [Claude Code GitHub Action](https://github.com/anthropics/claude-code-action), which essentially installs Claude Code command-line app and a MCP server with a number of common tools available to it. + +LLMs are accessed via [AWS Bedrock](https://aws.amazon.com/bedrock), the GitHub Actions workflow authenticates to AWS account with [OIDC](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws). + +To achieve the output that might have led you to this page, [a set of elaborate prompts](https://github.com/masoncl/review-prompts) were developed specifically targeting the Linux Kernel source code. +The workflow checks out the Linux and prompts repository and initiates the review with a trivial [trigger prompt](https://github.com/kernel-patches/vmtest/blob/master/.github/workflows/ai-code-review.yml#L91-L94). + +### Are the reviews even accurate? + +We make every effort for AI reviews to be high-signal messages. Although the nature of LLMs makes them prone to mistakes. + +At this point this is still an experiment, but the results so far have been promising. +For example, AI is pretty good at catching dumb mistakes (e.g. use-after-free) that humans can easily miss. +At the same time AI can miss context obvious to a human, such as relationships between newer and older changes. + +If you'd like to suggest an improvement to the prompts, open a PR to [review-prompts](https://github.com/masoncl/review-prompts) repository. + +### Will my patch get nacked because of the AI review? + +Paraphrasing IBM training manual: +> "A LLM can never be held accountable, therefore a LLM must never make an Ack/Nack decision" + +The review prompts are designed such that AI is only searching for the regressions it can provide evidence for. +For the majority of patches a review is not generated, so if you received one it's worth evaluating. + +It's unlikely that your patch gets discarded *just* because AI found something, especially if you address it or explain why AI is wrong. + +But if you ignore an AI review, human reviewers will likely ask for a reason. + +### What if I don't like it? + +Bring it up with the maintainers on the mailing list and elaborate. + +It is expected that AI may be mistaken. However it is also expected that the patch authors answer reasonable questions about the code changes they propose. + +If there is a technical issue (say with email notifications, formatting etc.), open an issue in [this repository](https://github.com/kernel-patches/vmtest/issues). + +### Who pays for the tokens? + +[Meta Platforms, Inc.](https://www.meta.com/) + +BPF CI in its current form has been developed and maintained by the Linux Kernel team at Meta. Most of the relevant hardware is also provided by Meta. + +### Who set this up? + +- [Chris Mason](https://github.com/masoncl) is the prompt engineer +- [Ihor Solodrai](https://github.com/theihor) is the infra plumber diff --git a/ci/claude/bpf-ci-agent.md b/ci/claude/bpf-ci-agent.md new file mode 100644 index 0000000000000..f973f6be884c1 --- /dev/null +++ b/ci/claude/bpf-ci-agent.md @@ -0,0 +1,304 @@ +You are an exploratory AI agent monitoring the Linux Kernel BPF CI +testing system. + +Your overarching goal is to improve the quality of the Linux Kernel +testing by suggesting self-contained, small incremental improvements +to the CI system code, existing test suites and in some cases Linux +Kernel codebase itself. + +## Rules + +### What to investigate + +- **Long term impact**: will addressing the issue solve an actual + problem Linux Kernel developers and users care about? +- **Testing quality, not kernel development**: If a failure is clearly + caused by a specific patch series, **do not consider** it — that is + the submitter's job. If the same failure happens across independent + PRs, **do** consider it (regression or CI-specific issue). +- **Human-prompted**: was this issue mentioned on the mailing list, in + commit messages or code comments? If yes, likely worth investigating. +- **Signal-to-noise**: Prefer flaky/repeating issues over one-offs. + Discount external dependency failures (e.g., GitHub outages). +- **Deduplication**: Check whether the issue is already reported in + `kernel-patches/vmtest` or fixed upstream — if so, discard it. + Check the skip list before investigating ANY issue. Never + re-investigate an issue already filed unless you have new + information. + +### How to work + +1. Follow phases in order. Do not skip phases. +2. Batch parallel tool calls (up to 4 `gh` commands per message). + Do not examine PRs/issues sequentially when batching is possible. +3. Use broad lore search patterns first, then narrow down. +4. Stop retrying after limits in the error handling table. +5. Attempt to reproduce test failures locally via vmtest when feasible. + Do not rely solely on reading code and CI logs. +6. Attempt to verify code fixes by building and running the relevant + test. If the test is flaky, verify correctness by code inspection. +7. **Never use `cd` in bash commands.** The working directory persists + between commands. Use `git -C ` for git operations in + companion repos, or absolute paths. If you `cd` into a subdirectory, + all subsequent commands (including `git`) will run against the wrong + repository. + +--- + +## Workspace + +NOTES.md contains your own notes from previous runs. The environment +may change between runs. + +Current directory is the root of the Linux Kernel source repository +(bpf-next) at the latest revision with full git history. + +You have access to: +- BPF CI workflow job logs via `gh` CLI and GitHub MCP tools + - BPF CI workflows run in `kernel-patches/bpf` GitHub repository +- semcode tools with indexed Linux source code and lore archive + (semcode may be unreliable; see Error Handling table for fallbacks) +- Any public information via GitHub CLI or web +- The `github/` directory contains relevant repositories: + - `kernel-patches/vmtest`, `kernel-patches/runner`, + `kernel-patches/kernel-patches-daemon`, `libbpf/ci` — BPF CI code + - `danobi/vmtest` — QEMU wrapper used in BPF CI to run VMs + - `facebookexperimental/semcode` — semcode source code + - `masoncl/review-prompts` — prompts with useful context about + Linux Kernel subsystems + - `nojb/public-inbox` — lei (local email interface) tool + +### Building and running tests + +`github/libbpf/ci/` contains the CI scripts. Key files: +- `build-linux/build.sh` — kernel build (config assembly + make) +- `build-selftests/build_selftests.sh` — selftest build +- `run-vmtest/run.sh` — test orchestration (VM setup + test dispatch) +- `run-vmtest/run-bpf-selftests.sh` — BPF test runner (inside VM) +- `run-vmtest/prepare-bpf-selftests.sh` — merges DENYLIST/ALLOWLIST +- `ci/vmtest/configs/` — kernel configs and DENYLIST files + +**Kernel config.** CI assembles .config by concatenating fragments: +``` +cat tools/testing/selftests/bpf/config \ + tools/testing/selftests/bpf/config.vm \ + tools/testing/selftests/bpf/config.x86_64 \ + github/kernel-patches/vmtest/ci/vmtest/configs/config \ + github/kernel-patches/vmtest/ci/vmtest/configs/config.x86_64 \ + > .config 2>/dev/null +make olddefconfig +``` +Replace `x86_64` with `aarch64` or `s390x` for other architectures. +The CI config adds KASAN, livepatch, and sample module options. + +**Build kernel and selftests:** +``` +make -j$(nproc) +make headers +make -C tools/testing/selftests/bpf -j$(nproc) +``` + +**Run tests via vmtest** (boots a QEMU VM with the built kernel): +``` +vmtest -k arch/x86/boot/bzImage -- \ + ./tools/testing/selftests/bpf/test_progs -t +``` +If `vmtest` is not installed, build from `github/danobi/vmtest` +(`cargo build --release`). test_progs flags: `-t ` (specific +test), `-j` (parallel), `-a@` / `-d@` (allow/denylist +from file), `-w` (watchdog timeout, CI uses 600). + +**DENYLIST/ALLOWLIST.** One test per line, `test/subtest` for subtests, +`#` for comments. Lists live in two places and are merged by CI: +- `tools/testing/selftests/bpf/DENYLIST[.arch]` (in-tree) +- `github/kernel-patches/vmtest/ci/vmtest/configs/DENYLIST[.arch]` + +--- + +## Protocol + +Print the completion banner at the end of each phase. + +### Phase 0: Load Context and Build Skip List + +**0.1** Read `NOTES.md` (if it exists) for known issues and status. + +**0.2** Check existing vmtest issues (dispatch in parallel): +``` +gh issue list --repo kernel-patches/vmtest --state open --limit 50 +gh issue list --repo kernel-patches/vmtest --state closed --limit 30 \ + --search "sort:updated-desc" +``` + +**0.3** Build a skip list (already filed, fix merged, in-flight): + +| Issue | Source | Reason to skip | +|-------|--------|----------------| + +``` +PHASE 0 COMPLETE: Context loaded + NOTES.md: + Open vmtest issues: + Skip list entries: +``` + +--- + +### Phase 1: Gather Candidates + +**1.1 CI logs.** List recent failed runs, then fetch logs for 5–8 +failed runs covering independent PRs: +``` +gh run list --repo kernel-patches/bpf --workflow vmtest \ + --status failure --limit 20 --json databaseId,displayTitle,conclusion,createdAt +gh run view --repo kernel-patches/bpf --log-failed 2>&1 | head -200 +``` +Look for test names failing across multiple independent PRs, infra +failures vs test failures, and patterns in failure messages. + +**1.2 Lore archive.** Search for recent BPF mailing list discussions +about CI issues, flaky tests, or improvements. Be over-inclusive. +Max 3 search attempts per query (see Error Handling). + +**1.3 CI configuration.** Check DENYLIST files, recently modified +tests, and recent commits to CI repositories. + +**1.4 Compile candidate list.** Every candidate MUST have all fields: + +| # | Name | Description | Frequency | Severity | Novelty | Skip? | +|---|------|-------------|-----------|----------|---------|-------| + +Frequency: every run / most / occasional / rare. Severity: blocks CI / +misleading signal / cosmetic. Novelty: new / known-unfixed / regression. +Check every candidate against the Phase 0 skip list. + +**Do NOT** list issues caused by a specific patch series, issues from a +single PR only, or skip-list issues without marking them. + +``` +PHASE 1 COMPLETE: Candidates gathered + CI runs examined: + Lore searches: / + Candidates found: + Candidates after skip-list filter: +``` + +--- + +### Phase 2: Select Issue + +Score each non-skipped candidate on (in priority order): +1. **Novelty** (highest) — not previously investigated or reported +2. **Frequency** — appears across more independent PRs +3. **Impact** — blocks CI or misleading signal over cosmetic +4. **Feasibility** — root cause likely identifiable in this session + +Select one issue. State which, why, and the investigation approach. + +``` +PHASE 2 COMPLETE: Issue selected + Selected: # + Reason: <1-2 sentences> +``` + +--- + +### Phase 3: Investigate + +**3.1 Reproduce and characterize.** Gather failure logs, identify the +exact failing test/component and failure mode. For test failures, +attempt local reproduction using the build and vmtest commands from +the Workspace section. Flaky or arch-specific failures may not +reproduce — record the result either way. If you skip reproduction, +state why (e.g., "infra issue, not a test failure" or "requires +s390x hardware"). + +**3.2 Root cause analysis.** Read test and kernel code. Use semcode +for functions/callers/call chains. Check git history. Search lore. + +Checklist: +- [ ] Failure logs from multiple CI runs +- [ ] Reproduction attempted (or reason for skipping stated) +- [ ] Test and kernel code read +- [ ] Git history checked +- [ ] Lore checked +- [ ] Root cause identified or best theory documented + +**3.3 Develop fix (if warranted).** Write and test the fix if +possible. For flaky tests, verify the fix is logically correct by +code inspection. For CI config changes, verify by examining the +configuration logic. + +**3.4 Decide whether to report.** **Do NOT generate output** if: +- The issue is a one-off that is no longer reproducing +- The issue was already fixed upstream (add to NOTES.md skip list) +- Root cause is unclear AND no actionable recommendation + +If not reporting, skip steps 4.1–4.2 but still update NOTES.md. + +``` +PHASE 3 COMPLETE: Investigation finished + Reproduction: + Root cause: + Fix: +``` + +--- + +### Phase 4: Generate Output + +**4.1** Create `output/summary.md` as a GitHub issue: + +```markdown +# + +## Summary +<1-3 sentences> + +## Failure Details +- **Test / Component:** +- **Frequency:** +- **Failure mode:** +- **Affected architectures:** +- **CI runs observed:** + +## Root Cause Analysis + + +## Proposed Fix + + +## Impact + + +## References +- +``` + +**4.2** Create `.patch` files if applicable, following Linux Kernel +conventions (`git log` for examples). Use the tag: + + Generated-by: BPF CI Bot ($LLM_MODEL_NAME) + +**4.3** Update `NOTES.md` — record the investigated issue, uninvestigated +candidates, and updated status of known issues. Keep it compact. + +``` +PHASE 4 COMPLETE: Output generated + Files in output/: + NOTES.md: +``` + +--- + +## Error Handling + +| Tool | Error | Action | +|------|-------|--------| +| semcode lore | Error or empty | Retry once → `lei` CLI → `git log --grep`. Max 3 total attempts per query. | +| semcode code | Error | Verify cwd with `pwd` (must be Linux repo root). Fall back to grep/find. | +| `gh run view` | Rate limit or error | Wait 10s, retry once. If still failing, skip that run. | +| `gh issue list` | Error | Retry once. If failing, proceed with empty skip list. | +| `lei` | Unavailable | Fall back to `git log --grep`. | +| `git` | Unexpected output | Run `pwd` to verify cwd is the Linux repo root. If wrong, run `cd $GITHUB_WORKSPACE` to return to the workspace root. | +| Build / vmtest | Failure | Record error, do not retry more than once. | diff --git a/ci/claude/mcp.json b/ci/claude/mcp.json new file mode 100644 index 0000000000000..15cd5c7365fa8 --- /dev/null +++ b/ci/claude/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "semcode": { + "command": "semcode-mcp", + "args": ["-d", "/ci/.semcode.db"] + } + } +} diff --git a/ci/claude/post-pr-comment.js b/ci/claude/post-pr-comment.js new file mode 100644 index 0000000000000..31331e5e3e663 --- /dev/null +++ b/ci/claude/post-pr-comment.js @@ -0,0 +1,32 @@ +module.exports = async ({github, context}) => { + const fs = require('fs'); + + const jobSummaryUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const reviewContent = fs.readFileSync(process.env.REVIEW_FILE, 'utf8'); + const subject = process.env.PATCH_SUBJECT || 'Could not determine patch subject'; + const commentBody = ` +\`\`\` +${reviewContent} +\`\`\` + +--- +AI reviewed your patch. Please fix the bug or email reply why it's not a bug. +See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md + +In-Reply-To-Subject: \`${subject}\` +CI run summary: ${jobSummaryUrl}`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["ai-review"], + }); +}; diff --git a/ci/claude/settings.json b/ci/claude/settings.json new file mode 100644 index 0000000000000..5d622d5876b0f --- /dev/null +++ b/ci/claude/settings.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": ["Bash", "Edit", "MultiEdit", "Write", "WebFetch", "mcp__semcode__*", "mcp__github_ci__*"], + "deny": ["mcp__github__*"], + "defaultMode": "acceptEdits" + } +} diff --git a/ci/diffs/.keep b/ci/diffs/.keep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ci/diffs/20260223-s390-bpf-Do-not-increment-tailcall-count-when-prog-i.patch b/ci/diffs/20260223-s390-bpf-Do-not-increment-tailcall-count-when-prog-i.patch new file mode 100644 index 0000000000000..12f6e2d43be24 --- /dev/null +++ b/ci/diffs/20260223-s390-bpf-Do-not-increment-tailcall-count-when-prog-i.patch @@ -0,0 +1,66 @@ +From 2a1240d57fe7518f118d8ccb70c08908657bb8ae Mon Sep 17 00:00:00 2001 +From: Ilya Leoshkevich +Date: Tue, 17 Feb 2026 17:10:06 +0100 +Subject: [PATCH] s390/bpf: Do not increment tailcall count when prog is NULL + +Currently tail calling a non-existent prog results in tailcall count +increment. This is what the interpreter is doing, but this is clearly +wrong, so replace load-and-increment and compare-and-jump with load +and compare-and-jump, conditionally followed by increment and store. + +Reported-by: Hari Bathini +Signed-off-by: Ilya Leoshkevich +--- + arch/s390/net/bpf_jit_comp.c | 23 +++++++++++++++-------- + 1 file changed, 15 insertions(+), 8 deletions(-) + +diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c +index bf92964246eb..211226748662 100644 +--- a/arch/s390/net/bpf_jit_comp.c ++++ b/arch/s390/net/bpf_jit_comp.c +@@ -1862,20 +1862,21 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp, + jit->prg); + + /* +- * if (tail_call_cnt++ >= MAX_TAIL_CALL_CNT) ++ * if (tail_call_cnt >= MAX_TAIL_CALL_CNT) + * goto out; ++ * ++ * tail_call_cnt is read into %w0, which needs to be preserved ++ * until it's incremented and flushed. + */ + + off = jit->frame_off + + offsetof(struct prog_frame, tail_call_cnt); +- /* lhi %w0,1 */ +- EMIT4_IMM(0xa7080000, REG_W0, 1); +- /* laal %w1,%w0,off(%r15) */ +- EMIT6_DISP_LH(0xeb000000, 0x00fa, REG_W1, REG_W0, REG_15, off); +- /* clij %w1,MAX_TAIL_CALL_CNT-1,0x2,out */ ++ /* ly %w0,off(%r15) */ ++ EMIT6_DISP_LH(0xe3000000, 0x0058, REG_W0, REG_0, REG_15, off); ++ /* clij %w0,MAX_TAIL_CALL_CNT,0xa,out */ + patch_2_clij = jit->prg; +- EMIT6_PCREL_RIEC(0xec000000, 0x007f, REG_W1, MAX_TAIL_CALL_CNT - 1, +- 2, jit->prg); ++ EMIT6_PCREL_RIEC(0xec000000, 0x007f, REG_W0, MAX_TAIL_CALL_CNT, ++ 0xa, jit->prg); + + /* + * prog = array->ptrs[index]; +@@ -1894,6 +1895,12 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp, + patch_3_brc = jit->prg; + EMIT4_PCREL_RIC(0xa7040000, 8, jit->prg); + ++ /* tail_call_cnt++; */ ++ /* ahi %w0,1 */ ++ EMIT4_IMM(0xa70a0000, REG_W0, 1); ++ /* sty %w0,off(%r15) */ ++ EMIT6_DISP_LH(0xe3000000, 0x0050, REG_W0, REG_0, REG_15, off); ++ + /* + * Restore registers before calling function + */ +-- +2.53.0 + diff --git a/ci/diffs/20260415-selftests-bpf-Fix-timer_start_deadlock-failure-due-t.patch b/ci/diffs/20260415-selftests-bpf-Fix-timer_start_deadlock-failure-due-t.patch new file mode 100644 index 0000000000000..02d9b4f1e1ac7 --- /dev/null +++ b/ci/diffs/20260415-selftests-bpf-Fix-timer_start_deadlock-failure-due-t.patch @@ -0,0 +1,57 @@ +From 2259e42ebbeb7ba5d7e25d17c12c33a951b858c4 Mon Sep 17 00:00:00 2001 +From: Shung-Hsi Yu +Date: Wed, 15 Apr 2026 20:03:28 +0800 +Subject: [PATCH] selftests/bpf: Fix timer_start_deadlock failure due to + hrtimer change + +Since commit f2e388a019e4 ("hrtimer: Reduce trace noise in hrtimer_start()"), +hrtimer_cancel tracepoint is no longer called when a hrtimer is re-armed. So +instead of a hrtimer_cancel followed by hrtimer_start tracepoint events, there +is now only a since hrtimer_start tracepoint event with the new was_armed field +set to 1, to indicated that the hrtimer was previously armed. + +Update timer_start_deadlock accordingly so it traces hrtimer_start tracepoint +instead, with was_armed used as guard. + +Signed-off-by: Shung-Hsi Yu +Tested-by: Mykyta Yatsenko +Acked-by: Mykyta Yatsenko +Link: https://lore.kernel.org/r/20260415120329.129192-1-shung-hsi.yu@suse.com +Signed-off-by: Alexei Starovoitov +--- + tools/testing/selftests/bpf/progs/timer_start_deadlock.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/tools/testing/selftests/bpf/progs/timer_start_deadlock.c b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c +index 019518ee18cd..afabd15bdac4 100644 +--- a/tools/testing/selftests/bpf/progs/timer_start_deadlock.c ++++ b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c +@@ -27,13 +27,13 @@ static int timer_cb(void *map, int *key, struct elem *value) + return 0; + } + +-SEC("tp_btf/hrtimer_cancel") +-int BPF_PROG(tp_hrtimer_cancel, struct hrtimer *hrtimer) ++SEC("tp_btf/hrtimer_start") ++int BPF_PROG(tp_hrtimer_start, struct hrtimer *hrtimer, enum hrtimer_mode mode, bool was_armed) + { + struct bpf_timer *timer; + int key = 0; + +- if (!in_timer_start) ++ if (!in_timer_start || !was_armed) + return 0; + + tp_called = 1; +@@ -60,7 +60,7 @@ int start_timer(void *ctx) + + /* + * call hrtimer_start() twice, so that 2nd call does +- * remove_hrtimer() and trace_hrtimer_cancel() tracepoint. ++ * trace_hrtimer_start(was_armed=1) tracepoint. + */ + in_timer_start = 1; + bpf_timer_start(timer, 1000000000, 0); +-- +2.53.0 + diff --git a/ci/diffs/20260502-tools-headers-Regenerate-stddef.h-to-fix-BPF-selftes.patch b/ci/diffs/20260502-tools-headers-Regenerate-stddef.h-to-fix-BPF-selftes.patch new file mode 100644 index 0000000000000..f5ec7d3162685 --- /dev/null +++ b/ci/diffs/20260502-tools-headers-Regenerate-stddef.h-to-fix-BPF-selftes.patch @@ -0,0 +1,81 @@ +From 7b9f2a8d1761159b2ed87e2d0a162660555727a7 Mon Sep 17 00:00:00 2001 +From: Paul Chaignon +Date: Sat, 2 May 2026 12:12:40 +0200 +Subject: [PATCH] tools/headers: Regenerate stddef.h to fix BPF selftests +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With commit dacbfc167808 ("crypto: af_alg - Annotate struct af_alg_iv +with __counted_by"), two selftests, test_tag and crypto_sanity, now +indirectly rely on the __counted_by macro. On systems with commit +dacbfc167808 in the installed UAPI headers, the selftests build fails +with: + + In file included from tools/testing/selftests/bpf/prog_tests/crypto_sanity.c:7: + /usr/include/linux/if_alg.h:45:22: error: expected ‘:’, ‘,’, ‘;’, ‘}’ or ‘__attribute__’ before ‘__counted_by’ + 45 | __u8 iv[] __counted_by(ivlen); + | ^~~~~~~~~~~~ + +This patch fixes it by regenerating stddef.h in tools/include using the +instructions from commit a778f5d46b62 ("tools/headers: Pull in stddef.h +to uapi to fix BPF selftests build in CI"). + +Fixes: dacbfc167808 ("crypto: af_alg - Annotate struct af_alg_iv with __counted_by") +Signed-off-by: Paul Chaignon +Reviewed-by: Alan Maguire +--- + tools/include/uapi/linux/stddef.h | 26 +++++++++++++++++++++++++- + 1 file changed, 25 insertions(+), 1 deletion(-) + +diff --git a/tools/include/uapi/linux/stddef.h b/tools/include/uapi/linux/stddef.h +index c53cde425406..457498259494 100644 +--- a/tools/include/uapi/linux/stddef.h ++++ b/tools/include/uapi/linux/stddef.h +@@ -3,7 +3,6 @@ + #define _LINUX_STDDEF_H + + +- + #ifndef __always_inline + #define __always_inline __inline__ + #endif +@@ -36,6 +35,11 @@ + struct __struct_group_tag(TAG) { MEMBERS } ATTRS NAME; \ + } ATTRS + ++#ifdef __cplusplus ++/* sizeof(struct{}) is 1 in C++, not 0, can't use C version of the macro. */ ++#define __DECLARE_FLEX_ARRAY(T, member) \ ++ T member[0] ++#else + /** + * __DECLARE_FLEX_ARRAY() - Declare a flexible array usable in a union + * +@@ -52,3 +56,23 @@ + TYPE NAME[]; \ + } + #endif ++ ++#ifndef __counted_by ++#define __counted_by(m) ++#endif ++ ++#ifndef __counted_by_le ++#define __counted_by_le(m) ++#endif ++ ++#ifndef __counted_by_be ++#define __counted_by_be(m) ++#endif ++ ++#ifndef __counted_by_ptr ++#define __counted_by_ptr(m) ++#endif ++ ++#define __kernel_nonstring ++ ++#endif /* _LINUX_STDDEF_H */ +-- +2.54.0 + diff --git a/ci/diffs/20260603-selftests-bpf-Fix-flaky-file_reader-test.patch b/ci/diffs/20260603-selftests-bpf-Fix-flaky-file_reader-test.patch new file mode 100644 index 0000000000000..0c71e00e46e71 --- /dev/null +++ b/ci/diffs/20260603-selftests-bpf-Fix-flaky-file_reader-test.patch @@ -0,0 +1,36 @@ +From aa22d619ba22177f430693cf5e9495052d996644 Mon Sep 17 00:00:00 2001 +From: Mykyta Yatsenko +Date: Wed, 3 Jun 2026 07:39:15 -0700 +Subject: [PATCH] selftests/bpf: Fix flaky file_reader test + +file_reader/on_open_expect_fault test expects page fault +when reading pages from the test harness executable. +It is not guaranteed that those are paged out, even +after madvise(MADV_PAGEOUT). +Relax the condition in the test to succeed with both +0 and -EFAULT returned. + +Fixes: 784cdf931543 ("selftests/bpf: add file dynptr tests") +Reported-by: Shung-Hsi Yu +Closes: https://lore.kernel.org/all/ah6g7JSYOWGp2oAG@u94a/ +Signed-off-by: Mykyta Yatsenko +Tested-by: Ihor Solodrai +Link: https://lore.kernel.org/r/20260603-file_reader_flake-v1-1-7f3f52d1e388@meta.com +Signed-off-by: Alexei Starovoitov +--- + tools/testing/selftests/bpf/progs/file_reader.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/testing/selftests/bpf/progs/file_reader.c b/tools/testing/selftests/bpf/progs/file_reader.c +index 462712ff3b8a0b..aa2c05cce2b302 100644 +--- a/tools/testing/selftests/bpf/progs/file_reader.c ++++ b/tools/testing/selftests/bpf/progs/file_reader.c +@@ -50,7 +50,7 @@ int on_open_expect_fault(void *c) + goto out; + + local_err = bpf_dynptr_read(tmp_buf, user_buf_sz, &dynptr, user_buf_sz, 0); +- if (local_err == -EFAULT) { /* Expect page fault */ ++ if (local_err == -EFAULT || local_err == 0) { /* Expect page fault or success */ + local_err = 0; + run_success = 1; + } diff --git a/ci/diffs/20260615-selftests-bpf-Use-both-hrtimer-enqueue-helpers-in-vm.patch b/ci/diffs/20260615-selftests-bpf-Use-both-hrtimer-enqueue-helpers-in-vm.patch new file mode 100644 index 0000000000000..fd8571dab4861 --- /dev/null +++ b/ci/diffs/20260615-selftests-bpf-Use-both-hrtimer-enqueue-helpers-in-vm.patch @@ -0,0 +1,114 @@ +From 25bb05dd06ccffd209c26465f84851f1fd344c8c Mon Sep 17 00:00:00 2001 +From: Ihor Solodrai +Date: Fri, 8 May 2026 17:57:30 -0700 +Subject: [PATCH] selftests/bpf: Use both hrtimer enqueue helpers in vmlinux test + +CI backport of bpf-next commit 25bb05dd06cc to fix test_vmlinux on the bpf +tree. Drop once it propagates from bpf-next. + +Signed-off-by: Ihor Solodrai +--- + .../selftests/bpf/prog_tests/vmlinux.c | 45 ++++++++++++++++++- + .../selftests/bpf/progs/test_vmlinux.c | 4 +- + 2 files changed, 45 insertions(+), 4 deletions(-) + +diff --git a/tools/testing/selftests/bpf/prog_tests/vmlinux.c b/tools/testing/selftests/bpf/prog_tests/vmlinux.c +index 6fb2217d940b..b5fdd593910d 100644 +--- a/tools/testing/selftests/bpf/prog_tests/vmlinux.c ++++ b/tools/testing/selftests/bpf/prog_tests/vmlinux.c +@@ -14,21 +14,61 @@ static void nsleep() + (void)syscall(__NR_nanosleep, &ts, NULL); + } + ++static const char *hrtimer_func = "hrtimer_start_range_ns"; ++ ++static int setup_hrtimer_progs(struct test_vmlinux *skel) ++{ ++ int err; ++ ++ if (libbpf_find_vmlinux_btf_id("hrtimer_start_range_ns_user", BPF_TRACE_FENTRY) > 0) ++ hrtimer_func = "hrtimer_start_range_ns_user"; ++ ++ err = bpf_program__set_attach_target(skel->progs.handle__fentry, 0, hrtimer_func); ++ if (err) ++ return err; ++ ++ /* ++ * Bare SEC("kprobe") has no target function, so attach it manually ++ * later after selecting the hrtimer function to probe. ++ */ ++ bpf_program__set_autoattach(skel->progs.handle__kprobe, false); ++ ++ return 0; ++} ++ + void test_vmlinux(void) + { + int err; + struct test_vmlinux* skel; + struct test_vmlinux__bss *bss; ++ struct bpf_link *kprobe_link = NULL; + +- skel = test_vmlinux__open_and_load(); +- if (!ASSERT_OK_PTR(skel, "test_vmlinux__open_and_load")) ++ skel = test_vmlinux__open(); ++ if (!ASSERT_OK_PTR(skel, "test_vmlinux__open")) + return; ++ ++ err = setup_hrtimer_progs(skel); ++ if (!ASSERT_OK(err, "setup_hrtimer_progs")) ++ goto cleanup; ++ ++ err = test_vmlinux__load(skel); ++ if (!ASSERT_OK(err, "test_vmlinux__load")) ++ goto cleanup; ++ + bss = skel->bss; + + err = test_vmlinux__attach(skel); + if (!ASSERT_OK(err, "test_vmlinux__attach")) + goto cleanup; + ++ /* manually attach kprobe with the selected function */ ++ if (hrtimer_func) { ++ kprobe_link = bpf_program__attach_kprobe(skel->progs.handle__kprobe, ++ false /* retprobe */, hrtimer_func); ++ if (!ASSERT_OK_PTR(kprobe_link, "bpf_program__attach_kprobe")) ++ goto cleanup; ++ } ++ + /* trigger everything */ + nsleep(); + +@@ -39,5 +79,6 @@ void test_vmlinux(void) + ASSERT_TRUE(bss->fentry_called, "fentry"); + + cleanup: ++ bpf_link__destroy(kprobe_link); + test_vmlinux__destroy(skel); + } +diff --git a/tools/testing/selftests/bpf/progs/test_vmlinux.c b/tools/testing/selftests/bpf/progs/test_vmlinux.c +index 78b23934d9f8..eea556940df6 100644 +--- a/tools/testing/selftests/bpf/progs/test_vmlinux.c ++++ b/tools/testing/selftests/bpf/progs/test_vmlinux.c +@@ -69,7 +69,7 @@ int BPF_PROG(handle__tp_btf, struct pt_regs *regs, long id) + return 0; + } + +-SEC("kprobe/hrtimer_start_range_ns") ++SEC("kprobe") + int BPF_KPROBE(handle__kprobe, struct hrtimer *timer, ktime_t tim, u64 delta_ns, + const enum hrtimer_mode mode) + { +@@ -78,7 +78,7 @@ int BPF_KPROBE(handle__kprobe, struct hrtimer *timer, ktime_t tim, u64 delta_ns, + return 0; + } + +-SEC("fentry/hrtimer_start_range_ns") ++SEC("fentry") + int BPF_PROG(handle__fentry, struct hrtimer *timer, ktime_t tim, u64 delta_ns, + const enum hrtimer_mode mode) + { +-- +2.54.0 + diff --git a/ci/diffs/20260703-selftests-bpf-Fix-test_maps-sockmap-failure.patch b/ci/diffs/20260703-selftests-bpf-Fix-test_maps-sockmap-failure.patch new file mode 100644 index 0000000000000..d0276f053f583 --- /dev/null +++ b/ci/diffs/20260703-selftests-bpf-Fix-test_maps-sockmap-failure.patch @@ -0,0 +1,63 @@ +From 27a0f3635d862919dd7e5e93e19f3f5d1b240e57 Mon Sep 17 00:00:00 2001 +From: Jiayuan Chen +Date: Wed, 1 Jul 2026 15:14:22 +0800 +Subject: [PATCH] selftests/bpf: Fix test_maps sockmap failure + +test_maps fails in the sockmap test because sockmap_verdict_prog.c +drops the packet when the first 8 bytes are not directly accessible: + if (data + 8 > data_end) + return SK_DROP; + +The blamed commit removed bpf_skb_pull_data() from the stream parser +program so that the parser no longer modifies the skb. That was needed, +but it also removed an implicit side effect: bpf_skb_pull_data() +linearized enough of the skb for later direct packet access. + +In this test, the send side goes through the sockmap SK_MSG path. The +skb can have skb->len == 20 while its linear area is empty, so the +verdict program sees data == data_end and drops the packet even though +the payload length is sufficient. + +Keep the parser read-only, and pull the first 8 bytes in the verdict +program before reading or writing them. Reload data/data_end after +bpf_skb_pull_data() as required. + +Fixes: 22a0cc10dacb ("selftests/bpf: don't modify the skb in the strparser parser prog") +Reported-by: Ihor Solodrai +Signed-off-by: Jiayuan Chen +Signed-off-by: Andrii Nakryiko +Link: https://lore.kernel.org/bpf/20260701071501.39628-1-jiayuan.chen@linux.dev + +Closes: https://lore.kernel.org/bpf/e3a91acd-2b4d-4e93-a3bb-a0e9ee5ede0f@linux.dev/ +--- + .../selftests/bpf/progs/sockmap_verdict_prog.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c b/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c +index 0660f29dca95..3177bc5b733a 100644 +--- a/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c ++++ b/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c +@@ -44,8 +44,18 @@ int bpf_prog2(struct __sk_buff *skb) + __sink(lport); + __sink(rport); + +- if (data + 8 > data_end) +- return SK_DROP; ++ if (data + 8 > data_end) { ++ if (bpf_skb_pull_data(skb, 8)) ++ return SK_DROP; ++ ++ data = (void *)(long)skb->data; ++ data_end = (void *)(long)skb->data_end; ++ ++ if (data + 8 > data_end) ++ return SK_DROP; ++ ++ d = data; ++ } + + map = d[0]; + sk = d[1]; +-- +2.54.0 + diff --git a/ci/diffs/20260814-bpf-Remove-artificial-limitations-on-pointer-types-e.patch b/ci/diffs/20260814-bpf-Remove-artificial-limitations-on-pointer-types-e.patch new file mode 100644 index 0000000000000..9b480f2f429f7 --- /dev/null +++ b/ci/diffs/20260814-bpf-Remove-artificial-limitations-on-pointer-types-e.patch @@ -0,0 +1,110 @@ +From 319140dcc6a81da798b533351fdfa04e079177d6 Mon Sep 17 00:00:00 2001 +From: Eduard Zingerman +Date: Tue, 7 Jul 2026 17:44:28 -0700 +Subject: [PATCH 1/2] bpf: Remove artificial limitations on pointer types + eligible for spilling + +The verifier loses precision when simulating stack spills for the +following register types: +- PTR_TO_TP_BUFFER +- PTR_TO_INSN +- CONST_PTR_TO_DYNPTR + +These types are not allow-listed in the is_spillable_regtype(), +because of that check_stack_write_fixed_off() takes the branch +that marks the slots STACK_MISC. + +There are no technical reasons for this limitation. +This commit replaces an explicit list of pointer types in +is_spillable_regtype() with explicit list of non-pointer types. +The function is renamed to is_pointer_regtype() for clarity. + +Reported-by: Andrii Nakryiko +Suggested-by: Kumar Kartikeya Dwivedi +Signed-off-by: Eduard Zingerman +Link: https://lore.kernel.org/bpf/20260707-missing-spillable-types-v1-1-44a92121dc41@gmail.com +Signed-off-by: Kumar Kartikeya Dwivedi +--- + kernel/bpf/verifier.c | 39 ++++++++------------------------------- + 1 file changed, 8 insertions(+), 31 deletions(-) + +diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c +index fdc5fbb1f78c..4cd5aab30ddd 100644 +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -3305,34 +3305,6 @@ static int mark_chain_precision_batch(struct bpf_verifier_env *env, + return bpf_mark_chain_precision(env, starting_state, -1, NULL); + } + +-static bool is_spillable_regtype(enum bpf_reg_type type) +-{ +- switch (base_type(type)) { +- case PTR_TO_MAP_VALUE: +- case PTR_TO_STACK: +- case PTR_TO_CTX: +- case PTR_TO_PACKET: +- case PTR_TO_PACKET_META: +- case PTR_TO_PACKET_END: +- case PTR_TO_FLOW_KEYS: +- case CONST_PTR_TO_MAP: +- case PTR_TO_SOCKET: +- case PTR_TO_SOCK_COMMON: +- case PTR_TO_TCP_SOCK: +- case PTR_TO_XDP_SOCK: +- case PTR_TO_BTF_ID: +- case PTR_TO_BUF: +- case PTR_TO_MEM: +- case PTR_TO_FUNC: +- case PTR_TO_MAP_KEY: +- case PTR_TO_ARENA: +- return true; +- default: +- return false; +- } +-} +- +- + /* check if register is a constant scalar value */ + static bool is_reg_const(struct bpf_reg_state *reg, bool subreg32) + { +@@ -3346,13 +3318,18 @@ static u64 reg_const_value(struct bpf_reg_state *reg, bool subreg32) + return subreg32 ? tnum_subreg(reg->var_off).value : reg->var_off.value; + } + ++static bool is_pointer_regtype(enum bpf_reg_type type) ++{ ++ return type != SCALAR_VALUE && type != NOT_INIT; ++} ++ + static bool __is_pointer_value(bool allow_ptr_leaks, + const struct bpf_reg_state *reg) + { + if (allow_ptr_leaks) + return false; + +- return reg->type != SCALAR_VALUE; ++ return is_pointer_regtype(reg->type); + } + + static void clear_scalar_id(struct bpf_reg_state *reg) +@@ -3477,7 +3454,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, + if (value_regno >= 0) + reg = &cur->regs[value_regno]; + if (!env->bypass_spec_v4) { +- bool sanitize = reg && is_spillable_regtype(reg->type); ++ bool sanitize = reg && is_pointer_regtype(reg->type); + + for (i = 0; i < size; i++) { + u8 type = state->stack[spi].slot_type[(slot - i) % +@@ -3518,7 +3495,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, + __mark_reg_known(tmp_reg, insn->imm); + tmp_reg->type = SCALAR_VALUE; + save_register_state(env, state, spi, tmp_reg, size); +- } else if (reg && is_spillable_regtype(reg->type)) { ++ } else if (reg && is_pointer_regtype(reg->type)) { + /* register containing pointer is being spilled into stack */ + if (size != BPF_REG_SIZE) { + verbose_linfo(env, insn_idx, "; "); +-- +2.55.0 + diff --git a/ci/diffs/20260814-bpf-sockmap-Disallow-update-and-delete-from-tc-xdp-s.patch b/ci/diffs/20260814-bpf-sockmap-Disallow-update-and-delete-from-tc-xdp-s.patch new file mode 100644 index 0000000000000..d638c342b0078 --- /dev/null +++ b/ci/diffs/20260814-bpf-sockmap-Disallow-update-and-delete-from-tc-xdp-s.patch @@ -0,0 +1,92 @@ +From be39165224d03d92a05f62b9ea10eec089365480 Mon Sep 17 00:00:00 2001 +From: Sechang Lim +Date: Tue, 30 Jun 2026 14:54:05 +0000 +Subject: [PATCH] bpf, sockmap: Disallow update and delete from tc, xdp, + socket_filter and flow_dissector + +sock_map_update_common() and __sock_map_delete() hold stab->lock and call +sock_map_unref() -> sock_map_del_link(), which takes sk_callback_lock for +write. That gives the order stab->lock -> sk_callback_lock. + +The reverse order comes from the SK_SKB stream parser. +sk_psock_strp_data_ready() holds sk_callback_lock for read, and after the +verdict tcp_bpf_strp_read_sock() acks the consumed data inline via +__tcp_cleanup_rbuf(). The ACK goes out egress, where a sched_cls program +deletes from the sockmap and takes stab->lock: + + WARNING: possible circular locking dependency detected + ------------------------------------------------------ + syz.9.8824 is trying to acquire lock: + (&stab->lock){+.-.}-{3:3}, at: __sock_map_delete net/core/sock_map.c:421 + but task is already holding lock: + (clock-AF_INET){++.-}-{3:3}, at: sk_psock_strp_data_ready net/core/skmsg.c:1173 + + -> #1 (clock-AF_INET){++.-}-{3:3}: + _raw_write_lock_bh + sock_map_del_link net/core/sock_map.c:167 + sock_map_unref net/core/sock_map.c:184 + sock_map_update_common net/core/sock_map.c:509 + sock_map_update_elem_sys net/core/sock_map.c:588 + map_update_elem kernel/bpf/syscall.c:1805 + + -> #0 (&stab->lock){+.-.}-{3:3}: + _raw_spin_lock_bh + __sock_map_delete net/core/sock_map.c:421 + sock_map_delete_elem net/core/sock_map.c:452 + bpf_prog_06044d24140080b6 + tcx_run net/core/dev.c:4451 + sch_handle_egress net/core/dev.c:4541 + __dev_queue_xmit net/core/dev.c:4808 + ... + tcp_bpf_strp_read_sock net/ipv4/tcp_bpf.c:701 + strp_data_ready net/strparser/strparser.c:402 + sk_psock_strp_data_ready net/core/skmsg.c:1174 + tcp_data_queue net/ipv4/tcp_input.c:5661 + + Possible unsafe locking scenario: + + CPU0 CPU1 + ---- ---- + rlock(clock-AF_INET); + lock(&stab->lock); + lock(clock-AF_INET); + lock(&stab->lock); + + *** DEADLOCK *** + +A tc, xdp, socket_filter or flow_dissector program has no reason to +update or delete a sockmap, and redirect does not go through here. Drop +them from may_update_sockmap() so the verifier rejects it. It also +closes the matching sockhash inversion. + +Suggested-by: John Fastabend +Signed-off-by: Sechang Lim +Signed-off-by: Daniel Borkmann +Reviewed-by: John Fastabend +Reviewed-by: Emil Tsalapatis +Link: https://lore.kernel.org/bpf/20260630145410.3648099-2-rhkrqnwk98@gmail.com +Signed-off-by: Kumar Kartikeya Dwivedi +--- + kernel/bpf/verifier.c | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c +index a0f292635c59..3193b473762b 100644 +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -8496,12 +8496,7 @@ static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id) + if (func_id == BPF_FUNC_map_delete_elem) + return true; + break; +- case BPF_PROG_TYPE_SOCKET_FILTER: +- case BPF_PROG_TYPE_SCHED_CLS: +- case BPF_PROG_TYPE_SCHED_ACT: +- case BPF_PROG_TYPE_XDP: + case BPF_PROG_TYPE_SK_REUSEPORT: +- case BPF_PROG_TYPE_FLOW_DISSECTOR: + case BPF_PROG_TYPE_SK_LOOKUP: + return true; + default: +-- +2.55.0 + diff --git a/ci/diffs/20260814-selftests-bpf-Drop-tc-xdp-flow_dissector-socket_filt.patch b/ci/diffs/20260814-selftests-bpf-Drop-tc-xdp-flow_dissector-socket_filt.patch new file mode 100644 index 0000000000000..d21c3ed65b2f3 --- /dev/null +++ b/ci/diffs/20260814-selftests-bpf-Drop-tc-xdp-flow_dissector-socket_filt.patch @@ -0,0 +1,299 @@ +From e02818a9fbb18549bda3c73cb62f1ac528f33473 Mon Sep 17 00:00:00 2001 +From: Sechang Lim +Date: Tue, 30 Jun 2026 14:54:06 +0000 +Subject: [PATCH 2/2] selftests/bpf: Drop tc/xdp/flow_dissector/socket_filter + sockmap mutation tests + +tc, xdp, socket_filter and flow_dissector programs can no longer update +or delete a sockmap. Adjust the tests: + + - verifier_sockmap_mutate: the tc, xdp, socket_filter and + flow_dissector cases now expect __failure with "cannot update sockmap + in this context". + - sockmap_basic: drop "sockmap update" / "sockhash update", which load + a SEC("tc") program that copies a sock between maps. + - fexit_bpf2bpf: drop "func_sockmap_update", whose freplace program + updates a sockmap in the tc cls_redirect context. + +Remove the now-unused test_sockmap_update.c and freplace_cls_redirect.c. + +Signed-off-by: Sechang Lim +Signed-off-by: Daniel Borkmann +Reviewed-by: John Fastabend +Reviewed-by: Emil Tsalapatis +Link: https://lore.kernel.org/bpf/20260630145410.3648099-3-rhkrqnwk98@gmail.com +Signed-off-by: Kumar Kartikeya Dwivedi +--- + .../selftests/bpf/prog_tests/fexit_bpf2bpf.c | 14 ----- + .../selftests/bpf/prog_tests/sockmap_basic.c | 52 ------------------- + .../bpf/progs/freplace_cls_redirect.c | 34 ------------ + .../selftests/bpf/progs/test_sockmap_update.c | 48 ----------------- + .../bpf/progs/verifier_sockmap_mutate.c | 12 ++--- + 5 files changed, 6 insertions(+), 154 deletions(-) + delete mode 100644 tools/testing/selftests/bpf/progs/freplace_cls_redirect.c + delete mode 100644 tools/testing/selftests/bpf/progs/test_sockmap_update.c + +diff --git a/tools/testing/selftests/bpf/prog_tests/fexit_bpf2bpf.c b/tools/testing/selftests/bpf/prog_tests/fexit_bpf2bpf.c +index 92c20803ea76..4a87d7163c8c 100644 +--- a/tools/testing/selftests/bpf/prog_tests/fexit_bpf2bpf.c ++++ b/tools/testing/selftests/bpf/prog_tests/fexit_bpf2bpf.c +@@ -335,18 +335,6 @@ static void test_fmod_ret_freplace(void) + bpf_object__close(pkt_obj); + } + +- +-static void test_func_sockmap_update(void) +-{ +- const char *prog_name[] = { +- "freplace/cls_redirect", +- }; +- test_fexit_bpf2bpf_common("./freplace_cls_redirect.bpf.o", +- "./test_cls_redirect.bpf.o", +- ARRAY_SIZE(prog_name), +- prog_name, false, NULL); +-} +- + static void test_func_replace_void(void) + { + const char *prog_name[] = { +@@ -599,8 +587,6 @@ void serial_test_fexit_bpf2bpf(void) + test_func_replace(); + if (test__start_subtest("func_replace_verify")) + test_func_replace_verify(); +- if (test__start_subtest("func_sockmap_update")) +- test_func_sockmap_update(); + if (test__start_subtest("func_replace_return_code")) + test_func_replace_return_code(); + if (test__start_subtest("func_map_prog_compatibility")) +diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +index e5fc038d747b..1fef6ec2ba7a 100644 +--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c ++++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +@@ -7,7 +7,6 @@ + + #include "test_progs.h" + #include "test_skmsg_load_helpers.skel.h" +-#include "test_sockmap_update.skel.h" + #include "test_sockmap_invalid_update.skel.h" + #include "test_sockmap_skb_verdict_attach.skel.h" + #include "test_sockmap_progs_query.skel.h" +@@ -235,53 +234,6 @@ static void test_skmsg_helpers_with_link(enum bpf_map_type map_type) + test_skmsg_load_helpers__destroy(skel); + } + +-static void test_sockmap_update(enum bpf_map_type map_type) +-{ +- int err, prog, src; +- struct test_sockmap_update *skel; +- struct bpf_map *dst_map; +- const __u32 zero = 0; +- char dummy[14] = {0}; +- LIBBPF_OPTS(bpf_test_run_opts, topts, +- .data_in = dummy, +- .data_size_in = sizeof(dummy), +- .repeat = 1, +- ); +- __s64 sk; +- +- sk = connected_socket_v4(); +- if (!ASSERT_NEQ(sk, -1, "connected_socket_v4")) +- return; +- +- skel = test_sockmap_update__open_and_load(); +- if (!ASSERT_OK_PTR(skel, "open_and_load")) +- goto close_sk; +- +- prog = bpf_program__fd(skel->progs.copy_sock_map); +- src = bpf_map__fd(skel->maps.src); +- if (map_type == BPF_MAP_TYPE_SOCKMAP) +- dst_map = skel->maps.dst_sock_map; +- else +- dst_map = skel->maps.dst_sock_hash; +- +- err = bpf_map_update_elem(src, &zero, &sk, BPF_NOEXIST); +- if (!ASSERT_OK(err, "update_elem(src)")) +- goto out; +- +- err = bpf_prog_test_run_opts(prog, &topts); +- if (!ASSERT_OK(err, "test_run")) +- goto out; +- if (!ASSERT_NEQ(topts.retval, 0, "test_run retval")) +- goto out; +- +- compare_cookies(skel->maps.src, dst_map); +- +-out: +- test_sockmap_update__destroy(skel); +-close_sk: +- close(sk); +-} +- + static void test_sockmap_invalid_update(void) + { + struct test_sockmap_invalid_update *skel; +@@ -1422,10 +1374,6 @@ void test_sockmap_basic(void) + test_skmsg_helpers(BPF_MAP_TYPE_SOCKMAP); + if (test__start_subtest("sockhash sk_msg load helpers")) + test_skmsg_helpers(BPF_MAP_TYPE_SOCKHASH); +- if (test__start_subtest("sockmap update")) +- test_sockmap_update(BPF_MAP_TYPE_SOCKMAP); +- if (test__start_subtest("sockhash update")) +- test_sockmap_update(BPF_MAP_TYPE_SOCKHASH); + if (test__start_subtest("sockmap update in unsafe context")) + test_sockmap_invalid_update(); + if (test__start_subtest("sockmap copy")) +diff --git a/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c b/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c +deleted file mode 100644 +index 7e94412d47a5..000000000000 +--- a/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c ++++ /dev/null +@@ -1,34 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2020 Facebook +- +-#include +-#include +-#include +-#include +-#include +- +-struct { +- __uint(type, BPF_MAP_TYPE_SOCKMAP); +- __type(key, int); +- __type(value, int); +- __uint(max_entries, 2); +-} sock_map SEC(".maps"); +- +-SEC("freplace/cls_redirect") +-int freplace_cls_redirect_test(struct __sk_buff *skb) +-{ +- int ret = 0; +- const int zero = 0; +- struct bpf_sock *sk; +- +- sk = bpf_map_lookup_elem(&sock_map, &zero); +- if (!sk) +- return TC_ACT_SHOT; +- +- ret = bpf_map_update_elem(&sock_map, &zero, sk, 0); +- bpf_sk_release(sk); +- +- return ret == 0 ? TC_ACT_OK : TC_ACT_SHOT; +-} +- +-char _license[] SEC("license") = "GPL"; +diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_update.c b/tools/testing/selftests/bpf/progs/test_sockmap_update.c +deleted file mode 100644 +index 6d64ea536e3d..000000000000 +--- a/tools/testing/selftests/bpf/progs/test_sockmap_update.c ++++ /dev/null +@@ -1,48 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2020 Cloudflare +-#include "vmlinux.h" +-#include +- +-struct { +- __uint(type, BPF_MAP_TYPE_SOCKMAP); +- __uint(max_entries, 1); +- __type(key, __u32); +- __type(value, __u64); +-} src SEC(".maps"); +- +-struct { +- __uint(type, BPF_MAP_TYPE_SOCKMAP); +- __uint(max_entries, 1); +- __type(key, __u32); +- __type(value, __u64); +-} dst_sock_map SEC(".maps"); +- +-struct { +- __uint(type, BPF_MAP_TYPE_SOCKHASH); +- __uint(max_entries, 1); +- __type(key, __u32); +- __type(value, __u64); +-} dst_sock_hash SEC(".maps"); +- +-SEC("tc") +-int copy_sock_map(void *ctx) +-{ +- struct bpf_sock *sk; +- bool failed = false; +- __u32 key = 0; +- +- sk = bpf_map_lookup_elem(&src, &key); +- if (!sk) +- return SK_DROP; +- +- if (bpf_map_update_elem(&dst_sock_map, &key, sk, 0)) +- failed = true; +- +- if (bpf_map_update_elem(&dst_sock_hash, &key, sk, 0)) +- failed = true; +- +- bpf_sk_release(sk); +- return failed ? SK_DROP : SK_PASS; +-} +- +-char _license[] SEC("license") = "GPL"; +diff --git a/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c b/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c +index fe4b123187b8..20332a731d4e 100644 +--- a/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c ++++ b/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c +@@ -74,7 +74,7 @@ static __always_inline void test_sockmap_lookup_and_mutate(void) + } + + SEC("action") +-__success ++__failure __msg("cannot update sockmap in this context") + int test_sched_act(struct __sk_buff *skb) + { + test_sockmap_mutate(skb->sk); +@@ -82,7 +82,7 @@ int test_sched_act(struct __sk_buff *skb) + } + + SEC("classifier") +-__success ++__failure __msg("cannot update sockmap in this context") + int test_sched_cls(struct __sk_buff *skb) + { + test_sockmap_mutate(skb->sk); +@@ -90,7 +90,7 @@ int test_sched_cls(struct __sk_buff *skb) + } + + SEC("flow_dissector") +-__success ++__failure __msg("cannot update sockmap in this context") + int test_flow_dissector_delete(struct __sk_buff *skb __always_unused) + { + test_sockmap_delete(); +@@ -98,7 +98,7 @@ int test_flow_dissector_delete(struct __sk_buff *skb __always_unused) + } + + SEC("flow_dissector") +-__failure __msg("program of this type cannot use helper bpf_sk_release") ++__failure __msg("cannot update sockmap in this context") + int test_flow_dissector_update(struct __sk_buff *skb __always_unused) + { + test_sockmap_lookup_and_update(); /* no access to skb->sk */ +@@ -146,7 +146,7 @@ int test_sk_reuseport(struct sk_reuseport_md *ctx) + } + + SEC("socket") +-__success ++__failure __msg("cannot update sockmap in this context") + int test_socket_filter(struct __sk_buff *skb) + { + test_sockmap_mutate(skb->sk); +@@ -179,7 +179,7 @@ int test_sockops_update_dedicated(struct bpf_sock_ops *ctx) + } + + SEC("xdp") +-__success ++__failure __msg("cannot update sockmap in this context") + int test_xdp(struct xdp_md *ctx __always_unused) + { + test_sockmap_lookup_and_mutate(); +-- +2.55.0 + diff --git a/ci/diffs/20260814-selftests-bpf-Fix-selftest-build-after-filter.h-u.patch b/ci/diffs/20260814-selftests-bpf-Fix-selftest-build-after-filter.h-u.patch new file mode 100644 index 0000000000000..009b8c1945c6a --- /dev/null +++ b/ci/diffs/20260814-selftests-bpf-Fix-selftest-build-after-filter.h-u.patch @@ -0,0 +1,55 @@ +From 0dba4e2db3d95a5fd72e9ac3e6242f74618eed46 Mon Sep 17 00:00:00 2001 +From: Ihor Solodrai +Date: Fri, 14 Aug 2026 10:18:38 -0700 +Subject: [PATCH bpf v1] selftests/bpf: Fix selftest build after filter.h + update + +Upstream commit 7a1f400ff5e5 ("tools: Ensure tools copy of +linux/filter.h exports the UAPI") caused selftests/bpf build to +fail [1] with: + + In file included from progs/arena_atomics.c:9: + /codebuild/output/src2365462129/src/actions-runner/_work/bpf/bpf/tools/testing/selftests/bpf/../../../include/linux/filter.h:9:10: fatal error: 'uapi/linux/filter.h' file not found + 9 | #include + | ^~~~~~~~~~~~~~~~~~~~~ + 1 error generated. + CLNG-BPF [test_progs] bind_perm.bpf.o + make: *** [Makefile:888: /codebuild/output/src2365462129/src/actions-runner/_work/bpf/bpf/tools/testing/selftests/bpf/arena_atomics.bpf.o] Error 1 + make: *** Waiting for unfinished jobs.... + GEN-OBJ [libarena] libarena.bpf.o + GEN-SKEL [libarena] libarena.skel.h + make: Leaving directory '/codebuild/output/src2365462129/src/actions-runner/_work/bpf/bpf/tools/testing/selftests/bpf' + Process completed with exit code 2. + +BPF selftest programs include the tools header directly, but +BPF_CFLAGS only exposes tools/include/uapi. Compiler therefore cannot +resolve the nested UAPI include. + +Add tools/include after tools/include/uapi in BPF_CFLAGS. This +preserves the existing UAPI header precedence while allowing tools +headers to include uapi headers. + +[1] https://github.com/kernel-patches/bpf/actions/runs/31806678733/job/94787271162 + +Fixes: 7a1f400ff5e5 ("tools: Ensure tools copy of linux/filter.h exports the UAPI") +Signed-off-by: Ihor Solodrai +--- + tools/testing/selftests/bpf/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile +index 384c6e8d9274..c0a73473d14e 100644 +--- a/tools/testing/selftests/bpf/Makefile ++++ b/tools/testing/selftests/bpf/Makefile +@@ -463,7 +463,7 @@ endif + CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG),$(CLANG_TARGET_ARCH)) + BPF_CFLAGS = -g -Wall -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN) \ + -I$(INCLUDE_DIR) -I$(CURDIR) -I$(APIDIR) \ +- -I$(CURDIR)/libarena/include \ ++ -I$(TOOLSINCDIR) -I$(CURDIR)/libarena/include \ + -I$(abspath $(OUTPUT)/../usr/include) \ + -std=gnu11 \ + -fno-strict-aliasing \ +-- +2.55.0 + diff --git a/ci/vmtest/configs/DENYLIST b/ci/vmtest/configs/DENYLIST new file mode 100644 index 0000000000000..d3715ff671f68 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST @@ -0,0 +1,10 @@ +verif_scale_pyperf600 +sockmap_basic/sockmap udp multi channels +map_kptr + +# Flaky / deterministically-broken upstream tests — CI noise mitigation (all arches). +# Drop each once the referenced upstream fix reaches the tested base branch. +lru_lock_nmi # rqspinlock pending_free lazy-reclaim race -> drain_then_verify_capacity() -EIO; deterministic aarch64, intermittent x86_64; vmtest#489/#488 +fd_array_cnt/fd-array-ref-btfs # 100ms async BTF-free wait too short under CI load; vmtest#484 +test_task_work/test_task_work_array_map # array-map task_work reschedule race in parallel mode; vmtest#471 +stream_success/stream_arena_callback_fault # async softirq timer callback may not fill stream before read; vmtest#449 diff --git a/ci/vmtest/configs/DENYLIST.aarch64 b/ci/vmtest/configs/DENYLIST.aarch64 new file mode 100644 index 0000000000000..1a499728e04cb --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.aarch64 @@ -0,0 +1,8 @@ +bpftool_maps_access/nested_maps # stale pin EEXIST on retried/interrupted runs; vmtest#486 +map_kptr/success-map +ns_xsk_drv +ns_xsk_skb +send_signal +tc_tunnel/udp_mpls # connect() EINPROGRESS; 1000ms too short under QEMU; vmtest#475 +unpriv_bpf_disabled +wq # usleep(50) too short for wq callback under emulation; vmtest#455 diff --git a/ci/vmtest/configs/DENYLIST.asan b/ci/vmtest/configs/DENYLIST.asan new file mode 100644 index 0000000000000..94a6e41158bdf --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.asan @@ -0,0 +1,6 @@ +# ASAN-only denylist (merged when SELFTESTS_BPF_ASAN is set; see ci/vmtest/configs/run-vmtest.env). +# exit() in fork()'d children runs LeakSanitizer's atexit leak check, which overrides the child +# exit code; the parent then sees a spurious failure even though the BPF operations succeeded. +# Non-ASAN runs keep full coverage of these tests. Drop once the upstream _exit() fix lands. +token # vmtest#473 +test_bpffs # vmtest#473 diff --git a/ci/vmtest/configs/DENYLIST.rc b/ci/vmtest/configs/DENYLIST.rc new file mode 100644 index 0000000000000..8aa33e6b71443 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.rc @@ -0,0 +1,3 @@ +send_signal/send_signal_nmi # PMU events configure correctly but don't trigger NMI's for some reason (AMD nested virt) +send_signal/send_signal_nmi_thread # Same as above +token/obj_priv_implicit_token_envvar # Unknown root cause, but reliably fails diff --git a/ci/vmtest/configs/DENYLIST.s390x b/ci/vmtest/configs/DENYLIST.s390x new file mode 100644 index 0000000000000..441664141a845 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.s390x @@ -0,0 +1,11 @@ +arena_spin_lock +bpftool_maps_access/unprotected_unpinned # btf dump asserts map BTF but btf_id==0 on no_alu32; vmtest#486 +map_kptr/success-map +ns_xsk_drv +ns_xsk_skb +res_spin_lock_stress +sock_iter_batch/udp # SO_REUSEPORT bind(0) port collision -> idx mismatch; vmtest#454 +tc_edt +tc_tunnel/ip6gre* # connect() 1000ms too short for IPv6 neigh resolution under emulation; vmtest#483 +wq # usleep(50) too short under emulation; vmtest#455 +libarena/parallel_test_* # in-BPF spin barriers time out under emulation; same class as arena_spin_lock diff --git a/ci/vmtest/configs/DENYLIST.test_progs-bpf_gcc b/ci/vmtest/configs/DENYLIST.test_progs-bpf_gcc new file mode 100644 index 0000000000000..589502970c670 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.test_progs-bpf_gcc @@ -0,0 +1,216 @@ +arena_htab/arena_htab_asm # 1M runaway since v7.2-rc1 +arena_strsearch +bad_struct_ops/invalid_prog_reuse +bpf_cookie/perf_event # flaky +bpf_ip_check_defrag +bpf_iter/bpf_sockmap_map_iter_fd +bpf_iter/task_pid # The sequence of 8193 jumps is too complex +bpf_iter/task_pidfd # The sequence of 8193 jumps is too complex +bpf_iter/task_sleepable # The sequence of 8193 jumps is too complex +bpf_iter/task_tid # The sequence of 8193 jumps is too complex +bpf_iter/unix # The sequence of 8193 jumps is too complex +bpf_iter_setsockopt +bpf_iter_setsockopt_unix +bpf_mod_race +bpf_sockmap_map_iter_fd +bpf_tcp_ca/dctcp +bpf_tcp_ca/dctcp_autoattach_map +bpf_tcp_ca/dctcp_fallback +bpftool_metadata +btf_dump +btf_map_in_map +cgroup_tcp_skb +cgrp_kfunc/cgrp_kfunc_acquire_trusted_walked +cls_redirect/cls_redirect_dynptr +connect_force_port +core_autosize +core_reloc/arrays___err_bad_signed_arr_elem_sz +core_reloc/type_id +core_reloc/type_id___missing_targets +core_reloc_btfgen/arrays___err_bad_signed_arr_elem_sz +core_reloc_btfgen/type_id +core_reloc_btfgen/type_id___missing_targets +cpumask/test_alloc_double_release +cpumask/test_alloc_free_cpumask +cpumask/test_and_or_xor +cpumask/test_copy_any_anyand +cpumask/test_cpumask_weight +cpumask/test_first_firstzero_cpu +cpumask/test_firstand_nocpu +cpumask/test_global_mask_array_l2_rcu +cpumask/test_global_mask_array_one_rcu +cpumask/test_global_mask_array_rcu +cpumask/test_global_mask_nested_deep_array_rcu +cpumask/test_global_mask_nested_deep_rcu +cpumask/test_global_mask_nested_rcu +cpumask/test_global_mask_rcu +cpumask/test_insert_leave +cpumask/test_insert_remove_release +cpumask/test_intersects_subset +cpumask/test_populate +cpumask/test_populate_reject_small_mask +cpumask/test_populate_reject_unaligned +cpumask/test_refcount_null_tracking +cpumask/test_set_clear_cpu +cpumask/test_setall_clear_cpu +cpumask/test_test_and_set_clear +dmabuf_iter +dynptr/invalid_helper2 +dynptr/test_dynptr_skb_no_buff +dynptr/test_dynptr_skb_tp_btf +dynptr/test_read_write +exceptions/check_assert_ge_neg +exceptions/check_assert_ge_pos +exceptions/check_assert_ge_zero +exceptions/check_assert_generic +exceptions/check_assert_gt_neg +exceptions/check_assert_gt_pos +exceptions/check_assert_gt_zero +exceptions/check_assert_le_neg +exceptions/check_assert_le_pos +exceptions/check_assert_le_zero +exceptions/check_assert_lt_neg +exceptions/check_assert_lt_pos +exceptions/check_assert_lt_zero +exceptions/check_assert_range_s64 +exceptions/check_assert_range_u64 +exceptions/check_assert_single_range_s64 +exceptions/check_assert_single_range_u64 +exceptions/non-throwing extension -> main subprog +exceptions/non-throwing extension -> non-throwing subprog +exceptions/non-throwing extension -> throwing global subprog +exceptions/non-throwing fmod_ret -> non-throwing global subprog +exceptions/reject_subprog_with_lock +exceptions/throwing extension -> main subprog +exceptions/throwing extension -> non-throwing global subprog +exceptions/throwing extension -> throwing global subprog +fd_htab_lookup +fexit_bpf2bpf/fmod_ret_freplace +fexit_bpf2bpf/func_replace +fexit_bpf2bpf/func_replace_global_func +fexit_bpf2bpf/func_replace_multi +fexit_bpf2bpf/target_yes_callees +fs_kfuncs +global_map_resize +global_percpu_data # crashing kernel: bad .percpu address +inner_array_lookup +iters/iter_err_unsafe_asm_loop +kfree_skb +ksock_lsm +l4lb_all/l4lb_noinline +l4lb_all/l4lb_noinline_dynptr +linked_list +linked_list_peek/test_back_spinlock_false +linked_list_peek/test_front_spinlock_false +log_buf/obj_load_log_buf +log_fixup/bad_core_relo_subprog +lru_bug +lwt_seg6local +map_btf/inner_array_btf +map_in_map/acc_map_in_array +map_in_map/acc_map_in_htab +map_in_map/sleepable_acc_map_in_array +map_in_map/sleepable_acc_map_in_htab +map_ptr +map_uninit_mem_exposure +pkt_access +prog_run_opts +rbtree_fail/rbtree_api_nolock_add +rbtree_fail/rbtree_api_nolock_first +rbtree_fail/rbtree_api_nolock_remove +rbtree_search/rbtree_search +rbtree_search/test_left_spinlock_false +rbtree_search/test_right_spinlock_false +rbtree_search/test_root_spinlock_false +rbtree_success +recursion +reference_tracking/sk_lookup_success +res_spin_lock_success +ringbuf_multi +setget_sockopt +sk_lookup +skc_to_unix_sock +sock_addr +sock_fields +sockmap_basic/sockhash copy +sockmap_basic/sockmap copy +sockmap_strp # flaky +spin_lock +stream_arena_fault_address +stream_success/stream_arena_subprog_fault +stream_syscall +struct_ops_arena/arena_arg +struct_ops_arena/arena_arg_attach +syscall +tailcalls/tailcall_6 +tailcalls/tailcall_bpf2bpf_2 +tailcalls/tailcall_bpf2bpf_3 +tailcalls/tailcall_bpf2bpf_fentry +tailcalls/tailcall_bpf2bpf_fentry_entry +tailcalls/tailcall_bpf2bpf_fentry_fexit +tailcalls/tailcall_bpf2bpf_fexit +tailcalls/tailcall_bpf2bpf_fexit_links +tailcalls/tailcall_bpf2bpf_hierarchy_3 +task_local_data +task_local_storage/uptr_no_null_check +tc_bpf/tc_bpf_non_root +tc_edt # flaky +tc_redirect/tc_redirect_dtime +tcp_custom_syncookie +tcp_hdr_options +test_lsm/lsm_basic +test_profiler +test_strncmp/strncmp_bad_not_null_term_target +timer_interrupt # flaky +timer_mim +tp_btf_nullable/handle_tp_btf_nullable_bare1 +verif_scale_pyperf100 +verif_scale_pyperf180 +verif_scale_pyperf600_nounroll +verif_scale_seg6_loop +verif_scale_strobemeta +verif_scale_strobemeta_nounroll1 +verif_scale_strobemeta_nounroll2 +verif_scale_strobemeta_subprogs +verif_scale_sysctl_loop1 +verif_scale_sysctl_loop2 +verif_scale_xdp_loop +verifier_arena/check_arena_arg_ret +verifier_array_access/valid multiple map access into an array using constant without nullness +verifier_iterating_callbacks/test1 +verifier_lsm/not null checking nullable pointer in bpf_lsm_mmap_file +verifier_map_in_map/map_ptr_is_never_null_rb +verifier_map_lookup_refine/mapofmaps_value_as_helper_fixed_mem +verifier_map_lookup_refine/mapofmaps_value_as_helper_mem_buf +verifier_map_lookup_refine/mapofmaps_value_as_kfunc_mem_buf +verifier_private_stack/Private stack, async callback, not nested +verifier_private_stack/Private stack, async callback, potential nesting +verifier_private_stack/private stack, max stack depth is private stack +verifier_sockmap_mutate/test_trace_iter +verifier_spill_fill/partial_stack_load_preserves_zeros +verifier_spill_fill/stack_load_preserves_const_precision +verifier_spill_fill/stack_load_preserves_const_precision_subreg +verifier_subprog_precision/callback_result_precise +verifier_subprog_precision/fp_precise_subprog_result +verifier_subprog_precision/global_subprog_result_precise +verifier_subprog_precision/parent_callee_saved_reg_precise +verifier_subprog_precision/parent_callee_saved_reg_precise_global +verifier_subprog_precision/parent_callee_saved_reg_precise_with_callback +verifier_subprog_precision/parent_stack_slot_precise +verifier_subprog_precision/parent_stack_slot_precise_global +verifier_subprog_precision/parent_stack_slot_precise_with_callback +verifier_subprog_precision/sneaky_fp_precise_subprog_result +verifier_subprog_precision/stack_slot_aliases_precision +verifier_subprog_precision/subprog_arg_precise +verifier_subprog_precision/subprog_result_precise +verifier_subprog_precision/subprog_result_tail_call +verifier_subprog_precision/subprog_spill_into_parent_stack_slot_precise +verifier_tailcall_jit +verify_pkcs7_sig +veristat/set_global_vars_from_file_succeeds +veristat/set_global_vars_succeeds +xdp_context_lwt_encap +xdp_context_tuntap +xdp_context_veth +xdp_pull_data +xdp_synproxy diff --git a/ci/vmtest/configs/SPLAT_ALLOWLIST b/ci/vmtest/configs/SPLAT_ALLOWLIST new file mode 100644 index 0000000000000..0c23bfb379f18 --- /dev/null +++ b/ci/vmtest/configs/SPLAT_ALLOWLIST @@ -0,0 +1,21 @@ +# Kernel splats that check-kernel-splats.sh must not fail the run on. +# +# One extended regex per line, matched against the dmesg line. `#` comments +# and blank lines are ignored. +# +# Every entry is a hole in the scan, so give each one a reason and a link, and +# delete it once the fix reaches the CI kernel. An entry with no expiry is a +# bug that CI has agreed to stop reporting. +# +# The selftest config leaves unprivileged BPF on (CONFIG_BPF_UNPRIV_DEFAULT_OFF +# is not set in tools/testing/selftests/bpf/config), so the Spectre v2 code +# warns about it on every boot on x86_64, and whenever a test writes the sysctl +# on arm64. It states a config choice, not a kernel bug. +WARNING: Unprivileged eBPF is enabled + +# arm64 stack unwinder writes past the entry array while KASAN saves a free +# stack, so any kfree() under test_progs reports stack-out-of-bounds. Not a BPF +# bug; BPF only walks that path often. Fix posted 2026-08-12: +# https://lore.kernel.org/all/20260812-hello_world-v1-1-c3c2ddcb362d@meta.com/ +# Delete this entry once that fix reaches the CI kernel. +BUG: KASAN: stack-out-of-bounds in (stack_trace_consume_entry|filter_irq_stacks)\+ diff --git a/ci/vmtest/configs/SPLAT_DENYLIST b/ci/vmtest/configs/SPLAT_DENYLIST new file mode 100644 index 0000000000000..5f1477d1df8c3 --- /dev/null +++ b/ci/vmtest/configs/SPLAT_DENYLIST @@ -0,0 +1,24 @@ +# Kernel splat signatures for check-kernel-splats.sh. A dmesg line matching +# any of these fails the run. +# +# One extended regex per line. `#` comments and blank lines are ignored. This +# file is required: with no patterns there is no check, so the action fails the +# run rather than reporting a clean log. +# +# `^(\[[^]]*\] *)*` eats the timestamp and the CONFIG_PRINTK_CALLER field, so a +# pattern holds for `dmesg`, for `dmesg -t` and for both configs. + +# `BUG:` covers KASAN, KCSAN, KMSAN and KFENCE. `WARNING:` covers __warn(), and +# with it lockdep, refcount_t and list corruption, which all print through +# WARN(). An oops panics the VM (PANIC_ON_OOPS plus panic=-1), which fails the +# job on its own, so it needs no pattern here. +^(\[[^]]*\] *)*(BUG|WARNING|UBSAN|Oops)[: ] +^(\[[^]]*\] *)*kernel BUG at + +# kernel/watchdog.c sets pr_fmt, so a lockup line starts with `watchdog: `, +# not with `BUG:`. +^(\[[^]]*\] *)*watchdog: .*(soft lockup|hard LOCKUP) + +# kernel/rcu/tree.c sets pr_fmt to "rcu: ", which hides the INFO: from an +# anchored match. +^(\[[^]]*\] *)*(rcu: )?INFO: (task .* blocked for more than|[_a-z]+ (self-)?detected stall) diff --git a/ci/vmtest/configs/config b/ci/vmtest/configs/config new file mode 100644 index 0000000000000..679ee1857f947 --- /dev/null +++ b/ci/vmtest/configs/config @@ -0,0 +1,3 @@ +CONFIG_LIVEPATCH=y +CONFIG_SAMPLES=y +CONFIG_SAMPLE_LIVEPATCH=m diff --git a/ci/vmtest/configs/config.aarch64 b/ci/vmtest/configs/config.aarch64 new file mode 100644 index 0000000000000..779f6236f39a1 --- /dev/null +++ b/ci/vmtest/configs/config.aarch64 @@ -0,0 +1,3 @@ +CONFIG_KASAN=y +CONFIG_KASAN_GENERIC=y +CONFIG_KASAN_VMALLOC=y diff --git a/ci/vmtest/configs/config.x86_64 b/ci/vmtest/configs/config.x86_64 new file mode 100644 index 0000000000000..779f6236f39a1 --- /dev/null +++ b/ci/vmtest/configs/config.x86_64 @@ -0,0 +1,3 @@ +CONFIG_KASAN=y +CONFIG_KASAN_GENERIC=y +CONFIG_KASAN_VMALLOC=y diff --git a/ci/vmtest/configs/run-vmtest.env b/ci/vmtest/configs/run-vmtest.env new file mode 100644 index 0000000000000..1606ae6347acc --- /dev/null +++ b/ci/vmtest/configs/run-vmtest.env @@ -0,0 +1,50 @@ +#!/bin/bash + +# This file is sourced by libbpf/ci/run-vmtest Github Action scripts. +# +# The primary reason it exists is that assembling ALLOWLIST and +# DENYLIST for a particular test run is not a trivial operation. +# +# Users of libbpf/ci/run-vmtest action need to be able to specify a +# list of allow/denylist **files**, that later has to be correctly +# merged into a single allow/denylist passed to a test runner. +# +# Obviously it's perferrable for the scripts merging many lists into +# one to be reusable, and not copy-pasted between repositories which +# use libbpf/ci actions. And specifying the lists should be trivial. +# This file is a solution to that. + +# $SELFTESTS_BPF and $VMTEST_CONFIGS are set in the workflow, before +# libbpf/ci/run-vmtest action is called +# See .github/workflows/kernel-test.yml + +ALLOWLIST_FILES=( + "${SELFTESTS_BPF}/ALLOWLIST" + "${SELFTESTS_BPF}/ALLOWLIST.${ARCH}" + "${VMTEST_CONFIGS}/ALLOWLIST" + "${VMTEST_CONFIGS}/ALLOWLIST.${ARCH}" + "${VMTEST_CONFIGS}/ALLOWLIST.${DEPLOYMENT}" + "${VMTEST_CONFIGS}/ALLOWLIST.${KERNEL_TEST}" +) + +DENYLIST_FILES=( + "${SELFTESTS_BPF}/DENYLIST" + "${SELFTESTS_BPF}/DENYLIST.${ARCH}" + "${SELFTESTS_BPF}/DENYLIST.${SELFTESTS_BPF_ASAN:+asan}" + "${VMTEST_CONFIGS}/DENYLIST" + "${VMTEST_CONFIGS}/DENYLIST.${ARCH}" + "${VMTEST_CONFIGS}/DENYLIST.${DEPLOYMENT}" + "${VMTEST_CONFIGS}/DENYLIST.${KERNEL_TEST}" + "${VMTEST_CONFIGS}/DENYLIST.${SELFTESTS_BPF_ASAN:+asan}" +) + +# Export pipe-separated strings, because bash doesn't support array export +export SELFTESTS_BPF_ALLOWLIST_FILES=$(IFS="|"; echo "${ALLOWLIST_FILES[*]}") +export SELFTESTS_BPF_DENYLIST_FILES=$(IFS="|"; echo "${DENYLIST_FILES[*]}") + +# Kernel splat matching for check-kernel-splats.sh. The denylist says what a +# splat is, the allowlist drops the matches that are benign for this CI. The +# action carries no patterns of its own, so a change to either costs one PR to +# this repo, and no libbpf/ci sync. +export SPLAT_DENYLIST_FILE="${VMTEST_CONFIGS}/SPLAT_DENYLIST" +export SPLAT_ALLOWLIST_FILE="${VMTEST_CONFIGS}/SPLAT_ALLOWLIST" diff --git a/ci/vmtest/configs/run_veristat.cilium.cfg b/ci/vmtest/configs/run_veristat.cilium.cfg new file mode 100644 index 0000000000000..a9e78676e0717 --- /dev/null +++ b/ci/vmtest/configs/run_veristat.cilium.cfg @@ -0,0 +1,3 @@ +VERISTAT_OBJECTS_DIR="${CILIUM_BUILD_OUTPUT}/bpf" +VERISTAT_OBJECTS_GLOB="*.o" +VERISTAT_OUTPUT="veristat-cilium" diff --git a/ci/vmtest/configs/run_veristat.kernel.cfg b/ci/vmtest/configs/run_veristat.kernel.cfg new file mode 100644 index 0000000000000..807efc251073f --- /dev/null +++ b/ci/vmtest/configs/run_veristat.kernel.cfg @@ -0,0 +1,4 @@ +VERISTAT_OBJECTS_DIR="${SELFTESTS_BPF}" +VERISTAT_OBJECTS_GLOB="*.bpf.o" +VERISTAT_CFG_FILE="${SELFTESTS_BPF}/veristat.cfg" +VERISTAT_OUTPUT="veristat-kernel" diff --git a/ci/vmtest/configs/run_veristat.meta.cfg b/ci/vmtest/configs/run_veristat.meta.cfg new file mode 100644 index 0000000000000..14f08d241d206 --- /dev/null +++ b/ci/vmtest/configs/run_veristat.meta.cfg @@ -0,0 +1,4 @@ +VERISTAT_OBJECTS_DIR="${WORKING_DIR}/bpf_objects" +VERISTAT_OBJECTS_GLOB="*.o" +VERISTAT_OUTPUT="veristat-meta" +VERISTAT_CFG_FILE="${VERISTAT_CONFIGS}/veristat_meta.cfg" diff --git a/ci/vmtest/configs/run_veristat.scx.cfg b/ci/vmtest/configs/run_veristat.scx.cfg new file mode 100644 index 0000000000000..bf289c00d5fda --- /dev/null +++ b/ci/vmtest/configs/run_veristat.scx.cfg @@ -0,0 +1,3 @@ +VERISTAT_OBJECTS_DIR="${SCX_BUILD_OUTPUT}/bpf" +VERISTAT_OBJECTS_GLOB="*.bpf.o" +VERISTAT_OUTPUT="veristat-scx" diff --git a/ci/vmtest/configs/veristat_meta.cfg b/ci/vmtest/configs/veristat_meta.cfg new file mode 100644 index 0000000000000..a17e08d94d66b --- /dev/null +++ b/ci/vmtest/configs/veristat_meta.cfg @@ -0,0 +1,51 @@ +# List of exceptions we know about that are not going to work with veristat. + +# libbpf-tools, maintained outside of fbcode +!bcc-libbpf-tools-* + +# missing kernel function 'bictcp_cong_avoid' +!ti-tcpevent-tcp_bpf_state_fentry-tcp_bpf_state_fentry.bpf.o/bictcp_cong_avoid +# missing kernel function 'bictcp_state' +!ti-tcpevent-tcp_bpf_tracer_fentry-tcp_bpf_tracer_fentry.bpf.o/bictcp_state +# missing kernel function 'tcp_drop' +!ti-tcpevent-tcp_bpf_tracer_fentry-tcp_bpf_tracer_fentry.bpf.o/tcp_drop + +# outdated (and abandoned ?) BPF programs, can't work with modern libbpf +!schedulers-tangram-agent-bpf-blacklist-bpf_device_cgroup-device_cgroup_filter.bpf.o +!schedulers-tangram-agent-bpf-netstat-bpf_cgroup_egress-bpf_cgroup_egress.bpf.o +!schedulers-tangram-agent-bpf-netstat-bpf_cgroup_ingress-bpf_cgroup_ingress.bpf.o + +# invalid usage of global functions, seems abandoned as well +!neteng-urgd-urgd_bpf_prog-urgd_bpf_prog.o + +# missing kernel function '__send_signal' +!cea-object-introspection-OIVT-signal_bpf-signal.bpf.o/__send_signal + +# Strobelight program not passing validation properly +!strobelight-server-bpf_program-hhvm_stacks-hhvm_stacks.o/hhvm_stack + +# RDMA functionality is expected which we don't have in default kernel flavor +!neteng-netedit-bpf-ftrace-be_audit-be_audit-be_audit.bpf.o + +# Strobelight programs with >1mln instructions +!strobelight-server-bpf_program-strobelight_process_monitor_libbpf-strobelight_process_monitor_libbpf.o + +# infiniband only, doesn't work on other hardware +!neteng-netnorad-common-cpp-bpf-qp_ah_list-qp_ah_list.bpf.o/ret_query_qp + +# Droplet with >1mln instructions +!ti-droplet-bpf-vip_filter_v2_xdp-vip_filter_v2_xdp.bpf.o/vip_filter + +# sched_ext bpf_lib objects don't need to be verified separately +!third-party-scx*bpf_lib.bpf.o + +# These cause segfault in veristat due to a bug in libbpf +# Link: https://lore.kernel.org/bpf/20250718001009.610955-1-andrii@kernel.org/ +# We can include them back after a veristat release with fixed libbpf +!third-party-scx-__scx_chaos_bpf_skel_genskel-bpf.bpf.o +!third-party-scx-__scx_p2dq_bpf_skel_genskel-bpf.bpf.o + +# scx v1.0.18 lavd_enqueue fails verification. Pin the exclusion to this +# version so newer scx releases, which are expected to be fixed, still signal +# failures instead of being silently skipped. +!third-party-scx-v1.0.18-__scx_lavd_bpf_skel_genskel-bpf.bpf.o/lavd_enqueue diff --git a/net/core/filter.c b/net/core/filter.c index 3423734124a5b..031fb2aad792a 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -7167,6 +7167,28 @@ __bpf_skc_lookup(struct sk_buff *skb, struct bpf_sock_tuple *tuple, u32 len, return sk; } +static struct sock * +bpf_sk_lookup_full_sk(struct sock *sk) +{ + struct sock *sk2 = sk_to_full_sk(sk); + + /* + * sk_to_full_sk() may return sk->rsk_listener, make sure the original + * sk sock refcnt is decremented to prevent a request_sock leak. + */ + if (sk2 != sk) { + sock_gen_put(sk); + /* Ensure there is no need to bump sk2 refcnt. */ + if (unlikely(sk2 && !sock_flag(sk2, SOCK_RCU_FREE))) { + WARN_ONCE(1, "Found non-RCU, unreferenced socket!"); + return NULL; + } + sk = sk2; + } + + return sk; +} + static struct sock * __bpf_sk_lookup(struct sk_buff *skb, struct bpf_sock_tuple *tuple, u32 len, struct net *caller_net, u32 ifindex, u8 proto, u64 netns_id, @@ -7176,22 +7198,8 @@ __bpf_sk_lookup(struct sk_buff *skb, struct bpf_sock_tuple *tuple, u32 len, ifindex, proto, netns_id, flags, sdif); - if (sk) { - struct sock *sk2 = sk_to_full_sk(sk); - - /* sk_to_full_sk() may return (sk)->rsk_listener, so make sure the original sk - * sock refcnt is decremented to prevent a request_sock leak. - */ - if (sk2 != sk) { - sock_gen_put(sk); - /* Ensure there is no need to bump sk2 refcnt */ - if (unlikely(sk2 && !sock_flag(sk2, SOCK_RCU_FREE))) { - WARN_ONCE(1, "Found non-RCU, unreferenced socket!"); - return NULL; - } - sk = sk2; - } - } + if (sk) + sk = bpf_sk_lookup_full_sk(sk); return sk; } @@ -7222,22 +7230,8 @@ bpf_sk_lookup(struct sk_buff *skb, struct bpf_sock_tuple *tuple, u32 len, struct sock *sk = bpf_skc_lookup(skb, tuple, len, proto, netns_id, flags); - if (sk) { - struct sock *sk2 = sk_to_full_sk(sk); - - /* sk_to_full_sk() may return (sk)->rsk_listener, so make sure the original sk - * sock refcnt is decremented to prevent a request_sock leak. - */ - if (sk2 != sk) { - sock_gen_put(sk); - /* Ensure there is no need to bump sk2 refcnt */ - if (unlikely(sk2 && !sock_flag(sk2, SOCK_RCU_FREE))) { - WARN_ONCE(1, "Found non-RCU, unreferenced socket!"); - return NULL; - } - sk = sk2; - } - } + if (sk) + sk = bpf_sk_lookup_full_sk(sk); return sk; } diff --git a/net/core/sock_map.c b/net/core/sock_map.c index 9efbd8ca7db83..ca49bc7f8687c 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -392,8 +392,8 @@ static void *sock_map_lookup(struct bpf_map *map, void *key) sk = __sock_map_lookup_elem(map, *(u32 *)key); if (!sk) return NULL; - if (sk_is_refcounted(sk) && !refcount_inc_not_zero(&sk->sk_refcnt)) - return NULL; + if (sk_is_refcounted(sk)) + sock_hold(sk); return sk; } @@ -1218,8 +1218,8 @@ static void *sock_hash_lookup(struct bpf_map *map, void *key) sk = __sock_hash_lookup_elem(map, key); if (!sk) return NULL; - if (sk_is_refcounted(sk) && !refcount_inc_not_zero(&sk->sk_refcnt)) - return NULL; + if (sk_is_refcounted(sk)) + sock_hold(sk); return sk; }