Skip to content

Commit 959c244

Browse files
authored
Merge pull request #14243 from NixOS/canon-path-nul-bytes
libutil: Ensure that CanonPath does not contain NUL bytes
2 parents c44d2d5 + 1633cea commit 959c244

File tree

3 files changed

+36
-6
lines changed

3 files changed

+36
-6
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: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,41 @@
33
#include "nix/util/file-path-impl.hh"
44
#include "nix/util/strings-inline.hh"
55

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

8-
CanonPath CanonPath::root = CanonPath("/");
10+
const CanonPath CanonPath::root = CanonPath("/");
911

1012
static std::string absPathPure(std::string_view path)
1113
{
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: 7 additions & 5 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
{};
@@ -69,7 +71,7 @@ public:
6971
*/
7072
CanonPath(const std::vector<std::string> & elems);
7173

72-
static CanonPath root;
74+
static const CanonPath root;
7375

7476
/**
7577
* If `raw` starts with a slash, return

0 commit comments

Comments
 (0)