mz_os: skip symlink check at or above destination dir

mz_dir_has_unsafe_symlink walks every component of the destination
path and rejects any symlink with an absolute target. That's too
strict: when the user-chosen destination dir (or one of its ancestors)
happens to be a symlink, extracting into it gets rejected with -107.
The check only needs to police symlinks that appear strictly inside
the destination, since those could be zip-controlled.

Skip the symlink check when check_path is at or above base_path; let
the existing logic enforce on components below.

Fixes #943.
This commit is contained in:
Nathan Moin Vaziri 2026-04-25 00:46:46 -07:00
parent 8ec500fd38
commit 483f0568b0

11
mz_os.c
View file

@ -287,6 +287,7 @@ int32_t mz_dir_has_unsafe_symlink(const char *path, const char *base_path) {
size_t max_path = 1024;
size_t parent_len = 0;
size_t pos = 0;
size_t cmp_len = 0;
int32_t err = MZ_OK;
if (!path || *path == 0 || !base_path)
@ -322,6 +323,16 @@ int32_t mz_dir_has_unsafe_symlink(const char *path, const char *base_path) {
if (mz_os_is_symlink(check_path) != MZ_OK)
continue;
/* Skip components at or above the base dir. */
cmp_len = pos;
if (mz_path_has_slash(check_path) == MZ_OK)
cmp_len--;
if (cmp_len <= base_len && strncmp(check_path, base_path, cmp_len) == 0) {
/* Verify that the prefix match is on a directory boundary. */
if (cmp_len == base_len || mz_os_is_dir_separator(base_path[cmp_len]))
continue;
}
/* Allocate symlink buffers on first use */
if (!symlink_target) {
symlink_target = (char *)calloc(1, max_path);