about summary refs log tree commit diff
path: root/pkgs/os-specific
diff options
context:
space:
mode:
authorEmily <vcs@emily.moe>2020-04-23 18:43:44 +0100
committerEmily <vcs@emily.moe>2020-04-23 18:50:26 +0100
commit2c1db9649e66e9399fa572d3bb0da67c6e6e1962 (patch)
treeac0e44212792dbe015dd4c6b76d9404237650de7 /pkgs/os-specific
parentb16a5e2847b4e78a29be9fb2d157706f4ca84083 (diff)
linux_*_hardened: index patches by major kernel version
This will avoid breaking the build whenever a non-major kernel update
happens. In the update script, we map each kernel version to the latest
patch for the latest kernel version less than or equal to what we
have packaged.
Diffstat (limited to 'pkgs/os-specific')
-rw-r--r--pkgs/os-specific/linux/kernel/hardened-patches.json30
-rw-r--r--pkgs/os-specific/linux/kernel/patches.nix13
-rwxr-xr-xpkgs/os-specific/linux/kernel/update-hardened.py151
3 files changed, 108 insertions, 86 deletions
diff --git a/pkgs/os-specific/linux/kernel/hardened-patches.json b/pkgs/os-specific/linux/kernel/hardened-patches.json
index f916ad13488fc..eecb27cdb6696 100644
--- a/pkgs/os-specific/linux/kernel/hardened-patches.json
+++ b/pkgs/os-specific/linux/kernel/hardened-patches.json
@@ -1,27 +1,27 @@
 {
-    "4.14.176": {
+    "4.14": {
+        "name": "linux-hardened-4.14.176.a.patch",
         "sha256": "0pr3m2j63mc746fcbzg1hlwv85im9f87qkl6r4033gwnpa9brcgk",
-        "url": "https://github.com/anthraxx/linux-hardened/releases/download/4.14.176.a/linux-hardened-4.14.176.a.patch",
-        "version_suffix": "a"
+        "url": "https://github.com/anthraxx/linux-hardened/releases/download/4.14.176.a/linux-hardened-4.14.176.a.patch"
     },
-    "4.19.117": {
+    "4.19": {
+        "name": "linux-hardened-4.19.117.a.patch",
         "sha256": "0c8dvh49nzypxwvsls10i896smvpdrk40x8ybljb3qk3r8j7niaw",
-        "url": "https://github.com/anthraxx/linux-hardened/releases/download/4.19.117.a/linux-hardened-4.19.117.a.patch",
-        "version_suffix": "a"
+        "url": "https://github.com/anthraxx/linux-hardened/releases/download/4.19.117.a/linux-hardened-4.19.117.a.patch"
     },
-    "5.4.35": {
+    "5.4": {
+        "name": "linux-hardened-5.4.34.a.patch",
         "sha256": "1xwpqr9nzpjg837b3wnzb8fmrl2g9rz8gz5yb55vnnllbzbz36v6",
-        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.4.34.a/linux-hardened-5.4.34.a.patch",
-        "version_suffix": "a"
+        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.4.34.a/linux-hardened-5.4.34.a.patch"
     },
-    "5.5.19": {
+    "5.5": {
+        "name": "linux-hardened-5.5.19.a.patch",
         "sha256": "1ya5nsfhr3nwz6qiz4pdhvm6k9mx1kr0prhdvhx3p40f1vk281sc",
-        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.5.19.a/linux-hardened-5.5.19.a.patch",
-        "version_suffix": "a"
+        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.5.19.a/linux-hardened-5.5.19.a.patch"
     },
-    "5.6.7": {
+    "5.6": {
+        "name": "linux-hardened-5.6.6.a.patch",
         "sha256": "0jiqh0frxirjbccgfdk007fca6r6n36n0pkqq4jszkckn59ayl7r",
-        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.6.6.a/linux-hardened-5.6.6.a.patch",
-        "version_suffix": "a"
+        "url": "https://github.com/anthraxx/linux-hardened/releases/download/5.6.6.a/linux-hardened-5.6.6.a.patch"
     }
 }
diff --git a/pkgs/os-specific/linux/kernel/patches.nix b/pkgs/os-specific/linux/kernel/patches.nix
index 69b0197d4e681..1c4af8c32a6fc 100644
--- a/pkgs/os-specific/linux/kernel/patches.nix
+++ b/pkgs/os-specific/linux/kernel/patches.nix
@@ -39,16 +39,9 @@
   };
 
   hardened = let
-    mkPatch = kernelVersion: patch: let
-      fullVersion = "${kernelVersion}.${patch.version_suffix}";
-      name = "linux-hardened-${fullVersion}";
-    in {
-      inherit name;
-      patch = fetchurl {
-        name = "${name}.patch";
-        inherit (patch) url sha256;
-        meta.maintainers = with lib.maintainers; [ emily ];
-      };
+    mkPatch = kernelVersion: src: {
+      name = lib.removeSuffix ".patch" src.name;
+      patch = fetchurl src;
     };
     patches = builtins.fromJSON (builtins.readFile ./hardened-patches.json);
   in lib.mapAttrs mkPatch patches;
diff --git a/pkgs/os-specific/linux/kernel/update-hardened.py b/pkgs/os-specific/linux/kernel/update-hardened.py
index 089e991d06bcb..7f6949653afc2 100755
--- a/pkgs/os-specific/linux/kernel/update-hardened.py
+++ b/pkgs/os-specific/linux/kernel/update-hardened.py
@@ -17,17 +17,7 @@ HERE = os.path.dirname(os.path.realpath(__file__))
 HARDENED_GITHUB_REPO = 'anthraxx/linux-hardened'
 HARDENED_TRUSTED_KEY = os.path.join(HERE, 'anthraxx.asc')
 HARDENED_PATCHES_PATH = os.path.join(HERE, 'hardened-patches.json')
-MIN_KERNEL = (4, 14)
-
-HARDENED_VERSION_RE = re.compile(r'''
-    (?P<kernel_version> [\d.]+) \.
-    (?P<version_suffix> [a-z]+)
-''', re.VERBOSE)
-
-def parse_version(version):
-    match = HARDENED_VERSION_RE.fullmatch(version)
-    if match:
-        return match.groups()
+MIN_KERNEL_VERSION = [4, 14]
 
 def run(*args, **kwargs):
     try:
@@ -78,11 +68,12 @@ def fetch_patch(*, name, release):
         except StopIteration:
             raise KeyError(filename)
 
+    patch_filename = f'{name}.patch'
     try:
-        patch_url = find_asset(f'{name}.patch')
-        sig_url = find_asset(f'{name}.patch.sig')
+        patch_url = find_asset(patch_filename)
+        sig_url = find_asset(patch_filename + '.sig')
     except KeyError:
-        print(f'error: {name}.patch{{,sig}} not present', file=sys.stderr)
+        print(f'error: {patch_filename}{{,.sig}} not present', file=sys.stderr)
         return None
 
     sha256, patch_path = nix_prefetch_url(patch_url)
@@ -97,16 +88,32 @@ def fetch_patch(*, name, release):
         return None
 
     return {
+        'name': patch_filename,
         'url': patch_url,
         'sha256': sha256,
     }
 
-def commit_patches(*, kernel_version, message):
+def parse_version(version_str):
+    version = []
+    for component in version_str.split('.'):
+        try:
+            version.append(int(component))
+        except ValueError:
+            version.append(component)
+    return version
+
+def version_string(version):
+    return '.'.join(str(component) for component in version)
+
+def major_kernel_version_key(kernel_version):
+    return version_string(kernel_version[:-1])
+
+def commit_patches(*, kernel_key, message):
     with open(HARDENED_PATCHES_PATH + '.new', 'w') as new_patches_file:
         json.dump(patches, new_patches_file, indent=4, sort_keys=True)
         new_patches_file.write('\n')
     os.rename(HARDENED_PATCHES_PATH + '.new', HARDENED_PATCHES_PATH)
-    message = f'linux/hardened-patches/{kernel_version}: {message}'
+    message = f'linux/hardened-patches/{kernel_key}: {message}'
     print(message)
     if os.environ.get('COMMIT'):
         run(
@@ -125,74 +132,96 @@ NIX_VERSION_RE = re.compile(r'''
 ''', re.VERBOSE)
 
 # Get the set of currently packaged kernel versions.
-kernel_versions = set()
+kernel_versions = {}
 for filename in os.listdir(HERE):
     filename_match = re.fullmatch(r'linux-(\d+)\.(\d+)\.nix', filename)
     if filename_match:
-        if tuple(int(v) for v in filename_match.groups()) < MIN_KERNEL:
-            continue
         with open(os.path.join(HERE, filename)) as nix_file:
             for nix_line in nix_file:
                 match = NIX_VERSION_RE.fullmatch(nix_line)
                 if match:
-                    kernel_versions.add(match.group('version'))
+                    kernel_version = parse_version(match.group('version'))
+                    if kernel_version < MIN_KERNEL_VERSION:
+                        continue
+                    kernel_key = major_kernel_version_key(kernel_version)
+                    kernel_versions[kernel_key] = kernel_version
 
-# Remove patches for old kernel versions.
-for kernel_version in patches.keys() - kernel_versions:
-    del patches[kernel_version]
-    commit_patches(kernel_version=kernel_version, message='remove')
+# Remove patches for unpackaged kernel versions.
+for kernel_key in sorted(patches.keys() - kernel_versions.keys()):
+    commit_patches(kernel_key=kernel_key, message='remove')
 
 g = Github(os.environ.get('GITHUB_TOKEN'))
 repo = g.get_repo(HARDENED_GITHUB_REPO)
-releases = repo.get_releases()
 
-found_kernel_versions = set()
 failures = False
 
-for release in releases:
-    remaining_kernel_versions = kernel_versions - found_kernel_versions
-
-    if not remaining_kernel_versions:
-        break
+# Match each kernel version with the best patch version.
+releases = {}
+for release in repo.get_releases():
+    version = parse_version(release.tag_name)
+    # needs to look like e.g. 5.6.3.a
+    if len(version) < 4:
+        continue
 
-    version = release.tag_name
-    name = f'linux-hardened-{version}'
-    version_info = parse_version(version)
-    if not version_info:
+    kernel_version = version[:-1]
+    kernel_key = major_kernel_version_key(kernel_version)
+    try:
+        packaged_kernel_version = kernel_versions[kernel_key]
+    except KeyError:
         continue
-    kernel_version, version_suffix = version_info
 
-    if kernel_version in remaining_kernel_versions:
-        found_kernel_versions.add(kernel_version)
-        try:
-            old_version_suffix = patches[kernel_version]['version_suffix']
-            old_version = f'{kernel_version}.{old_version_suffix}'
-            update = old_version_suffix < version_suffix
-        except KeyError:
-            update = True
-            old_version = None
-
-        if update:
-            patch = fetch_patch(name=name, release=release)
-            if patch is None:
-                failures = True
+    release_info = {
+        '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 kernel_version > 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, release_info in releases.items():
+    release = release_info['release']
+    version = release_info['version']
+    version_str = release.tag_name
+    name = f'linux-hardened-{version_str}'
+
+    try:
+        old_filename = patches[kernel_key]['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
+        old_version = None
+
+    if update:
+        patch = fetch_patch(name=name, release=release)
+        if patch is None:
+            failures = True
+        else:
+            patches[kernel_key] = patch
+            if old_version:
+                message = f'{old_version_str} -> {version_str}'
             else:
-                patch['version_suffix'] = version_suffix
-                patches[kernel_version] = patch
-                if old_version:
-                    message = f'{old_version} -> {version}'
-                else:
-                    message = f'init at {version}'
-                commit_patches(kernel_version=kernel_version, message=message)
+                message = f'init at {version_str}'
+            commit_patches(kernel_key=kernel_key, message=message)
 
-missing_kernel_versions = kernel_versions - patches.keys()
+missing_kernel_versions = kernel_versions.keys() - patches.keys()
 
 if missing_kernel_versions:
     print(
         f'warning: no patches for kernel versions ' +
-        ', '.join(missing_kernel_versions) +
-        '\nwarning: consider manually backporting older patches (bump '
-        'JSON key, set version_suffix to "NixOS-a")',
+        ', '.join(missing_kernel_versions),
         file=sys.stderr,
     )