diff --git a/src/libutil-tests/canon-path.cc b/src/libutil-tests/canon-path.cc index 971a9cc96..aae9285c4 100644 --- a/src/libutil-tests/canon-path.cc +++ b/src/libutil-tests/canon-path.cc @@ -42,6 +42,15 @@ TEST(CanonPath, basic) } } +TEST(CanonPath, nullBytes) +{ + std::string s = "/hello/world"; + s[8] = '\0'; + ASSERT_THROW(CanonPath("/").push(std::string(1, '\0')), BadCanonPath); + ASSERT_THROW(CanonPath(std::string_view(s)), BadCanonPath); + ASSERT_THROW(CanonPath(s, CanonPath::root), BadCanonPath); +} + TEST(CanonPath, from_existing) { CanonPath p0("foo//bar/"); diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc index 3b4777ef7..22ca3e066 100644 --- a/src/libutil/canon-path.cc +++ b/src/libutil/canon-path.cc @@ -3,6 +3,8 @@ #include "nix/util/file-path-impl.hh" #include "nix/util/strings-inline.hh" +#include + namespace nix { const CanonPath CanonPath::root = CanonPath("/"); @@ -12,14 +14,30 @@ static std::string absPathPure(std::string_view path) return canonPathInner(path, [](auto &, auto &) {}); } +static void ensureNoNullBytes(std::string_view s) +{ + if (std::memchr(s.data(), '\0', s.size())) [[unlikely]] { + using namespace std::string_view_literals; + auto str = replaceStrings(std::string(s), "\0"sv, "␀"sv); + throw BadCanonPath("path segment '%s' must not contain null (\\0) bytes", str); + } +} + CanonPath::CanonPath(std::string_view raw) : path(absPathPure(concatStrings("/", raw))) +{ + ensureNoNullBytes(raw); +} + +CanonPath::CanonPath(const char * raw) + : path(absPathPure(concatStrings("/", raw))) { } CanonPath::CanonPath(std::string_view raw, const CanonPath & root) : path(absPathPure(raw.size() > 0 && raw[0] == '/' ? raw : concatStrings(root.abs(), "/", raw))) { + ensureNoNullBytes(raw); } CanonPath::CanonPath(const std::vector & elems) @@ -80,6 +98,7 @@ void CanonPath::push(std::string_view c) { assert(c.find('/') == c.npos); assert(c != "." && c != ".."); + ensureNoNullBytes(c); if (!isRoot()) path += '/'; path += c; diff --git a/src/libutil/include/nix/util/canon-path.hh b/src/libutil/include/nix/util/canon-path.hh index a9c173d71..b9b2fff25 100644 --- a/src/libutil/include/nix/util/canon-path.hh +++ b/src/libutil/include/nix/util/canon-path.hh @@ -1,6 +1,7 @@ #pragma once ///@file +#include "nix/util/error.hh" #include #include #include @@ -12,6 +13,8 @@ namespace nix { +MakeError(BadCanonPath, Error); + /** * A canonical representation of a path. It ensures the following: * @@ -23,6 +26,8 @@ namespace nix { * * - There are no components equal to '.' or '..'. * + * - It does not contain NUL bytes. + * * `CanonPath` are "virtual" Nix paths for abstract file system objects; * they are always Unix-style paths, regardless of what OS Nix is * running on. The `/` root doesn't denote the ambient host file system @@ -51,10 +56,7 @@ public: */ CanonPath(std::string_view raw); - explicit CanonPath(const char * raw) - : CanonPath(std::string_view(raw)) - { - } + explicit CanonPath(const char * raw); struct unchecked_t {};