[PATCH v4 14/14] Validate configuration parameters

Demi Marie Obenour
2025-11-22T01:28:15Z

Wrong values for the version or update URL will cause very confusing
build-time or runtime errors. Provide a better user experience by
validating them up-front.

The update URL validator is loose. It rejects only URLs that cannot
possibly work: either appending /SHA256SUMS to them doesn't append to
the path, or they will definitely be rejected by curl due to being
malformed.

The version validator is in lib/config.nix, as the version number is
used in many places. It checks that the version only uses characters
that are permitted by systemd's version number specification [1] and
that will not break code that uses them in shell or sed commands.

[1]: https://uapi-group.org/specifications/specs/version_format_specification

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
Changes since v3:
- Validate compression level.

Changes since v2:
- Use loose URL validation: allow anything that might work.
- Only reject versions that violate the specification.
---
Documentation/installation/getting-spectrum.adoc | 2 +-
host/rootfs/default.nix | 19 ++++++++++++++++++-
lib/config.nix | 12 +++++++++++-
release/combined/eosimages.nix | 8 +++++++-
4 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/Documentation/installation/getting-spectrum.adoc b/Documentation/installation/getting-spectrum.adoc
index 0abc83a9e6fc01084b3faa9b93eb38398b0aef27..919b28f86eddff1b92570d46b62a1fbddc32f2d5 100644
--- a/Documentation/installation/getting-spectrum.adoc
+++ b/Documentation/installation/getting-spectrum.adoc
@@ -93,7 +93,7 @@ you must provide your own. You can do this via
xref:../development/build-configuration.adoc[build configuration].
The default sets the signing key to `/dev/null` and the server
URL to an invalid value, so updates won't work. To enable updates,
-set `update-url` to the URL of your server and `update-signing-key`
+set `updateUrl` to the URL of your server and `updateSigningKey`
to a binary GnuPG keyring to verify the updates with. Not all possible
URLs will work, but most invalid URLs will cause an error during the
build rather than runtime misbehavior.
diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix
index 1ebaf11cd7e9d61444b6524de6053a0f3cfb82c8..fed99013f960287c3be3941ca593b22c55a6f79a 100644
--- a/host/rootfs/default.nix
+++ b/host/rootfs/default.nix
@@ -85,6 +85,23 @@ let
appvm-systemd-sysupdate = callSpectrumPackage ../../vm/app/systemd-sysupdate {};
};

+ update-url =
+ let update-url = config.updateUrl; in
+ # Use builtins.fromJSON because it supports \uXXXX escapes.
+ # This is the same check done by check-url.awk in the update VM.
+ # The update code is careful to escape any metacharacters, but some
+ # simply cannot be made to work. Concatenating the URL with /SHA256SUMS
+ # must append to the path portion of the URL, and the URL must be one
+ # that libcurl will accept. I don't know how Unicode space is handled,
+ # but it is a bad idea.
+ if builtins.match (builtins.fromJSON "\"^[^\\u0001- #?\\u007F[:space:]]+$\"" update-url) == null then
+ builtins.abort ''
+ Update URL ${builtins.toJSON update-url} has forbidden characters.
+ Query strings, and fragment specifiers are not supported.
+ ASCII control characters and whitespace must be %-encoded.
+ ''
+ else
+ update-url;
packagesSysroot = runCommand "packages-sysroot" {
depsBuildBuild = [ inkscape ];
nativeBuildInputs = [ xorg.lndir ];
@@ -152,7 +169,7 @@ stdenvNoCC.mkDerivation {
name = "signing-key";
path = config.updateSigningKey;
};
- UPDATE_URL = config.updateUrl;
+ UPDATE_URL = update-url;
VERSION = config.version;
};

diff --git a/lib/config.nix b/lib/config.nix
index bc5b42f506b7bfd2f66db48610491809351d1a2c..2065be83ad97f8eb011f070d8c3f3249104d07f4 100644
--- a/lib/config.nix
+++ b/lib/config.nix
@@ -18,6 +18,16 @@ let
callConfig = config: if builtins.typeOf config == "lambda" then config {
inherit default;
} else config;
+ finalConfig = default // callConfig config;
in

-default // callConfig config
+# Version is used in many files, so validate it here.
+# See https://uapi-group.org/specifications/specs/version_format_specification
+# for allowed version strings.
+if builtins.match "[[:alnum:]_.~^-]+" finalConfig.version == null then
+ builtins.abort ''
+ Version ${builtins.toJSON finalConfig.version} has forbidden characters.
+ Only ASCII alphanumerics, ".", "_", "~", "^", "+", and "-" are allowed.
+ ''
+else
+ finalConfig
diff --git a/release/combined/eosimages.nix b/release/combined/eosimages.nix
index 9cb35dcecee54c17392b609c493272ec83062e9b..5d1e3a67bb81cbb737823bfa3c75d88f18b31f2a 100644
--- a/release/combined/eosimages.nix
+++ b/release/combined/eosimages.nix
@@ -4,6 +4,12 @@
import ../../lib/call-package.nix (
{ callSpectrumPackage, runCommand, e2fsprogs, tar2ext4, config }:

+let
+ compressionLevel = config.compressionLevel;
+in
+if compressionLevel < 1 || compressionLevel > 9 then
+ builtins.abort "Compression level ${builtins.toString compressionLevel} is invalid (< 1 or > 9)"
+else
runCommand "eosimages.img" {
nativeBuildInputs = [ e2fsprogs tar2ext4 ];
imageName = "Spectrum-0.0-x86_64-generic.0.Live.img";
@@ -16,7 +22,7 @@ runCommand "eosimages.img" {
mkdir dir
cd dir
ln -s -- "$image" "$imageName"
- gzip -${builtins.toString (0 + config.compressionLevel)} < "$image" > "$imageName.gz"
+ gzip -${builtins.toString compressionLevel} < "$image" > "$imageName.gz"
sha256sum -- "$imageName.gz" > "$imageName.gz.sha256"
tar -ch -- "$imageName.gz" "$imageName.gz.sha256" | tar2ext4 -o "$out"
e2label "$out" eosimages

--
2.52.0