Compare commits

..

6 commits

Author SHA1 Message Date
b256350fbc Version 1.6
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
Build for Releases / ensure-release-exists (push) Successful in 1s
Build for Releases / push-build-x86_64 (push) Successful in 8s
Build for Releases / push-build-aarch64 (push) Successful in 15s
Build for Releases / push-build-x86_64_debian (push) Successful in 33s
Build for Releases / push-build-aarch64_debian (push) Successful in 5m0s
2024-10-28 13:45:21 +09:00
5d67e0dc50 Update Changelog.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
2024-10-24 16:41:46 +09:00
a415ab22ad Add option to preserve symlinks exactly
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 16s
The "--preserve-symlinks" option preserves the symlink target instead of
deriving absolute/relative-paths from it. If archived symlinks are
absolute-paths, then it is NOT recommended to use this option as the
symlinks can be clobbered on extraction (unless if "--no-safe-links" is
specified on extraction).
2024-10-24 16:37:55 +09:00
f81d007e7c Minor cleanup
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
2024-10-24 14:51:46 +09:00
14986f5c4a Update Changelog.md
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 5s
2024-10-24 14:00:06 +09:00
fb1c24ba2c Safe-links enforce on extract, fixes/refactorings
All checks were successful
Run Unit Tests / build-and-run-unit-tests (push) Successful in 6s
Resolves #20
2024-10-24 13:40:29 +09:00
7 changed files with 398 additions and 94 deletions

View file

@ -2,6 +2,18 @@
## Upcoming Changes ## Upcoming Changes
## Version 1.6
Enforce "safe-links" on extraction by ensuring every extracted symlink actually
points to a file in the archive. Additionally any extracted symlinks that don't
point to a valid destination is removed. This "enforce safe-links on extract"
can be disabled with the "--no-safe-links" option.
Add "--preserve-symlinks" option that will verbatim store the symlinks' target.
Not recommended if symlinks are pointing to absolute paths, which will be
clobbered on extraction to a different directory unless if "--no-safe-links" is
specified on extraction.
## Version 1.5 ## Version 1.5
Previous file-format-v1 implementation of "safe links" still created a symlink Previous file-format-v1 implementation of "safe links" still created a symlink

View file

@ -27,6 +27,7 @@ API calls.
--overwrite-create : allows overwriting an archive file --overwrite-create : allows overwriting an archive file
--overwrite-extract : allows overwriting when extracting --overwrite-extract : allows overwriting when extracting
--no-abs-symlink : do not store absolute paths for symlinks --no-abs-symlink : do not store absolute paths for symlinks
--preserve-symlinks : preserve the symlink's path on archive creation instead of deriving abs/relative paths, ignores "--no-abs-symlink" (It is not recommended to use this option, as absolute-path-symlinks may be clobbered on extraction)
--no-safe-links : keep symlinks that link to outside archive contents --no-safe-links : keep symlinks that link to outside archive contents
--temp-files-dir <dir> : where to store temporary files created when compressing (defaults to current working directory) --temp-files-dir <dir> : where to store temporary files created when compressing (defaults to current working directory)
--write-version <version> : Force write version file format (default 1) --write-version <version> : Force write version file format (default 1)

View file

