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