1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-09 20:16:03 +01:00

libutil: Ensure that CanonPath does not contain NUL bytes

This, alongside the other invariants of the CanonPath is important
to uphold. std::filesystem happily crashes on NUL bytes in the constructor,
as we've seen with `path:%00` prior to c436b7a32a.
Best to stay clear of NUL bytes when we're talking about syscalls, especially
on Unix where strings are null terminated.

Very nice to have if we decide to switch over to pascal-style strings.
This commit is contained in:
Sergei Zimmerman 2025-10-14 02:33:38 +03:00
parent edf9163c22
commit 1633ceaff2
No known key found for this signature in database
3 changed files with 34 additions and 4 deletions

View file

@ -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/");

View file

@ -3,6 +3,8 @@
#include "nix/util/file-path-impl.hh"
#include "nix/util/strings-inline.hh"
#include <cstring>
namespace nix {
const CanonPath CanonPath::root = CanonPath("/");
@ -12,14 +14,30 @@ static std::string absPathPure(std::string_view path)
return canonPathInner<UnixPathTrait>(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<std::string> & 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;

View file

@ -1,6 +1,7 @@
#pragma once
///@file
#include "nix/util/error.hh"
#include <string>
#include <optional>
#include <cassert>
@ -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
{};