@ -759,13 +759,28 @@ int write_files_fn(void *data, void *ud) {
// Get absolute path. // Get absolute path.
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL; simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_MAC || \
SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_LINUX
abs_path = realpath(file_info->filename, NULL);
#endif
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL; simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
if ((state->parsed->flags & 0x100) != 0) {
// Preserve symlink target.
char *path_buf = malloc(1024);
ssize_t ret = readlink(file_info->filename, path_buf, 1023);
if (ret == -1) {
fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
free(path_buf);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} else {
path_buf[ret] = 0;
if (path_buf[0] == '/') {
abs_path = path_buf;
((uint8_t *)temp_to_write->buf)[1] |= 0x4;
} else {
rel_path = path_buf;
}
}
} else {
abs_path = realpath(file_info->filename, NULL);
if (abs_path) { if (abs_path) {
// Get relative path. // Get relative path.
// First get absolute path of link. // First get absolute path of link.
@ -778,13 +793,15 @@ int write_files_fn(void *data, void *ud) {
// fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n", // fprintf(stderr, "DEBUG: abs_path: %s\nDEBUG: link_abs_path: %s\n",
// (char*)abs_path, (char*)link_abs_path); // (char*)abs_path, (char*)link_abs_path);
rel_path = rel_path = simple_archiver_filenames_to_relative_path(link_abs_path,
simple_archiver_filenames_to_relative_path(link_abs_path, abs_path); abs_path);
}
} }
} }
// Check if absolute path refers to one of the filenames. // Check if absolute path refers to one of the filenames.
if (abs_path && (state->parsed->flags & 0x20) == 0 && if (abs_path && (state->parsed->flags & 0x20) == 0 &&
(state->parsed->flags & 0x100) == 0 &&
!simple_archiver_hash_map_get(state->map, abs_path, !simple_archiver_hash_map_get(state->map, abs_path,
strlen(abs_path) + 1)) { strlen(abs_path) + 1)) {
// Is not a filename being archived. // Is not a filename being archived.
@ -804,6 +821,43 @@ int write_files_fn(void *data, void *ud) {
file_info->filename); file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8; ((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} }
} else if ((state->parsed->flags & 0x100) != 0 &&
(state->parsed->flags & 0x80) == 0 &&
(((uint8_t *)temp_to_write->buf)[1] & 0x8) == 0) {
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *resolved_path = NULL;
if (abs_path || rel_path) {
resolved_path = realpath(file_info->filename, NULL);
if (!resolved_path) {
fprintf(stderr,
"WARNING: Symlink \"%s\" is invalid, will not be stored! "
"(Use \"--no-safe-links\" to disable this behavior)\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} else if (!simple_archiver_hash_map_get(state->map, resolved_path,
strlen(resolved_path) + 1)) {
fprintf(stderr,
"WARNING: Symlink \"%s\" points to outside archive contents, "
"will not be stored! (Use \"--no-safe-links\" to disable "
"this behavior)\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
}
} else {
fprintf(stderr,
"WARNING: Unable to get target path from symlink \"%s\"!\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
}
}
if (!abs_path && !rel_path) {
// No valid paths, set as invalid.
fprintf(stderr,
"WARNING: Could not get valid abs/rel path for symlink \"%s\" "
"(invalid symlink)!\n",
file_info->filename);
((uint8_t *)temp_to_write->buf)[1] |= 0x8;
} }
// Store the 4 byte bit-flags for file. // Store the 4 byte bit-flags for file.
@ -822,8 +876,10 @@ int write_files_fn(void *data, void *ud) {
// Store the absolute and relative paths. // Store the absolute and relative paths.
if (!abs_path) { if (!abs_path) {
if ((state->parsed->flags & 0x100) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to get absolute path of link destination!\n"); "WARNING: Failed to get absolute path of link destination!\n");
}
temp_to_write = malloc(sizeof(SDArchiverInternalToWrite)); temp_to_write = malloc(sizeof(SDArchiverInternalToWrite));
temp_to_write->buf = malloc(2); temp_to_write->buf = malloc(2);
temp_to_write->size = 2; temp_to_write->size = 2;
@ -896,9 +952,17 @@ int write_files_fn(void *data, void *ud) {
// Write all previously set data. // Write all previously set data.
fprintf(stderr, "Writing symlink info: %s\n", file_info->filename); fprintf(stderr, "Writing symlink info: %s\n", file_info->filename);
if ((state->parsed->flags & 0x20) == 0) { if ((state->parsed->flags & 0x20) == 0) {
if (abs_path) {
fprintf(stderr, " abs path: %s\n", (char *)abs_path); fprintf(stderr, " abs path: %s\n", (char *)abs_path);
} else {
fprintf(stderr, " abs path is NOT set\n");
} }
}
if (rel_path) {
fprintf(stderr, " rel path: %s\n", (char *)rel_path); fprintf(stderr, " rel path: %s\n", (char *)rel_path);
} else {
fprintf(stderr, " rel path is NOT set\n");
}
simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f); simple_archiver_list_get(to_write, write_list_datas_fn, state->out_f);
simple_archiver_list_free(&to_write); simple_archiver_list_free(&to_write);
} }
@ -974,10 +1038,15 @@ int filenames_to_abs_map_fn(void *data, void *ud) {
char *fullpath_dirname_copy = malloc(strlen(fullpath_dirname) + 1); char *fullpath_dirname_copy = malloc(strlen(fullpath_dirname) + 1);
strncpy(fullpath_dirname_copy, fullpath_dirname, strncpy(fullpath_dirname_copy, fullpath_dirname,
strlen(fullpath_dirname) + 1); strlen(fullpath_dirname) + 1);
if (!simple_archiver_hash_map_get(abs_filenames, fullpath_dirname_copy,
strlen(fullpath_dirname_copy) + 1)) {
simple_archiver_hash_map_insert( simple_archiver_hash_map_insert(
abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy, abs_filenames, fullpath_dirname_copy, fullpath_dirname_copy,
strlen(fullpath_dirname_copy) + 1, strlen(fullpath_dirname_copy) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL); simple_archiver_helper_datastructure_cleanup_nop, NULL);
} else {
free(fullpath_dirname_copy);
}
} }
prev = fullpath_dirname; prev = fullpath_dirname;
} }
@ -1680,6 +1749,29 @@ int files_to_chunk_count(void *data, void *ud) {
int greater_fn(int64_t a, int64_t b) { return a > b; } int greater_fn(int64_t a, int64_t b) { return a > b; }
void simple_archiver_internal_paths_to_files_map(SDArchiverHashMap *files_map,
const char *filename) {
simple_archiver_hash_map_insert(
files_map, (void *)1, strdup((const char *)filename),
strlen((const char *)filename) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL);
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *filename_copy =
strdup(filename);
char *filename_dirname = dirname(filename_copy);
while (strcmp(filename_dirname, ".") != 0) {
if (!simple_archiver_hash_map_get(files_map, filename_dirname,
strlen(filename_dirname) + 1)) {
simple_archiver_hash_map_insert(
files_map, (void *)1, strdup(filename_dirname),
strlen(filename_dirname) + 1,
simple_archiver_helper_datastructure_cleanup_nop, NULL);
}
filename_dirname = dirname(filename_dirname);
}
}
char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) { char *simple_archiver_error_to_string(enum SDArchiverStateReturns error) {
switch (error) { switch (error) {
case SDAS_SUCCESS: case SDAS_SUCCESS:
@ -2059,15 +2151,36 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
node = node->next; node = node->next;
++u32; ++u32;
memset(buf, 0, 2); memset(buf, 0, 2);
uint_fast8_t is_invalid = 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
// Check if symlink points to thing to be stored into archive. __attribute__((cleanup(
__attribute__(( simple_archiver_helper_cleanup_malloced))) void *abs_path = NULL;
cleanup(simple_archiver_helper_cleanup_malloced))) void *abs_path =
realpath(node->data, NULL);
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL; simple_archiver_helper_cleanup_malloced))) void *rel_path = NULL;
if ((state->parsed->flags & 0x100) != 0) {
// Preserve symlink target.
char *path_buf = malloc(1024);
ssize_t ret = readlink(node->data, path_buf, 1023);
if (ret == -1) {
fprintf(stderr, "WARNING: Failed to get symlink's target!\n");
free(path_buf);
is_invalid = 1;
} else {
path_buf[ret] = 0;
if (path_buf[0] == '/') {
abs_path = path_buf;
buf[0] |= 1;
} else {
rel_path = path_buf;
}
}
} else {
abs_path = realpath(node->data, NULL);
// Check if symlink points to thing to be stored into archive.
if (abs_path) { if (abs_path) {
__attribute__((cleanup( __attribute__((cleanup(
simple_archiver_helper_cleanup_malloced))) void *link_abs_path = simple_archiver_helper_cleanup_malloced))) void *link_abs_path =
@ -2079,10 +2192,10 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
abs_path); abs_path);
} }
} }
}
uint_fast8_t is_invalid = 0;
if (abs_path && (state->parsed->flags & 0x20) == 0 && if (abs_path && (state->parsed->flags & 0x20) == 0 &&
(state->parsed->flags & 0x100) == 0 &&
!simple_archiver_hash_map_get(abs_filenames, abs_path, !simple_archiver_hash_map_get(abs_filenames, abs_path,
strlen(abs_path) + 1)) { strlen(abs_path) + 1)) {
// Is not a filename being archived. // Is not a filename being archived.
@ -2098,6 +2211,36 @@ int simple_archiver_write_v1(FILE *out_f, SDArchiverState *state,
// Safe links disabled, set preference to absolute path. // Safe links disabled, set preference to absolute path.
buf[0] |= 1; buf[0] |= 1;
} }
} else if ((state->parsed->flags & 0x100) != 0 &&
(state->parsed->flags & 0x80) == 0 && !is_invalid) {
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *target_realpath =
realpath(node->data, NULL);
if (!target_realpath) {
fprintf(
stderr,
"WARNING: \"%s\" is an invalid symlink and \"--no-safe-links\" "
"not specified, will skip this symlink!\n",
(const char *)node->data);
is_invalid = 1;
} else if (!simple_archiver_hash_map_get(abs_filenames, target_realpath,
strlen(target_realpath) + 1)) {
fprintf(
stderr,
"WARNING: \"%s\" points to outside of archived files and "
"\"--no-safe-links\" not specified, will skip this symlink!\n",
(const char *)node->data);
is_invalid = 1;
}
}
if (!abs_path && !rel_path) {
// No valid paths, mark as invalid.
fprintf(stderr,
"WARNING: \"%s\" is an invalid symlink, will not store rel/abs "
"link paths!\n",
(const char *)node->data);
is_invalid = 1;
} }
// Get symlink stats for permissions. // Get symlink stats for permissions.
@ -2842,6 +2985,18 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
// fprintf(stderr, "\"%s\" put in map\n", key); // fprintf(stderr, "\"%s\" put in map\n", key);
} }
} }
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *links_list =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_list_init();
__attribute__((cleanup(simple_archiver_hash_map_free)))
SDArchiverHashMap *files_map =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_hash_map_init();
for (uint32_t idx = 0; idx < size; ++idx) { for (uint32_t idx = 0; idx < size; ++idx) {
if (is_sig_int_occurred) { if (is_sig_int_occurred) {
return SDAS_SIGINT; return SDAS_SIGINT;
@ -2972,6 +3127,10 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
cleanup_overwrite_filename_delete_simple(&to_overwrite_dest); cleanup_overwrite_filename_delete_simple(&to_overwrite_dest);
} }
if (files_map && !skip && out_f_name) {
simple_archiver_internal_paths_to_files_map(files_map, out_f_name);
}
#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
@ -3450,19 +3609,19 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (retry_symlink) { if (retry_symlink) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to create symlink after removing " " WARNING: Failed to create symlink after removing "
"existing symlink!\n"); "existing symlink!\n");
goto V0_SYMLINK_CREATE_AFTER_0; goto V0_SYMLINK_CREATE_AFTER_0;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V0_SYMLINK_CREATE_AFTER_0; goto V0_SYMLINK_CREATE_AFTER_0;
} else { } else {
fprintf(stderr, fprintf(stderr,
"NOTICE: Symlink already exists and " " NOTICE: Symlink already exists and "
"\"--overwrite-extract\" specified, attempting to " "\"--overwrite-extract\" specified, attempting to "
"overwrite...\n"); "overwrite...\n");
unlink(out_f_name); unlink(out_f_name);
@ -3473,16 +3632,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = fchmodat(AT_FDCWD, out_f_name, permissions, ret = fchmodat(AT_FDCWD, out_f_name, permissions,
AT_SYMLINK_NOFOLLOW); AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not " " NOTICE: Setting permissions of symlink is not "
"supported by FS/OS!\n"); "supported by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(
"WARNING: Failed to set permissions of symlink (%d)!\n", stderr,
" WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3500,19 +3663,19 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (retry_symlink) { if (retry_symlink) {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to create symlink after removing " " WARNING: Failed to create symlink after removing "
"existing symlink!\n"); "existing symlink!\n");
goto V0_SYMLINK_CREATE_AFTER_1; goto V0_SYMLINK_CREATE_AFTER_1;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V0_SYMLINK_CREATE_AFTER_1; goto V0_SYMLINK_CREATE_AFTER_1;
} else { } else {
fprintf(stderr, fprintf(stderr,
"NOTICE: Symlink already exists and " " NOTICE: Symlink already exists and "
"\"--overwrite-extract\" specified, attempting to " "\"--overwrite-extract\" specified, attempting to "
"overwrite...\n"); "overwrite...\n");
unlink(out_f_name); unlink(out_f_name);
@ -3523,16 +3686,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = fchmodat(AT_FDCWD, out_f_name, permissions, ret = fchmodat(AT_FDCWD, out_f_name, permissions,
AT_SYMLINK_NOFOLLOW); AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not " " NOTICE: Setting permissions of symlink is not "
"supported by FS/OS!\n"); "supported by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(
"WARNING: Failed to set permissions of symlink (%d)!\n", stderr,
" WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3548,16 +3715,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = ret =
fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(
"NOTICE: Setting permissions of symlink is not supported " stderr,
" NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3570,16 +3741,20 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
return SDAS_FAILED_TO_EXTRACT_SYMLINK; return SDAS_FAILED_TO_EXTRACT_SYMLINK;
} }
if (links_list) {
simple_archiver_list_add(links_list, strdup(out_f_name), NULL);
}
ret = ret =
fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW); fchmodat(AT_FDCWD, out_f_name, permissions, AT_SYMLINK_NOFOLLOW);
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(
"NOTICE: Setting permissions of symlink is not supported " stderr,
" NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3587,12 +3762,16 @@ int simple_archiver_parse_archive_version_0(FILE *in_f, int_fast8_t do_extract,
} else { } else {
fprintf( fprintf(
stderr, stderr,
"WARNING: Symlink entry in archive has no paths to link to!\n"); " WARNING: Symlink entry in archive has no paths to link to!\n");
} }
} }
} }
} }
if (do_extract && links_list && files_map) {
simple_archiver_safe_links_enforce(links_list, files_map);
}
if (is_sig_int_occurred) { if (is_sig_int_occurred) {
return SDAS_SIGINT; return SDAS_SIGINT;
} }
@ -3637,6 +3816,21 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
#endif #endif
} }
__attribute__((cleanup(simple_archiver_list_free)))
SDArchiverLinkedList *links_list =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_list_init();
__attribute__((cleanup(simple_archiver_hash_map_free)))
SDArchiverHashMap *files_map =
state && state->parsed && state->parsed->flags & 0x80
? NULL
: simple_archiver_hash_map_init();
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *cwd_realpath =
realpath(".", NULL);
const int_fast8_t is_compressed = (buf[0] & 1) ? 1 : 0; const int_fast8_t is_compressed = (buf[0] & 1) ? 1 : 0;
__attribute__((cleanup( __attribute__((cleanup(
@ -3704,9 +3898,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
const uint_fast8_t is_invalid = (buf[1] & 4) ? 1 : 0; const uint_fast8_t is_invalid = (buf[1] & 4) ? 1 : 0;
if (is_invalid) { if (is_invalid) {
fprintf(stderr, fprintf(stderr, " WARNING: This symlink entry was marked invalid!\n");
" WARNING: This symlink entry was marked invalid (not a safe "
"link)!\n");
} }
#if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \ #if SIMPLE_ARCHIVER_PLATFORM == SIMPLE_ARCHIVER_PLATFORM_COSMOPOLITAN || \
@ -3789,21 +3981,22 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
ret = symlink(path, link_name); ret = symlink(path, link_name);
if (ret == -1) { if (ret == -1) {
if (link_create_retry) { if (link_create_retry) {
fprintf(stderr, fprintf(
"WARNING: Failed to create symlink after removing existing " stderr,
" WARNING: Failed to create symlink after removing existing "
"symlink!\n"); "symlink!\n");
goto V1_SYMLINK_CREATE_AFTER_0; goto V1_SYMLINK_CREATE_AFTER_0;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V1_SYMLINK_CREATE_AFTER_0; goto V1_SYMLINK_CREATE_AFTER_0;
} else { } else {
fprintf( fprintf(stderr,
stderr, " NOTICE: Symlink already exists and "
"NOTICE: Symlink already exists and \"--overwrite-extract\" " "\"--overwrite-extract\" specified, attempting to "
"specified, attempting to overwrite...\n"); "overwrite...\n");
unlink(link_name); unlink(link_name);
link_create_retry = 1; link_create_retry = 1;
goto V1_SYMLINK_CREATE_RETRY_0; goto V1_SYMLINK_CREATE_RETRY_0;
@ -3815,11 +4008,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not supported " " NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3828,7 +4021,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
V1_SYMLINK_CREATE_AFTER_0: V1_SYMLINK_CREATE_AFTER_0:
link_create_retry = 1; link_create_retry = 1;
#endif #endif
} else { } else if (!do_extract) {
fprintf(stderr, " Abs path: %s\n", path); fprintf(stderr, " Abs path: %s\n", path);
} }
} else if (!do_extract) { } else if (!do_extract) {
@ -3861,21 +4054,22 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
ret = symlink(path, link_name); ret = symlink(path, link_name);
if (ret == -1) { if (ret == -1) {
if (link_create_retry) { if (link_create_retry) {
fprintf(stderr, fprintf(
"WARNING: Failed to create symlink after removing existing " stderr,
" WARNING: Failed to create symlink after removing existing "
"symlink!\n"); "symlink!\n");
goto V1_SYMLINK_CREATE_AFTER_1; goto V1_SYMLINK_CREATE_AFTER_1;
} else if (errno == EEXIST) { } else if (errno == EEXIST) {
if ((state->parsed->flags & 8) == 0) { if ((state->parsed->flags & 8) == 0) {
fprintf(stderr, fprintf(stderr,
"WARNING: Symlink already exists and " " WARNING: Symlink already exists and "
"\"--overwrite-extract\" is not specified, skipping!\n"); "\"--overwrite-extract\" is not specified, skipping!\n");
goto V1_SYMLINK_CREATE_AFTER_1; goto V1_SYMLINK_CREATE_AFTER_1;
} else { } else {
fprintf( fprintf(stderr,
stderr, " NOTICE: Symlink already exists and "
"NOTICE: Symlink already exists and \"--overwrite-extract\" " "\"--overwrite-extract\" specified, attempting to "
"specified, attempting to overwrite...\n"); "overwrite...\n");
unlink(link_name); unlink(link_name);
link_create_retry = 1; link_create_retry = 1;
goto V1_SYMLINK_CREATE_RETRY_1; goto V1_SYMLINK_CREATE_RETRY_1;
@ -3887,11 +4081,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
if (ret == -1) { if (ret == -1) {
if (errno == EOPNOTSUPP) { if (errno == EOPNOTSUPP) {
fprintf(stderr, fprintf(stderr,
"NOTICE: Setting permissions of symlink is not supported " " NOTICE: Setting permissions of symlink is not supported "
"by FS/OS!\n"); "by FS/OS!\n");
} else { } else {
fprintf(stderr, fprintf(stderr,
"WARNING: Failed to set permissions of symlink (%d)!\n", " WARNING: Failed to set permissions of symlink (%d)!\n",
errno); errno);
} }
} }
@ -3900,7 +4094,7 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
V1_SYMLINK_CREATE_AFTER_1: V1_SYMLINK_CREATE_AFTER_1:
link_create_retry = 1; link_create_retry = 1;
#endif #endif
} else { } else if (!do_extract) {
fprintf(stderr, " Rel path: %s\n", path); fprintf(stderr, " Rel path: %s\n", path);
} }
} else if (!do_extract) { } else if (!do_extract) {
@ -3911,6 +4105,9 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
!skip_due_to_invalid) { !skip_due_to_invalid) {
fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n", fprintf(stderr, " WARNING: Symlink \"%s\" was not created!\n",
link_name); link_name);
} else if (do_extract && link_extracted && !skip_due_to_map &&
!skip_due_to_invalid && links_list) {
simple_archiver_list_add(links_list, strdup(link_name), NULL);
} }
} }
@ -4011,6 +4208,11 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
simple_archiver_helper_64_bit_be(&u64); simple_archiver_helper_64_bit_be(&u64);
file_info->file_size = u64; file_info->file_size = u64;
if (files_map) {
simple_archiver_internal_paths_to_files_map(files_map,
file_info->filename);
}
simple_archiver_list_add(file_info_list, file_info, simple_archiver_list_add(file_info_list, file_info,
free_internal_file_info); free_internal_file_info);
file_info = NULL; file_info = NULL;
@ -4340,6 +4542,10 @@ int simple_archiver_parse_archive_version_1(FILE *in_f, int_fast8_t do_extract,
} }
} }
if (do_extract && links_list && files_map) {
simple_archiver_safe_links_enforce(links_list, files_map);
}
return SDAS_SUCCESS; return SDAS_SUCCESS;
} }
@ -4523,3 +4729,69 @@ int simple_archiver_validate_file_path(const char *filepath) {
return 0; return 0;
} }
void simple_archiver_safe_links_enforce(SDArchiverLinkedList *links_list,
SDArchiverHashMap *files_map) {
uint_fast8_t need_to_print_note = 1;
// safe-links: Check that every link maps to a file in the files_map.
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *path_to_cwd =
realpath(".", NULL);
// Ensure path_to_cwd ends with '/'.
uint32_t idx = 0;
while (path_to_cwd[idx] != 0) {
++idx;
}
if (path_to_cwd[idx - 1] != '/') {
char *temp = malloc(idx + 2);
memcpy(temp, path_to_cwd, idx);
temp[idx] = '/';
temp[idx + 1] = 0;
free(path_to_cwd);
path_to_cwd = temp;
}
// Check every link to make sure it points to an existing file.
SDArchiverLLNode *links_node = links_list->head;
while (links_node->next != links_list->tail) {
links_node = links_node->next;
__attribute__((
cleanup(simple_archiver_helper_cleanup_c_string))) char *link_realpath =
realpath(links_node->data, NULL);
if (link_realpath) {
// Get local path.
__attribute__((cleanup(
simple_archiver_helper_cleanup_c_string))) char *link_localpath =
simple_archiver_filenames_to_relative_path(path_to_cwd,
link_realpath);
if (!simple_archiver_hash_map_get(files_map, link_localpath,
strlen(link_localpath) + 1)) {
// Invalid symlink.
fprintf(stderr,
"Symlink \"%s\" is invalid (not pointing to archived file), "
"removing...\n",
(const char *)links_node->data);
unlink(links_node->data);
if (need_to_print_note) {
fprintf(stderr,
"NOTE: Disable this behavior with \"--no-safe-links\" if "
"needed.\n");
need_to_print_note = 0;
}
}
} else {
// Invalid symlink.
fprintf(stderr,
"Symlink \"%s\" is invalid (failed to resolve), removing...\n",
(const char *)links_node->data);
unlink(links_node->data);
if (need_to_print_note) {
fprintf(stderr,
"NOTE: Disable this behavior with \"--no-safe-links\" if "
"needed.\n");
need_to_print_note = 0;
}
}
}
}

