]> git.seodisparate.com - SimpleArchiver/commitdiff
Add filename validation for test/extracting
authorStephen Seo <seo.disparate@gmail.com>
Fri, 4 Oct 2024 12:24:10 +0000 (21:24 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Fri, 4 Oct 2024 12:24:10 +0000 (21:24 +0900)
This should prevent creation of files/symlinks outside of
current-working-directory or user-set-cwd.

invalid_file_format_0_example_0 [new file with mode: 0644]
invalid_file_format_0_example_1 [new file with mode: 0644]
invalid_file_format_1_example_0 [new file with mode: 0644]
invalid_file_format_1_example_1 [new file with mode: 0644]
invalid_file_format_1_example_2 [new file with mode: 0644]
src/archiver.c
src/archiver.h
src/test.c

diff --git a/invalid_file_format_0_example_0 b/invalid_file_format_0_example_0
new file mode 100644 (file)
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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..9b003b0
Binary files /dev/null and b/invalid_file_format_1_example_2 differ
index 407764cb7103aa4d56db8e960d84fdc8db816381..087bf569f5fbb41c43df0fbda33363d3cc35515c 100644 (file)
@@ -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;
+}
index 6b62ac3949efc1a406a3c4685e255fa6e85b9aab..012fd39c98b1027ad00cb45fdd6318484474e043 100644 (file)
@@ -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
index 13caba92f5845d8707b97e7fba6b06f09826037b..420fc30627d31242bd0f642db0bc95210cf99e5c 100644 (file)
@@ -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);