diff --git a/Dockerfile b/Dockerfile index 1293f72..21ffad4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,20 +14,18 @@ RUN apt-get update && \ bison \ build-essential \ cpio \ - curl \ debhelper \ fakeroot \ flex \ gcc-12-plugin-dev \ - gettext-base \ git \ kmod \ libelf-dev \ liblz4-tool \ libssl-dev \ - lsb-release \ ncurses-dev \ python3 \ + python3-jinja2 \ python3-requests \ rsync \ wget \ @@ -35,9 +33,10 @@ RUN apt-get update && \ RUN groupadd -g ${GID} ${USERNAME} && useradd -m -d /home/${USERNAME} -g ${GID} -u ${UID} ${USERNAME} -COPY build-kernel.sh /usr/local/bin/build-kernel.sh +COPY build-kernel.py /usr/local/bin/build-kernel.py COPY grsecurity-urls.py /usr/local/bin/grsecurity-urls.py COPY debian /debian +COPY pubkeys/ /pubkeys RUN mkdir -p -m 0755 /kernel /patches-grsec /output RUN chown ${USERNAME}:${USERNAME} /kernel /patches-grsec /output @@ -46,6 +45,5 @@ WORKDIR /kernel # VOLUME ["/kernel"] USER ${USERNAME} -COPY pubkeys/ /pubkeys -CMD ["/usr/local/bin/build-kernel.sh"] +CMD ["/usr/local/bin/build-kernel.py"] diff --git a/build-kernel.py b/build-kernel.py new file mode 100755 index 0000000..adc7e0c --- /dev/null +++ b/build-kernel.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path + +import jinja2 +import requests + + +def render_template(filename, context): + template_content = Path(f"{filename}.j2").read_text() + template = jinja2.Template(template_content) + rendered_content = template.render(context) + Path(filename).write_text(rendered_content) + + +def main(): + # Whether to use grsecurity patches + grsecurity = os.environ.get("GRSECURITY") == "1" + # Desired specific Linux version to download (e.g. "5.15.100") + linux_version = os.environ.get("LINUX_VERSION") + # Desired Linux major version to download (e.g. "5.15", "6.6") + linux_major_version = os.environ.get("LINUX_MAJOR_VERSION") + # Local version flavor, e.g. "securedrop", "workstation", "tiny" + local_version = os.environ["LOCALVERSION"] + # Increment if we need to build the same specific version again (e.g. metapackage changes) + build_version = os.environ.get("BUILD_VERSION", "1") + + source_date_epoch = os.environ["SOURCE_DATE_EPOCH"] + source_date_epoch_formatted = subprocess.check_output( + ["date", "-R", "-d", f"@{source_date_epoch}"], text=True + ).strip() + for line in Path("/etc/os-release").read_text().splitlines(): + if line.startswith("VERSION_CODENAME="): + version_codename = line.split("=")[1].strip() + break + + # Check output directory + if not os.path.isdir("/output") or not os.access("/output", os.W_OK): + print("WARNING: Output directory /output not found or not writable") + print("WARNING: To save packages, you must mount /output as a volume") + sys.exit(1) + + # Fetch grsecurity patchset if desired + if grsecurity: + # TODO: invoke this through normal Python means + linux_version = subprocess.check_output( + ["/usr/local/bin/grsecurity-urls.py", "--print-version"], text=True + ).strip() + print("Will include grsecurity patch for kernel", linux_version) + with open("/patches-grsec/grsec", "wb") as f: + # TODO: invoke this through normal Python means + subprocess.run(["/usr/local/bin/grsecurity-urls.py"], stdout=f) + else: + print("Skipping grsecurity patch set") + + # If we're don't have a kernel version yet, look it up + if not linux_version: + if not linux_major_version: + print("ERROR: $LINUX_MAJOR_VERSION must be set") + sys.exit(1) + print(f"Looking up latest release of {linux_major_version} from kernel.org") + response = requests.get("https://www.kernel.org/") + response.raise_for_status() + linux_version = re.search( + rf"({re.escape(linux_major_version)}\.(\d+?))", + response.text, + ).group(1) + + # Fetch Linux kernel source + folder = linux_version.split(".")[0] + ".x" + print(f"Fetching Linux kernel source {linux_version}") + subprocess.check_call( + [ + "wget", + f"https://cdn.kernel.org/pub/linux/kernel/v{folder}/linux-{linux_version}.tar.xz", + ] + ) + subprocess.check_call( + [ + "wget", + f"https://cdn.kernel.org/pub/linux/kernel/v{folder}/linux-{linux_version}.tar.sign", + ] + ) + print(f"Extracting Linux kernel source {linux_version}") + subprocess.check_call( + ["xz", "-d", "-T", "0", "-v", f"linux-{linux_version}.tar.xz"] + ) + subprocess.check_call( + [ + "gpgv", + "--keyring", + "/pubkeys/kroah_hartman.gpg", + f"linux-{linux_version}.tar.sign", + f"linux-{linux_version}.tar", + ] + ) + shutil.unpack_archive( + f"linux-{linux_version}.tar" + ) + # Copy custom /config + print("Copying custom config for kernel source", linux_version) + shutil.copy("/config", f"linux-{linux_version}/.config") + + # Apply grsec patches + if grsecurity: + print(f"Applying grsec patches for kernel source {linux_version}") + subprocess.check_call( + ["patch", "-p", "1", "-i", "/patches-grsec/grsec"], + cwd=f"linux-{linux_version}", + ) + + # Generate orig tarball + print("Generating orig tarball") + linux_build_version = f"{linux_version}-{build_version}" + version_suffix = ("grsec-" if grsecurity else "") + local_version + subprocess.check_call( + [ + "tar", + "--use-compress-program=xz -T 0", + "-cf", + f"linux-upstream_{linux_build_version}-{version_suffix}.orig.tar.xz", + f"linux-{linux_version}", + ] + ) + + os.chdir(f"linux-{linux_version}") + # Copy debian/ + print("Setting up our debian/ tree") + shutil.copytree("/debian", "debian") + + template_variables = { + "linux_build_version": linux_build_version, + "source_date_epoch_formatted": source_date_epoch_formatted, + "version_suffix": version_suffix, + "build_version": build_version, + "version_codename": version_codename, + "debarch": "amd64", + "kernelarch": "x86", + "local_version": local_version, + } + + render_template("debian/control", template_variables) + render_template("debian/changelog", template_variables) + render_template("debian/rules.vars", template_variables) + + # Building Linux kernel source + print("Building Linux kernel source", linux_version) + subprocess.check_call( + ["dpkg-buildpackage", "-uc", "-us"], + ) + + os.chdir("..") + # Storing build artifacts + print("Storing build artifacts for", linux_version) + # Because Python doesn't support brace-fnmatch globbing + extensions = ['buildinfo', 'changes', 'dsc', 'deb', 'tar.xz'] + artifacts = [] + for extension in extensions: + artifacts.extend(Path(".").glob(f"*.{extension}")) + for artifact in artifacts: + print(f"Copying {artifact}...") + shutil.copy(str(artifact), "/output") + + +if __name__ == "__main__": + main() diff --git a/build-kernel.sh b/build-kernel.sh deleted file mode 100755 index ded6fd3..0000000 --- a/build-kernel.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash -set -euxo pipefail - - -# Patching with grsecurity is disabled by default. -# Can be renabled vai env var or cli flag. -GRSECURITY="${GRSECURITY:-}" -LINUX_VERSION="${LINUX_VERSION:-}" -LINUX_MAJOR_VERSION="${LINUX_MAJOR_VERSION:-}" -LINUX_CUSTOM_CONFIG="${LINUX_CUSTOM_CONFIG:-/config}" -# "securedrop" or "workstation" (or "tiny" in CI) -LOCALVERSION="${LOCALVERSION:-}" -# Increment this if we need to rebuild the same kernel version for whatever reason -export BUILD_VERSION="${BUILD_VERSION:-1}" -export SOURCE_DATE_EPOCH -export SOURCE_DATE_EPOCH_FORMATTED=$(date -R -d @$SOURCE_DATE_EPOCH) -export KBUILD_BUILD_TIMESTAMP -export DEB_BUILD_TIMESTAMP -# Get the current Debian codename so we can vary based on version -eval "export $(cat /etc/os-release | grep CODENAME)" -export VERSION_CODENAME - -if [[ $# > 0 ]]; then - x="$1" - shift - if [[ "$x" = "--grsecurity" ]]; then - GRSECURITY=1 - else - echo "Usage: $0 [--grsecurity]" - exit 1 - fi -fi - -# If there's no output directory, then deb packages will be -# lost in the ephemeral container. -if [[ ! -d /output && ! -w /output ]]; then - echo "WARNING: Output directory /output not found" >&2 - echo "WARNING: to save packages, you must mount /output as a volume" >&2 - exit 1 -fi - -if [[ -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then - LINUX_VERSION="$(/usr/local/bin/grsecurity-urls.py --print-version)" - echo "Will include grsecurity patch for kernel $LINUX_VERSION" - /usr/local/bin/grsecurity-urls.py > /patches-grsec/grsec -else - echo "Skipping grsecurity patch set" -fi - -if [[ -z "$LINUX_VERSION" ]]; then - if [[ -z "$LINUX_MAJOR_VERSION" ]]; then - echo "ERROR: \$LINUX_MAJOR_VERSION must be set" - exit 1 - fi - # Get the latest patch version of this version series from kernel.org - echo "Looking up latest release of $LINUX_MAJOR_VERSION from kernel.org" - LINUX_VERSION="$(curl -s https://www.kernel.org/ | grep -m1 -F "$LINUX_MAJOR_VERSION" -A1 | head -n1 | grep -oP '[\d\.]+')" -fi -export LINUX_VERSION - -# 5.15.120 -> 5 -FOLDER="$(cut -d. -f1 <<< "$LINUX_VERSION").x" -echo "Fetching Linux kernel source $LINUX_VERSION" -wget https://cdn.kernel.org/pub/linux/kernel/v${FOLDER}/linux-${LINUX_VERSION}.tar.{xz,sign} - -echo "Extracting Linux kernel source $LINUX_VERSION" -xz -d -T 0 -v linux-${LINUX_VERSION}.tar.xz -gpgv --keyring /pubkeys/kroah_hartman.gpg linux-${LINUX_VERSION}.tar.sign linux-${LINUX_VERSION}.tar -tar -xf linux-${LINUX_VERSION}.tar -cd linux-${LINUX_VERSION} - -if [[ -e "$LINUX_CUSTOM_CONFIG" ]]; then - echo "Copying custom config for kernel source $LINUX_VERSION" - cp "$LINUX_CUSTOM_CONFIG" .config -fi - -if [[ -e /patches-grsec && -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then - echo "Applying grsec patches for kernel source $LINUX_VERSION" - find /patches-grsec -maxdepth 1 -type f -exec patch -p 1 -i {} \; -fi - -export LINUX_BUILD_VERSION="${LINUX_VERSION}-${BUILD_VERSION}" -if [[ -n "$GRSECURITY" && "$GRSECURITY" = "1" ]]; then - export VERSION_SUFFIX="grsec-${LOCALVERSION}" -else - export VERSION_SUFFIX="${LOCALVERSION}" -fi - -# Generate the orig tarball -tar --use-compress-program="xz -T 0" -cf ../linux-upstream_${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}.orig.tar.xz . - -echo "Copying in our debian/" -cp -R /debian debian - -export DEBARCH="amd64" - -cat debian/control.in | envsubst > debian/control -echo "" >> debian/control -if [[ "$LOCALVERSION" = "workstation" ]]; then - echo "Generating d/control for workstation" - cat debian/control.workstation | envsubst >> debian/control -else - echo "Generating d/control for server" - cat debian/control.server | envsubst >> debian/control -fi -cat debian/changelog.in | envsubst > debian/changelog - -cat < debian/rules.vars -ARCH := x86 -KERNELRELEASE := ${LINUX_VERSION} -EOF - -echo "Building Linux kernel source $LINUX_VERSION" - -# TODO set parallel build here -dpkg-buildpackage -uc -us - -echo "Storing build artifacts for $LINUX_VERSION" -if [[ -d /output ]]; then - rsync -a --info=progress2 ../*.{buildinfo,changes,dsc,deb,tar.xz} /output/ -fi diff --git a/debian/changelog.in b/debian/changelog.in deleted file mode 100644 index 0c6177d..0000000 --- a/debian/changelog.in +++ /dev/null @@ -1,5 +0,0 @@ -linux-upstream (${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}-${BUILD_VERSION}) ${VERSION_CODENAME}; urgency=low - - * Custom built Linux kernel. - - -- SecureDrop Team ${SOURCE_DATE_EPOCH_FORMATTED} diff --git a/debian/changelog.j2 b/debian/changelog.j2 new file mode 100644 index 0000000..fc9f392 --- /dev/null +++ b/debian/changelog.j2 @@ -0,0 +1,5 @@ +linux-upstream ({{linux_build_version}}-{{version_suffix}}-{{build_version}}) {{version_codename}}; urgency=low + + * Custom built Linux kernel. + + -- SecureDrop Team {{source_date_epoch_formatted}} diff --git a/debian/control.in b/debian/control.in deleted file mode 100644 index a39b55f..0000000 --- a/debian/control.in +++ /dev/null @@ -1,36 +0,0 @@ -Source: linux-upstream -Section: kernel -Priority: optional -Maintainer: SecureDrop Team -Rules-Requires-Root: no -Build-Depends: bc, debhelper, rsync, kmod, cpio, bison, flex, libelf-dev, libssl-dev -Homepage: https://securedrop.org/ - -Package: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} -Architecture: ${DEBARCH} -Description: Linux kernel, version ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} - This package contains the Linux kernel, modules and corresponding other - files, version: ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}. - -Package: linux-libc-dev -Section: devel -Provides: linux-kernel-headers -Architecture: ${DEBARCH} -Description: Linux support headers for userspace development - This package provides userspaces headers from the Linux kernel. These headers - are used by the installed headers for GNU glibc and other system libraries. -Multi-Arch: same - -Package: linux-headers-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} -Architecture: ${DEBARCH} -Description: Linux kernel headers for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} on ${DEBARCH} - This package provides kernel header files for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} on ${DEBARCH} - . - This is useful for people who need to build external modules - -Package: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}-dbg -Section: debug -Architecture: ${DEBARCH} -Description: Linux kernel debugging symbols for ${LINUX_BUILD_VERSION}-${VERSION_SUFFIX} - This package will come in handy if you need to debug the kernel. It provides - all the necessary debug symbols for the kernel and its modules. diff --git a/debian/control.j2 b/debian/control.j2 new file mode 100644 index 0000000..3a673ec --- /dev/null +++ b/debian/control.j2 @@ -0,0 +1,56 @@ +Source: linux-upstream +Section: kernel +Priority: optional +Maintainer: SecureDrop Team +Rules-Requires-Root: no +Build-Depends: bc, debhelper, rsync, kmod, cpio, bison, flex, libelf-dev, libssl-dev +Homepage: https://securedrop.org/ + +Package: linux-image-{{linux_build_version}}-{{version_suffix}} +Architecture: {{debarch}} +Description: Linux kernel, version {{linux_build_version}}-{{version_suffix}} + This package contains the Linux kernel, modules and corresponding other + files, version: {{linux_build_version}}-{{version_suffix}}. + +Package: linux-libc-dev +Section: devel +Provides: linux-kernel-headers +Architecture: {{debarch}} +Description: Linux support headers for userspace development + This package provides userspaces headers from the Linux kernel. These headers + are used by the installed headers for GNU glibc and other system libraries. +Multi-Arch: same + +Package: linux-headers-{{linux_build_version}}-{{version_suffix}} +Architecture: {{debarch}} +Description: Linux kernel headers for {{linux_build_version}}-{{version_suffix}} on {{debarch}} + This package provides kernel header files for {{linux_build_version}}-{{version_suffix}} on {{debarch}} + . + This is useful for people who need to build external modules + +Package: linux-image-{{linux_build_version}}-{{version_suffix}}-dbg +Section: debug +Architecture: {{debarch}} +Description: Linux kernel debugging symbols for {{linux_build_version}}-{{version_suffix}} + This package will come in handy if you need to debug the kernel. It provides + all the necessary debug symbols for the kernel and its modules. + +{% if local_version == "workstation" %} +Package: securedrop-workstation-grsec +Section: admin +Architecture: {{debarch}} +Pre-Depends: qubes-kernel-vm-support (>=4.0.31) +Depends: linux-image-{{linux_build_version}}-{{version_suffix}}, libelf-dev, paxctld +Description: Linux for SecureDrop Workstation template (meta-package) + Metapackage providing a grsecurity-patched Linux kernel for use in SecureDrop + Workstation Qubes templates. Depends on the most recently built patched kernel + maintained by FPF. +{% else %} +Package: securedrop-grsec +Section: admin +Architecture: {{debarch}} +Depends: linux-image-{{linux_build_version}}-{{version_suffix}}, intel-microcode, amd64-microcode, paxctld +Description: Metapackage providing a grsecurity-patched Linux kernel for use + with SecureDrop. Depends on the most recently built patched kernel maintained + by FPF. Package also includes sysctl and PaX flags calls for GRUB. +{% endif %} diff --git a/debian/control.server b/debian/control.server deleted file mode 100644 index 9e518a4..0000000 --- a/debian/control.server +++ /dev/null @@ -1,7 +0,0 @@ -Package: securedrop-grsec -Section: admin -Architecture: ${DEBARCH} -Depends: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}, intel-microcode, amd64-microcode, paxctld -Description: Metapackage providing a grsecurity-patched Linux kernel for use - with SecureDrop. Depends on the most recently built patched kernel maintained - by FPF. Package also includes sysctl and PaX flags calls for GRUB. diff --git a/debian/control.workstation b/debian/control.workstation deleted file mode 100644 index 982e634..0000000 --- a/debian/control.workstation +++ /dev/null @@ -1,9 +0,0 @@ -Package: securedrop-workstation-grsec -Section: admin -Architecture: ${DEBARCH} -Pre-Depends: qubes-kernel-vm-support (>=4.0.31) -Depends: linux-image-${LINUX_BUILD_VERSION}-${VERSION_SUFFIX}, libelf-dev, paxctld -Description: Linux for SecureDrop Workstation template (meta-package) - Metapackage providing a grsecurity-patched Linux kernel for use in SecureDrop - Workstation Qubes templates. Depends on the most recently built patched kernel - maintained by FPF. diff --git a/debian/rules.vars.j2 b/debian/rules.vars.j2 new file mode 100644 index 0000000..e4979a5 --- /dev/null +++ b/debian/rules.vars.j2 @@ -0,0 +1,2 @@ +ARCH := {{kernelarch}} +KERNELRELEASE := {{linux_build_version}}-{{version_suffix}}