depot/third_party/nixpkgs/pkgs/applications/virtualization/xen/generic/default.nix
Default email 159e378cbb Project import generated by Copybara.
GitOrigin-RevId: c04d5652cfa9742b1d519688f65d1bbccea9eb7e
2024-09-19 17:19:46 +03:00

716 lines
24 KiB
Nix

versionDefinition:
{
lib,
stdenv,
autoPatchelfHook,
cmake,
ninja,
pkg-config,
testers,
which,
fetchgit,
fetchFromGitHub,
# Xen
acpica-tools,
bison,
bzip2,
dev86,
e2fsprogs,
flex,
libnl,
libuuid,
lzo,
ncurses,
ocamlPackages,
perl,
python311Packages,
systemdMinimal,
xz,
yajl,
zlib,
zstd,
# Xen Optional
withInternalQEMU ? true,
pixman,
glib,
withInternalSeaBIOS ? true,
withSeaBIOS ? !withInternalSeaBIOS,
seabios,
withInternalOVMF ? true,
withOVMF ? !withInternalOVMF,
OVMF,
nasm,
withInternalIPXE ? true,
withIPXE ? !withInternalIPXE,
ipxe,
withFlask ? false,
checkpolicy,
efiVendor ? "nixos", # Allow downstreams with custom branding to quickly override the EFI Vendor string.
withEFI ? true,
binutils-unwrapped,
# Documentation
pandoc,
# Scripts
bridge-utils,
coreutils,
diffutils,
gawk,
gnugrep,
gnused,
inetutils,
iproute2,
iptables,
multipath-tools,
nbd,
openvswitch,
util-linux,
...
}@packageDefinition:
let
#TODO: fix paths instead.
scriptEnvPath = lib.strings.makeSearchPathOutput "out" "bin" [
bridge-utils
coreutils
diffutils
gawk
gnugrep
gnused
inetutils
iproute2
iptables
multipath-tools
nbd
openvswitch
perl
util-linux.bin
which
];
# Inherit attributes from a versionDefinition.
inherit (versionDefinition) pname;
inherit (versionDefinition) branch;
inherit (versionDefinition) version;
inherit (versionDefinition) latest;
inherit (versionDefinition) pkg;
# Mark versions older than minSupportedVersion as EOL.
minSupportedVersion = "4.17";
## Pre-fetched Source Handling ##
# Main attribute set for sources needed to build tools and firmwares.
# Each source takes in:
# * A `src` attribute, which contains the actual fetcher,
# * A 'patches` attribute, which is a list of patches that need to be applied in the source.
# * A `path` attribute, which is the destination of the source inside the Xen tree.
prefetchedSources =
lib.attrsets.optionalAttrs withInternalQEMU {
qemu = {
src = fetchgit {
url = "https://xenbits.xen.org/git-http/qemu-xen.git";
fetchSubmodules = true;
inherit (pkg.qemu) rev;
inherit (pkg.qemu) hash;
};
patches = lib.lists.optionals (lib.attrsets.hasAttrByPath [ "patches" ] pkg.qemu) pkg.qemu.patches;
path = "tools/qemu-xen";
};
}
// lib.attrsets.optionalAttrs withInternalSeaBIOS {
seaBIOS = {
src = fetchgit {
url = "https://xenbits.xen.org/git-http/seabios.git";
inherit (pkg.seaBIOS) rev;
inherit (pkg.seaBIOS) hash;
};
patches = lib.lists.optionals (lib.attrsets.hasAttrByPath [
"patches"
] pkg.seaBIOS) pkg.seaBIOS.patches;
path = "tools/firmware/seabios-dir-remote";
};
}
// lib.attrsets.optionalAttrs withInternalOVMF {
ovmf = {
src = fetchgit {
url = "https://xenbits.xen.org/git-http/ovmf.git";
fetchSubmodules = true;
inherit (pkg.ovmf) rev;
inherit (pkg.ovmf) hash;
};
patches = lib.lists.optionals (lib.attrsets.hasAttrByPath [ "patches" ] pkg.ovmf) pkg.ovmf.patches;
path = "tools/firmware/ovmf-dir-remote";
};
}
// lib.attrsets.optionalAttrs withInternalIPXE {
ipxe = {
src = fetchFromGitHub {
owner = "ipxe";
repo = "ipxe";
inherit (pkg.ipxe) rev;
inherit (pkg.ipxe) hash;
};
patches = lib.lists.optionals (lib.attrsets.hasAttrByPath [ "patches" ] pkg.ipxe) pkg.ipxe.patches;
path = "tools/firmware/etherboot/ipxe.git";
};
};
# Gets a list containing the names of the top-level attribute for each pre-fetched
# source, to be used in the map functions below.
prefetchedSourcesList = lib.attrsets.mapAttrsToList (name: value: name) prefetchedSources;
# Produces bash commands that will copy each pre-fetched source.
copyPrefetchedSources =
# Finish the deployment by concatnating the list of commands together.
lib.strings.concatLines (
# Iterate on each pre-fetched source.
builtins.map (
source:
# Only produce a copy command if patches exist.
lib.strings.optionalString (lib.attrsets.hasAttrByPath [ "${source}" ] prefetchedSources)
# The actual copy command. `src` is always an absolute path to a fetcher output
# inside the /nix/store, and `path` is always a path relative to the Xen root.
# We need to `mkdir -p` the target directory first, and `chmod +w` the contents last,
# as the copied files will still be edited by the postPatchPhase.
''
echo "Copying ${prefetchedSources.${source}.src} -> ${prefetchedSources.${source}.path}"
mkdir --parents ${prefetchedSources.${source}.path}
cp --recursive --no-target-directory ${prefetchedSources.${source}.src} ${
prefetchedSources.${source}.path
}
chmod --recursive +w ${prefetchedSources.${source}.path}
''
) prefetchedSourcesList
);
# Produces strings with `patch` commands to be ran on postPatch.
# These deploy the .patch files for each pre-fetched source.
deployPrefetchedSourcesPatches =
# Finish the deployment by concatnating the list of commands together.
lib.strings.concatLines (
# The double map functions create a list of lists. Flatten it so we can concatnate it.
lib.lists.flatten (
# Iterate on each pre-fetched source.
builtins.map (
source:
# Iterate on each available patch.
(builtins.map (
patch:
# Only produce a patch command if patches exist.
lib.strings.optionalString
(lib.attrsets.hasAttrByPath [
"${source}"
"patches"
] prefetchedSources)
# The actual patch command. It changes directories to the correct source each time.
''
echo "Applying patch ${patch} to ${source}."
patch --directory ${prefetchedSources.${source}.path} --strip 1 < ${patch}
''
) prefetchedSources.${source}.patches)
) prefetchedSourcesList
)
);
## XSA Patches Description Builder ##
# Sometimes patches are sourced through a path, like ./0000-xen.patch.
# This would break the patch attribute parser functions, so we normalise
# all patches sourced through paths by setting them to a { type = "path"; }
# attribute set.
# Patches from fetchpatch are already attribute sets.
normalisedPatchList = builtins.map (
patch:
if !builtins.isAttrs patch then
if builtins.isPath patch then
{ type = "path"; }
else
throw "xen/generic/default.nix: normalisedPatchList attempted to normalise something that is not a Path or an Attribute Set."
else
patch
) pkg.xen.patches;
# Simple counter for the number of attrsets (patches) in the patches list after normalisation.
numberOfPatches = lib.lists.count (patch: builtins.isAttrs patch) normalisedPatchList;
# builtins.elemAt's index begins at 0, so we subtract 1 from the number of patches in order to
# produce the range that will be used in the following builtin.map calls.
availablePatchesToTry = lib.lists.range 0 (numberOfPatches - 1);
# Takes in an attrByPath input, and outputs the attribute value for each patch in a list.
# If a patch does not have a given attribute, returns `null`. Use lib.lists.remove null
# to remove these junk values, if necessary.
retrievePatchAttributes =
attributeName:
builtins.map (
x: lib.attrsets.attrByPath attributeName null (builtins.elemAt normalisedPatchList x)
) availablePatchesToTry;
# Produces a list of newline-separated strings that lists the vulnerabilities this
# Xen is NOT affected by, due to the applied Xen Security Advisory patches. This is
# then used in meta.longDescription, to let users know their Xen is patched against
# known vulnerabilities, as the package version isn't always the best indicator.
#
# Produces something like this: (one string for each XSA)
# * [Xen Security Advisory #1](https://xenbits.xenproject.org/xsa/advisory-1.html): **Title for XSA.**
# >Description of issue in XSA
#Extra lines
#are not indented,
#but markdown should be
#fine with it.
# Fixes:
# * [CVE-1999-00001](https://www.cve.org/CVERecord?id=CVE-1999-00001)
# * [CVE-1999-00002](https://www.cve.org/CVERecord?id=CVE-1999-00002)
# * [CVE-1999-00003](https://www.cve.org/CVERecord?id=CVE-1999-00003)
writeAdvisoryDescription =
if (lib.lists.remove null (retrievePatchAttributes [ "xsa" ]) != [ ]) then
lib.lists.zipListsWith (a: b: a + b)
(lib.lists.zipListsWith (a: b: a + "**" + b + ".**\n >")
(lib.lists.zipListsWith (a: b: "* [Xen Security Advisory #" + a + "](" + b + "): ")
(lib.lists.remove null (retrievePatchAttributes [ "xsa" ]))
(
lib.lists.remove null (retrievePatchAttributes [
"meta"
"homepage"
])
)
)
(
lib.lists.remove null (retrievePatchAttributes [
"meta"
"description"
])
)
)
(
lib.lists.remove null (retrievePatchAttributes [
"meta"
"longDescription"
])
)
else
[ ];
## Binutils Override ##
# Originally, there were two versions of binutils being used: the standard one and
# this patched one. Unfortunately, that required patches to the Xen Makefiles, and
# quickly became too complex to maintain. The new solution is to simply build this
# efi-binutils derivation and use it for the whole build process, except if
# enableEFI is disabled; it'll then use `binutils`.
efiBinutils = binutils-unwrapped.overrideAttrs (oldAttrs: {
name = "efi-binutils";
configureFlags = oldAttrs.configureFlags ++ [ "--enable-targets=x86_64-pep" ];
doInstallCheck = false; # We get a spurious failure otherwise, due to a host/target mismatch.
meta.mainProgram = "ld"; # We only really care for `ld`.
});
in
stdenv.mkDerivation (finalAttrs: {
inherit pname;
inherit version;
outputs = [
"out" # TODO: Split $out in $bin for binaries and $lib for libraries.
"man" # Manual pages for Xen userspace utilities.
"doc" # The full Xen documentation in HTML format.
"dev" # Development headers.
"boot" # xen.gz kernel, policy file if Flask is enabled, xen.efi if EFI is enabled.
];
# Main Xen source.
src = fetchgit {
url = "https://xenbits.xen.org/git-http/xen.git";
inherit (pkg.xen) rev;
inherit (pkg.xen) hash;
};
patches =
# Generic Xen patches that apply to all Xen versions.
[ ./0000-xen-ipxe-src-generic.patch ]
# Gets the patches from the pkg.xen.patches attribute from the versioned files.
++ lib.lists.optionals (lib.attrsets.hasAttrByPath [ "patches" ] pkg.xen) pkg.xen.patches;
nativeBuildInputs =
[
autoPatchelfHook
bison
cmake
flex
pandoc
pkg-config
]
++ lib.lists.optionals withInternalQEMU [
ninja
python311Packages.sphinx
];
buildInputs =
[
# Xen
acpica-tools
bzip2
dev86
e2fsprogs.dev
libnl
libuuid
lzo
ncurses
perl
python311Packages.python
xz
yajl
zlib
zstd
# oxenstored
ocamlPackages.findlib
ocamlPackages.ocaml
# Python Fixes
python311Packages.wrapPython
]
++ lib.lists.optionals withInternalQEMU [
glib
pixman
]
++ lib.lists.optional withInternalOVMF nasm
++ lib.lists.optional withFlask checkpolicy
++ lib.lists.optional (lib.strings.versionOlder version "4.19") systemdMinimal;
configureFlags =
[
"--enable-systemd"
"--disable-qemu-traditional"
]
++ lib.lists.optional (!withInternalQEMU) "--with-system-qemu"
++ lib.lists.optional withSeaBIOS "--with-system-seabios=${seabios}/share/seabios"
++ lib.lists.optional (!withInternalSeaBIOS && !withSeaBIOS) "--disable-seabios"
++ lib.lists.optional withOVMF "--with-system-ovmf=${OVMF.firmware}"
++ lib.lists.optional withInternalOVMF "--enable-ovmf"
++ lib.lists.optional withIPXE "--with-system-ipxe=${ipxe}"
++ lib.lists.optional withInternalIPXE "--enable-ipxe"
++ lib.lists.optional withFlask "--enable-xsmpolicy";
makeFlags =
[
"PREFIX=$(out)"
"CONFIG_DIR=/etc"
"XEN_SCRIPT_DIR=$(CONFIG_DIR)/xen/scripts"
"BASH_COMPLETION_DIR=$(PREFIX)/share/bash-completion/completions"
]
++ lib.lists.optionals withEFI [
"EFI_VENDOR=${efiVendor}"
"INSTALL_EFI_STRIP=1"
"LD=${lib.meta.getExe efiBinutils}" # See the comment in the efiBinutils definition above.
]
# These flags set the CONFIG_* options in /boot/xen.config
# and define if the default policy file is built. However,
# the Flask binaries always get compiled by default.
++ lib.lists.optionals withFlask [
"XSM_ENABLE=y"
"FLASK_ENABLE=y"
]
++ (pkg.xen.makeFlags or [ ]);
buildFlags = [
"xen" # Build the Xen Hypervisor.
"tools" # Build the userspace tools, such as `xl`.
"docs" # Build the Xen Documentation
# TODO: Enable the Stubdomains target. This requires another pre-fetched source: mini-os. Currently, Xen appears to build a limited version of stubdomains which does not include mini-os.
# "stubdom"
];
enableParallelBuilding = true;
env.NIX_CFLAGS_COMPILE = builtins.toString (
[
"-Wno-error=maybe-uninitialized"
"-Wno-error=array-bounds"
]
++ lib.lists.optionals withInternalOVMF [
"-Wno-error=format-security"
"-Wno-error=use-after-free"
"-Wno-error=vla-parameter"
"-Wno-error=dangling-pointer"
"-Wno-error=stringop-overflow"
]
);
dontUseCmakeConfigure = true;
dontUseNinjaBuild = withInternalQEMU;
prePatch =
# Xen's stubdoms, tools and firmwares need various sources that
# are usually fetched at build time using wget and git. We can't
# have that, so we pre-fetch them in the versioned Nix expressions,
# and produce fake wget and git executables for debugging purposes.
#
# We also produce a fake hostname executable to prevent spurious
# command-not-found errors during compilation.
#
# The snippet below produces executables that simply print in stdout
# what they were supposed to fetch, and exit gracefully.
''
mkdir fake-bin
cat > fake-bin/wget << EOF
#!${stdenv.shell} -e
echo ===== FAKE WGET: Not fetching \$*
[ -e \$3 ]
EOF
cat > fake-bin/git << EOF
#!${stdenv.shell}
echo ===== FAKE GIT: Not cloning \$*
[ -e \$3 ]
EOF
cat > fake-bin/hostname << EOF
#!${stdenv.shell}
echo ${efiVendor}
[ -e \$3 ]
EOF
chmod +x fake-bin/*
export PATH=$PATH:$PWD/fake-bin
''
# Remove in-tree QEMU sources, as we either pre-fetch them through
# the versioned Nix expressions if withInternalQEMU is true, or we
# don't build QEMU at all if withInternalQEMU is false.
+ ''
rm --recursive --force tools/qemu-xen tools/qemu-xen-traditional
''
# Call copyPrefetchedSources, which copies all aviable sources to their correct positions.
+ ''
${copyPrefetchedSources}
'';
postPatch =
# The following patch forces Xen to install xen.efi on $out/boot
# instead of $out/boot/efi/efi/nixos, as the latter directory
# would otherwise need to be created manually. This also creates
# a more consistent output for downstreams who override the
# efiVendor attribute above.
''
substituteInPlace xen/Makefile \
--replace-fail "\$(D)\$(EFI_MOUNTPOINT)/efi/\$(EFI_VENDOR)/\$(T)-\$(XEN_FULLVERSION).efi" \
"\$(D)\$(BOOT_DIR)/\$(T)-\$(XEN_FULLVERSION).efi"
''
# The following patch fixes the call to /bin/mkdir on the
# launch_xenstore.sh helper script.
+ ''
substituteInPlace tools/hotplug/Linux/launch-xenstore.in \
--replace-fail "/bin/mkdir" "${coreutils}/bin/mkdir"
''
# The following expression fixes the paths called by Xen's systemd
# units, so we can use them in the NixOS module.
+ ''
substituteInPlace \
tools/hotplug/Linux/systemd/{xen-init-dom0,xen-qemu-dom0-disk-backend,xenconsoled,xendomains,xenstored}.service.in \
--replace-fail /bin/grep ${gnugrep}/bin/grep
substituteInPlace \
tools/hotplug/Linux/systemd/{xen-qemu-dom0-disk-backend,xenconsoled}.service.in \
--replace-fail "/bin/mkdir" "${coreutils}/bin/mkdir"
''
# # Call deployPrefetchedSourcesPatches, which patches all pre-fetched sources with their specified patchlists.
+ ''
${deployPrefetchedSourcesPatches}
''
# Patch shebangs for QEMU and OVMF build scripts.
+ ''
patchShebangs --build tools/qemu-xen/scripts/tracetool.py
patchShebangs --build tools/firmware/ovmf-dir-remote/OvmfPkg/build.sh tools/firmware/ovmf-dir-remote/BaseTools/BinWrappers/PosixLike/{AmlToC,BrotliCompress,build,GenFfs,GenFv,GenFw,GenSec,LzmaCompress,TianoCompress,Trim,VfrCompile}
'';
installPhase =
let
cpFlags = builtins.toString [
"--preserve=mode,ownership,timestamps,link"
"--recursive"
"--verbose"
"--no-dereference"
];
in
# Run the preInstall tasks.
''
runHook preInstall
''
# Create $out directories and copy build output.
+ ''
mkdir --parents $out $out/share $boot
cp ${cpFlags} dist/install/nix/store/*/* $out/
cp ${cpFlags} dist/install/etc $out
cp ${cpFlags} dist/install/boot $boot
''
# Run the postInstall tasks.
+ ''
runHook postInstall
'';
postInstall =
# Wrap xencov_split, xenmon and xentrace_format.
''
wrapPythonPrograms
''
# We also need to wrap pygrub, which lies in $out/libexec/xen/bin.
+ ''
wrapPythonProgramsIn "$out/libexec/xen/bin" "$out $pythonPath"
''
# Fix shebangs in Xen's various scripts.
#TODO: Remove any and all usage of `sed` and replace these complicated magic runes with readable code.
+ ''
shopt -s extglob
for i in $out/etc/xen/scripts/!(*.sh); do
sed --in-place "2s@^@export PATH=$out/bin:${scriptEnvPath}\n@" $i
done
'';
postFixup =
# Fix binaries in $out/libexec/xen/bin.
''
addAutoPatchelfSearchPath $out/lib
autoPatchelf $out/libexec/xen/bin
''
# Flask is particularly hard to disable. Even after
# setting the make flags to `n`, it still gets compiled.
# If withFlask is disabled, delete the extra binaries.
+ lib.strings.optionalString (!withFlask) ''
rm -f $out/bin/flask-*
'';
passthru = {
efi =
if withEFI then "boot/xen-${version}.efi" else throw "This Xen was compiled without an EFI binary.";
flaskPolicy =
if withFlask then
"boot/xenpolicy-${version}"
else
throw "This Xen was compiled without FLASK support.";
qemu-system-i386 =
if withInternalQEMU then
"libexec/xen/bin/qemu-system-i386"
else
throw "This Xen was compiled without a built-in QEMU.";
# This test suite is very simple, as Xen's userspace
# utilities require the hypervisor to be booted.
tests = {
pkg-config = testers.hasPkgConfigModules {
package = finalAttrs.finalPackage;
moduleNames = [
"xencall"
"xencontrol"
"xendevicemodel"
"xenevtchn"
"xenforeignmemory"
"xengnttab"
"xenguest"
"xenhypfs"
"xenlight"
"xenstat"
"xenstore"
"xentoolcore"
"xentoollog"
"xenvchan"
"xlutil"
];
};
};
};
meta = {
inherit branch;
# Short description for Xen.
description =
"Xen Hypervisor"
# The "and related components" addition is automatically hidden if said components aren't being built.
+ lib.strings.optionalString (prefetchedSources != { }) " and related components"
# To alter the description inside the paranthesis, edit ./packages.nix.
+ lib.strings.optionalString (lib.attrsets.hasAttrByPath [
"meta"
"description"
] packageDefinition) " (${packageDefinition.meta.description})";
# Long description for Xen.
longDescription =
# Starts with the longDescription from ./packages.nix.
(packageDefinition.meta.longDescription or "")
+ lib.strings.optionalString (!withInternalQEMU) (
"\nUse with `qemu_xen_${lib.strings.stringAsChars (x: if x == "." then "_" else x) branch}`"
+ lib.strings.optionalString latest " or `qemu_xen`"
+ ".\n"
)
# Then, if any of the optional with* components are being built, add the "Includes:" string.
+
lib.strings.optionalString
(
withInternalQEMU
|| withInternalSeaBIOS
|| withInternalOVMF
|| withInternalIPXE
|| withEFI
|| withFlask
)
(
"\nIncludes:"
# Originally, this was a call for the complicated withPrefetchedSources. Since there aren't
# that many optional components, we just use lib.strings.optionalString, because it's simpler.
# Optional components that aren't being built are automatically hidden.
+ lib.strings.optionalString withEFI "\n* `xen.efi`: Xen's [EFI binary](https://xenbits.xenproject.org/docs/${branch}-testing/misc/efi.html), available on the `boot` output of this package."
+ lib.strings.optionalString withFlask "\n* `xsm-flask`: The [FLASK Xen Security Module](https://wiki.xenproject.org/wiki/Xen_Security_Modules_:_XSM-FLASK). The `xenpolicy-${version}` file is available on the `boot` output of this package."
+ lib.strings.optionalString withInternalQEMU "\n* `qemu-xen`: Xen's mirror of [QEMU](https://www.qemu.org/)."
+ lib.strings.optionalString withInternalSeaBIOS "\n* `seabios-xen`: Xen's mirror of [SeaBIOS](https://www.seabios.org/SeaBIOS)."
+ lib.strings.optionalString withInternalOVMF "\n* `ovmf-xen`: Xen's mirror of [OVMF](https://github.com/tianocore/tianocore.github.io/wiki/OVMF)."
+ lib.strings.optionalString withInternalIPXE "\n* `ipxe-xen`: Xen's pinned version of [iPXE](https://ipxe.org/)."
)
# Finally, we write a notice explaining which vulnerabilities this Xen is NOT vulnerable to.
# This will hopefully give users the peace of mind that their Xen is secure, without needing
# to search the source code for the XSA patches.
+ lib.strings.optionalString (writeAdvisoryDescription != [ ]) (
"\n\nThis Xen (${version}) has been patched against the following known security vulnerabilities:\n"
+ lib.strings.removeSuffix "\n" (lib.strings.concatLines writeAdvisoryDescription)
);
homepage = "https://xenproject.org/";
downloadPage = "https://downloads.xenproject.org/release/xen/${version}/";
changelog = "https://wiki.xenproject.org/wiki/Xen_Project_${branch}_Release_Notes";
license = with lib.licenses; [
# Documentation.
cc-by-40
# Most of Xen is licensed under the GPL v2.0.
gpl2Only
# Xen Libraries and the `xl` command-line utility.
lgpl21Only
# Development headers in $dev/include.
mit
];
# This automatically removes maintainers from EOL versions of Xen, so we aren't bothered about versions we don't explictly support.
maintainers = lib.lists.optionals (lib.strings.versionAtLeast version minSupportedVersion) (
with lib.maintainers; [ sigmasquadron ]
);
mainProgram = "xl";
# Evaluates to x86_64-linux.
platforms = lib.lists.intersectLists lib.platforms.linux lib.platforms.x86_64;
knownVulnerabilities = lib.lists.optional (lib.strings.versionOlder version minSupportedVersion) "Xen ${version} is no longer supported by the Xen Security Team. See https://xenbits.xenproject.org/docs/unstable/support-matrix.html";
};
})