Skip to content

Commit 1633cea

Browse files
committed
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 c436b7a. 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.
1 parent edf9163 commit 1633cea

File tree

3 files changed

+34
-4
lines changed

3 files changed

+34
-4
lines changed

src/libutil-tests/canon-path.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ TEST(CanonPath, basic)
4242
}
4343
}
4444

45+
TEST(CanonPath, nullBytes)
46+
{
47+
std::string s = "/hello/world";
48+
s[8] = '\0';
49+
ASSERT_THROW(CanonPath("/").push(std::string(1, '\0')), BadCanonPath);
50+
ASSERT_THROW(CanonPath(std::string_view(s)), BadCanonPath);
51+
ASSERT_THROW(CanonPath(s, CanonPath::root), BadCanonPath);
52+
}
53+
4554
TEST(CanonPath, from_existing)
4655
{
4756
CanonPath p0("foo//bar/");

src/libutil/canon-path.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "nix/util/file-path-impl.hh"
44
#include "nix/util/strings-inline.hh"
55

6+
#include <cstring>
7+
68
namespace nix {
79

810
const CanonPath CanonPath::root = CanonPath("/");
@@ -12,14 +14,30 @@ static std::string absPathPure(std::string_view path)
1214
return canonPathInner<UnixPathTrait>(path, [](auto &, auto &) {});
1315
}
1416

17+
static void ensureNoNullBytes(std::string_view s)
18+
{
19+
if (std::memchr(s.data(), '\0', s.size())) [[unlikely]] {
20+
using namespace std::string_view_literals;
21+
auto str = replaceStrings(std::string(s), "\0"sv, ""sv);
22+
throw BadCanonPath("path segment '%s' must not contain null (\\0) bytes", str);
23+
}
24+
}
25+
1526
CanonPath::CanonPath(std::string_view raw)
1627
: path(absPathPure(concatStrings("/", raw)))
28+
{
29+
ensureNoNullBytes(raw);
30+
}
31+
32+
CanonPath::CanonPath(const char * raw)
33+
: path(absPathPure(concatStrings("/", raw)))
1734
{
1835
}
1936

2037
CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
2138
: path(absPathPure(raw.size() > 0 && raw[0] == '/' ? raw : concatStrings(root.abs(), "/", raw)))
2239
{
40+
ensureNoNullBytes(raw);
2341
}
2442

2543
CanonPath::CanonPath(const std::vector<std::string> & elems)
@@ -80,6 +98,7 @@ void CanonPath::push(std::string_view c)
8098
{
8199
assert(c.find('/') == c.npos);
82100
assert(c != "." && c != "..");
101+
ensureNoNullBytes(c);
83102
if (!isRoot())
84103
path += '/';
85104
path += c;

src/libutil/include/nix/util/canon-path.hh

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
///@file
33

4+
#include "nix/util/error.hh"
45
#include <string>
56
#include <optional>
67
#include <cassert>
@@ -12,6 +13,8 @@
1213

1314
namespace nix {
1415

16+
MakeError(BadCanonPath, Error);
17+
1518
/**
1619
* A canonical representation of a path. It ensures the following:
1720
*
@@ -23,6 +26,8 @@ namespace nix {
2326
*
2427
* - There are no components equal to '.' or '..'.
2528
*
29+
* - It does not contain NUL bytes.
30+
*
2631
* `CanonPath` are "virtual" Nix paths for abstract file system objects;
2732
* they are always Unix-style paths, regardless of what OS Nix is
2833
* running on. The `/` root doesn't denote the ambient host file system
@@ -51,10 +56,7 @@ public:
5156
*/
5257
CanonPath(std::string_view raw);
5358

54-
explicit CanonPath(const char * raw)
55-
: CanonPath(std::string_view(raw))
56-
{
57-
}
59+
explicit CanonPath(const char * raw);
5860

5961
struct unchecked_t
6062
{};

0 commit comments

Comments
 (0)