mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
Validate serialized sub pathnames in content files to prevent path traversal
This commit is contained in:
parent
95ac8d7b51
commit
4aef405259
4 changed files with 123 additions and 12 deletions
|
|
@ -1027,6 +1027,57 @@ static void test_path(void)
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct {
|
||||
const char* path;
|
||||
int expected;
|
||||
} sub_tests[] = {
|
||||
{ "", 0 },
|
||||
{ "/", 0 },
|
||||
{ "/a", 0 },
|
||||
{ "a/", 0 },
|
||||
{ "a//b", 0 },
|
||||
{ ".", 0 },
|
||||
{ "..", 0 },
|
||||
{ "./a", 0 },
|
||||
{ "../a", 0 },
|
||||
{ "a/.", 0 },
|
||||
{ "a/..", 0 },
|
||||
{ "a/./b", 0 },
|
||||
{ "a/../b", 0 },
|
||||
#ifdef _WIN32
|
||||
{ "\\a", 0 },
|
||||
{ "a\\b", 0 },
|
||||
{ "C:a", 0 },
|
||||
{ "C:/a", 0 },
|
||||
{ "d:file.txt", 0 },
|
||||
#else
|
||||
{ "\\a", 1 },
|
||||
{ "a\\b", 1 },
|
||||
{ "C:a", 1 },
|
||||
{ "C:/a", 1 },
|
||||
{ "d:file.txt", 1 },
|
||||
#endif
|
||||
{ "a", 1 },
|
||||
{ "file.txt", 1 },
|
||||
{ "dir/file.txt", 1 },
|
||||
{ "a/b/c/d.txt", 1 },
|
||||
{ ".hidden", 1 },
|
||||
{ "dir/.hidden", 1 },
|
||||
{ ".../file", 1 },
|
||||
{ "..file", 1 },
|
||||
{ "file..", 1 },
|
||||
{ "a/b:c/d", 1 },
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof(sub_tests) / sizeof(sub_tests[0]); ++i) {
|
||||
if (path_is_sub(sub_tests[i].path) != sub_tests[i].expected) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal(EINTERNAL, "Failed path_is_sub test for '%s', expected %d\n",
|
||||
sub_tests[i].path, sub_tests[i].expected);
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -2398,10 +2398,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
if (!*sub) {
|
||||
if (!path_is_sub(sub)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Null file!\n");
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid file '%s'!\n", sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -2774,10 +2774,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
if (!*sub) {
|
||||
if (!path_is_sub(sub)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Null dealloc!\n");
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid dealloc '%s'!\n", sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -2868,10 +2868,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (!*sub) {
|
||||
if (!path_is_sub(sub)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Null symlink!\n");
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid symlink '%s'!\n", sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -2919,10 +2919,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (!*sub) {
|
||||
if (!path_is_sub(sub)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Null hardlink!\n");
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid hardlink '%s'!\n", sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -2935,10 +2935,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (!*linkto) {
|
||||
if (!path_is_sub(linkto)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Empty hardlink '%s'!\n", sub);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid hardlink target '%s' for '%s'!\n", linkto, sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -2977,10 +2977,10 @@ static void state_read_content(struct snapraid_state* state, const char* path, S
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (!*sub) {
|
||||
if (!path_is_sub(sub)) {
|
||||
/* LCOV_EXCL_START */
|
||||
decoding_error(path, f);
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Null dir!\n");
|
||||
log_fatal(EINTERNAL, "Internal inconsistency: Invalid dir '%s'!\n", sub);
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,54 @@ int path_is_root_of(const char* root, const char* path)
|
|||
return *root == 0 && *path != 0;
|
||||
}
|
||||
|
||||
int path_is_sub(const char* sub)
|
||||
{
|
||||
const char* p;
|
||||
size_t len;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* cannot start with a Windows drive letter like C: */
|
||||
if (isalpha((unsigned char)sub[0]) && sub[1] == ':')
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
len = 0;
|
||||
p = sub;
|
||||
while (1) {
|
||||
char c = *p;
|
||||
|
||||
if (c == '/' || c == 0) {
|
||||
/* empty component (leading, consecutive, trailing slash, or empty string) */
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
/* dot component */
|
||||
if (len == 1 && *(p - 1) == '.')
|
||||
return 0;
|
||||
|
||||
/* dot-dot component */
|
||||
if (len == 2 && *(p - 2) == '.' && *(p - 1) == '.')
|
||||
return 0;
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
len = 0;
|
||||
#ifdef _WIN32
|
||||
} else if (c == '\\') {
|
||||
/* cannot contain backslash in Windows */
|
||||
return 0;
|
||||
#endif
|
||||
} else {
|
||||
++len;
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* file-system */
|
||||
|
||||
|
|
|
|||
|
|
@ -369,6 +369,18 @@ int pathncmp(const char* a, const char* b, size_t n);
|
|||
*/
|
||||
int path_is_root_of(const char* root, const char* path);
|
||||
|
||||
/**
|
||||
* Check if the path is a canonical relative sub-path.
|
||||
*
|
||||
* A valid sub-path must:
|
||||
* - Not be empty
|
||||
* - Not start or end with a slash '/'
|
||||
* - Not contain backslashes '\\' (on Windows)
|
||||
* - Not start with a drive letter (e.g., "C:" on Windows)
|
||||
* - Not contain empty components ("//"), "." or ".."
|
||||
*/
|
||||
int path_is_sub(const char* sub);
|
||||
|
||||
/****************************************************************************/
|
||||
/* file-system */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue