diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2018-02-20 15:57:45 +0100 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2018-02-20 16:06:53 +0100 |
commit | a84db18fc71d09e801df0ebca048d82e90b32c6a (patch) | |
tree | 1eda8cd659f78406f1b9f86d5e0b5757af20bad9 | |
parent | 95121d98e6da12599246d8e25c3f300e422a06fb (diff) | |
download | busybox-w32-a84db18fc71d09e801df0ebca048d82e90b32c6a.tar.gz busybox-w32-a84db18fc71d09e801df0ebca048d82e90b32c6a.tar.bz2 busybox-w32-a84db18fc71d09e801df0ebca048d82e90b32c6a.zip |
tar,unzip: postpone creation of symlinks with "suspicious" targets
This mostly reverts commit bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7
"libarchive: do not extract unsafe symlinks unless $EXTRACT_UNSAFE_SYMLINKS=1"
Users report that it is somewhat too restrictive. See
https://bugs.busybox.net/show_bug.cgi?id=8411
In particular, this interferes with unpacking of busybox-based
filesystems with links like "sbin/applet" -> "../bin/busybox".
The change is made smaller by deleting ARCHIVE_EXTRACT_QUIET flag -
it is unused since 2010, and removing conditionals on it
allows commonalizing some error message codes.
function old new delta
create_or_remember_symlink - 94 +94
create_symlinks_from_list - 64 +64
tar_main 1002 1006 +4
unzip_main 2732 2724 -8
data_extract_all 984 891 -93
unsafe_symlink_target 147 - -147
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 1/2 up/down: 162/-248) Total: -86 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | archival/libarchive/data_extract_all.c | 28 | ||||
-rw-r--r-- | archival/libarchive/unsafe_symlink_target.c | 59 | ||||
-rw-r--r-- | archival/tar.c | 2 | ||||
-rw-r--r-- | archival/unzip.c | 25 | ||||
-rw-r--r-- | include/bb_archive.h | 23 | ||||
-rwxr-xr-x | testsuite/tar.tests | 10 |
6 files changed, 68 insertions, 79 deletions
diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index d3a6df5e8..8fa69ffaf 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c | |||
@@ -107,9 +107,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
107 | } | 107 | } |
108 | } | 108 | } |
109 | else if (existing_sb.st_mtime >= file_header->mtime) { | 109 | else if (existing_sb.st_mtime >= file_header->mtime) { |
110 | if (!(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | 110 | if (!S_ISDIR(file_header->mode)) { |
111 | && !S_ISDIR(file_header->mode) | ||
112 | ) { | ||
113 | bb_error_msg("%s not created: newer or " | 111 | bb_error_msg("%s not created: newer or " |
114 | "same age file exists", dst_name); | 112 | "same age file exists", dst_name); |
115 | } | 113 | } |
@@ -125,7 +123,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
125 | /* Handle hard links separately */ | 123 | /* Handle hard links separately */ |
126 | if (hard_link) { | 124 | if (hard_link) { |
127 | res = link(hard_link, dst_name); | 125 | res = link(hard_link, dst_name); |
128 | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { | 126 | if (res != 0) { |
129 | /* shared message */ | 127 | /* shared message */ |
130 | bb_perror_msg("can't create %slink '%s' to '%s'", | 128 | bb_perror_msg("can't create %slink '%s' to '%s'", |
131 | "hard", dst_name, hard_link | 129 | "hard", dst_name, hard_link |
@@ -165,10 +163,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
165 | } | 163 | } |
166 | case S_IFDIR: | 164 | case S_IFDIR: |
167 | res = mkdir(dst_name, file_header->mode); | 165 | res = mkdir(dst_name, file_header->mode); |
168 | if ((res == -1) | 166 | if ((res != 0) |
169 | && (errno != EISDIR) /* btw, Linux doesn't return this */ | 167 | && (errno != EISDIR) /* btw, Linux doesn't return this */ |
170 | && (errno != EEXIST) | 168 | && (errno != EEXIST) |
171 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | ||
172 | ) { | 169 | ) { |
173 | bb_perror_msg("can't make dir %s", dst_name); | 170 | bb_perror_msg("can't make dir %s", dst_name); |
174 | } | 171 | } |
@@ -198,27 +195,16 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
198 | * | 195 | * |
199 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. | 196 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. |
200 | */ | 197 | */ |
201 | if (!unsafe_symlink_target(file_header->link_target)) { | 198 | create_or_remember_symlink(&archive_handle->symlink_placeholders, |
202 | res = symlink(file_header->link_target, dst_name); | 199 | file_header->link_target, |
203 | if (res != 0 | 200 | dst_name); |
204 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | ||
205 | ) { | ||
206 | /* shared message */ | ||
207 | bb_perror_msg("can't create %slink '%s' to '%s'", | ||
208 | "sym", | ||
209 | dst_name, file_header->link_target | ||
210 | ); | ||
211 | } | ||
212 | } | ||
213 | break; | 201 | break; |
214 | case S_IFSOCK: | 202 | case S_IFSOCK: |
215 | case S_IFBLK: | 203 | case S_IFBLK: |
216 | case S_IFCHR: | 204 | case S_IFCHR: |
217 | case S_IFIFO: | 205 | case S_IFIFO: |
218 | res = mknod(dst_name, file_header->mode, file_header->device); | 206 | res = mknod(dst_name, file_header->mode, file_header->device); |
219 | if ((res == -1) | 207 | if (res != 0) { |
220 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | ||
221 | ) { | ||
222 | bb_perror_msg("can't create node %s", dst_name); | 208 | bb_perror_msg("can't create node %s", dst_name); |
223 | } | 209 | } |
224 | break; | 210 | break; |
diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c index 441ba8b24..8dcafeaa1 100644 --- a/archival/libarchive/unsafe_symlink_target.c +++ b/archival/libarchive/unsafe_symlink_target.c | |||
@@ -5,44 +5,37 @@ | |||
5 | #include "libbb.h" | 5 | #include "libbb.h" |
6 | #include "bb_archive.h" | 6 | #include "bb_archive.h" |
7 | 7 | ||
8 | int FAST_FUNC unsafe_symlink_target(const char *target) | 8 | void FAST_FUNC create_or_remember_symlink(llist_t **symlink_placeholders, |
9 | const char *target, | ||
10 | const char *linkname) | ||
9 | { | 11 | { |
10 | const char *dot; | 12 | if (target[0] == '/' || strstr(target, "..")) { |
11 | 13 | llist_add_to(symlink_placeholders, | |
12 | if (target[0] == '/') { | 14 | xasprintf("%s%c%s", linkname, '\0', target) |
13 | const char *var; | 15 | ); |
14 | unsafe: | 16 | return; |
15 | var = getenv("EXTRACT_UNSAFE_SYMLINKS"); | 17 | } |
16 | if (var) { | 18 | if (symlink(target, linkname) != 0) { |
17 | if (LONE_CHAR(var, '1')) | 19 | /* shared message */ |
18 | return 0; /* pretend it's safe */ | 20 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
19 | return 1; /* "UNSAFE!" */ | 21 | "sym", linkname, target |
20 | } | ||
21 | bb_error_msg("skipping unsafe symlink to '%s' in archive," | ||
22 | " set %s=1 to extract", | ||
23 | target, | ||
24 | "EXTRACT_UNSAFE_SYMLINKS" | ||
25 | ); | 22 | ); |
26 | /* Prevent further messages */ | ||
27 | setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0); | ||
28 | return 1; /* "UNSAFE!" */ | ||
29 | } | 23 | } |
24 | } | ||
30 | 25 | ||
31 | dot = target; | 26 | void FAST_FUNC create_symlinks_from_list(llist_t *list) |
32 | for (;;) { | 27 | { |
33 | dot = strchr(dot, '.'); | 28 | while (list) { |
34 | if (!dot) | 29 | char *target; |
35 | return 0; /* safe target */ | ||
36 | 30 | ||
37 | /* Is it a path component starting with ".."? */ | 31 | target = list->data + strlen(list->data) + 1; |
38 | if ((dot[1] == '.') | 32 | if (symlink(target, list->data)) { |
39 | && (dot == target || dot[-1] == '/') | 33 | /* shared message */ |
40 | /* Is it exactly ".."? */ | 34 | bb_error_msg_and_die("can't create %slink '%s' to '%s'", |
41 | && (dot[2] == '/' || dot[2] == '\0') | 35 | "sym", |
42 | ) { | 36 | list->data, target |
43 | goto unsafe; | 37 | ); |
44 | } | 38 | } |
45 | /* NB: it can even be trailing ".", should only add 1 */ | 39 | list = list->link; |
46 | dot += 1; | ||
47 | } | 40 | } |
48 | } | 41 | } |
diff --git a/archival/tar.c b/archival/tar.c index 9ed3821d5..415ebde0d 100644 --- a/archival/tar.c +++ b/archival/tar.c | |||
@@ -1244,6 +1244,8 @@ int tar_main(int argc UNUSED_PARAM, char **argv) | |||
1244 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) | 1244 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) |
1245 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ | 1245 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ |
1246 | 1246 | ||
1247 | create_symlinks_from_list(tar_handle->symlink_placeholders); | ||
1248 | |||
1247 | /* Check that every file that should have been extracted was */ | 1249 | /* Check that every file that should have been extracted was */ |
1248 | while (tar_handle->accept) { | 1250 | while (tar_handle->accept) { |
1249 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) | 1251 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) |
diff --git a/archival/unzip.c b/archival/unzip.c index da4b2a544..0d00d8dc9 100644 --- a/archival/unzip.c +++ b/archival/unzip.c | |||
@@ -345,7 +345,9 @@ static void unzip_create_leading_dirs(const char *fn) | |||
345 | } | 345 | } |
346 | 346 | ||
347 | #if ENABLE_FEATURE_UNZIP_CDF | 347 | #if ENABLE_FEATURE_UNZIP_CDF |
348 | static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) | 348 | static void unzip_extract_symlink(llist_t **symlink_placeholders, |
349 | zip_header_t *zip, | ||
350 | const char *dst_fn) | ||
349 | { | 351 | { |
350 | char *target; | 352 | char *target; |
351 | 353 | ||
@@ -370,15 +372,9 @@ static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) | |||
370 | target[xstate.mem_output_size] = '\0'; | 372 | target[xstate.mem_output_size] = '\0'; |
371 | #endif | 373 | #endif |
372 | } | 374 | } |
373 | if (!unsafe_symlink_target(target)) { | 375 | create_or_remember_symlink(symlink_placeholders, |
374 | //TODO: libbb candidate | 376 | target, |
375 | if (symlink(target, dst_fn)) { | 377 | dst_fn); |
376 | /* shared message */ | ||
377 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", | ||
378 | "sym", dst_fn, target | ||
379 | ); | ||
380 | } | ||
381 | } | ||
382 | free(target); | 378 | free(target); |
383 | } | 379 | } |
384 | #endif | 380 | #endif |
@@ -490,6 +486,9 @@ int unzip_main(int argc, char **argv) | |||
490 | llist_t *zaccept = NULL; | 486 | llist_t *zaccept = NULL; |
491 | llist_t *zreject = NULL; | 487 | llist_t *zreject = NULL; |
492 | char *base_dir = NULL; | 488 | char *base_dir = NULL; |
489 | #if ENABLE_FEATURE_UNZIP_CDF | ||
490 | llist_t *symlink_placeholders = NULL; | ||
491 | #endif | ||
493 | int i; | 492 | int i; |
494 | char key_buf[80]; /* must match size used by my_fgets80 */ | 493 | char key_buf[80]; /* must match size used by my_fgets80 */ |
495 | 494 | ||
@@ -954,7 +953,7 @@ int unzip_main(int argc, char **argv) | |||
954 | #if ENABLE_FEATURE_UNZIP_CDF | 953 | #if ENABLE_FEATURE_UNZIP_CDF |
955 | if (S_ISLNK(file_mode)) { | 954 | if (S_ISLNK(file_mode)) { |
956 | if (dst_fd != STDOUT_FILENO) /* not -p? */ | 955 | if (dst_fd != STDOUT_FILENO) /* not -p? */ |
957 | unzip_extract_symlink(&zip, dst_fn); | 956 | unzip_extract_symlink(&symlink_placeholders, &zip, dst_fn); |
958 | } else | 957 | } else |
959 | #endif | 958 | #endif |
960 | { | 959 | { |
@@ -990,6 +989,10 @@ int unzip_main(int argc, char **argv) | |||
990 | total_entries++; | 989 | total_entries++; |
991 | } | 990 | } |
992 | 991 | ||
992 | #if ENABLE_FEATURE_UNZIP_CDF | ||
993 | create_symlinks_from_list(symlink_placeholders); | ||
994 | #endif | ||
995 | |||
993 | if ((opts & OPT_l) && quiet <= 1) { | 996 | if ((opts & OPT_l) && quiet <= 1) { |
994 | if (!verbose) { | 997 | if (!verbose) { |
995 | // " Length Date Time Name\n" | 998 | // " Length Date Time Name\n" |
diff --git a/include/bb_archive.h b/include/bb_archive.h index 8ed20d70e..a5c61e95b 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h | |||
@@ -64,6 +64,9 @@ typedef struct archive_handle_t { | |||
64 | /* Currently processed file's header */ | 64 | /* Currently processed file's header */ |
65 | file_header_t *file_header; | 65 | file_header_t *file_header; |
66 | 66 | ||
67 | /* List of symlink placeholders */ | ||
68 | llist_t *symlink_placeholders; | ||
69 | |||
67 | /* Process the header component, e.g. tar -t */ | 70 | /* Process the header component, e.g. tar -t */ |
68 | void FAST_FUNC (*action_header)(const file_header_t *); | 71 | void FAST_FUNC (*action_header)(const file_header_t *); |
69 | 72 | ||
@@ -119,15 +122,14 @@ typedef struct archive_handle_t { | |||
119 | #define ARCHIVE_RESTORE_DATE (1 << 0) | 122 | #define ARCHIVE_RESTORE_DATE (1 << 0) |
120 | #define ARCHIVE_CREATE_LEADING_DIRS (1 << 1) | 123 | #define ARCHIVE_CREATE_LEADING_DIRS (1 << 1) |
121 | #define ARCHIVE_UNLINK_OLD (1 << 2) | 124 | #define ARCHIVE_UNLINK_OLD (1 << 2) |
122 | #define ARCHIVE_EXTRACT_QUIET (1 << 3) | 125 | #define ARCHIVE_EXTRACT_NEWER (1 << 3) |
123 | #define ARCHIVE_EXTRACT_NEWER (1 << 4) | 126 | #define ARCHIVE_DONT_RESTORE_OWNER (1 << 4) |
124 | #define ARCHIVE_DONT_RESTORE_OWNER (1 << 5) | 127 | #define ARCHIVE_DONT_RESTORE_PERM (1 << 5) |
125 | #define ARCHIVE_DONT_RESTORE_PERM (1 << 6) | 128 | #define ARCHIVE_NUMERIC_OWNER (1 << 6) |
126 | #define ARCHIVE_NUMERIC_OWNER (1 << 7) | 129 | #define ARCHIVE_O_TRUNC (1 << 7) |
127 | #define ARCHIVE_O_TRUNC (1 << 8) | 130 | #define ARCHIVE_REMEMBER_NAMES (1 << 8) |
128 | #define ARCHIVE_REMEMBER_NAMES (1 << 9) | ||
129 | #if ENABLE_RPM | 131 | #if ENABLE_RPM |
130 | #define ARCHIVE_REPLACE_VIA_RENAME (1 << 10) | 132 | #define ARCHIVE_REPLACE_VIA_RENAME (1 << 9) |
131 | #endif | 133 | #endif |
132 | 134 | ||
133 | 135 | ||
@@ -197,7 +199,10 @@ void seek_by_jump(int fd, off_t amount) FAST_FUNC; | |||
197 | void seek_by_read(int fd, off_t amount) FAST_FUNC; | 199 | void seek_by_read(int fd, off_t amount) FAST_FUNC; |
198 | 200 | ||
199 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; | 201 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; |
200 | int unsafe_symlink_target(const char *target) FAST_FUNC; | 202 | void create_or_remember_symlink(llist_t **symlink_placeholders, |
203 | const char *target, | ||
204 | const char *linkname) FAST_FUNC; | ||
205 | void create_symlinks_from_list(llist_t *list) FAST_FUNC; | ||
201 | 206 | ||
202 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; | 207 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; |
203 | const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; | 208 | const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; |
diff --git a/testsuite/tar.tests b/testsuite/tar.tests index b7cd74ca5..1675b07b1 100755 --- a/testsuite/tar.tests +++ b/testsuite/tar.tests | |||
@@ -279,7 +279,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |||
279 | testing "tar does not extract into symlinks" "\ | 279 | testing "tar does not extract into symlinks" "\ |
280 | >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | 280 | >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$? |
281 | " "\ | 281 | " "\ |
282 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract | 282 | tar: can't create symlink 'passwd' to '/tmp/passwd' |
283 | 0 | 283 | 0 |
284 | " \ | 284 | " \ |
285 | "" "\ | 285 | "" "\ |
@@ -299,7 +299,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |||
299 | testing "tar -k does not extract into symlinks" "\ | 299 | testing "tar -k does not extract into symlinks" "\ |
300 | >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | 300 | >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$? |
301 | " "\ | 301 | " "\ |
302 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract | 302 | tar: can't create symlink 'passwd' to '/tmp/passwd' |
303 | 0 | 303 | 0 |
304 | " \ | 304 | " \ |
305 | "" "\ | 305 | "" "\ |
@@ -324,11 +324,11 @@ rm -rf etc usr | |||
324 | ' "\ | 324 | ' "\ |
325 | etc/ssl/certs/3b2716e5.0 | 325 | etc/ssl/certs/3b2716e5.0 |
326 | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 326 | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
327 | tar: skipping unsafe symlink to '/usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract | ||
328 | etc/ssl/certs/f80cc7f6.0 | 327 | etc/ssl/certs/f80cc7f6.0 |
329 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt | 328 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt |
330 | 0 | 329 | 0 |
331 | etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 330 | etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
331 | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem -> /usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt | ||
332 | etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 332 | etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
333 | " \ | 333 | " \ |
334 | "" "" | 334 | "" "" |
@@ -346,9 +346,9 @@ ls symlink/bb_test_evilfile | |||
346 | ' "\ | 346 | ' "\ |
347 | anything.txt | 347 | anything.txt |
348 | symlink | 348 | symlink |
349 | tar: skipping unsafe symlink to '/tmp' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract | ||
350 | symlink/bb_test_evilfile | 349 | symlink/bb_test_evilfile |
351 | 0 | 350 | tar: can't create symlink 'symlink' to '/tmp' |
351 | 1 | ||
352 | ls: /tmp/bb_test_evilfile: No such file or directory | 352 | ls: /tmp/bb_test_evilfile: No such file or directory |
353 | ls: bb_test_evilfile: No such file or directory | 353 | ls: bb_test_evilfile: No such file or directory |
354 | symlink/bb_test_evilfile | 354 | symlink/bb_test_evilfile |