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..dd25c290bcd83 --- /dev/null +++ b/.github/scripts/matrix.py @@ -0,0 +1,288 @@ +#!/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") + + # Don't run GCC BPF runner, because too many tests are failing + # See: https://lore.kernel.org/bpf/87bjw6qpje.fsf@oracle.com/ + # if self.arch == Arch.X86_64: + # tests_list.append("test_progs-bpf_gcc") + + 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..7166a23432207 --- /dev/null +++ b/.github/workflows/ai-code-review.yml @@ -0,0 +1,192 @@ +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 + - name: Move linux source in place + shell: bash + run: | + rm -rf .git .github ci + cd .kernel + 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-4-8 + 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..e7c816bba513c --- /dev/null +++ b/.github/workflows/gcc-bpf.yml @@ -0,0 +1,100 @@ +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 + +jobs: + test: + name: GCC BPF + 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 }} + 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 }} diff --git a/.github/workflows/kernel-build-test.yml b/.github/workflows/kernel-build-test.yml new file mode 100644 index 0000000000000..87416c70325cc --- /dev/null +++ b/.github/workflows/kernel-build-test.yml @@ -0,0 +1,198 @@ +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: + # GCC BPF does not need /dev/kvm, so use the "build" runners + runs_on: ${{ inputs.build_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 }} 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..dc8330bdf2736 --- /dev/null +++ b/.github/workflows/kernel-test.yml @@ -0,0 +1,102 @@ +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/* 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/arch/x86/Kconfig b/arch/x86/Kconfig index bdad90f210e4b..705a9ab85f3a9 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -233,6 +233,7 @@ config X86 select HAVE_SAMPLE_FTRACE_DIRECT if X86_64 select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64 select HAVE_EBPF_JIT + select HAVE_EBPF_JIT_KASAN if X86_64 select HAVE_EFFICIENT_UNALIGNED_ACCESS select HAVE_EISA if X86_32 select HAVE_EXIT_THREAD diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index de7515ea1beae..9dbe3a52444e3 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -21,6 +21,17 @@ #include #include +#if IS_ENABLED(CONFIG_BPF_JIT_KASAN) +void __asan_load1(void *p); +void __asan_store1(void *p); +void __asan_load2(void *p); +void __asan_store2(void *p); +void __asan_load4(void *p); +void __asan_store4(void *p); +void __asan_load8(void *p); +void __asan_store8(void *p); +#endif + static bool all_callee_regs_used[4] = {true, true, true, true}; static u8 *emit_code(u8 *ptr, u32 bytes, unsigned int len) @@ -1110,6 +1121,90 @@ static void maybe_emit_1mod(u8 **pprog, u32 reg, bool is64) *pprog = prog; } +static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog, + u32 addr_reg, struct bpf_insn *insn, u8 *ip, + bool is_write, bool accesses_stack_only) +{ +#ifdef CONFIG_BPF_JIT_KASAN + u32 bpf_size = BPF_SIZE(insn->code); + s32 off = insn->off; + u8 *prog = *pprog; + void *kasan_func; + + if (!env) + return 0; + + if (accesses_stack_only) + return 0; + + /* Derive KASAN check function from access type and size */ + switch (bpf_size) { + case BPF_B: + kasan_func = is_write ? __asan_store1 : __asan_load1; + break; + case BPF_H: + kasan_func = is_write ? __asan_store2 : __asan_load2; + break; + case BPF_W: + kasan_func = is_write ? __asan_store4 : __asan_load4; + break; + case BPF_DW: + kasan_func = is_write ? __asan_store8 : __asan_load8; + break; + default: + return -EINVAL; + } + + /* Save rax */ + EMIT1(0x50); + /* Save rcx */ + EMIT1(0x51); + /* Save rdx */ + EMIT1(0x52); + /* Save rsi */ + EMIT1(0x56); + /* Save rdi */ + EMIT1(0x57); + /* Save r8 */ + EMIT2(0x41, 0x50); + /* Save r9 */ + EMIT2(0x41, 0x51); + + /* mov rdi, addr_reg */ + EMIT_mov(BPF_REG_1, addr_reg); + + /* add rdi, off (if offset is non-zero) */ + if (off) { + if (is_imm8(off)) { + /* add rdi, imm8 */ + EMIT4(0x48, 0x83, 0xC7, (u8)off); + } else { + /* add rdi, imm32 */ + EMIT3_off32(0x48, 0x81, 0xC7, off); + } + } + + /* Adjust ip to account for the instrumentation generated so far */ + ip += (prog - *pprog); + /* We emit a call, so update call depth counting */ + ip += x86_call_depth_emit_accounting(&prog, kasan_func, ip); + /* call kasan_func */ + if (emit_call(&prog, kasan_func, ip)) + return -ERANGE; + + EMIT2(0x41, 0x59); + EMIT2(0x41, 0x58); + EMIT1(0x5F); + EMIT1(0x5E); + EMIT1(0x5A); + EMIT1(0x59); + EMIT1(0x58); + + *pprog = prog; +#endif /* CONFIG_BPF_JIT_KASAN */ + return 0; +} + /* LDX: dst_reg = *(u8*)(src_reg + off) */ static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off) { @@ -1315,6 +1410,64 @@ static void emit_st_index(u8 **pprog, u32 size, u32 dst_reg, u32 index_reg, int *pprog = prog; } +static void emit_st(u8 **pprog, struct bpf_insn *insn, int dst_reg, + s32 outgoing_arg_base, u16 outgoing_rsp) +{ + s32 imm32 = insn->imm; + u8 *prog = *pprog; + s32 insn_off; + + switch (BPF_SIZE(insn->code)) { + case BPF_B: + if (is_ereg(dst_reg)) + EMIT2(0x41, 0xC6); + else + EMIT1(0xC6); + break; + case BPF_H: + if (is_ereg(dst_reg)) + EMIT3(0x66, 0x41, 0xC7); + else + EMIT2(0x66, 0xC7); + break; + case BPF_W: + if (is_ereg(dst_reg)) + EMIT2(0x41, 0xC7); + else + EMIT1(0xC7); + break; + case BPF_DW: + if (dst_reg == BPF_REG_PARAMS && insn->off == -8) { + /* Arg 6: store immediate in r9 register */ + emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, + (u32)imm32); + *pprog = prog; + return; + } + EMIT2(add_1mod(0x48, dst_reg), 0xC7); + break; + } + + insn_off = insn->off; + if (dst_reg == BPF_REG_PARAMS) { + /* + * Args 7+: reverse BPF negative offsets to + * x86 positive rsp offsets. + * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ... + */ + insn_off = outgoing_arg_base - outgoing_rsp - + insn_off - 16; + dst_reg = BPF_REG_FP; + } + if (is_imm8(insn_off)) + EMIT2(add_1reg(0x40, dst_reg), insn_off); + else + EMIT1_off32(add_1reg(0x80, dst_reg), insn_off); + + EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code))); + *pprog = prog; +} + static void emit_st_r12(u8 **pprog, u32 size, u32 dst_reg, int off, int imm) { emit_st_index(pprog, size, dst_reg, X86_REG_R12, off, imm); @@ -1423,17 +1576,31 @@ static int emit_atomic_rmw_index(u8 **pprog, u32 atomic_op, u32 size, return 0; } -static int emit_atomic_ld_st(u8 **pprog, u32 atomic_op, u32 dst_reg, - u32 src_reg, s16 off, u8 bpf_size) +static int emit_atomic_ld_st(struct bpf_verifier_env *env, u8 **pprog, + struct bpf_insn *insn, u8 *ip, u32 dst_reg, + u32 src_reg, bool accesses_stack_only) { + u32 atomic_op = insn->imm; + int err; + switch (atomic_op) { case BPF_LOAD_ACQ: + err = emit_kasan_check(env, pprog, src_reg, insn, ip, false, + accesses_stack_only); + if (err) + return err; /* dst_reg = smp_load_acquire(src_reg + off16) */ - emit_ldx(pprog, bpf_size, dst_reg, src_reg, off); + emit_ldx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg, + insn->off); break; case BPF_STORE_REL: + err = emit_kasan_check(env, pprog, dst_reg, insn, ip, true, + accesses_stack_only); + if (err) + return err; /* smp_store_release(dst_reg + off16, src_reg) */ - emit_stx(pprog, bpf_size, dst_reg, src_reg, off); + emit_stx(pprog, BPF_SIZE(insn->code), dst_reg, src_reg, + insn->off); break; default: pr_err("bpf_jit: unknown atomic load/store opcode %02x\n", @@ -1811,10 +1978,12 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int * const s32 imm32 = insn->imm; u32 dst_reg = insn->dst_reg; u32 src_reg = insn->src_reg; + bool accesses_stack_only; u8 b2 = 0, b3 = 0; u8 *start_of_ldx; s64 jmp_offset; s32 insn_off; + int insn_idx; u8 jmp_cond; u8 *func; int nops; @@ -1831,6 +2000,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int * EMIT_ENDBR(); ip = image + addrs[i - 1] + (prog - temp); + insn_idx = i - 1 + bpf_prog->aux->subprog_start; + accesses_stack_only = + env ? !env->insn_aux_data[insn_idx].non_stack_access : + false; switch (insn->code) { /* ALU */ @@ -2207,49 +2380,17 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int * EMIT_LFENCE(); break; - /* ST: *(u8*)(dst_reg + off) = imm */ case BPF_ST | BPF_MEM | BPF_B: - if (is_ereg(dst_reg)) - EMIT2(0x41, 0xC6); - else - EMIT1(0xC6); - goto st; case BPF_ST | BPF_MEM | BPF_H: - if (is_ereg(dst_reg)) - EMIT3(0x66, 0x41, 0xC7); - else - EMIT2(0x66, 0xC7); - goto st; case BPF_ST | BPF_MEM | BPF_W: - if (is_ereg(dst_reg)) - EMIT2(0x41, 0xC7); - else - EMIT1(0xC7); - goto st; case BPF_ST | BPF_MEM | BPF_DW: - if (dst_reg == BPF_REG_PARAMS && insn->off == -8) { - /* Arg 6: store immediate in r9 register */ - emit_mov_imm64(&prog, X86_REG_R9, imm32 >> 31, (u32)imm32); - break; - } - EMIT2(add_1mod(0x48, dst_reg), 0xC7); - -st: insn_off = insn->off; - if (dst_reg == BPF_REG_PARAMS) { - /* - * Args 7+: reverse BPF negative offsets to - * x86 positive rsp offsets. - * BPF off=-16 → [rsp+0], off=-24 → [rsp+8], ... - */ - insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16; - dst_reg = BPF_REG_FP; - } - if (is_imm8(insn_off)) - EMIT2(add_1reg(0x40, dst_reg), insn_off); - else - EMIT1_off32(add_1reg(0x80, dst_reg), insn_off); + err = emit_kasan_check(env, &prog, dst_reg, insn, ip, + true, accesses_stack_only); + if (err) + return err; - EMIT(imm32, bpf_size_to_x86_bytes(BPF_SIZE(insn->code))); + emit_st(&prog, insn, dst_reg, outgoing_arg_base, + outgoing_rsp); break; /* STX: *(u8*)(dst_reg + off) = src_reg */ @@ -2267,6 +2408,10 @@ st: insn_off = insn->off; insn_off = outgoing_arg_base - outgoing_rsp - insn_off - 16; dst_reg = BPF_REG_FP; } + err = emit_kasan_check(env, &prog, dst_reg, insn, ip, + true, accesses_stack_only); + if (err) + return err; emit_stx(&prog, BPF_SIZE(insn->code), dst_reg, src_reg, insn_off); break; @@ -2428,6 +2573,12 @@ st: insn_off = insn->off; /* populate jmp_offset for JAE above to jump to start_of_ldx */ start_of_ldx = prog; end_of_jmp[-1] = start_of_ldx - end_of_jmp; + } else { + err = emit_kasan_check(env, &prog, src_reg, + insn, ip, false, + accesses_stack_only); + if (err) + return err; } if (BPF_MODE(insn->code) == BPF_PROBE_MEMSX || BPF_MODE(insn->code) == BPF_MEMSX) @@ -2489,15 +2640,16 @@ st: insn_off = insn->off; } fallthrough; case BPF_STX | BPF_ATOMIC | BPF_W: - case BPF_STX | BPF_ATOMIC | BPF_DW: - if (insn->imm == (BPF_AND | BPF_FETCH) || - insn->imm == (BPF_OR | BPF_FETCH) || - insn->imm == (BPF_XOR | BPF_FETCH)) { - bool is64 = BPF_SIZE(insn->code) == BPF_DW; - u32 real_src_reg = src_reg; - u32 real_dst_reg = dst_reg; - u8 *branch_target; - + case BPF_STX | BPF_ATOMIC | BPF_DW: { + bool is64 = BPF_SIZE(insn->code) == BPF_DW; + u32 real_src_reg = src_reg; + u32 real_dst_reg = dst_reg; + u8 *branch_target; + bool is_atomic_fetch = + (insn->imm == (BPF_AND | BPF_FETCH) || + insn->imm == (BPF_OR | BPF_FETCH) || + insn->imm == (BPF_XOR | BPF_FETCH)); + if (is_atomic_fetch) { /* * Can't be implemented with a single x86 insn. * Need to do a CMPXCHG loop. @@ -2510,7 +2662,17 @@ st: insn_off = insn->off; if (dst_reg == BPF_REG_0) real_dst_reg = BPF_REG_AX; + ip += 3; + } + if (!bpf_atomic_is_load_store(insn)) { + err = emit_kasan_check(env, &prog, real_dst_reg, + insn, ip, true, + accesses_stack_only); + if (err) + return err; branch_target = prog; + } + if (is_atomic_fetch) { /* Load old value */ emit_ldx(&prog, BPF_SIZE(insn->code), BPF_REG_0, real_dst_reg, insn->off); @@ -2542,15 +2704,16 @@ st: insn_off = insn->off; } if (bpf_atomic_is_load_store(insn)) - err = emit_atomic_ld_st(&prog, insn->imm, dst_reg, src_reg, - insn->off, BPF_SIZE(insn->code)); + err = emit_atomic_ld_st(env, &prog, insn, ip, + dst_reg, src_reg, + accesses_stack_only); else err = emit_atomic_rmw(&prog, insn->imm, dst_reg, src_reg, insn->off, BPF_SIZE(insn->code)); if (err) return err; break; - + } case BPF_STX | BPF_PROBE_ATOMIC | BPF_B: case BPF_STX | BPF_PROBE_ATOMIC | BPF_H: if (!bpf_atomic_is_load_store(insn)) { 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/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..255201ffd2233 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.s390x @@ -0,0 +1,10 @@ +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 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..a3c745d1f5b52 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.test_progs-bpf_gcc @@ -0,0 +1,904 @@ +arena_htab +async_stack_depth +bad_struct_ops/invalid_prog_reuse +bpf_cookie +bpf_iter/bpf_hash_map +bpf_iter/ksym +bpf_iter/tcp4 +bpf_iter/tcp6 +bpf_iter/udp4 +bpf_iter/udp6 +bpf_iter/unix +bpf_iter_setsockopt +bpf_iter_setsockopt_unix +bpf_mod_race +bpf_nf/tc-bpf-ct +bpf_nf/xdp-ct +bpf_tcp_ca/cubic +btf_dump/btf_dump: bitfields +btf_dump/btf_dump: packing +btf_dump/btf_dump: padding +btf_dump/btf_dump: syntax +btf_map_in_map +cb_refs +cgroup_get_current_cgroup_id +cgroup_iter/cgroup_iter__self_only_css_task +cgroup_tcp_skb +cgrp_kfunc +cls_redirect/cls_redirect_dynptr +connect_force_port +core_autosize +core_read_macros +core_reloc/type_id +core_reloc/type_id___missing_targets +core_reloc_btfgen/type_id +core_reloc_btfgen/type_id___missing_targets +cpumask/test_acquire_wrong_cpumask +cpumask/test_alloc_double_release +cpumask/test_alloc_free_cpumask +cpumask/test_alloc_no_release +cpumask/test_and_or_xor +cpumask/test_copy_any_anyand +cpumask/test_cpumask_null +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_no_null_check +cpumask/test_global_mask_out_of_rcu +cpumask/test_global_mask_rcu +cpumask/test_global_mask_rcu_no_null_check +cpumask/test_insert_leave +cpumask/test_insert_remove_no_release +cpumask/test_insert_remove_release +cpumask/test_intersects_subset +cpumask/test_invalid_nested_array +cpumask/test_mutate_cpumask +cpumask/test_set_clear_cpu +cpumask/test_setall_clear_cpu +cpumask/test_test_and_set_clear +crypto_basic/crypto_acquire +crypto_sanity +deny_namespace +dummy_st_ops/test_unsupported_field_sleepable +dynptr/add_dynptr_to_map1 +dynptr/add_dynptr_to_map2 +dynptr/clone_invalid1 +dynptr/clone_invalid2 +dynptr/clone_invalidate1 +dynptr/clone_invalidate2 +dynptr/clone_invalidate3 +dynptr/clone_invalidate4 +dynptr/clone_invalidate5 +dynptr/clone_invalidate6 +dynptr/clone_skb_packet_data +dynptr/clone_xdp_packet_data +dynptr/data_slice_missing_null_check1 +dynptr/data_slice_missing_null_check2 +dynptr/data_slice_out_of_bounds_map_value +dynptr/data_slice_out_of_bounds_ringbuf +dynptr/data_slice_out_of_bounds_skb +dynptr/data_slice_use_after_release1 +dynptr/data_slice_use_after_release2 +dynptr/dynptr_adjust_invalid +dynptr/dynptr_from_mem_invalid_api +dynptr/dynptr_invalidate_slice_failure +dynptr/dynptr_invalidate_slice_or_null +dynptr/dynptr_invalidate_slice_reinit +dynptr/dynptr_is_null_invalid +dynptr/dynptr_is_rdonly_invalid +dynptr/dynptr_overwrite_ref +dynptr/dynptr_partial_slot_invalidate +dynptr/dynptr_pruning_overwrite +dynptr/dynptr_pruning_type_confusion +dynptr/dynptr_read_into_slot +dynptr/dynptr_size_invalid +dynptr/dynptr_slice_var_len1 +dynptr/dynptr_slice_var_len2 +dynptr/dynptr_var_off_overwrite +dynptr/global +dynptr/invalid_data_slices +dynptr/invalid_helper1 +dynptr/invalid_helper2 +dynptr/invalid_offset +dynptr/invalid_read1 +dynptr/invalid_read2 +dynptr/invalid_read3 +dynptr/invalid_read4 +dynptr/invalid_slice_rdwr_rdonly +dynptr/invalid_write1 +dynptr/invalid_write2 +dynptr/invalid_write3 +dynptr/invalid_write4 +dynptr/release_twice +dynptr/release_twice_callback +dynptr/ringbuf_invalid_api +dynptr/ringbuf_missing_release1 +dynptr/ringbuf_missing_release2 +dynptr/ringbuf_missing_release_callback +dynptr/ringbuf_release_uninit_dynptr +dynptr/skb_invalid_ctx +dynptr/skb_invalid_ctx_fentry +dynptr/skb_invalid_ctx_fexit +dynptr/skb_invalid_data_slice1 +dynptr/skb_invalid_data_slice2 +dynptr/skb_invalid_data_slice3 +dynptr/skb_invalid_data_slice4 +dynptr/skb_invalid_slice_write +dynptr/test_dynptr_reg_type +dynptr/test_dynptr_skb_no_buff +dynptr/test_dynptr_skb_small_buff +dynptr/test_dynptr_skb_tp_btf +dynptr/test_read_write +dynptr/uninit_write_into_slot +dynptr/use_after_invalid +dynptr/xdp_invalid_ctx +dynptr/xdp_invalid_data_slice1 +dynptr/xdp_invalid_data_slice2 +exceptions/check_assert_eq_int_max +exceptions/check_assert_eq_int_min +exceptions/check_assert_eq_llong_max +exceptions/check_assert_eq_llong_min +exceptions/check_assert_eq_zero +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/check_assert_with_return +exceptions/exception_ext +exceptions/exception_ext_mod_cb_runtime +exceptions/non-throwing extension -> non-throwing subprog +exceptions/non-throwing extension -> throwing global subprog +exceptions/non-throwing fentry -> exception_cb +exceptions/non-throwing fexit -> exception_cb +exceptions/non-throwing fmod_ret -> non-throwing global subprog +exceptions/reject_async_callback_throw +exceptions/reject_exception_throw_cb +exceptions/reject_exception_throw_cb_diff +exceptions/reject_set_exception_cb_bad_ret2 +exceptions/reject_subprog_with_lock +exceptions/reject_subprog_with_rcu_read_lock +exceptions/reject_with_cb +exceptions/reject_with_cb_reference +exceptions/reject_with_lock +exceptions/reject_with_rbtree_add_throw +exceptions/reject_with_rcu_read_lock +exceptions/reject_with_reference +exceptions/reject_with_subprog_reference +exceptions/throwing extension (with custom cb) -> exception_cb +exceptions/throwing extension -> global func in exception_cb +exceptions/throwing extension -> non-throwing global subprog +exceptions/throwing extension -> throwing global subprog +exceptions/throwing fentry -> exception_cb +exceptions/throwing fexit -> exception_cb +failures_wq +fexit_bpf2bpf/fmod_ret_freplace +fexit_bpf2bpf/func_replace +fexit_bpf2bpf/func_replace_global_func +fexit_bpf2bpf/func_replace_multi +fexit_bpf2bpf/func_sockmap_update +fexit_bpf2bpf/target_yes_callees +global_func_dead_code +global_map_resize +inner_array_lookup +irq/irq_flag_overwrite +irq/irq_flag_overwrite_partial +irq/irq_global_subprog +irq/irq_ooo_refs_array +irq/irq_restore_4_subprog +irq/irq_restore_bad_arg +irq/irq_restore_invalid +irq/irq_restore_iter +irq/irq_restore_missing_1_subprog +irq/irq_restore_missing_2 +irq/irq_restore_missing_2_subprog +irq/irq_restore_missing_3 +irq/irq_restore_missing_3_minus_2 +irq/irq_restore_missing_3_minus_2_subprog +irq/irq_restore_missing_3_subprog +irq/irq_restore_ooo +irq/irq_restore_ooo_3 +irq/irq_restore_ooo_3_subprog +irq/irq_save_bad_arg +irq/irq_save_invalid +irq/irq_save_iter +irq/irq_sleepable_helper +irq/irq_sleepable_kfunc +iters/compromise_iter_w_direct_write_and_skip_destroy_fail +iters/compromise_iter_w_direct_write_fail +iters/compromise_iter_w_helper_write_fail +iters/create_and_forget_to_destroy_fail +iters/css_task +iters/delayed_precision_mark +iters/delayed_read_mark +iters/destroy_without_creating_fail +iters/double_create_fail +iters/double_destroy_fail +iters/iter_css_lock_and_unlock +iters/iter_css_task_for_each +iters/iter_css_without_lock +iters/iter_destroy_bad_arg +iters/iter_err_too_permissive1 +iters/iter_err_too_permissive2 +iters/iter_err_too_permissive3 +iters/iter_err_unsafe_asm_loop +iters/iter_err_unsafe_c_loop +iters/iter_nested_iters +iters/iter_new_bad_arg +iters/iter_next_bad_arg +iters/iter_next_ptr_mem_not_trusted +iters/iter_next_rcu_not_trusted +iters/iter_next_rcu_or_null +iters/iter_next_trusted_or_null +iters/iter_obfuscate_counter +iters/iter_subprog_iters +iters/iter_tasks_lock_and_unlock +iters/iter_tasks_without_lock +iters/leak_iter_from_subprog_fail +iters/loop_state_deps1 +iters/loop_state_deps2 +iters/missing_null_check_fail +iters/next_after_destroy_fail +iters/next_without_new_fail +iters/read_from_iter_slot_fail +iters/stacksafe_should_not_conflate_stack_spill_and_iter +iters/testmod_seq_getter_after_bad +iters/testmod_seq_getter_before_bad +iters/wrong_sized_read_fail +jeq_infer_not_null +jit_probe_mem +kfree_skb +kfunc_call/kfunc_call_ctx +kfunc_call/kfunc_call_test1 +kfunc_call/kfunc_call_test2 +kfunc_call/kfunc_call_test4 +kfunc_call/kfunc_call_test_get_mem +kfunc_call/kfunc_call_test_ref_btf_id +kfunc_call/kfunc_call_test_static_unused_arg +kfunc_call/kfunc_syscall_test +kfunc_call/kfunc_syscall_test_null +kfunc_dynptr_param/not_ptr_to_stack +kfunc_dynptr_param/not_valid_dynptr +kfunc_param_nullable/kfunc_dynptr_nullable_test3 +kprobe_multi_test/kprobe_session_return_2 +kptr_xchg_inline +l4lb_all/l4lb_noinline +l4lb_all/l4lb_noinline_dynptr +linked_list +local_kptr_stash/drop_rb_node_off +local_kptr_stash/local_kptr_stash_local_with_root +local_kptr_stash/local_kptr_stash_plain +local_kptr_stash/local_kptr_stash_simple +local_kptr_stash/local_kptr_stash_unstash +local_kptr_stash/refcount_acquire_without_unstash +local_kptr_stash/stash_rb_nodes +log_buf/obj_load_log_buf +log_fixup/bad_core_relo_subprog +log_fixup/bad_core_relo_trunc_full +lru_bug +map_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_kptr/correct_btf_id_check_size +map_kptr/inherit_untrusted_on_walk +map_kptr/kptr_xchg_possibly_null +map_kptr/kptr_xchg_ref_state +map_kptr/mark_ref_as_untrusted_or_null +map_kptr/marked_as_untrusted_or_null +map_kptr/non_const_var_off +map_kptr/non_const_var_off_kptr_xchg +map_kptr/reject_bad_type_xchg +map_kptr/reject_kptr_xchg_on_unref +map_kptr/reject_member_of_ref_xchg +map_kptr/reject_untrusted_xchg +map_kptr/success-map +map_ptr +nested_trust/test_invalid_nested_user_cpus +nested_trust/test_invalid_skb_field +percpu_alloc/array +percpu_alloc/array_sleepable +percpu_alloc/cgrp_local_storage +percpu_alloc/test_array_map_1 +percpu_alloc/test_array_map_2 +percpu_alloc/test_array_map_3 +percpu_alloc/test_array_map_4 +percpu_alloc/test_array_map_5 +percpu_alloc/test_array_map_6 +percpu_alloc/test_array_map_7 +percpu_alloc/test_array_map_8 +perf_branches/perf_branches_no_hw +pkt_access +preempt_lock/preempt_global_subprog_test +preempt_lock/preempt_lock_missing_1 +preempt_lock/preempt_lock_missing_1_subprog +preempt_lock/preempt_lock_missing_2 +preempt_lock/preempt_lock_missing_2_minus_1_subprog +preempt_lock/preempt_lock_missing_2_subprog +preempt_lock/preempt_lock_missing_3 +preempt_lock/preempt_lock_missing_3_minus_2 +preempt_lock/preempt_sleepable_helper +preempt_lock/preempt_sleepable_kfunc +preempted_bpf_ma_op +prog_run_opts +prog_tests_framework +raw_tp_null +rbtree_fail +rbtree_success +recursion +refcounted_kptr +refcounted_kptr_fail +refcounted_kptr_wrong_owner +reference_tracking/sk_lookup_success +ringbuf_multi +setget_sockopt +sk_lookup +skc_to_unix_sock +sock_addr/recvmsg4: attach prog with wrong attach type +sock_addr/recvmsg4: recvfrom (dgram) +sock_addr/recvmsg6: attach prog with wrong attach type +sock_addr/recvmsg6: recvfrom (dgram) +sock_addr/sendmsg4: attach prog with wrong attach type +sock_addr/sendmsg4: kernel_sendmsg (dgram) +sock_addr/sendmsg4: kernel_sendmsg deny (dgram) +sock_addr/sendmsg4: sendmsg (dgram) +sock_addr/sendmsg4: sendmsg deny (dgram) +sock_addr/sendmsg4: sock_sendmsg (dgram) +sock_addr/sendmsg4: sock_sendmsg deny (dgram) +sock_addr/sendmsg6: attach prog with wrong attach type +sock_addr/sendmsg6: kernel_sendmsg (dgram) +sock_addr/sendmsg6: kernel_sendmsg [::] (BSD'ism) (dgram) +sock_addr/sendmsg6: kernel_sendmsg deny (dgram) +sock_addr/sendmsg6: sendmsg (dgram) +sock_addr/sendmsg6: sendmsg IPv4-mapped IPv6 (dgram) +sock_addr/sendmsg6: sendmsg [::] (BSD'ism) (dgram) +sock_addr/sendmsg6: sendmsg deny (dgram) +sock_addr/sendmsg6: sendmsg dst IP = [::] (BSD'ism) (dgram) +sock_addr/sendmsg6: sock_sendmsg (dgram) +sock_addr/sendmsg6: sock_sendmsg [::] (BSD'ism) (dgram) +sock_addr/sendmsg6: sock_sendmsg deny (dgram) +sock_destroy/trace_tcp_destroy_sock +sock_fields +sockmap_listen/sockhash IPv4 TCP test_reuseport_mixed_groups +sockmap_listen/sockhash IPv4 TCP test_reuseport_select_connected +sockmap_listen/sockhash IPv4 UDP test_reuseport_mixed_groups +sockmap_listen/sockhash IPv4 UDP test_reuseport_select_connected +sockmap_listen/sockhash IPv6 TCP test_reuseport_mixed_groups +sockmap_listen/sockhash IPv6 TCP test_reuseport_select_connected +sockmap_listen/sockhash IPv6 UDP test_reuseport_mixed_groups +sockmap_listen/sockhash IPv6 UDP test_reuseport_select_connected +sockmap_listen/sockmap IPv4 TCP test_reuseport_mixed_groups +sockmap_listen/sockmap IPv4 TCP test_reuseport_select_connected +sockmap_listen/sockmap IPv4 UDP test_reuseport_mixed_groups +sockmap_listen/sockmap IPv4 UDP test_reuseport_select_connected +sockmap_listen/sockmap IPv6 TCP test_reuseport_mixed_groups +sockmap_listen/sockmap IPv6 TCP test_reuseport_select_connected +sockmap_listen/sockmap IPv6 UDP test_reuseport_mixed_groups +sockmap_listen/sockmap IPv6 UDP test_reuseport_select_connected +spin_lock +struct_ops_module/unsupported_ops +syscall +tailcalls/classifier_0 +tailcalls/classifier_1 +tailcalls/reject_tail_call_preempt_lock +tailcalls/reject_tail_call_rcu_lock +tailcalls/reject_tail_call_ref +tailcalls/reject_tail_call_spin_lock +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_hierarchy_2 +tailcalls/tailcall_bpf2bpf_hierarchy_3 +task_kfunc +task_local_storage/uptr_across_pages +task_local_storage/uptr_basic +task_local_storage/uptr_kptr_xchg +task_local_storage/uptr_map_failure_e2big +task_local_storage/uptr_map_failure_kstruct +task_local_storage/uptr_map_failure_size0 +task_local_storage/uptr_no_null_check +task_local_storage/uptr_obj_new +task_local_storage/uptr_update_failure +tc_bpf/tc_bpf_non_root +tc_redirect/tc_redirect_dtime +tcp_custom_syncookie +tcp_hdr_options +test_bpf_ma +test_global_funcs/arg_tag_ctx_kprobe +test_global_funcs/arg_tag_ctx_perf +test_global_funcs/arg_tag_ctx_raw_tp +test_global_funcs/global_func1 +test_global_funcs/global_func10 +test_global_funcs/global_func11 +test_global_funcs/global_func12 +test_global_funcs/global_func13 +test_global_funcs/global_func14 +test_global_funcs/global_func15 +test_global_funcs/global_func15_tricky_pruning +test_global_funcs/global_func17 +test_global_funcs/global_func3 +test_global_funcs/global_func5 +test_global_funcs/global_func6 +test_global_funcs/global_func7 +test_lsm/lsm_basic +test_profiler +test_strncmp/strncmp_bad_not_null_term_target +timer +timer_mim +token +tp_btf_nullable/handle_tp_btf_nullable_bare1 +tunnel +uprobe_multi_test/uprobe_sesison_return_2 +user_ringbuf/user_ringbuf_callback_bad_access1 +user_ringbuf/user_ringbuf_callback_bad_access2 +user_ringbuf/user_ringbuf_callback_const_ptr_to_dynptr_reg_off +user_ringbuf/user_ringbuf_callback_discard_dynptr +user_ringbuf/user_ringbuf_callback_invalid_return +user_ringbuf/user_ringbuf_callback_null_context_read +user_ringbuf/user_ringbuf_callback_null_context_write +user_ringbuf/user_ringbuf_callback_reinit_dynptr_mem +user_ringbuf/user_ringbuf_callback_reinit_dynptr_ringbuf +user_ringbuf/user_ringbuf_callback_submit_dynptr +user_ringbuf/user_ringbuf_callback_write_forbidden +verif_scale_pyperf100 +verif_scale_pyperf180 +verif_scale_pyperf600 +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_and/invalid_and_of_negative_number +verifier_and/invalid_range_check +verifier_arena/iter_maps2 +verifier_arena/iter_maps3 +verifier_array_access/a_read_only_array_1_2 +verifier_array_access/a_read_only_array_2_2 +verifier_array_access/a_write_only_array_1_2 +verifier_array_access/a_write_only_array_2_2 +verifier_array_access/an_array_with_a_constant_2 +verifier_array_access/an_array_with_a_register_2 +verifier_array_access/an_array_with_a_variable_2 +verifier_array_access/array_with_no_floor_check +verifier_array_access/with_a_invalid_max_check_1 +verifier_array_access/with_a_invalid_max_check_2 +verifier_basic_stack/invalid_fp_arithmetic +verifier_basic_stack/misaligned_read_from_stack +verifier_basic_stack/stack_out_of_bounds +verifier_bitfield_write +verifier_bits_iter/destroy_uninit +verifier_bits_iter/next_uninit +verifier_bits_iter/no_destroy +verifier_bounds/bounds_map_value_variant_1 +verifier_bounds/bounds_map_value_variant_2 +verifier_bounds/of_boundary_crossing_range_1 +verifier_bounds/of_boundary_crossing_range_2 +verifier_bounds/on_sign_extended_mov_test1 +verifier_bounds/on_sign_extended_mov_test2 +verifier_bounds/reg32_any_reg32_xor_3 +verifier_bounds/reg_any_reg_xor_3 +verifier_bounds/shift_of_maybe_negative_number +verifier_bounds/shift_with_64_bit_input +verifier_bounds/shift_with_oversized_count_operand +verifier_bounds/size_signed_32bit_overflow_test1 +verifier_bounds/size_signed_32bit_overflow_test2 +verifier_bounds/size_signed_32bit_overflow_test3 +verifier_bounds/size_signed_32bit_overflow_test4 +verifier_bounds/var_off_insn_off_test1 +verifier_bounds/var_off_insn_off_test2 +verifier_bounds_deduction/deducing_bounds_from_const_1 +verifier_bounds_deduction/deducing_bounds_from_const_10 +verifier_bounds_deduction/deducing_bounds_from_const_3 +verifier_bounds_deduction/deducing_bounds_from_const_5 +verifier_bounds_deduction/deducing_bounds_from_const_6 +verifier_bounds_deduction/deducing_bounds_from_const_7 +verifier_bounds_deduction/deducing_bounds_from_const_8 +verifier_bounds_deduction/deducing_bounds_from_const_9 +verifier_bounds_mix_sign_unsign/checks_mixing_signed_and_unsigned +verifier_bounds_mix_sign_unsign/signed_and_unsigned_positive_bounds +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_10 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_11 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_12 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_13 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_14 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_15 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_2 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_3 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_5 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_6 +verifier_bounds_mix_sign_unsign/signed_and_unsigned_variant_8 +verifier_btf_ctx_access/ctx_access_u32_pointer_reject_16 +verifier_btf_ctx_access/ctx_access_u32_pointer_reject_32 +verifier_btf_ctx_access/ctx_access_u32_pointer_reject_8 +verifier_cfg/conditional_loop +verifier_cfg/loop2_back_edge +verifier_cfg/loop_back_edge +verifier_cfg/out_of_range_jump +verifier_cfg/out_of_range_jump2 +verifier_cfg/uncond_loop_after_cond_jmp +verifier_cfg/uncond_loop_in_subprog_after_cond_jmp +verifier_cfg/unreachable +verifier_cfg/unreachable2 +verifier_cgroup_inv_retcode/with_invalid_return_code_test1 +verifier_cgroup_inv_retcode/with_invalid_return_code_test3 +verifier_cgroup_inv_retcode/with_invalid_return_code_test5 +verifier_cgroup_inv_retcode/with_invalid_return_code_test6 +verifier_cgroup_inv_retcode/with_invalid_return_code_test7 +verifier_cgroup_skb/data_meta_for_cgroup_skb +verifier_cgroup_skb/flow_keys_for_cgroup_skb +verifier_cgroup_skb/napi_id_for_cgroup_skb +verifier_cgroup_skb/tc_classid_for_cgroup_skb +verifier_cgroup_storage/cpu_cgroup_storage_access_1 +verifier_cgroup_storage/cpu_cgroup_storage_access_2 +verifier_cgroup_storage/cpu_cgroup_storage_access_3 +verifier_cgroup_storage/cpu_cgroup_storage_access_4 +verifier_cgroup_storage/cpu_cgroup_storage_access_5 +verifier_cgroup_storage/cpu_cgroup_storage_access_6 +verifier_cgroup_storage/invalid_cgroup_storage_access_1 +verifier_cgroup_storage/invalid_cgroup_storage_access_2 +verifier_cgroup_storage/invalid_cgroup_storage_access_3 +verifier_cgroup_storage/invalid_cgroup_storage_access_4 +verifier_cgroup_storage/invalid_cgroup_storage_access_5 +verifier_cgroup_storage/invalid_cgroup_storage_access_6 +verifier_const/bprm +verifier_const/tcx1 +verifier_const/tcx4 +verifier_const/tcx7 +verifier_const_or/not_bypass_stack_boundary_checks_1 +verifier_const_or/not_bypass_stack_boundary_checks_2 +verifier_ctx/context_stores_via_bpf_atomic +verifier_ctx/ctx_pointer_to_helper_1 +verifier_ctx/ctx_pointer_to_helper_2 +verifier_ctx/ctx_pointer_to_helper_3 +verifier_ctx/make_ptr_to_ctx_unusable +verifier_ctx/null_check_4_ctx_const +verifier_ctx/null_check_8_null_bind +verifier_ctx/or_null_check_3_1 +verifier_ctx_sk_msg/of_size_in_sk_msg +verifier_ctx_sk_msg/past_end_of_sk_msg +verifier_ctx_sk_msg/read_offset_in_sk_msg +verifier_d_path/d_path_reject +verifier_direct_packet_access/access_test15_spill_with_xadd +verifier_direct_packet_access/direct_packet_access_test3 +verifier_direct_packet_access/id_in_regsafe_bad_access +verifier_direct_packet_access/packet_access_test10_write_invalid +verifier_direct_packet_access/pkt_end_reg_bad_access +verifier_direct_packet_access/pkt_end_reg_both_accesses +verifier_direct_packet_access/test16_arith_on_data_end +verifier_direct_packet_access/test23_x_pkt_ptr_4 +verifier_direct_packet_access/test26_marking_on_bad_access +verifier_direct_packet_access/test28_marking_on_bad_access +verifier_direct_stack_access_wraparound +verifier_global_ptr_args +verifier_global_subprogs +verifier_helper_access_var_len/bitwise_and_jmp_wrong_max +verifier_helper_access_var_len/jmp_signed_no_min_check +verifier_helper_access_var_len/map_adjusted_jmp_wrong_max +verifier_helper_access_var_len/memory_map_jmp_wrong_max +verifier_helper_access_var_len/memory_stack_jmp_bounds_offset +verifier_helper_access_var_len/memory_stack_jmp_wrong_max +verifier_helper_access_var_len/ptr_to_mem_or_null_2 +verifier_helper_access_var_len/ptr_to_mem_or_null_8 +verifier_helper_access_var_len/ptr_to_mem_or_null_9 +verifier_helper_access_var_len/stack_jmp_no_max_check +verifier_helper_packet_access/cls_helper_fail_range_1 +verifier_helper_packet_access/cls_helper_fail_range_2 +verifier_helper_packet_access/cls_helper_fail_range_3 +verifier_helper_packet_access/packet_ptr_with_bad_range_1 +verifier_helper_packet_access/packet_ptr_with_bad_range_2 +verifier_helper_packet_access/packet_test2_unchecked_packet_ptr +verifier_helper_packet_access/ptr_with_too_short_range_1 +verifier_helper_packet_access/ptr_with_too_short_range_2 +verifier_helper_packet_access/test11_cls_unsuitable_helper_1 +verifier_helper_packet_access/test12_cls_unsuitable_helper_2 +verifier_helper_packet_access/test15_cls_helper_fail_sub +verifier_helper_packet_access/test20_pkt_end_as_input +verifier_helper_packet_access/test7_cls_unchecked_packet_ptr +verifier_helper_packet_access/to_packet_test21_wrong_reg +verifier_helper_restricted +verifier_helper_value_access/access_to_map_empty_range +verifier_helper_value_access/access_to_map_negative_range +verifier_helper_value_access/access_to_map_possibly_empty_range +verifier_helper_value_access/access_to_map_wrong_size +verifier_helper_value_access/bounds_check_using_bad_access_1 +verifier_helper_value_access/bounds_check_using_bad_access_2 +verifier_helper_value_access/check_using_s_bad_access_1 +verifier_helper_value_access/check_using_s_bad_access_2 +verifier_helper_value_access/const_imm_negative_range_adjustment_1 +verifier_helper_value_access/const_imm_negative_range_adjustment_2 +verifier_helper_value_access/const_reg_negative_range_adjustment_1 +verifier_helper_value_access/const_reg_negative_range_adjustment_2 +verifier_helper_value_access/imm_out_of_bound_1 +verifier_helper_value_access/imm_out_of_bound_2 +verifier_helper_value_access/imm_out_of_bound_range +verifier_helper_value_access/map_out_of_bound_range +verifier_helper_value_access/map_via_variable_empty_range +verifier_helper_value_access/reg_out_of_bound_1 +verifier_helper_value_access/reg_out_of_bound_2 +verifier_helper_value_access/reg_out_of_bound_range +verifier_helper_value_access/via_const_imm_empty_range +verifier_helper_value_access/via_const_reg_empty_range +verifier_helper_value_access/via_variable_no_max_check_1 +verifier_helper_value_access/via_variable_no_max_check_2 +verifier_helper_value_access/via_variable_wrong_max_check_1 +verifier_helper_value_access/via_variable_wrong_max_check_2 +verifier_int_ptr/arg_ptr_to_long_misaligned +verifier_int_ptr/to_long_size_sizeof_long +verifier_iterating_callbacks/bpf_loop_iter_limit_overflow +verifier_iterating_callbacks/check_add_const_3regs +verifier_iterating_callbacks/check_add_const_3regs_2if +verifier_iterating_callbacks/check_add_const_regsafe_off +verifier_iterating_callbacks/iter_limit_bug +verifier_iterating_callbacks/jgt_imm64_and_may_goto +verifier_iterating_callbacks/loop_detection +verifier_iterating_callbacks/may_goto_self +verifier_iterating_callbacks/unsafe_find_vma +verifier_iterating_callbacks/unsafe_for_each_map_elem +verifier_iterating_callbacks/unsafe_on_2nd_iter +verifier_iterating_callbacks/unsafe_on_zero_iter +verifier_iterating_callbacks/unsafe_ringbuf_drain +verifier_jeq_infer_not_null/unchanged_for_jeq_false_branch +verifier_jeq_infer_not_null/unchanged_for_jne_true_branch +verifier_kfunc_prog_types/cgrp_kfunc_raw_tp +verifier_kfunc_prog_types/cpumask_kfunc_raw_tp +verifier_kfunc_prog_types/task_kfunc_raw_tp +verifier_ld_ind/ind_check_calling_conv_r1 +verifier_ld_ind/ind_check_calling_conv_r2 +verifier_ld_ind/ind_check_calling_conv_r3 +verifier_ld_ind/ind_check_calling_conv_r4 +verifier_ld_ind/ind_check_calling_conv_r5 +verifier_leak_ptr/leak_pointer_into_ctx_1 +verifier_leak_ptr/leak_pointer_into_ctx_2 +verifier_linked_scalars +verifier_loops1/bounded_recursion +verifier_loops1/infinite_loop_in_two_jumps +verifier_loops1/infinite_loop_three_jump_trick +verifier_loops1/loop_after_a_conditional_jump +verifier_lsm/bool_retval_test3 +verifier_lsm/bool_retval_test4 +verifier_lsm/disabled_hook_test1 +verifier_lsm/disabled_hook_test2 +verifier_lsm/disabled_hook_test3 +verifier_lsm/errno_zero_retval_test4 +verifier_lsm/errno_zero_retval_test5 +verifier_lsm/errno_zero_retval_test6 +verifier_lwt/not_permitted_for_lwt_prog +verifier_lwt/packet_write_for_lwt_in +verifier_lwt/packet_write_for_lwt_out +verifier_lwt/tc_classid_for_lwt_in +verifier_lwt/tc_classid_for_lwt_out +verifier_lwt/tc_classid_for_lwt_xmit +verifier_map_in_map/invalid_inner_map_pointer +verifier_map_in_map/on_the_inner_map_pointer +verifier_map_ptr/bpf_map_ptr_write_rejected +verifier_map_ptr/read_non_existent_field_rejected +verifier_map_ptr/read_with_negative_offset_rejected +verifier_map_ptr_mixing +verifier_map_ret_val +verifier_meta_access/meta_access_test10 +verifier_meta_access/meta_access_test2 +verifier_meta_access/meta_access_test3 +verifier_meta_access/meta_access_test4 +verifier_meta_access/meta_access_test5 +verifier_meta_access/meta_access_test6 +verifier_meta_access/meta_access_test9 +verifier_netfilter_ctx/with_invalid_ctx_access_test1 +verifier_netfilter_ctx/with_invalid_ctx_access_test2 +verifier_netfilter_ctx/with_invalid_ctx_access_test3 +verifier_netfilter_ctx/with_invalid_ctx_access_test4 +verifier_netfilter_ctx/with_invalid_ctx_access_test5 +verifier_netfilter_retcode/with_invalid_return_code_test1 +verifier_netfilter_retcode/with_invalid_return_code_test4 +verifier_or_jmp32_k +verifier_prevent_map_lookup +verifier_raw_stack/bytes_spilled_regs_corruption_2 +verifier_raw_stack/load_bytes_invalid_access_1 +verifier_raw_stack/load_bytes_invalid_access_2 +verifier_raw_stack/load_bytes_invalid_access_3 +verifier_raw_stack/load_bytes_invalid_access_4 +verifier_raw_stack/load_bytes_invalid_access_5 +verifier_raw_stack/load_bytes_invalid_access_6 +verifier_raw_stack/load_bytes_negative_len_2 +verifier_raw_stack/load_bytes_spilled_regs_corruption +verifier_raw_stack/skb_load_bytes_negative_len +verifier_raw_stack/skb_load_bytes_zero_len +verifier_raw_tp_writable +verifier_ref_tracking +verifier_reg_equal/subreg_equality_2 +verifier_regalloc/regalloc_and_spill_negative +verifier_regalloc/regalloc_negative +verifier_regalloc/regalloc_src_reg_negative +verifier_ringbuf/ringbuf_invalid_reservation_offset_1 +verifier_ringbuf/ringbuf_invalid_reservation_offset_2 +verifier_runtime_jit +verifier_scalar_ids/check_ids_in_regsafe +verifier_scalar_ids/check_ids_in_regsafe_2 +verifier_scalar_ids/linked_regs_broken_link_2 +verifier_search_pruning/for_u32_spills_u64_fill +verifier_search_pruning/liveness_pruning_and_write_screening +verifier_search_pruning/short_loop1 +verifier_search_pruning/should_be_verified_nop_operation +verifier_search_pruning/tracking_for_u32_spill_fill +verifier_search_pruning/varlen_map_value_access_pruning +verifier_sock/bpf_sk_fullsock_skb_sk +verifier_sock/bpf_sk_release_skb_sk +verifier_sock/bpf_tcp_sock_skb_sk +verifier_sock/dst_port_byte_load_invalid +verifier_sock/dst_port_half_load_invalid_1 +verifier_sock/dst_port_half_load_invalid_2 +verifier_sock/invalidate_pkt_pointers_by_tail_call +verifier_sock/invalidate_pkt_pointers_from_global_func +verifier_sock/map_lookup_elem_smap_key +verifier_sock/map_lookup_elem_sockhash_key +verifier_sock/map_lookup_elem_sockmap_key +verifier_sock/no_null_check_on_ret_1 +verifier_sock/no_null_check_on_ret_2 +verifier_sock/of_bpf_skc_to_helpers +verifier_sock/post_bind4_read_mark +verifier_sock/post_bind4_read_src_ip6 +verifier_sock/post_bind6_read_src_ip4 +verifier_sock/sk_1_1_value_1 +verifier_sock/sk_no_skb_sk_check_1 +verifier_sock/sk_no_skb_sk_check_2 +verifier_sock/sk_sk_type_fullsock_field_1 +verifier_sock/skb_sk_beyond_last_field_1 +verifier_sock/skb_sk_beyond_last_field_2 +verifier_sock/skb_sk_no_null_check +verifier_sock/sock_create_read_src_port +verifier_sock_addr/bind4_bad_return_code +verifier_sock_addr/bind6_bad_return_code +verifier_sock_addr/connect4_bad_return_code +verifier_sock_addr/connect6_bad_return_code +verifier_sock_addr/connect_unix_bad_return_code +verifier_sock_addr/getpeername4_bad_return_code +verifier_sock_addr/getpeername6_bad_return_code +verifier_sock_addr/getpeername_unix_bad_return_code +verifier_sock_addr/getsockname4_bad_return_code +verifier_sock_addr/getsockname6_bad_return_code +verifier_sock_addr/getsockname_unix_unix_bad_return_code +verifier_sock_addr/recvmsg4_bad_return_code +verifier_sock_addr/recvmsg6_bad_return_code +verifier_sock_addr/recvmsg_unix_bad_return_code +verifier_sock_addr/sendmsg4_bad_return_code +verifier_sock_addr/sendmsg6_bad_return_code +verifier_sock_addr/sendmsg_unix_bad_return_code +verifier_sockmap_mutate/test_flow_dissector_update +verifier_sockmap_mutate/test_raw_tp_delete +verifier_sockmap_mutate/test_raw_tp_update +verifier_sockmap_mutate/test_sockops_update +verifier_spill_fill/_6_offset_to_skb_data +verifier_spill_fill/addr_offset_to_skb_data +verifier_spill_fill/check_corrupted_spill_fill +verifier_spill_fill/fill_32bit_after_spill_64bit_clear_id +verifier_spill_fill/spill_16bit_of_32bit_fail +verifier_spill_fill/spill_32bit_of_64bit_fail +verifier_spill_fill/u64_offset_to_skb_data +verifier_spill_fill/with_invalid_reg_offset_0 +verifier_spin_lock/call_within_a_locked_region +verifier_spin_lock/lock_test2_direct_ld_st +verifier_spin_lock/lock_test3_direct_ld_st +verifier_spin_lock/lock_test4_direct_ld_st +verifier_spin_lock/lock_test7_unlock_without_lock +verifier_spin_lock/reg_id_for_map_value +verifier_spin_lock/spin_lock_test6_missing_unlock +verifier_spin_lock/spin_lock_test8_double_lock +verifier_spin_lock/spin_lock_test9_different_lock +verifier_spin_lock/test11_ld_abs_under_lock +verifier_stack_ptr/load_bad_alignment_on_off +verifier_stack_ptr/load_bad_alignment_on_reg +verifier_stack_ptr/load_out_of_bounds_high +verifier_stack_ptr/load_out_of_bounds_low +verifier_stack_ptr/to_stack_check_high_4 +verifier_stack_ptr/to_stack_check_high_5 +verifier_stack_ptr/to_stack_check_high_6 +verifier_stack_ptr/to_stack_check_high_7 +verifier_stack_ptr/to_stack_check_low_3 +verifier_stack_ptr/to_stack_check_low_4 +verifier_stack_ptr/to_stack_check_low_5 +verifier_stack_ptr/to_stack_check_low_6 +verifier_stack_ptr/to_stack_check_low_7 +verifier_subprog_precision/callback_precise_return_fail +verifier_tailcall_jit +verifier_uninit +verifier_unpriv +verifier_unpriv_perf +verifier_value/store_of_cleared_call_register +verifier_value_illegal_alu +verifier_value_or_null/map_access_from_else_condition +verifier_value_or_null/map_value_or_null_1 +verifier_value_or_null/map_value_or_null_2 +verifier_value_or_null/map_value_or_null_3 +verifier_value_or_null/multiple_map_lookup_elem_calls +verifier_value_or_null/null_check_ids_in_regsafe +verifier_value_ptr_arith/access_known_scalar_value_ptr_2 +verifier_value_ptr_arith/access_unknown_scalar_value_ptr +verifier_value_ptr_arith/access_value_ptr_known_scalar +verifier_value_ptr_arith/access_value_ptr_unknown_scalar +verifier_value_ptr_arith/access_value_ptr_value_ptr_1 +verifier_value_ptr_arith/access_value_ptr_value_ptr_2 +verifier_value_ptr_arith/lower_oob_arith_test_1 +verifier_value_ptr_arith/to_leak_tainted_dst_reg +verifier_value_ptr_arith/unknown_scalar_value_ptr_4 +verifier_value_ptr_arith/value_ptr_known_scalar_2_1 +verifier_value_ptr_arith/value_ptr_known_scalar_3 +verifier_var_off/access_max_out_of_bound +verifier_var_off/access_min_out_of_bound +verifier_var_off/stack_write_clobbers_spilled_regs +verifier_var_off/variable_offset_ctx_access +verifier_var_off/variable_offset_stack_access_unbounded +verifier_var_off/zero_sized_access_max_out_of_bound +verifier_vfs_reject +verifier_xadd/xadd_w_check_unaligned_map +verifier_xadd/xadd_w_check_unaligned_pkt +verifier_xadd/xadd_w_check_unaligned_stack +verifier_xdp_direct_packet_access/corner_case_1_bad_access_1 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_10 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_11 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_12 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_13 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_14 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_15 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_16 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_2 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_3 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_4 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_5 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_6 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_7 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_8 +verifier_xdp_direct_packet_access/corner_case_1_bad_access_9 +verifier_xdp_direct_packet_access/end_mangling_bad_access_1 +verifier_xdp_direct_packet_access/end_mangling_bad_access_2 +verifier_xdp_direct_packet_access/pkt_data_bad_access_1_1 +verifier_xdp_direct_packet_access/pkt_data_bad_access_1_2 +verifier_xdp_direct_packet_access/pkt_data_bad_access_1_3 +verifier_xdp_direct_packet_access/pkt_data_bad_access_1_4 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_1 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_2 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_3 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_4 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_5 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_6 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_7 +verifier_xdp_direct_packet_access/pkt_data_bad_access_2_8 +verifier_xdp_direct_packet_access/pkt_end_bad_access_1_1 +verifier_xdp_direct_packet_access/pkt_end_bad_access_1_2 +verifier_xdp_direct_packet_access/pkt_end_bad_access_2_1 +verifier_xdp_direct_packet_access/pkt_end_bad_access_2_2 +verifier_xdp_direct_packet_access/pkt_end_bad_access_2_3 +verifier_xdp_direct_packet_access/pkt_end_bad_access_2_4 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_1_1 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_1_2 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_2_1 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_2_2 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_2_3 +verifier_xdp_direct_packet_access/pkt_meta_bad_access_2_4 +verify_pkcs7_sig +xdp_synproxy 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..0a4e021604cf1 --- /dev/null +++ b/ci/vmtest/configs/run-vmtest.env @@ -0,0 +1,43 @@ +#!/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[*]}") 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/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 317e99b9acc0a..abe8459cc7012 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -723,6 +723,8 @@ struct bpf_insn_aux_data { u16 const_reg_map_mask; u16 const_reg_subprog_mask; u32 const_reg_vals[10]; + /* instruction can access non-stack memory */ + bool non_stack_access; }; #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */ diff --git a/include/linux/filter.h b/include/linux/filter.h index 14acb2455746f..1ebcd247ef4e3 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -1210,13 +1210,17 @@ struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off, #ifdef CONFIG_BPF_SYSCALL struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, - const struct bpf_insn *patch, u32 len); + const struct bpf_insn *patch, u32 len, + s32 insn_off_in_patch); struct bpf_insn_aux_data *bpf_dup_insn_aux_data(struct bpf_verifier_env *env); void bpf_restore_insn_aux_data(struct bpf_verifier_env *env, struct bpf_insn_aux_data *orig_insn_aux); #else -static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, - const struct bpf_insn *patch, u32 len) +static inline struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, + u32 off, + const struct bpf_insn *patch, + u32 len, + s32 insn_off_in_patch) { return ERR_PTR(-ENOTSUPP); } diff --git a/kernel/bpf/Kconfig b/kernel/bpf/Kconfig index eb3de35734f09..e1f3850d2f5a0 100644 --- a/kernel/bpf/Kconfig +++ b/kernel/bpf/Kconfig @@ -17,6 +17,10 @@ config HAVE_CBPF_JIT config HAVE_EBPF_JIT bool +# KASAN support for JIT compiler +config HAVE_EBPF_JIT_KASAN + bool + # Used by archs to tell that they want the BPF JIT compiler enabled by # default for kernels that were compiled with BPF JIT support. config ARCH_WANT_DEFAULT_BPF_JIT @@ -101,4 +105,17 @@ config BPF_LSM If you are unsure how to answer this question, answer N. +config BPF_JIT_KASAN + bool + depends on HAVE_EBPF_JIT_KASAN + depends on KASAN_GENERIC + depends on KASAN_VMALLOC + depends on BPF_JIT + default y if KASAN + help + Makes JIT compiler insert generic outline KASAN checks in BPF + programs when they are inserted in the kernel. This feature is + automatically enabled if the needed set of KASAN and BPF + configuration options is enabled. + endmenu # "BPF subsystem" diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 47fe047ad30b8..cff0035f401a9 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1599,7 +1599,7 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp continue; if (env) - tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten); + tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten, rewritten - 1); else tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten); diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index d3be972714b22..b1f221b256ec5 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -152,12 +152,25 @@ static int get_callee_stack_depth(struct bpf_verifier_env *env, } #endif +static bool is_mem_insn(struct bpf_insn *insn) +{ + if (BPF_CLASS(insn->code) != BPF_ST && + BPF_CLASS(insn->code) != BPF_STX && + BPF_CLASS(insn->code) != BPF_LDX) + return false; + + return (BPF_MODE(insn->code) == BPF_MEM || + BPF_MODE(insn->code) == BPF_MEMSX || + BPF_MODE(insn->code) == BPF_ATOMIC); +} + /* single env->prog->insni[off] instruction was replaced with the range * insni[off, off + cnt). Adjust corresponding insn_aux_data by copying * [0, off) and [off, end) to new locations, so the patched range stays zero */ static void adjust_insn_aux_data(struct bpf_verifier_env *env, - struct bpf_prog *new_prog, u32 off, u32 cnt) + struct bpf_prog *new_prog, u32 off, u32 cnt, + s32 insn_off_in_patch) { struct bpf_insn_aux_data *data = env->insn_aux_data; struct bpf_insn *insn = new_prog->insnsi; @@ -171,8 +184,14 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env, */ data[off].zext_dst = insn_has_def32(insn + off + cnt - 1); - if (cnt == 1) + if (cnt == 1) { + /* A non-memory accessing insn could have been replaced by a + * memory accessing insn, systematically mark it for non-stack + * access + */ + data[off].non_stack_access = is_mem_insn(insn + off); return; + } prog_len = new_prog->len; memmove(data + off + cnt - 1, data + off, @@ -182,7 +201,19 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env, /* Expand insni[off]'s seen count to the patched range. */ data[i].seen = old_seen; data[i].zext_dst = insn_has_def32(insn + i); + if (i == off + insn_off_in_patch) { + data[i].non_stack_access = data[off + cnt - 1].non_stack_access; + data[off + cnt - 1].non_stack_access = false; + } else if (is_mem_insn(insn + i)) { + data[i].non_stack_access = true; + } } + /* + * Last slot instruction could be a newly generated + * BPF_ST/BPF_LDX/BPF_STX + */ + if (is_mem_insn(insn + off + cnt - 1) && insn_off_in_patch != cnt - 1) + data[off + cnt - 1].non_stack_access = true; /* * The indirect_target flag of the original instruction was moved to the last of the @@ -245,7 +276,8 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, - const struct bpf_insn *patch, u32 len) + const struct bpf_insn *patch, u32 len, + s32 insn_off_in_patch) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; @@ -269,7 +301,7 @@ struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, env->insn_aux_data[off].orig_idx); return NULL; } - adjust_insn_aux_data(env, new_prog, off, len); + adjust_insn_aux_data(env, new_prog, off, len, insn_off_in_patch); adjust_subprog_starts(env, off, len); adjust_insn_arrays(env, off, len); adjust_poke_descs(new_prog, off, len); @@ -668,7 +700,7 @@ int bpf_opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env, patch = zext_patch; patch_len = 2; apply_patch_buffer: - new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len); + new_prog = bpf_patch_insn_data(env, adj_idx, patch, patch_len, 0); if (!new_prog) return -ENOMEM; env->prog = new_prog; @@ -713,7 +745,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) insn_buf[cnt++] = BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_1, -subprogs[0].stack_depth); insn_buf[cnt++] = env->prog->insnsi[0]; - new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt, 1); if (!new_prog) return -ENOMEM; env->prog = new_prog; @@ -736,7 +768,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) verifier_bug(env, "prologue is too long"); return -EFAULT; } else if (cnt) { - new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, 0, insn_buf, cnt, + cnt - 1); if (!new_prog) return -ENOMEM; @@ -759,6 +792,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) for (i = 0; i < insn_cnt; i++, insn++) { bpf_convert_ctx_access_t convert_ctx_access; + s32 insn_off_in_patch = -1; u8 mode; if (env->insn_aux_data[i + delta].nospec) { @@ -768,7 +802,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) *patch++ = BPF_ST_NOSPEC(); *patch++ = *insn; cnt = patch - insn_buf; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -841,7 +876,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) *patch++ = *insn; *patch++ = BPF_ST_NOSPEC(); cnt = patch - insn_buf; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, 0); if (!new_prog) return -ENOMEM; @@ -971,7 +1007,8 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env) size * 8, 0); patch_insn_buf: - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt, + insn_off_in_patch); if (!new_prog) return -ENOMEM; @@ -1463,7 +1500,7 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat * ones for the hidden subprog. Hence all of the adjustment operations * in bpf_patch_insn_data are no-ops. */ - prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len); + prog = bpf_patch_insn_data(env, env->prog->len - 1, patch, len, 0); if (!prog) return -ENOMEM; env->prog = prog; @@ -1548,7 +1585,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) cnt = patch - insn_buf; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, 0); if (!new_prog) return -ENOMEM; @@ -1568,6 +1606,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) bool is_sdiv = isdiv && insn->off == 1; bool is_smod = !isdiv && insn->off == 1; struct bpf_insn *patch = insn_buf; + s32 insn_off_in_patch; if (is_sdiv) { /* [R,W]x sdiv 0 -> 0 @@ -1594,6 +1633,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); *patch++ = *insn; cnt = patch - insn_buf; + insn_off_in_patch = cnt - 1; } else if (is_smod) { /* [R,W]x mod 0 -> [R,W]x */ /* [R,W]x mod -1 -> 0 */ @@ -1610,6 +1650,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) *patch++ = BPF_MOV32_IMM(insn->dst_reg, 0); *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); *patch++ = *insn; + insn_off_in_patch = patch - insn_buf - 1; if (!is64) { *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); @@ -1625,12 +1666,14 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); *patch++ = *insn; cnt = patch - insn_buf; + insn_off_in_patch = cnt - 1; } else { /* [R,W]x mod 0 -> [R,W]x */ *patch++ = BPF_RAW_INSN((is64 ? BPF_JMP : BPF_JMP32) | BPF_JEQ | BPF_K, insn->src_reg, 0, 1 + (is64 ? 0 : 1), 0); *patch++ = *insn; + insn_off_in_patch = patch - insn_buf - 1; if (!is64) { *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); @@ -1639,7 +1682,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) cnt = patch - insn_buf; } - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, insn_off_in_patch); if (!new_prog) return -ENOMEM; @@ -1655,6 +1699,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) BPF_MODE(insn->code) == BPF_PROBE_MEMSX)) { struct bpf_insn *patch = insn_buf; u64 uaddress_limit = bpf_arch_uaddress_limit(); + s32 insn_off_in_patch; if (!uaddress_limit) goto next_insn; @@ -1665,11 +1710,13 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) *patch++ = BPF_ALU64_IMM(BPF_RSH, BPF_REG_AX, 32); *patch++ = BPF_JMP_IMM(BPF_JLE, BPF_REG_AX, uaddress_limit >> 32, 2); *patch++ = *insn; + insn_off_in_patch = patch - insn_buf - 1; *patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1); *patch++ = BPF_MOV64_IMM(insn->dst_reg, 0); cnt = patch - insn_buf; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, insn_off_in_patch); if (!new_prog) return -ENOMEM; @@ -1689,7 +1736,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) return -EFAULT; } - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, 0); if (!new_prog) return -ENOMEM; @@ -1742,7 +1790,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) *patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1); cnt = patch - insn_buf; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -1787,7 +1836,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[6] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off_cnt); cnt = 7; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, 3); if (!new_prog) return -ENOMEM; @@ -1808,7 +1858,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[3] = BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_AX, stack_off); cnt = 4; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, 2); if (!new_prog) return -ENOMEM; @@ -1829,7 +1880,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) if (cnt == 0) goto next_insn; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -1916,7 +1968,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) map)->index_mask); insn_buf[2] = *insn; cnt = 3; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -1949,7 +2002,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[2] = *insn; cnt = 3; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -1968,7 +2022,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[1] = *insn; cnt = 2; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, cnt - 1); if (!new_prog) return -ENOMEM; @@ -2008,8 +2063,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) return -EFAULT; } - new_prog = bpf_patch_insn_data(env, i + delta, - insn_buf, cnt); + new_prog = bpf_patch_insn_data( + env, i + delta, insn_buf, cnt, -1); if (!new_prog) return -ENOMEM; @@ -2092,7 +2147,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) cnt = 3; new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, - cnt); + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2120,7 +2175,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[0] = BPF_ALU32_REG(BPF_XOR, BPF_REG_0, BPF_REG_0); cnt = 1; #endif - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2138,7 +2194,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[2] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_0, 0); cnt = 3; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2172,7 +2229,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[cnt++] = BPF_JMP_A(1); insn_buf[cnt++] = BPF_MOV64_IMM(BPF_REG_0, -EINVAL); - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2204,7 +2262,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) cnt = 1; } - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2230,7 +2289,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) cnt = 2; } - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2246,7 +2306,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) /* Load IP address from ctx - 16 */ insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -16); - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + 1, -1); if (!new_prog) return -ENOMEM; @@ -2301,7 +2362,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[10] = BPF_MOV64_IMM(BPF_REG_0, -ENOENT); cnt = 11; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2319,7 +2381,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) insn_buf[1] = BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_0, 0); cnt = 2; - new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, + cnt, -1); if (!new_prog) return -ENOMEM; @@ -2387,7 +2450,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env) /* Copy first actual insn to preserve it */ insn_buf[cnt++] = env->prog->insnsi[subprog_start]; - new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, subprog_start, insn_buf, cnt, cnt - 1); if (!new_prog) return -ENOMEM; env->prog = prog = new_prog; @@ -2486,7 +2549,7 @@ static struct bpf_prog *inline_bpf_loop(struct bpf_verifier_env *env, insn_buf[cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_10, r8_offset); *total_cnt = cnt; - new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt); + new_prog = bpf_patch_insn_data(env, position, insn_buf, cnt, -1); if (!new_prog) return new_prog; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 233472a871be5..bc3b4771396a9 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3207,6 +3207,11 @@ static void mark_indirect_target(struct bpf_verifier_env *env, int idx) env->insn_aux_data[idx].indirect_target = true; } +static void mark_non_stack_access(struct bpf_verifier_env *env, int idx) +{ + env->insn_aux_data[idx].non_stack_access = true; +} + #define LR_FRAMENO_BITS 4 #define LR_SPI_BITS 6 #define LR_ENTRY_BITS (LR_SPI_BITS + LR_FRAMENO_BITS + 1) @@ -6400,6 +6405,10 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b else coerce_reg_to_size_sx(®s[value_regno], size); } + + if (!err && reg->type != PTR_TO_STACK) + mark_non_stack_access(env, insn_idx); + return err; } diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c index 0222a9a5d0761..815f3e04540ff 100644 --- a/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c +++ b/tools/testing/selftests/bpf/prog_tests/bpf_insn_array.c @@ -227,42 +227,6 @@ static void check_incorrect_index(void) check_mid_insn_index(); } -static int set_bpf_jit_harden(char *level) -{ - char old_level; - int err = -1; - int fd = -1; - - fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK); - if (fd < 0) { - ASSERT_FAIL("open .../bpf_jit_harden returned %d (errno=%d)", fd, errno); - return -1; - } - - err = read(fd, &old_level, 1); - if (err != 1) { - ASSERT_FAIL("read from .../bpf_jit_harden returned %d (errno=%d)", err, errno); - err = -1; - goto end; - } - - lseek(fd, 0, SEEK_SET); - - err = write(fd, level, 1); - if (err != 1) { - ASSERT_FAIL("write to .../bpf_jit_harden returned %d (errno=%d)", err, errno); - err = -1; - goto end; - } - - err = 0; - *level = old_level; -end: - if (fd >= 0) - close(fd); - return err; -} - static void check_blindness(void) { struct bpf_insn insns[] = { @@ -272,7 +236,7 @@ static void check_blindness(void) BPF_MOV64_IMM(BPF_REG_0, 1), BPF_EXIT_INSN(), }; - int prog_fd = -1, map_fd; + int prog_fd = -1, map_fd, ret; struct bpf_insn_array_value val = {}; char bpf_jit_harden = '@'; /* non-exizsting value */ int i; @@ -291,7 +255,8 @@ static void check_blindness(void) goto cleanup; bpf_jit_harden = '2'; - if (set_bpf_jit_harden(&bpf_jit_harden)) { + ret = set_bpf_jit_harden(&bpf_jit_harden); + if (!ASSERT_OK(ret, "set bpf_jit_harden")) { bpf_jit_harden = '@'; /* open, read or write failed => no write was done */ goto cleanup; } @@ -313,7 +278,8 @@ static void check_blindness(void) cleanup: /* restore the old one */ if (bpf_jit_harden != '@') - set_bpf_jit_harden(&bpf_jit_harden); + ASSERT_OK(set_bpf_jit_harden(&bpf_jit_harden), + "restore hardening configuration"); close(prog_fd); close(map_fd); diff --git a/tools/testing/selftests/bpf/prog_tests/kasan.c b/tools/testing/selftests/bpf/prog_tests/kasan.c new file mode 100644 index 0000000000000..2bc545e962595 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/kasan.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +/* + * Tests validating that KASAN reports are properly instrumented and + * generated on a wide variety of instructions. The running kernel needs + * kasan_multi_shot to run multiple kasan-generating subtests at once + */ +#include +#include +#include +#include +#include +#include +#include +#include "kasan.skel.h" +#include "kasan_harden.skel.h" + +#define SUBTEST_NAME_MAX_LEN 128 +#define PROG_NAME_MAX_LEN 128 + +#define MAX_LOG_SIZE (8 * 1024) +#define READ_CHUNK_SIZE 256 + +#define KASAN_PATTERN_SLAB_UAF "BUG: KASAN: slab-use-after-free " \ + "in bpf_prog_%02x%02x%02x%02x%02x%02x%02x%02x_%s" +#define KASAN_PATTERN_REPORT "%s of size %d at addr" + +static char klog_buffer[MAX_LOG_SIZE]; +static char record[MAX_LOG_SIZE]; + +struct test_spec { + char *prog_type; + bool is_write; + bool only_32_or_64; + bool needs_load_acq_store_rel; + bool skip_multi_size_testing; + bool skip_on_stack_testing; + int run_size; + bool expect_no_report; + bool rnd_hi32; +}; + +struct kasan_write_val { + __u8 data_1; + __u16 data_2; + __u32 data_4; + __u64 data_8; +}; + +struct test_ctx { + __u8 prog_tag[BPF_TAG_SIZE]; + struct bpf_object *obj; + int *access_size; + bool skip_load_acq_store_rel; + struct bpf_program *prog; + char prog_name[SUBTEST_NAME_MAX_LEN]; + int klog_fd; +}; + +static int open_kernel_logs(void) +{ + int fd; + + fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); + + return fd; +} + +static void skip_kernel_logs(int fd) +{ + lseek(fd, 0, SEEK_END); +} + +static int read_kernel_logs(int fd, char *buf, size_t max_len) +{ + size_t total = 0; + ssize_t n; + + buf[0] = '\0'; + while (1) { + char *msg, *eol; + size_t len; + + n = read(fd, record, sizeof(record) - 1); + if (n < 0) { + if (errno == EAGAIN) + break; + return n; + } + record[n] = '\0'; + + /* + * Each kmsg record starts with some metadata, separated + * from the actual content by a semi-colon + */ + msg = strchr(record, ';'); + if (!msg) + continue; + msg++; + eol = strchr(msg, '\n'); + if (eol) + *eol = '\0'; + + len = strlen(msg); + if (total + len + 2 > max_len) + break; + memcpy(buf + total, msg, len); + total += len; + buf[total++] = '\n'; + buf[total] = '\0'; + } + + return total; +} + +static int check_kasan_report_in_kernel_logs(char *buf, struct test_ctx *ctx, + bool is_write, int size) +{ + char access_log[READ_CHUNK_SIZE]; + char *kasan_report_start; + int nsize; + + nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_SLAB_UAF, + ctx->prog_tag[0], ctx->prog_tag[1], ctx->prog_tag[2], + ctx->prog_tag[3], ctx->prog_tag[4], ctx->prog_tag[5], + ctx->prog_tag[6], ctx->prog_tag[7], ctx->prog_name); + if (!ASSERT_GE(nsize, 0, "format kasan access header line")) + return nsize; + /* + * Searched kasan report is valid if + * - it contains the expected kasan pattern + * - the description of the faulty access is found somewhere + * after the header (not necessarily on the very next line, + * because other kernel messages may interleave) + * - faulty access properties match the tested type and size + */ + kasan_report_start = strstr(buf, access_log); + + if (!kasan_report_start) + return 1; + + nsize = snprintf(access_log, READ_CHUNK_SIZE, KASAN_PATTERN_REPORT, + is_write ? "Write" : "Read", size); + if (!ASSERT_GE(nsize, 0, "format kasan access report line")) + return nsize; + + if (!strstr(kasan_report_start, access_log)) + return 1; + + return 0; +} + +static void exec_subtest(struct test_ctx *ctx, struct test_spec *test, + int access_size, bool on_stack) +{ + LIBBPF_OPTS(bpf_test_run_opts, topts); + struct bpf_prog_info info; + uint8_t buf[ETH_HLEN] = {0}; + int ret, prog_fd; + __u32 info_len; + + ctx->prog = bpf_object__find_program_by_name(ctx->obj, + ctx->prog_name); + if (!ASSERT_OK_PTR(ctx->prog, "find test prog")) + return; + + info_len = sizeof(info); + memset(&info, 0, info_len); + prog_fd = bpf_program__fd(ctx->prog); + if (!ASSERT_OK_FD(prog_fd, "get prog fd")) + return; + ret = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len); + if (!ASSERT_OK(ret, "fetch loaded program info")) + return; + memcpy(ctx->prog_tag, info.tag, BPF_TAG_SIZE); + + skip_kernel_logs(ctx->klog_fd); + + topts.sz = sizeof(struct bpf_test_run_opts); + topts.data_size_in = ETH_HLEN; + topts.data_in = buf; + if (ctx->access_size) + *ctx->access_size = access_size; + ret = bpf_prog_test_run_opts(bpf_program__fd(ctx->prog), + &topts); + if (!ASSERT_OK(ret, "run prog")) + return; + + ret = read_kernel_logs(ctx->klog_fd, klog_buffer, MAX_LOG_SIZE); + if (!ASSERT_GE(ret, 0, "read kernel logs")) + return; + + ret = check_kasan_report_in_kernel_logs(klog_buffer, ctx, + test->is_write, access_size); + if (on_stack || test->expect_no_report) + ASSERT_NEQ(ret, 0, "no report should be generated"); + else + ASSERT_OK(ret, "report should be generated"); +} + +static void run_subtest_with_size_and_location(struct test_ctx *ctx, + struct test_spec *test, + int access_size, + bool on_stack) +{ + char subtest_name[SUBTEST_NAME_MAX_LEN]; + + if (test->skip_multi_size_testing) { + snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s", + test->prog_type); + strncpy(ctx->prog_name, test->prog_type, PROG_NAME_MAX_LEN); + } else { + snprintf(subtest_name, SUBTEST_NAME_MAX_LEN, "%s_%d_%s", + test->prog_type, access_size, + on_stack ? "on_stack" : "not_on_stack"); + snprintf(ctx->prog_name, PROG_NAME_MAX_LEN, "%s_%s", + test->prog_type, + on_stack ? "on_stack" : "not_on_stack"); + } + + if (!test__start_subtest(subtest_name)) + return; + + if (test->needs_load_acq_store_rel && ctx->skip_load_acq_store_rel) { + test__skip(); + return; + } + + exec_subtest(ctx, test, access_size, on_stack); +} + +static void run_subtest_with_size(struct test_ctx *ctx, struct test_spec *test, + int size) +{ + run_subtest_with_size_and_location(ctx, test, size, false); + if (!test->skip_on_stack_testing) + run_subtest_with_size_and_location(ctx, test, size, true); +} + +static void run_subtest(struct test_ctx *ctx, struct test_spec *test) +{ + if (test->skip_multi_size_testing) { + run_subtest_with_size(ctx, test, test->run_size); + return; + } + + if (!test->only_32_or_64) { + run_subtest_with_size(ctx, test, 1); + run_subtest_with_size(ctx, test, 2); + } + run_subtest_with_size(ctx, test, 4); + run_subtest_with_size(ctx, test, 8); +} + +static void run_blinding_subtest(void) +{ + struct test_spec blinding_spec = { + .prog_type = "st_blinded", + .is_write = true, + }; + char bpf_jit_harden = '2'; + struct kasan_harden *skel; + struct test_ctx *ctx; + + if (!test__start_subtest("st_blinded")) + return; + + ctx = calloc(1, sizeof(*ctx)); + if (!ASSERT_OK_PTR(ctx, "alloc blinding ctx")) + return; + ctx->klog_fd = -1; + + if (set_bpf_jit_harden(&bpf_jit_harden)) + goto free_ctx; + + skel = kasan_harden__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load blinded prog")) + goto restore; + + ctx->klog_fd = open_kernel_logs(); + if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs")) + goto destroy; + + ctx->obj = skel->obj; + strncpy(ctx->prog_name, "st_blinded", PROG_NAME_MAX_LEN); + + exec_subtest(ctx, &blinding_spec, 1, false); + +destroy: + close(ctx->klog_fd); + kasan_harden__destroy(skel); +restore: + set_bpf_jit_harden(&bpf_jit_harden); +free_ctx: + free(ctx); +} + +static struct test_spec tests[] = { + { + .prog_type = "st", + .is_write = true + }, + { + .prog_type = "stx", + .is_write = true + }, + { + .prog_type = "ldx", + .is_write = false + }, + { + .prog_type = "simple_atomic", + .is_write = true, + .only_32_or_64 = true + }, + { + .prog_type = "load_acquire", + .is_write = false, + .needs_load_acq_store_rel = true + }, + { + .prog_type = "store_release", + .is_write = true, + .needs_load_acq_store_rel = true + }, + { + .prog_type = "ldx_patched", + .is_write = false, + .skip_multi_size_testing = true, + .skip_on_stack_testing = true, + .run_size = 4, + .rnd_hi32 = true + }, + { + .prog_type = "ldx_patched_on_stack", + .is_write = false, + .skip_multi_size_testing = true, + .skip_on_stack_testing = true, + .run_size = 4, + .expect_no_report = true, + .rnd_hi32 = true + }, + { + .prog_type = "verifier_paths_stack_and_non_stack", + .is_write = true, + .skip_multi_size_testing = true, + .skip_on_stack_testing = true, + .run_size = 1 + } +}; + +void test_kasan(void) +{ + struct kasan_write_val val; + struct test_spec *test; + struct test_ctx *ctx; + struct kasan *skel; + __u32 key = 0; + int i, ret; + + ctx = calloc(1, sizeof(struct test_ctx)); + if (!ASSERT_OK_PTR(ctx, "alloc test ctx")) + return; + + if (!is_jit_enabled() || !get_kasan_jit_enabled() || + !get_kasan_multi_shot_enabled()) { + test__skip(); + goto end; + } + + skel = kasan__open(); + if (!ASSERT_OK_PTR(skel, "open prog")) + goto end; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + struct bpf_program *prog; + + if (!tests[i].rnd_hi32) + continue; + + prog = bpf_object__find_program_by_name(skel->obj, + tests[i].prog_type); + if (!ASSERT_OK_PTR(prog, "find rnd_hi32 prog")) + goto destroy; + bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32); + } + + if (!ASSERT_OK(kasan__load(skel), "load prog")) + goto destroy; + + ctx->obj = skel->obj; + ctx->access_size = &skel->bss->access_size; + ctx->skip_load_acq_store_rel = skel->data->skip_load_acq_store_rel_tests; + + ctx->klog_fd = open_kernel_logs(); + if (!ASSERT_OK_FD(ctx->klog_fd, "open kernel logs")) + goto destroy; + + /* Fill map with recognizable values */ + ret = bpf_map__lookup_elem(skel->maps.test_map, &key, sizeof(key), + &val, sizeof(val), 0); + if (!ASSERT_OK(ret, "get map")) + goto close; + val.data_1 = 0xAA; + val.data_2 = 0xBBBB; + val.data_4 = 0xCCCCCCCC; + val.data_8 = 0xDDDDDDDDDDDDDDDD; + ret = bpf_map__update_elem(skel->maps.test_map, &key, sizeof(key), + &val, sizeof(val), 0); + if (!ASSERT_OK(ret, "set map")) + goto close; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + test = &tests[i]; + run_subtest(ctx, test); + } + + /* + * Blinding subtest is handled differently as it needs the + * corresponding program to be loaded with bpf_jit_harden raised + */ + run_blinding_subtest(); + +close: + close(ctx->klog_fd); +destroy: + kasan__destroy(skel); +end: + free(ctx); +} diff --git a/tools/testing/selftests/bpf/progs/kasan.c b/tools/testing/selftests/bpf/progs/kasan.c new file mode 100644 index 0000000000000..82ddce3059aee --- /dev/null +++ b/tools/testing/selftests/bpf/progs/kasan.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include +#include +#include +#include "bpf_misc.h" +#include + +extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym; +extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym; + +int access_size; + +struct kasan_write_val { + __u8 data_1; + __u16 data_2; + __u32 data_4; + __u64 data_8; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct kasan_write_val); +} test_map SEC(".maps"); + +SEC("tcx/ingress") +int st_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + val.data_1 = 0xAA; + break; + case 2: + val.data_2 = 0xAA; + break; + case 4: + val.data_4 = 0xAA; + break; + case 8: + val.data_8 = 0xAA; + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int st_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + val->data_1 = 0xAA; + break; + case 2: + val->data_2 = 0xAA; + break; + case 4: + val->data_4 = 0xAA; + break; + case 8: + val->data_8 = 0xAA; + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int stx_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + val.data_1 = access_size; + break; + case 2: + val.data_2 = access_size; + break; + case 4: + val.data_4 = access_size; + break; + case 8: + val.data_8 = access_size; + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int stx_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + val->data_1 = access_size; + break; + case 2: + val->data_2 = access_size; + break; + case 4: + val->data_4 = access_size; + break; + case 8: + val->data_8 = access_size; + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int ldx_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __sink(val.data_1); + break; + case 2: + __sink(val.data_2); + break; + case 4: + __sink(val.data_4); + break; + case 8: + __sink(val.data_8); + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int ldx_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __sink(val->data_1); + break; + case 2: + __sink(val->data_2); + break; + case 4: + __sink(val->data_4); + break; + case 8: + __sink(val->data_8); + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int ldx_patched(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + __sink(val->data_4); + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + + return 0; +} + +SEC("tcx/ingress") +int ldx_patched_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + __sink(val.data_4); + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + + return 0; +} + +SEC("tcx/ingress") +int simple_atomic_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 4: + __sync_fetch_and_add(&val.data_4, 4); + break; + case 8: + __sync_fetch_and_add(&val.data_8, 8); + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int simple_atomic_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 4: + __sync_fetch_and_add(&val->data_4, 4); + break; + case 8: + __sync_fetch_and_add(&val->data_8, 8); + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} + +#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL +bool skip_load_acq_store_rel_tests SEC(".data") = 0; + +SEC("tcx/ingress") +int load_acquire_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __atomic_load_n(&val.data_1, __ATOMIC_ACQUIRE); + break; + case 2: + __atomic_load_n(&val.data_2, __ATOMIC_ACQUIRE); + break; + case 4: + __atomic_load_n(&val.data_4, __ATOMIC_ACQUIRE); + break; + case 8: + __atomic_load_n(&val.data_8, __ATOMIC_ACQUIRE); + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int load_acquire_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __atomic_load_n(&val->data_1, __ATOMIC_ACQUIRE); + break; + case 2: + __atomic_load_n(&val->data_2, __ATOMIC_ACQUIRE); + break; + case 4: + __atomic_load_n(&val->data_4, __ATOMIC_ACQUIRE); + break; + case 8: + __atomic_load_n(&val->data_8, __ATOMIC_ACQUIRE); + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int store_release_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val val; + + bpf_kfunc_kasan_poison(&val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __atomic_store_n(&val.data_1, 0xAA, __ATOMIC_RELEASE); + break; + case 2: + __atomic_store_n(&val.data_2, 0xBBBB, __ATOMIC_RELEASE); + break; + case 4: + __atomic_store_n(&val.data_4, 0xCCCCCCCC, __ATOMIC_RELEASE); + break; + case 8: + __atomic_store_n(&val.data_8, 0xDDDDDDDDDDDDDDDD, + __ATOMIC_RELEASE); + break; + } + bpf_kfunc_kasan_unpoison(&val, sizeof(struct kasan_write_val)); + return 0; +} + +SEC("tcx/ingress") +int store_release_not_on_stack(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + switch (access_size) { + case 1: + __atomic_store_n(&val->data_1, 0xAA, __ATOMIC_RELEASE); + break; + case 2: + __atomic_store_n(&val->data_2, 0xBBBB, __ATOMIC_RELEASE); + break; + case 4: + __atomic_store_n(&val->data_4, 0xCCCCCCCC, __ATOMIC_RELEASE); + break; + case 8: + __atomic_store_n(&val->data_8, 0xDDDDDDDDDDDDDDDD, + __ATOMIC_RELEASE); + break; + } + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + return 0; +} +#else +bool skip_load_acq_store_rel_tests SEC(".data") = 1; +#endif + +SEC("tcx/ingress") +int verifier_paths_stack_and_non_stack(struct __sk_buff *skb) +{ + struct kasan_write_val stack_val = {}; + struct kasan_write_val *val; + void *ptr; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + if (access_size) + ptr = val; + else + ptr = &stack_val; + + bpf_kfunc_kasan_poison(val, sizeof(*val)); + *(__u8 *)ptr = 0xAA; + bpf_kfunc_kasan_unpoison(val, sizeof(*val)); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/kasan_harden.c b/tools/testing/selftests/bpf/progs/kasan_harden.c new file mode 100644 index 0000000000000..808d7da644216 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/kasan_harden.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +#include +#include +#include + +extern void bpf_kfunc_kasan_poison(void *mem, __u32 mem__sz) __ksym; +extern void bpf_kfunc_kasan_unpoison(void *mem, __u32 mem__sz) __ksym; + +struct kasan_write_val { + __u8 data_1; + __u16 data_2; + __u32 data_4; + __u64 data_8; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct kasan_write_val); +} test_map SEC(".maps"); + +SEC("tcx/ingress") +int st_blinded(struct __sk_buff *skb) +{ + struct kasan_write_val *val; + __u32 key = 0; + + val = bpf_map_lookup_elem(&test_map, &key); + if (!val) + return 0; + + bpf_kfunc_kasan_poison(val, sizeof(struct kasan_write_val)); + val->data_1 = 0xAA; + bpf_kfunc_kasan_unpoison(val, sizeof(struct kasan_write_val)); + + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 30f1cd23093cb..5d6a834f7d757 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -271,6 +271,26 @@ __bpf_kfunc void bpf_kfunc_put_default_trusted_ptr_test(struct prog_test_member */ } +#ifdef CONFIG_BPF_JIT_KASAN + +extern void kasan_poison(const void *addr, size_t size, u8 value, bool init); + +#define KASAN_SLAB_FREE 0xFB + +__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) +{ + kasan_poison(mem, mem__sz, KASAN_SLAB_FREE, false); +} + +__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) +{ + kasan_poison(mem, mem__sz, 0x00, false); +} +#else +__bpf_kfunc void bpf_kfunc_kasan_poison(void *mem, u32 mem__sz) { } +__bpf_kfunc void bpf_kfunc_kasan_unpoison(void *mem, u32 mem__sz) { } +#endif + __bpf_kfunc struct bpf_testmod_ctx * bpf_testmod_ctx_create(int *err) { @@ -740,6 +760,8 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1) BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2) BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test); BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test); +BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison) +BTF_ID_FLAGS(func, bpf_kfunc_kasan_unpoison) BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids) BTF_ID_LIST(bpf_testmod_dtor_ids) diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c index c970e7793dfcb..737f668b35e23 100644 --- a/tools/testing/selftests/bpf/testing_helpers.c +++ b/tools/testing/selftests/bpf/testing_helpers.c @@ -519,6 +519,38 @@ bool is_jit_enabled(void) return enabled; } +int set_bpf_jit_harden(char *level) +{ + char old_level; + int err = -1; + int fd = -1; + + fd = open("/proc/sys/net/core/bpf_jit_harden", O_RDWR | O_NONBLOCK); + if (fd < 0) + return -1; + + err = read(fd, &old_level, 1); + if (err != 1) { + err = -1; + goto end; + } + + lseek(fd, 0, SEEK_SET); + + err = write(fd, level, 1); + if (err != 1) { + err = -1; + goto end; + } + + err = 0; + *level = old_level; +end: + if (fd >= 0) + close(fd); + return err; +} + int stack_mprotect(void) { void *buf; diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h index 2edc6fb7fc521..e00642afe86f4 100644 --- a/tools/testing/selftests/bpf/testing_helpers.h +++ b/tools/testing/selftests/bpf/testing_helpers.h @@ -59,6 +59,7 @@ struct bpf_insn; int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt); int testing_prog_flags(void); bool is_jit_enabled(void); +int set_bpf_jit_harden(char *level); int stack_mprotect(void); #endif /* __TESTING_HELPERS_H */ diff --git a/tools/testing/selftests/bpf/unpriv_helpers.c b/tools/testing/selftests/bpf/unpriv_helpers.c index f997d7ec8fd08..95be839378837 100644 --- a/tools/testing/selftests/bpf/unpriv_helpers.c +++ b/tools/testing/selftests/bpf/unpriv_helpers.c @@ -142,3 +142,13 @@ bool get_unpriv_disabled(void) } return mitigations_off; } + +bool get_kasan_jit_enabled(void) +{ + return config_contains("CONFIG_BPF_JIT_KASAN=y") == 1; +} + +bool get_kasan_multi_shot_enabled(void) +{ + return cmdline_contains("kasan_multi_shot"); +} diff --git a/tools/testing/selftests/bpf/unpriv_helpers.h b/tools/testing/selftests/bpf/unpriv_helpers.h index 151f673296652..a7ceb51577cdc 100644 --- a/tools/testing/selftests/bpf/unpriv_helpers.h +++ b/tools/testing/selftests/bpf/unpriv_helpers.h @@ -5,3 +5,5 @@ #define UNPRIV_SYSCTL "kernel/unprivileged_bpf_disabled" bool get_unpriv_disabled(void); +bool get_kasan_jit_enabled(void); +bool get_kasan_multi_shot_enabled(void);