nixpkgs/pkgs/os-specific/linux/kernel/manual-config.nix
Dima bdccffa81c linux_5_2, linux_5_3: fixing nondeterminism
In 5.2 kernel a new mechanism was introduced which embeds the kernel
headers in the kernel image and exposes them in procfs for simplified
use by userland tools.

It was introduced in
43d8ce9d65
and later modified a bit in
f7b101d330

The archive containing the header files had nondeterminism through the
header files metadata - specifically `mtime`, but I also decided to
normalize some other aspects just in case.

In our default setup we currently compile this as a module, so to expose
the headers to test the functionality `kheaders` needs to be loaded.

See https://lkml.org/lkml/2019/10/4/1036 and
https://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild.git/commit/?h=fixes&id=2cc99c9cdc8fde5e92e34f9655829449cebd3e00

I commented out the documentation part of the patch to make it cleanly apply to
5.2 and 5.3, see remark in the patch itself.
2019-10-05 09:34:35 +02:00

309 lines
12 KiB
Nix

{ buildPackages, runCommand, nettools, bc, bison, flex, perl, rsync, gmp, libmpc, mpfr, openssl
, libelf, cpio
, utillinux
, writeTextFile
}:
let
readConfig = configfile: import (runCommand "config.nix" {} ''
echo "{" > "$out"
while IFS='=' read key val; do
[ "x''${key#CONFIG_}" != "x$key" ] || continue
no_firstquote="''${val#\"}";
echo ' "'"$key"'" = "'"''${no_firstquote%\"}"'";' >> "$out"
done < "${configfile}"
echo "}" >> $out
'').outPath;
in {
# Allow overriding stdenv on each buildLinux call
stdenv,
# The kernel version
version,
# The version of the kernel module directory
modDirVersion ? version,
# The kernel source (tarball, git checkout, etc.)
src,
# a list of { name=..., patch=..., extraConfig=...} patches
kernelPatches ? [],
# The kernel .config file
configfile,
# Manually specified nixexpr representing the config
# If unspecified, this will be autodetected from the .config
config ? stdenv.lib.optionalAttrs allowImportFromDerivation (readConfig configfile),
# Custom seed used for CONFIG_GCC_PLUGIN_RANDSTRUCT if enabled. This is
# automatically extended with extra per-version and per-config values.
randstructSeed ? "",
# Use defaultMeta // extraMeta
extraMeta ? {},
# Whether to utilize the controversial import-from-derivation feature to parse the config
allowImportFromDerivation ? false,
# ignored
features ? null,
}:
let
inherit (stdenv.lib)
hasAttr getAttr optional optionals optionalString optionalAttrs maintainers platforms;
# Dependencies that are required to build kernel modules
moduleBuildDependencies = optional (stdenv.lib.versionAtLeast version "4.14") libelf;
installkernel = writeTextFile { name = "installkernel"; executable=true; text = ''
#!${stdenv.shell} -e
mkdir -p $4
cp -av $2 $4
cp -av $3 $4
''; };
commonMakeFlags = [
"O=$(buildRoot)"
] ++ stdenv.lib.optionals (stdenv.hostPlatform.platform ? kernelMakeFlags)
stdenv.hostPlatform.platform.kernelMakeFlags;
drvAttrs = config_: platform: kernelPatches: configfile:
let
config = let attrName = attr: "CONFIG_" + attr; in {
isSet = attr: hasAttr (attrName attr) config;
getValue = attr: if config.isSet attr then getAttr (attrName attr) config else null;
isYes = attr: (config.getValue attr) == "y";
isNo = attr: (config.getValue attr) == "n";
isModule = attr: (config.getValue attr) == "m";
isEnabled = attr: (config.isModule attr) || (config.isYes attr);
isDisabled = attr: (!(config.isSet attr)) || (config.isNo attr);
} // config_;
isModular = config.isYes "MODULES";
installsFirmware = (config.isEnabled "FW_LOADER") &&
(isModular || (config.isDisabled "FIRMWARE_IN_KERNEL")) &&
(stdenv.lib.versionOlder version "4.14");
in (optionalAttrs isModular { outputs = [ "out" "dev" ]; }) // {
passthru = {
inherit version modDirVersion config kernelPatches configfile
moduleBuildDependencies stdenv;
};
inherit src;
patches =
map (p: p.patch) kernelPatches
# Required for deterministic builds along with some postPatch magic.
++ optional (stdenv.lib.versionAtLeast version "4.13") ./randstruct-provide-seed.patch
# Fixes determinism by normalizing metadata for the archive of kheaders
++ optional (stdenv.lib.versionAtLeast version "5.2") ./gen-kheaders-metadata.patch;
prePatch = ''
for mf in $(find -name Makefile -o -name Makefile.include -o -name install.sh); do
echo "stripping FHS paths in \`$mf'..."
sed -i "$mf" -e 's|/usr/bin/||g ; s|/bin/||g ; s|/sbin/||g'
done
sed -i Makefile -e 's|= depmod|= ${buildPackages.kmod}/bin/depmod|'
sed -i scripts/ld-version.sh -e "s|/usr/bin/awk|${buildPackages.gawk}/bin/awk|"
'';
postPatch = ''
# Set randstruct seed to a deterministic but diversified value. Note:
# we could have instead patched gen-random-seed.sh to take input from
# the buildFlags, but that would require also patching the kernel's
# toplevel Makefile to add a variable export. This would be likely to
# cause future patch conflicts.
if [ -f scripts/gcc-plugins/gen-random-seed.sh ]; then
substituteInPlace scripts/gcc-plugins/gen-random-seed.sh \
--replace NIXOS_RANDSTRUCT_SEED \
$(echo ${randstructSeed}${src} ${configfile} | sha256sum | cut -d ' ' -f 1 | tr -d '\n')
fi
'';
configurePhase = ''
runHook preConfigure
mkdir build
export buildRoot="$(pwd)/build"
echo "manual-config configurePhase buildRoot=$buildRoot pwd=$PWD"
if [ -f "$buildRoot/.config" ]; then
echo "Could not link $buildRoot/.config : file exists"
exit 1
fi
ln -sv ${configfile} $buildRoot/.config
# reads the existing .config file and prompts the user for options in
# the current kernel source that are not found in the file.
make $makeFlags "''${makeFlagsArray[@]}" oldconfig
runHook postConfigure
make $makeFlags "''${makeFlagsArray[@]}" prepare
actualModDirVersion="$(cat $buildRoot/include/config/kernel.release)"
if [ "$actualModDirVersion" != "${modDirVersion}" ]; then
echo "Error: modDirVersion ${modDirVersion} specified in the Nix expression is wrong, it should be: $actualModDirVersion"
exit 1
fi
# Note: we can get rid of this once http://permalink.gmane.org/gmane.linux.kbuild.devel/13800 is merged.
buildFlagsArray+=("KBUILD_BUILD_TIMESTAMP=$(date -u -d @$SOURCE_DATE_EPOCH)")
cd $buildRoot
'';
buildFlags = [
"KBUILD_BUILD_VERSION=1-NixOS"
platform.kernelTarget
"vmlinux" # for "perf" and things like that
] ++ optional isModular "modules";
installFlags = [
"INSTALLKERNEL=${installkernel}"
"INSTALL_PATH=$(out)"
] ++ (optional isModular "INSTALL_MOD_PATH=$(out)")
++ optional installsFirmware "INSTALL_FW_PATH=$(out)/lib/firmware";
# Some image types need special install targets (e.g. uImage is installed with make uinstall)
installTargets = [ (
if platform ? kernelInstallTarget then platform.kernelInstallTarget
else if platform.kernelTarget == "uImage" then "uinstall"
else if platform.kernelTarget == "zImage" || platform.kernelTarget == "Image.gz" then "zinstall"
else "install"
) ];
postInstall = (optionalString installsFirmware ''
mkdir -p $out/lib/firmware
'') + (if (platform ? kernelDTB && platform.kernelDTB) then ''
make $makeFlags "''${makeFlagsArray[@]}" dtbs dtbs_install INSTALL_DTBS_PATH=$out/dtbs
'' else "") + (if isModular then ''
mkdir -p $dev
cp vmlinux $dev/
if [ -z "$dontStrip" ]; then
installFlagsArray+=("INSTALL_MOD_STRIP=1")
fi
make modules_install $makeFlags "''${makeFlagsArray[@]}" \
$installFlags "''${installFlagsArray[@]}"
unlink $out/lib/modules/${modDirVersion}/build
unlink $out/lib/modules/${modDirVersion}/source
mkdir -p $dev/lib/modules/${modDirVersion}/{build,source}
# To save space, exclude a bunch of unneeded stuff when copying.
(cd .. && rsync --archive --prune-empty-dirs \
--exclude='/build/' \
* $dev/lib/modules/${modDirVersion}/source/)
cd $dev/lib/modules/${modDirVersion}/source
cp $buildRoot/{.config,Module.symvers} $dev/lib/modules/${modDirVersion}/build
make modules_prepare $makeFlags "''${makeFlagsArray[@]}" O=$dev/lib/modules/${modDirVersion}/build
# For reproducibility, removes accidental leftovers from a `cc1` call
# from a `try-run` call from the Makefile
rm -f $dev/lib/modules/${modDirVersion}/build/.[0-9]*.d
# Keep some extra files on some arches (powerpc, aarch64)
for f in arch/powerpc/lib/crtsavres.o arch/arm64/kernel/ftrace-mod.o; do
if [ -f "$buildRoot/$f" ]; then
cp $buildRoot/$f $dev/lib/modules/${modDirVersion}/build/$f
fi
done
# !!! No documentation on how much of the source tree must be kept
# If/when kernel builds fail due to missing files, you can add
# them here. Note that we may see packages requiring headers
# from drivers/ in the future; it adds 50M to keep all of its
# headers on 3.10 though.
chmod u+w -R ..
arch=$(cd $dev/lib/modules/${modDirVersion}/build/arch; ls)
# Remove unused arches
for d in $(cd arch/; ls); do
if [ "$d" = "$arch" ]; then continue; fi
if [ "$arch" = arm64 ] && [ "$d" = arm ]; then continue; fi
rm -rf arch/$d
done
# Remove all driver-specific code (50M of which is headers)
rm -fR drivers
# Keep all headers
find . -type f -name '*.h' -print0 | xargs -0 chmod u-w
# Keep linker scripts (they are required for out-of-tree modules on aarch64)
find . -type f -name '*.lds' -print0 | xargs -0 chmod u-w
# Keep root and arch-specific Makefiles
chmod u-w Makefile
chmod u-w arch/$arch/Makefile*
# Keep whole scripts dir
chmod u-w -R scripts
# Delete everything not kept
find . -type f -perm -u=w -print0 | xargs -0 rm
# Delete empty directories
find -empty -type d -delete
# Remove reference to kmod
sed -i Makefile -e 's|= ${buildPackages.kmod}/bin/depmod|= depmod|'
'' else optionalString installsFirmware ''
make firmware_install $makeFlags "''${makeFlagsArray[@]}" \
$installFlags "''${installFlagsArray[@]}"
'');
requiredSystemFeatures = [ "big-parallel" ];
meta = {
description =
"The Linux kernel" +
(if kernelPatches == [] then "" else
" (with patches: "
+ stdenv.lib.concatStringsSep ", " (map (x: x.name) kernelPatches)
+ ")");
license = stdenv.lib.licenses.gpl2;
homepage = https://www.kernel.org/;
repositories.git = https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git;
maintainers = [
maintainers.thoughtpolice
];
platforms = platforms.linux;
timeout = 14400; # 4 hours
} // extraMeta;
};
in
assert stdenv.lib.versionAtLeast version "4.14" -> libelf != null;
assert stdenv.lib.versionAtLeast version "4.15" -> utillinux != null;
stdenv.mkDerivation ((drvAttrs config stdenv.hostPlatform.platform kernelPatches configfile) // {
pname = "linux";
inherit version;
enableParallelBuilding = true;
depsBuildBuild = [ buildPackages.stdenv.cc ];
nativeBuildInputs = [ perl bc nettools openssl rsync gmp libmpc mpfr ]
++ optional (stdenv.hostPlatform.platform.kernelTarget == "uImage") buildPackages.ubootTools
++ optional (stdenv.lib.versionAtLeast version "4.14") libelf
++ optional (stdenv.lib.versionAtLeast version "4.15") utillinux
++ optionals (stdenv.lib.versionAtLeast version "4.16") [ bison flex ]
++ optional (stdenv.lib.versionAtLeast version "5.2") cpio
;
hardeningDisable = [ "bindnow" "format" "fortify" "stackprotector" "pic" "pie" ];
# Absolute paths for compilers avoid any PATH-clobbering issues.
makeFlags = commonMakeFlags ++ [
"CC=${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"
"HOSTCC=${buildPackages.stdenv.cc}/bin/${buildPackages.stdenv.cc.targetPrefix}cc"
"ARCH=${stdenv.hostPlatform.platform.kernelArch}"
] ++ stdenv.lib.optional (stdenv.hostPlatform != stdenv.buildPlatform) [
"CROSS_COMPILE=${stdenv.cc.targetPrefix}"
];
karch = stdenv.hostPlatform.platform.kernelArch;
})