View file

@ -109,4 +109,9 @@ char *simple_archiver_file_abs_path(const char *filename);
/// Returns 5 if "filepath" is NULL. /// Returns 5 if "filepath" is NULL.
int simple_archiver_validate_file_path(const char *filepath); int simple_archiver_validate_file_path(const char *filepath);
/// Removes links from "links_list" in cwd if it is not valid or does not point
/// to a file in "files_map".
void simple_archiver_safe_links_enforce(SDArchiverLinkedList *links_list,
SDArchiverHashMap *files_map);
#endif #endif

View file

@ -168,6 +168,12 @@ void simple_archiver_print_usage(void) {
fprintf(stderr, "--overwrite-extract : allows overwriting when extracting\n"); fprintf(stderr, "--overwrite-extract : allows overwriting when extracting\n");
fprintf(stderr, fprintf(stderr,
"--no-abs-symlink : do not store absolute paths for symlinks\n"); "--no-abs-symlink : do not store absolute paths for symlinks\n");
fprintf(
stderr,
"--preserve-symlinks : preserve the symlink's path on archive creation "
"instead of deriving abs/relative paths, ignores \"--no-abs-symlink\" "
"(It is not recommended to use this option, as absolute-path-symlinks "
"may be clobbered on extraction)\n");
fprintf(stderr, fprintf(stderr,
"--no-safe-links : keep symlinks that link to outside archive " "--no-safe-links : keep symlinks that link to outside archive "
"contents\n"); "contents\n");
@ -306,6 +312,8 @@ int simple_archiver_parse_args(int argc, const char **argv,
out->flags |= 0x8; out->flags |= 0x8;
} else if (strcmp(argv[0], "--no-abs-symlink") == 0) { } else if (strcmp(argv[0], "--no-abs-symlink") == 0) {
out->flags |= 0x20; out->flags |= 0x20;
} else if (strcmp(argv[0], "--preserve-symlinks") == 0) {
out->flags |= 0x100;
} else if (strcmp(argv[0], "--no-safe-links") == 0) { } else if (strcmp(argv[0], "--no-safe-links") == 0) {
out->flags |= 0x80; out->flags |= 0x80;
fprintf(stderr, fprintf(stderr,

View file

@ -37,6 +37,7 @@ typedef struct SDArchiverParsed {
/// 0b xx1x xxxx - Do not save absolute paths for symlinks. /// 0b xx1x xxxx - Do not save absolute paths for symlinks.
/// 0b x1xx xxxx - Sort files by size before archiving. /// 0b x1xx xxxx - Sort files by size before archiving.
/// 0b 1xxx xxxx - No safe links. /// 0b 1xxx xxxx - No safe links.
/// 0b xxxx xxx1 xxxx xxxx - Preserve symlink target.
uint32_t flags; uint32_t flags;
/// Null-terminated string. /// Null-terminated string.
char *filename; char *filename;

View file

@ -256,6 +256,11 @@ int main(void) {
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);
rel_path = simple_archiver_filenames_to_relative_path(
"/one/two/three/", "/one/two/three/four");
CHECK_STREQ(rel_path, "four");
simple_archiver_helper_cleanup_c_string(&rel_path);
CHECK_FALSE(simple_archiver_validate_file_path("Local/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("/Abs/Path"));
CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really")); CHECK_TRUE(simple_archiver_validate_file_path("Local/../../not/really"));