From 69b37a8e5d166328fa3d9a7c61b7ca1dfa772552 Mon Sep 17 00:00:00 2001 From: Reepca Russelstein Date: Sun, 21 Jun 2026 07:19:56 -0500 Subject: daemon: libstore: add assertStorePathStrict and helpers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isStorePath is a lightweight check that only verifies that the given path is in the store and doesn't have any '/' characters after the store prefix. This is insufficiently strict for verifying that a given path is actually a valid store path. isStoreName is extracted from checkStoreName, and to preserve the error messages may have a second argument giving a string to write to with the problem description in the case that isStoreName returns false. * nix/libstore/store-api.hh (assertStorePathStrict, isStoreName, isStoreBasenameStrict, isStorePathStrict): new functions. * nix/libstore/worker-protocol.hh (readStorePathStrict): new function. (readStorePathsStrict): new template. * nix/libstore/store-api.cc (assertStorePathStrict, isStoreName, isStoreBasenameStrict, isStorePathStrict, readStorePathStrict, readStorePathsStrict): provide implementations. Signed-off-by: Ludovic Courtès --- nix/libstore/store-api.cc | 111 ++++++++++++++++++++++++++++++++++------ nix/libstore/store-api.hh | 18 +++++++ nix/libstore/worker-protocol.hh | 3 ++ 3 files changed, 116 insertions(+), 16 deletions(-) (limited to 'nix') diff --git a/nix/libstore/store-api.cc b/nix/libstore/store-api.cc index 26d44f78b19..6445209652e 100644 --- a/nix/libstore/store-api.cc +++ b/nix/libstore/store-api.cc @@ -29,6 +29,71 @@ bool isStorePath(const Path & path) } +bool isStoreName(const string & name, string & problemDescription) +{ + const string validChars = "+-._?="; + if (name.empty()) { + problemDescription = "empty string is not a valid name"; + return false; + } + /* Disallow names starting with a dot for possible security + reasons (e.g., "." and ".."). */ + if (name.starts_with(".")) { + problemDescription = std::format("invalid name: `{}' (can't begin with dot)", + name); + return false; + } + for (const auto& i : name) + if (!((i >= 'A' && i <= 'Z') || + (i >= 'a' && i <= 'z') || + (i >= '0' && i <= '9') || + validChars.find(i) != string::npos)) + { + problemDescription = std::format("invalid character `{}' in name `{}'", + i, + name); + return false; + } + return true; +} + + +bool isStoreName(const string & name) +{ + string problemDescription; /* Placeholder */ + return isStoreName(name, problemDescription); +} + + +void checkStoreName(const string & name) +{ + string problemDescription; + if (!isStoreName(name, problemDescription)) + throw Error(problemDescription); +} + + +bool isStoreBasenameStrict(const string & name) +{ + /* 1. At least 34 characters long + 2. First 32 characters are all nix-base32 characters + 3. 33rd character (index 32) is a dash + 4. 34th character (index 33) and those following it form a valid store + name. */ + return name.size() >= 34 + && isHash32(string(name, 0, 32)) + && name[32] == '-' + && isStoreName(string(name, 33)); +} + + +bool isStorePathStrict(const Path & path) +{ + return isStorePath(path) + && isStoreBasenameStrict(string(path, settings.nixStore.size() + 1)); +} + + void assertStorePath(const Path & path) { if (!isStorePath(path)) @@ -36,6 +101,13 @@ void assertStorePath(const Path & path) } +void assertStorePathStrict(const Path & path) +{ + if (!isStorePathStrict(path)) + throw Error(std::format("path `{}' is not a valid store path", path)); +} + + Path toStorePath(const Path & path) { if (!isInStore(path)) @@ -55,22 +127,7 @@ string storePathToName(const Path & path) } -void checkStoreName(const string & name) -{ - string validChars = "+-._?="; - /* Disallow names starting with a dot for possible security - reasons (e.g., "." and ".."). */ - if (name.starts_with(".")) - throw Error(std::format("invalid name: `{}' (can't begin with dot)", name)); - for (const auto& i : name) - if (!((i >= 'A' && i <= 'Z') || - (i >= 'a' && i <= 'z') || - (i >= '0' && i <= '9') || - validChars.find(i) != string::npos)) - { - throw Error(std::format("invalid character `{}' in name `{}'", i, name)); - } -} + /* Store paths have the following form: @@ -250,6 +307,28 @@ template T readStorePaths(Source & from) return paths; } + template PathSet readStorePaths(Source & from); + +Path readStorePathStrict(Source & from) +{ + Path path = readString(from); + assertStorePathStrict(path); + return path; +} + + +template T readStorePathsStrict(Source & from) +{ + T paths = readStrings(from); + for (auto& i : paths) assertStorePathStrict(i); + return paths; +} + + +template PathSet readStorePathsStrict(Source & from); + } + + diff --git a/nix/libstore/store-api.hh b/nix/libstore/store-api.hh index b57f1e10842..bc90d145a29 100644 --- a/nix/libstore/store-api.hh +++ b/nix/libstore/store-api.hh @@ -301,6 +301,12 @@ public: /* Throw an exception if `path' is not directly in the Nix store. */ void assertStorePath(const Path & path); +/* Throw an exception if `path' is not a valid path for a store item. This + requires not only that it is directly in the store (like with + assertStorePath), but also that it has a valid hash part (32 nix-base32 + characters) followed by a dash ('-') followed by a valid store name as per + isStoreName and checkStoreName. */ +void assertStorePathStrict(const Path & path); bool isInStore(const Path & path); bool isStorePath(const Path & path); @@ -308,8 +314,20 @@ bool isStorePath(const Path & path); /* Extract the name part of the given store path. */ string storePathToName(const Path & path); +/* Return true iff `name' is a valid name for a store item. If false is + returned, `problemDescription' is set to a string suitable for use in an + Error exception. */ +bool isStoreName(const string & name, string & problemDescription); +bool isStoreName(const string & name); void checkStoreName(const string & name); +/* Return true iff `name' is a valid basename for a store item's path, e.g. of + the form hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh-NAME, where 'h' represents a + nix-base32 character and NAME is a valid store name per isStoreName. */ +bool isStoreBasenameStrict(const string & name); +/* Return true iff `path' is a valid path for a store item. */ +bool isStorePathStrict(const Path & path); + /* Chop off the parts after the top-level store name, e.g., /nix/store/abcd-foo/bar => /nix/store/abcd-foo. */ diff --git a/nix/libstore/worker-protocol.hh b/nix/libstore/worker-protocol.hh index ef259db2a01..8e3fb369193 100644 --- a/nix/libstore/worker-protocol.hh +++ b/nix/libstore/worker-protocol.hh @@ -59,5 +59,8 @@ typedef enum { Path readStorePath(Source & from); template T readStorePaths(Source & from); +Path readStorePathStrict(Source & from); +template T readStorePathsStrict(Source & from); + } -- cgit v1.2.3