Skip to content

Commit 1bc5d7c

Browse files
DemiMariemarmarek
authored andcommitted
Don't allow trailing slashes in paths being unpacked
They would result in a misleading ENOENT error due to passing an empty path to openat(). Instead, reject the path with EILSEQ to indicate a malformed pathname. (cherry picked from commit 044c4d4)
1 parent 641ba4d commit 1bc5d7c

File tree

3 files changed

+30
-5
lines changed

3 files changed

+30
-5
lines changed

qrexec-lib/pure.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ enum QubeNameValidationError {
253253
QUBES_PURE_PUBLIC enum QubeNameValidationError
254254
qubes_pure_is_valid_qube_name(const struct QubesSlice untrusted_str);
255255

256+
/// Flags for pathname validation functions.
256257
enum QubesFilenameValidationFlags {
257258
/// Disable Unicode charset restrictions and UTF-8 validity checks.
258259
QUBES_PURE_ALLOW_UNSAFE_CHARACTERS = (1 << 0),
@@ -264,6 +265,8 @@ enum QubesFilenameValidationFlags {
264265
QUBES_PURE_ALLOW_UNSAFE_SYMLINKS = (1 << 2),
265266
/// Allow all paths to be non-canonical, including symlinks.
266267
QUBES_PURE_ALLOW_NON_CANONICAL_PATHS = (1 << 3),
268+
/// Allow trailing slash.
269+
QUBES_PURE_ALLOW_TRAILING_SLASH = (1 << 4),
267270
};
268271

269272
#ifdef __cplusplus

qrexec-lib/unicode.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ static ssize_t validate_path(const uint8_t *const untrusted_name,
167167
return allow_non_canonical ? 0 : -ENOLINK; // empty path
168168
if (untrusted_name[0] == '/')
169169
return -ENOLINK; // absolute path
170-
for (size_t i = 0; untrusted_name[i]; i++) {
170+
size_t i;
171+
for (i = 0; untrusted_name[i]; i++) {
171172
if (i == 0 || untrusted_name[i - 1] == '/') {
172173
// Start of a path component
173174
switch (untrusted_name[i]) {
@@ -216,6 +217,14 @@ static ssize_t validate_path(const uint8_t *const untrusted_name,
216217
}
217218
}
218219
}
220+
if (i < 1 || untrusted_name[i]) {
221+
// ideally this would be COMPILETIME_UNREACHABLE but GCC can't prove this
222+
assert(0);
223+
return -EILSEQ;
224+
}
225+
if ((flags & QUBES_PURE_ALLOW_TRAILING_SLASH) == 0 &&
226+
untrusted_name[i - 1] == '/')
227+
return -EILSEQ;
219228
return non_dotdot_components;
220229
}
221230

@@ -224,6 +233,7 @@ static bool flag_check(const uint32_t flags)
224233
int const allowed = (QUBES_PURE_ALLOW_UNSAFE_CHARACTERS |
225234
QUBES_PURE_ALLOW_NON_CANONICAL_SYMLINKS |
226235
QUBES_PURE_ALLOW_NON_CANONICAL_PATHS |
236+
QUBES_PURE_ALLOW_TRAILING_SLASH |
227237
QUBES_PURE_ALLOW_UNSAFE_SYMLINKS);
228238
return (flags & ~(__typeof__(flags))allowed) == 0;
229239
}
@@ -243,7 +253,8 @@ qubes_pure_validate_file_name_v2(const uint8_t *const untrusted_filename,
243253
QUBES_PURE_PUBLIC bool
244254
qubes_pure_validate_file_name(const uint8_t *const untrusted_filename)
245255
{
246-
return qubes_pure_validate_file_name_v2(untrusted_filename, 0) == 0;
256+
return qubes_pure_validate_file_name_v2(untrusted_filename,
257+
QUBES_PURE_ALLOW_TRAILING_SLASH) == 0;
247258
}
248259

249260
QUBES_PURE_PUBLIC int
@@ -271,16 +282,19 @@ qubes_pure_validate_symbolic_link_v2(const uint8_t *untrusted_name,
271282
// (which resolves to "c"). Similarly and "a/b/c" can point to "../d"
272283
// (which resolves to "a/d") but not "../../d" (which resolves to "d").
273284
// This ensures that ~/QubesIncoming/QUBENAME/a/b cannot point outside
274-
// of ~/QubesIncoming/QUBENAME/a.
275-
ssize_t res = validate_path(untrusted_target, (size_t)(depth - 2), flags);
285+
// of ~/QubesIncoming/QUBENAME/a. Always allow trailing slash in the
286+
// symbolic link target, whether or not they are allowed in the path.
287+
ssize_t res = validate_path(untrusted_target, (size_t)(depth - 2),
288+
flags | QUBES_PURE_ALLOW_TRAILING_SLASH);
276289
return res < 0 ? res : 0;
277290
}
278291

279292
QUBES_PURE_PUBLIC bool
280293
qubes_pure_validate_symbolic_link(const uint8_t *untrusted_name,
281294
const uint8_t *untrusted_target)
282295
{
283-
return qubes_pure_validate_symbolic_link_v2(untrusted_name, untrusted_target, 0) == 0;
296+
return qubes_pure_validate_symbolic_link_v2(untrusted_name, untrusted_target,
297+
QUBES_PURE_ALLOW_TRAILING_SLASH) == 0;
284298
}
285299

286300
QUBES_PURE_PUBLIC bool

qrexec-lib/validator-test.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ int main(int argc, char **argv)
182182
TEST("a/b", "b//c", QUBES_PURE_ALLOW_NON_CANONICAL_PATHS, true),
183183
// ...and non-canonical paths.
184184
TEST("a//b", "b//c", QUBES_PURE_ALLOW_NON_CANONICAL_PATHS, true),
185+
// Symlinks may end in "/"...
186+
TEST("a/b/c", "a/", QUBES_PURE_ALLOW_TRAILING_SLASH, true),
187+
// ...even without QUBES_PURE_ALLOW_TRAILING_SLASH.
188+
TEST("a/b/c", "a/", 0, true),
189+
// but the path cannot...
190+
TEST("a/b/c/", "a/", 0, false),
191+
// ...unless QUBES_PURE_ALLOW_TRAILING_SLASH is passed.
192+
TEST("a/b/c/", "a/", QUBES_PURE_ALLOW_TRAILING_SLASH, true),
185193
};
186194
int failed = 0;
187195
#define SYMLINK_TEST(a) symlink_test(a, sizeof(a)/sizeof(a[0]))

0 commit comments

Comments
 (0)