Skip to content

Instantly share code, notes, and snippets.

@0xilis
Created April 24, 2025 13:55
Show Gist options
  • Save 0xilis/153f12a87a5183c182da9bed1c860cb2 to your computer and use it in GitHub Desktop.
Save 0xilis/153f12a87a5183c182da9bed1c860cb2 to your computer and use it in GitHub Desktop.
Patch CVE-2024-27876 libAppleArchive bug on jailbroken iOS
/* Please credit me (Snoolie K / 0xilis) for this patch */
%hookf(int, concatExtractPath, char *dest, size_t dest_size, const char *dir, const char *path) {
size_t dir_len = strlen(dir);
size_t path_len = strlen(path);
if (!dir_len) {
pc_log_error(__FILE__, __func__, 0x173, 3, 0, "invalid dir: %s", dir);
return -1;
}
if (path_len + dir_len + 1 >= dest_size) {
pc_log_error(__FILE__, __func__, 0x174, 3, 0, "dir/path too long: %s", dir);
return -1;
}
strlcpy(dest, dir, dest_size);
/* If there's no path to append, we're done */
if (!path_len) {
return 0;
}
size_t offset = 0;
while (offset < path_len) {
const char *current = path + offset;
char *next_slash = strchr(current, '/');
size_t segment_end = next_slash ? (next_slash - path) : path_len;
size_t segment_len = segment_end - offset;
/* Validate path segment */
if (segment_len == 1 && current[0] == '.') {
/* Single dot, skip if this is the last segment */
if (!next_slash) {
return 0;
}
} else if (segment_len == 2 && current[0] == '.' && current[1] == '.') {
/* Double dot / path traversal, invalid path */
pc_log_error(__FILE__, __func__, 0x18d, 3, 0, "invalid path: %s", path);
return -1;
} else if (segment_len == 0) {
/* Empty segment - invalid */
pc_log_error(__FILE__, __func__, 0x186, 3, 0, "invalid path: %s", path);
return -1;
}
/*
* Here is the main symlink check.
* This was originally problematic since
* a process can just replace the file
* after the check with a symlink, a classic
* TOCTOU! We add a mkdir() check to prevent this.
* Now, technically I know some will say this is still
* problematic since a process can do it
* after the mkdir() call, but this should prevent
* the cases where libAppleArchive races with itself,
* and on iOS that's really all that can happen anyway
* due to sandbox, so since we only care about iOS
* this is fine.
*/
struct stat st;
int stat_result = lstat(dest, &st);
if (offset == 0) {
if (stat_result != 0) {
pc_log_error(__FILE__, __func__, 0x194, 3, 0,
"dir doesn't exist, or is invalid: %s", dir);
return -1;
}
if (!S_ISDIR(st.st_mode)) {
pc_log_error(__FILE__, __func__, 0x194, 3, 0,
"dir doesn't exist, or is invalid: %s", dir);
return -1;
}
} else {
if (stat_result != 0) {
if (mkdir(dest, 0755) != 0) { /* We now check for mkdir() failure which will happen on self race */
pc_log_error(__FILE__, __func__, 0x199, 3, 0,
"failed to create directory: %s", dest);
return -1;
}
} else if (!S_ISDIR(st.st_mode)) {
pc_log_error(__FILE__, __func__, 0x199, 3, 0,
"a parent of path is not a directory: %s", path);
return -1;
}
}
if (dest[dir_len - 1] != '/') {
dest[dir_len] = '/';
dir_len++;
dest[dir_len] = '\0';
}
memcpy(dest + dir_len, current, segment_len);
dir_len += segment_len;
dest[dir_len] = '\0';
if (!next_slash) {
return 0;
}
offset = segment_end + 1;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment