diff --git a/invalid_file_format_0_example_0 b/invalid_file_format_0_example_0 new file mode 100644 index 0000000..9755999 Binary files /dev/null and b/invalid_file_format_0_example_0 differ diff --git a/invalid_file_format_0_example_1 b/invalid_file_format_0_example_1 new file mode 100644 index 0000000..77f4424 Binary files /dev/null and b/invalid_file_format_0_example_1 differ diff --git a/invalid_file_format_1_example_0 b/invalid_file_format_1_example_0 new file mode 100644 index 0000000..29d9159 Binary files /dev/null and b/invalid_file_format_1_example_0 differ diff --git a/invalid_file_format_1_example_1 b/invalid_file_format_1_example_1 new file mode 100644 index 0000000..87a8c65 Binary files /dev/null and b/invalid_file_format_1_example_1 differ diff --git a/invalid_file_format_1_example_2 b/invalid_file_format_1_example_2 new file mode 100644 index 0000000..9b003b0 Binary files /dev/null and b/invalid_file_format_1_example_2 differ diff --git a/src/archiver.c b/src/archiver.c index 407764c..087bf56 100644 --- a/src/archiver.c +++ b/src/archiver.c @@ -66,6 +66,8 @@ typedef struct SDArchiverInternalFileInfo { uint32_t uid; uint32_t gid; uint64_t file_size; + /// xxxx xxx1 - is invalid. + int_fast8_t other_flags; } SDArchiverInternalFileInfo; 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); char format_str[128]; 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))) SDArchiverHashMap *hash_map = NULL; 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) { + skip = 0; fprintf(stderr, format_str, idx + 1, size); if (feof(in_f) || ferror(in_f)) { 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; 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) { __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) 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; 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) { __attribute__((cleanup(simple_archiver_helper_cleanup_FILE))) 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 skip_due_to_map = 0; + uint_fast8_t skip_due_to_invalid = 0; if (fread(buf, 1, 2, in_f) != 2) { return SDAS_INVALID_FILE; @@ -3257,6 +3272,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract, #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 && simple_archiver_hash_map_get(working_files_map, link_name, u16 + 1) == NULL) { @@ -3278,7 +3298,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract, return ret; } 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 || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ 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; } 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 || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ 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"); } - if (do_extract && !link_extracted && !skip_due_to_map) { - fprintf(stderr, "WARNING Symlink \"%s\" was not created!\n", link_name); + if (do_extract && !link_extracted && !skip_due_to_map && !skip_due_to_invalid) { + 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; + 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) { int fd = open((const char *)buf, O_RDONLY | O_NOFOLLOW); 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) { skip_due_to_map = 1; 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 || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ 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; } #endif - } else if (!skip_due_to_map) { + } else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) { fprintf(stderr, " Permissions: "); permissions_from_bits_version_1(file_info->bit_flags, 1); 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) { skip_due_to_map = 1; 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 || \ SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \ 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; } #endif - } else if (!skip_due_to_map) { + } else if (!skip_due_to_map && (file_info->other_flags & 1) == 0) { fprintf(stderr, " Permissions: "); permissions_from_bits_version_1(file_info->bit_flags, 1); 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 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; +} diff --git a/src/archiver.h b/src/archiver.h index 6b62ac3..012fd39 100644 --- a/src/archiver.h +++ b/src/archiver.h @@ -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. 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 diff --git a/src/test.c b/src/test.c index 13caba9..420fc30 100644 --- a/src/test.c +++ b/src/test.c @@ -255,6 +255,12 @@ int main(void) { "/one/two/three/four/five", "/one/two/three/other/dir/"); CHECK_STREQ(rel_path, "../other/dir/"); 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);