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.
This commit is contained in:
Emily 2020-04-23 18:43:44 +01:00
parent b16a5e2847
commit 2c1db9649e
4 changed files with 109 additions and 87 deletions

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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
version = release.tag_name
name = f'linux-hardened-{version}'
version_info = parse_version(version)
if not version_info:
# 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
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
kernel_version = version[:-1]
kernel_key = major_kernel_version_key(kernel_version)
try:
packaged_kernel_version = kernel_versions[kernel_key]
except KeyError:
continue
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,
)

View File

@ -17047,7 +17047,7 @@ in
};
kernelPatches = kernel.kernelPatches ++ [
kernelPatches.tag_hardened
kernelPatches.hardened.${kernel.version}
kernelPatches.hardened.${kernel.meta.branch}
];
modDirVersionArg = kernel.modDirVersion + "-hardened";
});