Something went wrong. Try again.
What happens if I push all of nixpkgs
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310#! /usr/bin/env nix-shell#! nix-shell -i python -p "python3.withPackages (ps: [ps.pygithub ps.packaging])" git gnupg
# This is automatically called by ../update.sh.
from __future__ import annotations
import jsonimport osimport reimport subprocessimport sysfrom dataclasses import dataclassfrom pathlib import Pathfrom tempfile import TemporaryDirectoryfrom typing import ( Dict, Iterator, List, Optional, Sequence, Tuple, TypedDict, Union,)
from github import Githubfrom github.GitRelease import GitRelease
from packaging.version import parse as parse_version, Version
VersionComponent = Union[int, str]Version = List[VersionComponent]
PatchData = TypedDict("PatchData", {"name": str, "url": str, "sha256": str, "extra": str})Patch = TypedDict("Patch", { "patch": PatchData, "version": str, "sha256": str,})
def read_min_kernel_branch() -> List[str]: with open(NIXPKGS_KERNEL_PATH / "kernels-org.json") as f: return list(parse_version(sorted(json.load(f).keys())[0]).release)
@dataclassclass ReleaseInfo: version: Version release: GitRelease
HERE = Path(__file__).resolve().parentNIXPKGS_KERNEL_PATH = HERE.parentNIXPKGS_PATH = HERE.parents[4]HARDENED_GITHUB_REPO = "anthraxx/linux-hardened"HARDENED_TRUSTED_KEY = HERE / "anthraxx.asc"HARDENED_PATCHES_PATH = HERE / "patches.json"MIN_KERNEL_VERSION: Version = read_min_kernel_branch()
def run(*args: Union[str, Path]) -> subprocess.CompletedProcess[bytes]: try: return subprocess.run( args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) except subprocess.CalledProcessError as err: print( f"error: `{err.cmd}` failed unexpectedly\n" f"status code: {err.returncode}\n" f"stdout:\n{err.stdout.strip()}\n" f"stderr:\n{err.stderr.strip()}", file=sys.stderr, ) sys.exit(1)
def nix_prefetch_url(url: str) -> Tuple[str, Path]: output = run("nix-prefetch-url", "--print-path", url).stdout sha256, path = output.strip().split("\n") return sha256, Path(path)
def verify_openpgp_signature( *, name: str, trusted_key: Path, sig_path: Path, data_path: Path,) -> bool: with TemporaryDirectory(suffix=".nixpkgs-gnupg-home") as gnupg_home_str: gnupg_home = Path(gnupg_home_str) run("gpg", "--homedir", gnupg_home, "--import", trusted_key) keyring = gnupg_home / "pubring.kbx" try: subprocess.run( ("gpgv", "--keyring", keyring, sig_path, data_path), check=True, stderr=subprocess.PIPE, encoding="utf-8", ) return True except subprocess.CalledProcessError as err: print( f"error: signature for {name} failed to verify!", file=sys.stderr, ) print(err.stderr, file=sys.stderr, end="") return False
def fetch_patch(*, name: str, release_info: ReleaseInfo) -> Optional[Patch]: release = release_info.release extra = f'-{release_info.version[-1]}'
def find_asset(filename: str) -> str: try: it: Iterator[str] = ( asset.browser_download_url for asset in release.get_assets() if asset.name == filename ) return next(it) except StopIteration: raise KeyError(filename)
patch_filename = f"{name}.patch" try: patch_url = find_asset(patch_filename) sig_url = find_asset(patch_filename + ".sig") except KeyError: print(f"error: {patch_filename}{{,.sig}} not present", file=sys.stderr) return None
sha256, patch_path = nix_prefetch_url(patch_url) _, sig_path = nix_prefetch_url(sig_url) sig_ok = verify_openpgp_signature( name=name, trusted_key=HARDENED_TRUSTED_KEY, sig_path=sig_path, data_path=patch_path, ) if not sig_ok: return None
kernel_ver = re.sub(r"v?(.*)(-hardened[\d]+)$", r'\1', release_info.release.tag_name) major = kernel_ver.split('.')[0] sha256_kernel, _ = nix_prefetch_url(f"mirror://kernel/linux/kernel/v{major}.x/linux-{kernel_ver}.tar.xz")
return Patch( patch=PatchData(name=patch_filename, url=patch_url, sha256=sha256, extra=extra), version=kernel_ver, sha256=sha256_kernel )
def parse_version(version_str: str) -> Version: # There have been two variants v6.10[..] and 6.10[..], drop the v version_str_without_v = version_str[1:] if not version_str[0].isdigit() else version_str version: Version = []
for component in re.split(r'\.|\-', version_str_without_v): try: version.append(int(component)) except ValueError: version.append(component) return version
def version_string(version: Version) -> str: return ".".join(str(component) for component in version)
def major_kernel_version_key(kernel_version: Version) -> str: return version_string(kernel_version[:-1])
def commit_patches(*, kernel_key: str, message: str) -> None: new_patches_path = HARDENED_PATCHES_PATH.with_suffix(".new") with open(new_patches_path, "w") as new_patches_file: json.dump(patches, new_patches_file, indent=4, sort_keys=True) new_patches_file.write("\n") os.rename(new_patches_path, HARDENED_PATCHES_PATH) message = f"linux/hardened/patches/{kernel_key}: {message}" print(message) if os.environ.get("COMMIT"): run( "git", "-C", NIXPKGS_PATH, "commit", f"--message={message}", HARDENED_PATCHES_PATH, )
# Load the existing patches.patches: Dict[str, Patch]with open(HARDENED_PATCHES_PATH) as patches_file: patches = json.load(patches_file)
# Get the set of currently packaged kernel versions.kernel_versions = {}with open(NIXPKGS_KERNEL_PATH / "kernels-org.json") as kernel_versions_json: kernel_versions = json.load(kernel_versions_json) for kernel_branch_str in kernel_versions: if kernel_branch_str == "testing": continue kernel_branch = [int(i) for i in kernel_branch_str.split(".")] if kernel_branch < MIN_KERNEL_VERSION: continue kernel_version = [int(i) for i in kernel_versions[kernel_branch_str]["version"].split(".")] kernel_versions[kernel_branch_str] = kernel_version
# Remove patches for unpackaged kernel versions.for kernel_key in sorted(patches.keys() - kernel_versions.keys()): del patches[kernel_key] commit_patches(kernel_key=kernel_key, message="remove")
g = Github(os.environ.get("GITHUB_TOKEN"))repo = g.get_repo(HARDENED_GITHUB_REPO)failures = False
# Match each kernel version with the best patch version.releases = {}i = 0for release in repo.get_releases(): # Dirty workaround to make sure that we don't run into issues because # GitHub's API only allows fetching the last 1000 releases. # It's not reliable to exit earlier because not every kernel minor may # have hardened patches, hence the naive search below. i += 1 if i > 100: break
version = parse_version(release.tag_name) # needs to look like e.g. 5.6.3-hardened1 if len(version) < 4: continue
if not (isinstance(version[-2], int)): continue
kernel_version = version[:-1]
kernel_key = major_kernel_version_key(kernel_version) try: packaged_kernel_version = kernel_versions[kernel_key] except KeyError: continue
release_info = ReleaseInfo(version=version, release=release)
if kernel_version == packaged_kernel_version: releases[kernel_key] = release_info else: # Fall back to the latest patch for this major kernel version, # skipping patches for kernels newer than the packaged one. if '.'.join(str(x) for x in kernel_version) > '.'.join(str(x) for x in packaged_kernel_version): continue elif ( kernel_key not in releases or releases[kernel_key].version < version ): releases[kernel_key] = release_info
# Update hardened-patches.json for each release.for kernel_key in sorted(releases.keys()): release_info = releases[kernel_key] release = release_info.release version = release_info.version version_str = release.tag_name name = f"linux-hardened-{version_str}"
old_version: Optional[Version] = None old_version_str: Optional[str] = None update: bool try: old_filename = patches[kernel_key]["patch"]["name"] old_version_str = old_filename.replace("linux-hardened-", "").replace( ".patch", "" ) old_version = parse_version(old_version_str) update = old_version < version except KeyError: update = True
if update: patch = fetch_patch(name=name, release_info=release_info) if patch is None: failures = True else: patches[kernel_key] = patch if old_version: message = f"{old_version_str} -> {version_str}" else: message = f"init at {version_str}" commit_patches(kernel_key=kernel_key, message=message)
missing_kernel_versions = kernel_versions.keys() - patches.keys()
if missing_kernel_versions: print( f"warning: no patches for kernel versions " + ", ".join(missing_kernel_versions), file=sys.stderr, )
if failures: sys.exit(1)