Add filename validation for test/extracting
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 8s
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 8s
This should prevent creation of files/symlinks outside of current-working-directory or user-set-cwd.
This commit is contained in:
parent
b1745172f7
commit
b8c56026d1
8 changed files with 82 additions and 11 deletions
BIN
invalid_file_format_0_example_0
Normal file
BIN
invalid_file_format_0_example_0
Normal file
Binary file not shown.
BIN
invalid_file_format_0_example_1
Normal file
BIN
invalid_file_format_0_example_1
Normal file
Binary file not shown.
BIN
invalid_file_format_1_example_0
Normal file
BIN
invalid_file_format_1_example_0
Normal file
Binary file not shown.
BIN
invalid_file_format_1_example_1
Normal file
BIN
invalid_file_format_1_example_1
Normal file
Binary file not shown.
BIN
invalid_file_format_1_example_2
Normal file
BIN
invalid_file_format_1_example_2
Normal file
Binary file not shown.
|
@ -66,6 +66,8 @@ typedef struct SDArchiverInternalFileInfo {
|
||||||
uint32_t uid;
|
uint32_t uid;
|
||||||
uint32_t gid;
|
uint32_t gid;
|
||||||
uint64_t file_size;
|
uint64_t file_size;
|
||||||
|
/// xxxx xxx1 - is invalid.
|
||||||
|
int_fast8_t other_flags;
|
||||||
} SDArchiverInternalFileInfo;
|
} SDArchiverInternalFileInfo;
|
||||||
|
|
||||||
void free_internal_to_write(void *data) {
|
void free_internal_to_write(void *data) {
|
||||||
|
@ -2403,7 +2405,7 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
|
||||||
const size_t digits = simple_archiver_helper_num_digits(size);
|
const size_t digits = simple_archiver_helper_num_digits(size);
|
||||||
char format_str[128];
|
char format_str[128];
|
||||||
snprintf(format_str, 128, FILE_COUNTS_OUTPUT_FORMAT_STR_0, digits, digits);
|
snprintf(format_str, 128, FILE_COUNTS_OUTPUT_FORMAT_STR_0, digits, digits);
|
||||||
int_fast8_t skip = 0;
|
int_fast8_t skip;
|
||||||
__attribute__((cleanup(simple_archiver_hash_map_free)))
|
__attribute__((cleanup(simple_archiver_hash_map_free)))
|
||||||
SDArchiverHashMap *hash_map = NULL;
|
SDArchiverHashMap *hash_map = NULL;
|
||||||
if (state && state->parsed->working_files &&
|
if (state && state->parsed->working_files &&
|
||||||
|
@ -2421,6 +2423,7 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (uint32_t idx = 0; idx < size; ++idx) {
|
for (uint32_t idx = 0; idx < size; ++idx) {
|
||||||
|
skip = 0;
|
||||||
fprintf(stderr, format_str, idx + 1, size);
|
fprintf(stderr, format_str, idx + 1, size);
|
||||||
if (feof(in_f) || ferror(in_f)) {
|
if (feof(in_f) || ferror(in_f)) {
|
||||||
return SDAS_INVALID_FILE;
|
return SDAS_INVALID_FILE;
|
||||||
|
@ -2438,7 +2441,12 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
|
||||||
}
|
}
|
||||||
buf[1023] = 0;
|
buf[1023] = 0;
|
||||||
fprintf(stderr, " Filename: %s\n", buf);
|
fprintf(stderr, " Filename: %s\n", buf);
|
||||||
if (do_extract) {
|
if (simple_archiver_validate_file_path((char*)buf)) {
|
||||||
|
fprintf(stderr, " ERROR: Invalid filename!\n");
|
||||||
|
skip = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_extract && !skip) {
|
||||||
if ((state->parsed->flags & 0x8) == 0) {
|
if ((state->parsed->flags & 0x8) == 0) {
|
||||||
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
|
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
|
||||||
FILE *test_fd = fopen((const char *)buf, "rb");
|
FILE *test_fd = fopen((const char *)buf, "rb");
|
||||||
|
@ -2479,7 +2487,13 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
|
||||||
}
|
}
|
||||||
uc_heap_buf[u16] = 0;
|
uc_heap_buf[u16] = 0;
|
||||||
fprintf(stderr, " Filename: %s\n", uc_heap_buf);
|
fprintf(stderr, " Filename: %s\n", uc_heap_buf);
|
||||||
if (do_extract) {
|
|
||||||
|
if (simple_archiver_validate_file_path((char*)uc_heap_buf)) {
|
||||||
|
fprintf(stderr, " ERROR: Invalid filename!\n");
|
||||||
|
skip = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_extract && !skip) {
|
||||||
if ((state->parsed->flags & 0x8) == 0) {
|
if ((state->parsed->flags & 0x8) == 0) {
|
||||||
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
|
__attribute__((cleanup(simple_archiver_helper_cleanup_FILE)))
|
||||||
FILE *test_fd = fopen((const char *)uc_heap_buf, "rb");
|
FILE *test_fd = fopen((const char *)uc_heap_buf, "rb");
|
||||||
|
@ -3229,6 +3243,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
|
|
||||||
uint_fast8_t link_extracted = 0;
|
uint_fast8_t link_extracted = 0;
|
||||||
uint_fast8_t skip_due_to_map = 0;
|
uint_fast8_t skip_due_to_map = 0;
|
||||||
|
uint_fast8_t skip_due_to_invalid = 0;
|
||||||
|
|
||||||
if (fread(buf, 1, 2, in_f) != 2) {
|
if (fread(buf, 1, 2, in_f) != 2) {
|
||||||
return SDAS_INVALID_FILE;
|
return SDAS_INVALID_FILE;
|
||||||
|
@ -3257,6 +3272,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simple_archiver_validate_file_path(link_name)) {
|
||||||
|
fprintf(stderr, " WARNING: Invalid link name \"%s\"!\n", link_name);
|
||||||
|
skip_due_to_invalid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (working_files_map &&
|
if (working_files_map &&
|
||||||
simple_archiver_hash_map_get(working_files_map, link_name, u16 + 1) ==
|
simple_archiver_hash_map_get(working_files_map, link_name, u16 + 1) ==
|
||||||
NULL) {
|
NULL) {
|
||||||
|
@ -3278,7 +3298,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
path[u16] = 0;
|
path[u16] = 0;
|
||||||
if (do_extract && !skip_due_to_map && absolute_preferred) {
|
if (do_extract && !skip_due_to_map && !skip_due_to_invalid && absolute_preferred) {
|
||||||
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
||||||
|
@ -3348,7 +3368,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
path[u16] = 0;
|
path[u16] = 0;
|
||||||
if (do_extract && !skip_due_to_map && !absolute_preferred) {
|
if (do_extract && !skip_due_to_map && !skip_due_to_invalid && !absolute_preferred) {
|
||||||
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
||||||
|
@ -3404,8 +3424,8 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
fprintf(stderr, " No Relative path.\n");
|
fprintf(stderr, " No Relative path.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_extract && !link_extracted && !skip_due_to_map) {
|
if (do_extract && !link_extracted && !skip_due_to_map && !skip_due_to_invalid) {
|
||||||
fprintf(stderr, "WARNING Symlink \"%s\" was not created!\n", link_name);
|
fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n", link_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3451,6 +3471,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
}
|
}
|
||||||
file_info->filename[u16] = 0;
|
file_info->filename[u16] = 0;
|
||||||
|
|
||||||
|
if (simple_archiver_validate_file_path(file_info->filename)) {
|
||||||
|
fprintf(stderr, "ERROR: File idx %u: Invalid filename!\n", file_idx);
|
||||||
|
file_info->other_flags |= 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (state && state->parsed && (state->parsed->flags & 8) != 0) {
|
if (state && state->parsed && (state->parsed->flags & 8) != 0) {
|
||||||
int fd = open((const char *)buf, O_RDONLY | O_NOFOLLOW);
|
int fd = open((const char *)buf, O_RDONLY | O_NOFOLLOW);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
|
@ -3665,9 +3690,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
strlen(file_info->filename) + 1) == NULL) {
|
strlen(file_info->filename) + 1) == NULL) {
|
||||||
skip_due_to_map = 1;
|
skip_due_to_map = 1;
|
||||||
fprintf(stderr, " Skipping not specified in args...\n");
|
fprintf(stderr, " Skipping not specified in args...\n");
|
||||||
|
} else if ((file_info->other_flags & 1) != 0) {
|
||||||
|
fprintf(stderr, " Skipping invalid filename...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_extract && !skip_due_to_map) {
|
if (do_extract && !skip_due_to_map && (file_info->other_flags & 1) == 0) {
|
||||||
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
||||||
|
@ -3709,7 +3736,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
return SDAS_INTERNAL_ERROR;
|
return SDAS_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else if (!skip_due_to_map) {
|
} else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) {
|
||||||
fprintf(stderr, " Permissions: ");
|
fprintf(stderr, " Permissions: ");
|
||||||
permissions_from_bits_version_1(file_info->bit_flags, 1);
|
permissions_from_bits_version_1(file_info->bit_flags, 1);
|
||||||
fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid,
|
fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid,
|
||||||
|
@ -3761,9 +3788,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
strlen(file_info->filename) + 1) == NULL) {
|
strlen(file_info->filename) + 1) == NULL) {
|
||||||
skip_due_to_map = 1;
|
skip_due_to_map = 1;
|
||||||
fprintf(stderr, " Skipping not specified in args...\n");
|
fprintf(stderr, " Skipping not specified in args...\n");
|
||||||
|
} else if (file_info->other_flags & 1) {
|
||||||
|
fprintf(stderr, " Skipping invalid filename...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_extract && !skip_due_to_map) {
|
if (do_extract && !skip_due_to_map && (file_info->other_flags & 1) == 0) {
|
||||||
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
|
||||||
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
|
||||||
|
@ -3809,7 +3838,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
|
||||||
return SDAS_INTERNAL_ERROR;
|
return SDAS_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else if (!skip_due_to_map) {
|
} else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) {
|
||||||
fprintf(stderr, " Permissions: ");
|
fprintf(stderr, " Permissions: ");
|
||||||
permissions_from_bits_version_1(file_info->bit_flags, 1);
|
permissions_from_bits_version_1(file_info->bit_flags, 1);
|
||||||
fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid,
|
fprintf(stderr, "\n UID: %u\n GID: %u\n", file_info->uid,
|
||||||
|
@ -3991,3 +4020,29 @@ char *simple_archiver_file_abs_path(const char *filename) {
|
||||||
#endif
|
#endif
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int simple_archiver_validate_file_path(const char *filepath) {
|
||||||
|
if (!filepath) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t len = strlen(filepath);
|
||||||
|
|
||||||
|
if (len >= 1 && filepath[0] == '/') {
|
||||||
|
return 1;
|
||||||
|
} else if (len >= 3 && filepath[0] == '.' && filepath[1] == '.' && filepath[2] == '/') {
|
||||||
|
return 2;
|
||||||
|
} else if (len >= 3 && filepath[len - 1] == '.' && filepath[len - 2] == '.' && filepath[len - 3] == '/') {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 0; idx < len; ++idx) {
|
||||||
|
if (len - idx < 4) {
|
||||||
|
break;
|
||||||
|
} else if (strncmp(filepath + idx, "/../", 4) == 0) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -98,4 +98,14 @@ char *simple_archiver_filenames_to_relative_path(const char *from_abs,
|
||||||
/// Non-NULL on success, and must be free'd if non-NULL.
|
/// Non-NULL on success, and must be free'd if non-NULL.
|
||||||
char *simple_archiver_file_abs_path(const char *filename);
|
char *simple_archiver_file_abs_path(const char *filename);
|
||||||
|
|
||||||
|
/// Used to validate a file in a ".simplearchive" file to avoid writing outside
|
||||||
|
/// of current working directory.
|
||||||
|
/// Returns zero if file is OK.
|
||||||
|
/// Returns 1 if file starts with '/'.
|
||||||
|
/// Returns 2 if file contains '../' at the start.
|
||||||
|
/// Returns 3 if file contains '/../' in the middle.
|
||||||
|
/// Returns 4 if file contains '/..' at the end.
|
||||||
|
/// Returns 5 if "filepath" is NULL.
|
||||||
|
int simple_archiver_validate_file_path(const char *filepath);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -255,6 +255,12 @@ int main(void) {
|
||||||
"/one/two/three/four/five", "/one/two/three/other/dir/");
|
"/one/two/three/four/five", "/one/two/three/other/dir/");
|
||||||
CHECK_STREQ(rel_path, "../other/dir/");
|
CHECK_STREQ(rel_path, "../other/dir/");
|
||||||
simple_archiver_helper_cleanup_c_string(&rel_path);
|
simple_archiver_helper_cleanup_c_string(&rel_path);
|
||||||
|
|
||||||
|
CHECK_FALSE(simple_archiver_validate_file_path("Local/Path"));
|
||||||
|
CHECK_TRUE(simple_archiver_validate_file_path("/Abs/Path"));
|
||||||
|
CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really"));
|
||||||
|
CHECK_TRUE(simple_archiver_validate_file_path("./../almost"));
|
||||||
|
CHECK_TRUE(simple_archiver_validate_file_path("strange/.."));
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Checks checked: %u\n", checks_checked);
|
printf("Checks checked: %u\n", checks_checked);
|
||||||
|
|
Loading…
Reference in a new issue