Skip to content

Commit 6040f62

Browse files
DemiMariemarmarek
authored andcommitted
Allow symlinks ending in ".."
They were rejected without a good reason. They pose no danger not already posed by symlinks of the form "../a". (cherry picked from commit 34c92b5)
1 parent 7990a9c commit 6040f62

File tree

2 files changed

+16
-9
lines changed

2 files changed

+16
-9
lines changed

qrexec-lib/unicode.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,24 +124,29 @@ static int validate_utf8_char(const uint8_t *untrusted_c) {
124124
// is set to zero, and the number of non-".." components is incremented.
125125
//
126126
// The return value is the number of non-".." components on
127-
// success, or 0 on failure.
128-
static size_t validate_path(const uint8_t *const untrusted_name, size_t allowed_leading_dotdot)
127+
// success, or -1 on failure.
128+
static ssize_t validate_path(const uint8_t *const untrusted_name, size_t allowed_leading_dotdot)
129129
{
130-
size_t non_dotdot_components = 0, i = 0;
130+
// We assume that there are not SSIZE_MAX path components.
131+
// This cannot happen on hardware using a flat address space,
132+
// as this would require SIZE_MAX bytes in the path and leave
133+
// no space for the executable code.
134+
ssize_t non_dotdot_components = 0;
135+
size_t i = 0;
131136
do {
132137
if (i == 0 || untrusted_name[i - 1] == '/') {
133138
switch (untrusted_name[i]) {
134139
case '/': // repeated or initial slash
135140
case '\0': // trailing slash or empty string
136-
return 0;
141+
return -1;
137142
case '.':
138143
if (untrusted_name[i + 1] == '\0' || untrusted_name[i + 1] == '/')
139-
return 0;
144+
return -1;
140145
if ((untrusted_name[i + 1] == '.') &&
141146
(untrusted_name[i + 2] == '\0' || untrusted_name[i + 2] == '/')) {
142147
/* Check if the limit on leading ".." components has been exceeded */
143148
if (allowed_leading_dotdot < 1)
144-
return 0;
149+
return -1;
145150
allowed_leading_dotdot--;
146151
i += 2; // advance past ".."
147152
continue;
@@ -160,7 +165,7 @@ static size_t validate_path(const uint8_t *const untrusted_name, size_t allowed_
160165
if (utf8_ret > 0) {
161166
i += utf8_ret;
162167
} else {
163-
return 0;
168+
return -1;
164169
}
165170
}
166171
} while (untrusted_name[i]);
@@ -177,7 +182,7 @@ QUBES_PURE_PUBLIC bool
177182
qubes_pure_validate_symbolic_link(const uint8_t *untrusted_name,
178183
const uint8_t *untrusted_target)
179184
{
180-
size_t depth = validate_path(untrusted_name, 0);
185+
ssize_t depth = validate_path(untrusted_name, 0);
181186
// Symlink paths must have at least 2 components: "a/b" is okay
182187
// but "a" is not. This ensures that the toplevel "a" entry
183188
// is not a symbolic link.
@@ -190,7 +195,7 @@ qubes_pure_validate_symbolic_link(const uint8_t *untrusted_name,
190195
// (which resolves to "a/d") but not "../../d" (which resolves to "d").
191196
// This ensures that ~/QubesIncoming/QUBENAME/a/b cannot point outside
192197
// of ~/QubesIncoming/QUBENAME/a.
193-
return validate_path(untrusted_target, depth - 2) > 0;
198+
return validate_path(untrusted_target, (size_t)(depth - 2)) >= 0;
194199
}
195200

196201
QUBES_PURE_PUBLIC bool

qrexec-lib/validator-test.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,6 @@ int main(int argc, char **argv)
217217
assert(qubes_pure_validate_symbolic_link((const uint8_t *)"a/b/c", (const uint8_t *)"../a"));
218218
// Absolute symlinks are rejected
219219
assert(!qubes_pure_validate_symbolic_link((const uint8_t *)"a/b/c", (const uint8_t *)"/a"));
220+
// Symlinks may end in "..".
221+
assert(qubes_pure_validate_symbolic_link((const uint8_t *)"a/b/c", (const uint8_t *)".."));
220222
}

0 commit comments

Comments
 (0)