[PATCH v3 14/14] Validate configuration parameters

Demi Marie Obenour
2025-11-19T08:21:47Z

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 v2:
- Use loose URL validation: allow anything that might work.
- Only reject versions that violate the specification.
---
host/rootfs/default.nix | 19 ++++++++++++++++++-
lib/config.nix | 20 ++++++++++++++------
2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix
index f0b7e061a1f39b3e70d337ef4fe14c98a8f022c8..06147eb8d1b713faac9b69ffdf42138d0c3e3093 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.update-url; 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 ];
@@ -151,7 +168,7 @@ stdenvNoCC.mkDerivation {
sed p ${writeClosure [ packagesSysroot] } >>$out
'';
UPDATE_SIGNING_KEY = config.update-signing-key;
- UPDATE_URL = config.update-url;
+ UPDATE_URL = update-url;
VERSION = config.version;
};

diff --git a/lib/config.nix b/lib/config.nix
index d7edb967f339f2d0af97adef5c0302eb58950d19..4db6c34635abb1419224485f1e56119569375831 100644
--- a/lib/config.nix
+++ b/lib/config.nix
@@ -21,9 +21,17 @@ let
finalConfig = default // callConfig config;
in

-finalConfig // {
- update-signing-key = builtins.path {
- name = "signing-key";
- path = finalConfig.update-signing-key;
- };
-}
+# 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 // {
+ update-signing-key = builtins.path {
+ name = "signing-key";
+ path = finalConfig.update-signing-key;
+ };
+ }

--
2.52.0