← Commit list

3fb6b31c7

Denys Vlasenko
Denys Vlasenko
2026-01-29
SUCCESS Duration: 144s

Subject

tar: strip unsafe hardlink components - GNU tar does the same

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
gcc -Wp,-MD,applets/.applet_tables.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer       -o applets/applet_tables applets/applet_tables.c

Build Log

(log yok)

Commit Diff GitHub →

archival/libarchive/data_extract_all.c+3 -4
@@ -66,8 +66,8 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
}
#endif
#if ENABLE_FEATURE_PATH_TRAVERSAL_PROTECTION
- /* Strip leading "/" and up to last "/../" path component */
- dst_name = (char *)strip_unsafe_prefix(dst_name);
+ /* Skip leading "/" and past last ".." path component */
+ dst_name = (char *)skip_unsafe_prefix(dst_name);
#endif
// ^^^ This may be a problem if some applets do need to extract absolute names.
// (Probably will need to invent ARCHIVE_ALLOW_UNSAFE_NAME flag).
@@ -185,8 +185,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
/* To avoid a directory traversal attack via symlinks,
* do not restore symlinks with ".." components
- * or symlinks starting with "/", unless a magic
- * envvar is set.
+ * or symlinks starting with "/"
*
* For example, consider a .tar created via:
* $ tar cvf bug.tar anything.txt
archival/libarchive/get_header_tar.c+9 -2
@@ -454,8 +454,15 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle)
#endif
/* Everything up to and including last ".." component is stripped */
- overlapping_strcpy(file_header->name, strip_unsafe_prefix(file_header->name));
-//TODO: do the same for file_header->link_target?
+ strip_unsafe_prefix(file_header->name);
+ if (file_header->link_target) {
+ /* GNU tar 1.34 examples:
+ * tar: Removing leading '/' from hard link targets
+ * tar: Removing leading '../' from hard link targets
+ * tar: Removing leading 'etc/../' from hard link targets
+ */
+ strip_unsafe_prefix(file_header->link_target);
+ }
/* Strip trailing '/' in directories */
/* Must be done after mode is set as '/' is used to check if it's a directory */
archival/libarchive/unsafe_prefix.c+25 -5
@@ -5,11 +5,11 @@
#include "libbb.h"
#include "bb_archive.h"
-const char* FAST_FUNC strip_unsafe_prefix(const char *str)
+const char* FAST_FUNC skip_unsafe_prefix(const char *str)
{
const char *cp = str;
while (1) {
- char *cp2;
+ const char *cp2;
if (*cp == '/') {
cp++;
continue;
@@ -22,10 +22,25 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str)
cp += 3;
continue;
}
- cp2 = strstr(cp, "/../");
+ cp2 = cp;
+ find_dotdot:
+ cp2 = strstr(cp2, "/..");
if (!cp2)
- break;
- cp = cp2 + 4;
+ break; /* No (more) malicious components */
+
+ /* We found "/..something" */
+ cp2 += 3;
+ if (*cp2 != '/') {
+ if (*cp2 == '\0') {
+ /* Trailing "/..": malicious, return "" */
+ /* (causes harmless errors trying to create or hardlink a file named "") */
+ return cp2;
+ }
+ /* "/..name" is not malicious, look for next "/.." */
+ goto find_dotdot;
+ }
+ /* Found "/../": malicious, advance past it */
+ cp = cp2 + 1;
}
if (cp != str) {
static smallint warned = 0;
@@ -37,3 +52,8 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str)
}
return cp;
}
+
+void FAST_FUNC strip_unsafe_prefix(char *str)
+{
+ overlapping_strcpy(str, skip_unsafe_prefix(str));
+}
archival/libarchive/unsafe_symlink_target.c+1 -0
@@ -36,6 +36,7 @@ void FAST_FUNC create_links_from_list(llist_t *list)
*list->data ? "hard" : "sym",
list->data + 1, target
);
+ /* Note: GNU tar 1.34 errors out only _after_ all links are (attempted to be) created */
}
list = list->link;
}
archival/tar.c+1 -1
@@ -475,7 +475,7 @@ static int FAST_FUNC writeFileToTarball(struct recursive_state *state,
DBG("writeFileToTarball('%s')", fileName);
/* Strip leading '/' and such (must be before memorizing hardlink's name) */
- header_name = strip_unsafe_prefix(fileName);
+ header_name = skip_unsafe_prefix(fileName);
if (header_name[0] == '\0')
return TRUE;
archival/unzip.c+1 -1
@@ -860,7 +860,7 @@ int unzip_main(int argc, char **argv)
/* Guard against "/abspath", "/../" and similar attacks */
// NB: UnZip 6.00 has option -: to disable this
- overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn));
+ strip_unsafe_prefix(dst_fn);
/* Filter zip entries */
if (find_list_entry(zreject, dst_fn)
include/bb_archive.h+2 -1
@@ -202,7 +202,8 @@ char get_header_tar_xz(archive_handle_t *archive_handle) FAST_FUNC;
void seek_by_jump(int fd, off_t amount) FAST_FUNC;
void seek_by_read(int fd, off_t amount) FAST_FUNC;
-const char *strip_unsafe_prefix(const char *str) FAST_FUNC;
+const char *skip_unsafe_prefix(const char *str) FAST_FUNC;
+void strip_unsafe_prefix(char *str) FAST_FUNC;
void create_or_remember_link(llist_t **link_placeholders,
const char *target,
const char *linkname,
networking/httpd_ratelimit_cgi.c+7 -4
@@ -213,7 +213,8 @@ int main(int argc, char **argv)
write_and_die(1, "Status: 429 Too Many Requests\r\n"
"Content-Type: text/plain\r\n"
"Retry-After: 60\r\n"
- "Connection: close\r\n\r\n"
+ "Connection: close\r\n"
+ "\r\n"
"Too many concurrent requests\n"
);
return 0;
@@ -223,7 +224,9 @@ exec:
execv(argv[0], argv);
full_write2(2, "can't execute", argv[0]);
write_and_die(1, "Status: 500 Internal Server Error\r\n"
- "Content-Type: text/plain\r\n\r\n"
- "Failed to execute binary\n");
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "Can't execute binary\n"
+ );
return 1;
-}
+}