diff options
-rw-r--r-- | archival/libarchive/Kbuild.src | 2 | ||||
-rw-r--r-- | archival/libarchive/data_extract_all.c | 37 | ||||
-rw-r--r-- | archival/libarchive/unsafe_symlink_target.c | 48 | ||||
-rw-r--r-- | archival/tar.c | 19 | ||||
-rw-r--r-- | archival/unzip.c | 10 | ||||
-rw-r--r-- | coreutils/link.c | 5 | ||||
-rw-r--r-- | include/bb_archive.h | 4 | ||||
-rw-r--r-- | libbb/copy_file.c | 5 | ||||
-rwxr-xr-x | testsuite/tar.tests | 10 |
9 files changed, 85 insertions, 55 deletions
diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src index 942e755b9..e1a8a7529 100644 --- a/archival/libarchive/Kbuild.src +++ b/archival/libarchive/Kbuild.src | |||
@@ -12,6 +12,8 @@ COMMON_FILES:= \ | |||
12 | data_extract_all.o \ | 12 | data_extract_all.o \ |
13 | data_extract_to_stdout.o \ | 13 | data_extract_to_stdout.o \ |
14 | \ | 14 | \ |
15 | unsafe_symlink_target.o \ | ||
16 | \ | ||
15 | filter_accept_all.o \ | 17 | filter_accept_all.o \ |
16 | filter_accept_list.o \ | 18 | filter_accept_list.o \ |
17 | filter_accept_reject_list.o \ | 19 | filter_accept_reject_list.o \ |
diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index 1ce927c2f..e658444e0 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c | |||
@@ -129,9 +129,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
129 | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { | 129 | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { |
130 | /* shared message */ | 130 | /* shared message */ |
131 | bb_perror_msg("can't create %slink '%s' to '%s'", | 131 | bb_perror_msg("can't create %slink '%s' to '%s'", |
132 | "hard", | 132 | "hard", dst_name, hard_link |
133 | dst_name, | ||
134 | hard_link | ||
135 | ); | 133 | ); |
136 | } | 134 | } |
137 | /* Hardlinks have no separate mode/ownership, skip chown/chmod */ | 135 | /* Hardlinks have no separate mode/ownership, skip chown/chmod */ |
@@ -181,7 +179,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
181 | //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) | 179 | //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) |
182 | 180 | ||
183 | /* To avoid a directory traversal attack via symlinks, | 181 | /* To avoid a directory traversal attack via symlinks, |
184 | * for certain link targets postpone creation of symlinks. | 182 | * do not restore symlinks with ".." components |
183 | * or symlinks starting with "/", unless a magic | ||
184 | * envvar is set. | ||
185 | * | 185 | * |
186 | * For example, consider a .tar created via: | 186 | * For example, consider a .tar created via: |
187 | * $ tar cvf bug.tar anything.txt | 187 | * $ tar cvf bug.tar anything.txt |
@@ -199,24 +199,17 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
199 | * | 199 | * |
200 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. | 200 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. |
201 | */ | 201 | */ |
202 | if (file_header->link_target[0] == '/' | 202 | if (!unsafe_symlink_target(file_header->link_target)) { |
203 | || strstr(file_header->link_target, "..") | 203 | res = symlink(file_header->link_target, dst_name); |
204 | ) { | 204 | if (res != 0 |
205 | llist_add_to(&archive_handle->symlink_placeholders, | 205 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
206 | xasprintf("%s%c%s", file_header->name, '\0', file_header->link_target) | 206 | ) { |
207 | ); | 207 | /* shared message */ |
208 | break; | 208 | bb_perror_msg("can't create %slink '%s' to '%s'", |
209 | } | 209 | "sym", |
210 | res = symlink(file_header->link_target, dst_name); | 210 | dst_name, file_header->link_target |
211 | if (res != 0 | 211 | ); |
212 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | 212 | } |
213 | ) { | ||
214 | /* shared message */ | ||
215 | bb_perror_msg("can't create %slink '%s' to '%s'", | ||
216 | "sym", | ||
217 | dst_name, | ||
218 | file_header->link_target | ||
219 | ); | ||
220 | } | 213 | } |
221 | break; | 214 | break; |
222 | case S_IFSOCK: | 215 | case S_IFSOCK: |
diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c new file mode 100644 index 000000000..441ba8b24 --- /dev/null +++ b/archival/libarchive/unsafe_symlink_target.c | |||
@@ -0,0 +1,48 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
4 | */ | ||
5 | #include "libbb.h" | ||
6 | #include "bb_archive.h" | ||
7 | |||
8 | int FAST_FUNC unsafe_symlink_target(const char *target) | ||
9 | { | ||
10 | const char *dot; | ||
11 | |||
12 | if (target[0] == '/') { | ||
13 | const char *var; | ||
14 | unsafe: | ||
15 | var = getenv("EXTRACT_UNSAFE_SYMLINKS"); | ||
16 | if (var) { | ||
17 | if (LONE_CHAR(var, '1')) | ||
18 | return 0; /* pretend it's safe */ | ||
19 | return 1; /* "UNSAFE!" */ | ||
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 | ); | ||
26 | /* Prevent further messages */ | ||
27 | setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0); | ||
28 | return 1; /* "UNSAFE!" */ | ||
29 | } | ||
30 | |||
31 | dot = target; | ||
32 | for (;;) { | ||
33 | dot = strchr(dot, '.'); | ||
34 | if (!dot) | ||
35 | return 0; /* safe target */ | ||
36 | |||
37 | /* Is it a path component starting with ".."? */ | ||
38 | if ((dot[1] == '.') | ||
39 | && (dot == target || dot[-1] == '/') | ||
40 | /* Is it exactly ".."? */ | ||
41 | && (dot[2] == '/' || dot[2] == '\0') | ||
42 | ) { | ||
43 | goto unsafe; | ||
44 | } | ||
45 | /* NB: it can even be trailing ".", should only add 1 */ | ||
46 | dot += 1; | ||
47 | } | ||
48 | } | ||
diff --git a/archival/tar.c b/archival/tar.c index 73c14ca81..9a5bcc7fe 100644 --- a/archival/tar.c +++ b/archival/tar.c | |||
@@ -278,23 +278,6 @@ static void chksum_and_xwrite(int fd, struct tar_header_t* hp) | |||
278 | xwrite(fd, hp, sizeof(*hp)); | 278 | xwrite(fd, hp, sizeof(*hp)); |
279 | } | 279 | } |
280 | 280 | ||
281 | static void replace_symlink_placeholders(llist_t *list) | ||
282 | { | ||
283 | while (list) { | ||
284 | char *target; | ||
285 | |||
286 | target = list->data + strlen(list->data) + 1; | ||
287 | if (symlink(target, list->data)) { | ||
288 | /* shared message */ | ||
289 | bb_error_msg_and_die("can't create %slink '%s' to '%s'", | ||
290 | "sym", | ||
291 | list->data, target | ||
292 | ); | ||
293 | } | ||
294 | list = list->link; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS | 281 | #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS |
299 | static void writeLongname(int fd, int type, const char *name, int dir) | 282 | static void writeLongname(int fd, int type, const char *name, int dir) |
300 | { | 283 | { |
@@ -1255,8 +1238,6 @@ int tar_main(int argc UNUSED_PARAM, char **argv) | |||
1255 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) | 1238 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) |
1256 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ | 1239 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ |
1257 | 1240 | ||
1258 | replace_symlink_placeholders(tar_handle->symlink_placeholders); | ||
1259 | |||
1260 | /* Check that every file that should have been extracted was */ | 1241 | /* Check that every file that should have been extracted was */ |
1261 | while (tar_handle->accept) { | 1242 | while (tar_handle->accept) { |
1262 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) | 1243 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) |
diff --git a/archival/unzip.c b/archival/unzip.c index 8ed9ae7d5..604166063 100644 --- a/archival/unzip.c +++ b/archival/unzip.c | |||
@@ -368,9 +368,15 @@ static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) | |||
368 | target[xstate.mem_output_size] = '\0'; | 368 | target[xstate.mem_output_size] = '\0'; |
369 | #endif | 369 | #endif |
370 | } | 370 | } |
371 | if (!unsafe_symlink_target(target)) { | ||
371 | //TODO: libbb candidate | 372 | //TODO: libbb candidate |
372 | if (symlink(target, dst_fn)) | 373 | if (symlink(target, dst_fn)) { |
373 | bb_perror_msg_and_die("can't create symlink '%s'", dst_fn); | 374 | /* shared message */ |
375 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", | ||
376 | "sym", dst_fn, target | ||
377 | ); | ||
378 | } | ||
379 | } | ||
374 | free(target); | 380 | free(target); |
375 | } | 381 | } |
376 | #endif | 382 | #endif |
diff --git a/coreutils/link.c b/coreutils/link.c index 81808b778..d8d583b7b 100644 --- a/coreutils/link.c +++ b/coreutils/link.c | |||
@@ -31,9 +31,8 @@ int link_main(int argc UNUSED_PARAM, char **argv) | |||
31 | argv += optind; | 31 | argv += optind; |
32 | if (link(argv[0], argv[1]) != 0) { | 32 | if (link(argv[0], argv[1]) != 0) { |
33 | /* shared message */ | 33 | /* shared message */ |
34 | bb_perror_msg_and_die("can't create %slink " | 34 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
35 | "'%s' to '%s'", "hard", | 35 | "hard", argv[1], argv[0] |
36 | argv[1], argv[0] | ||
37 | ); | 36 | ); |
38 | } | 37 | } |
39 | return EXIT_SUCCESS; | 38 | return EXIT_SUCCESS; |
diff --git a/include/bb_archive.h b/include/bb_archive.h index d3762415f..d3a02cf18 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h | |||
@@ -64,9 +64,6 @@ 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 | |||
70 | /* Process the header component, e.g. tar -t */ | 67 | /* Process the header component, e.g. tar -t */ |
71 | void FAST_FUNC (*action_header)(const file_header_t *); | 68 | void FAST_FUNC (*action_header)(const file_header_t *); |
72 | 69 | ||
@@ -200,6 +197,7 @@ void seek_by_jump(int fd, off_t amount) FAST_FUNC; | |||
200 | void seek_by_read(int fd, off_t amount) FAST_FUNC; | 197 | void seek_by_read(int fd, off_t amount) FAST_FUNC; |
201 | 198 | ||
202 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; | 199 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; |
200 | int unsafe_symlink_target(const char *target) FAST_FUNC; | ||
203 | 201 | ||
204 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; | 202 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; |
205 | const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; | 203 | const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; |
diff --git a/libbb/copy_file.c b/libbb/copy_file.c index 23c0f8320..be9006631 100644 --- a/libbb/copy_file.c +++ b/libbb/copy_file.c | |||
@@ -371,7 +371,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags) | |||
371 | int r = symlink(lpath, dest); | 371 | int r = symlink(lpath, dest); |
372 | free(lpath); | 372 | free(lpath); |
373 | if (r < 0) { | 373 | if (r < 0) { |
374 | bb_perror_msg("can't create symlink '%s'", dest); | 374 | /* shared message */ |
375 | bb_perror_msg("can't create %slink '%s' to '%s'", | ||
376 | "sym", dest, lpath | ||
377 | ); | ||
375 | return -1; | 378 | return -1; |
376 | } | 379 | } |
377 | if (flags & FILEUTILS_PRESERVE_STATUS) | 380 | if (flags & FILEUTILS_PRESERVE_STATUS) |
diff --git a/testsuite/tar.tests b/testsuite/tar.tests index 1675b07b1..b7cd74ca5 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: can't create symlink 'passwd' to '/tmp/passwd' | 282 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
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: can't create symlink 'passwd' to '/tmp/passwd' | 302 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
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 | ||
327 | etc/ssl/certs/f80cc7f6.0 | 328 | etc/ssl/certs/f80cc7f6.0 |
328 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt | 329 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt |
329 | 0 | 330 | 0 |
330 | etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 331 | 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 | ||
349 | symlink/bb_test_evilfile | 350 | symlink/bb_test_evilfile |
350 | tar: can't create symlink 'symlink' to '/tmp' | 351 | 0 |
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 |