blob: 0926107bea29a03dd33c54956f05be22cbae7a45 [file] [log] [blame]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001busybox-1.27.2: Fix CVE-2011-5325
2
3[No upstream tracking] -- https://bugs.busybox.net/show_bug.cgi?id=8411
4
5libarchive: do not extract unsafe symlinks
6
7Prevent unsafe links extracting unless env variable $EXTRACT_UNSAFE_SYMLINKS=1
8is not set. Untarring file with -C DESTDIR parameter could be extracted with
9unwanted symlinks. This doesn't feel right, and IIRC GNU tar doesn't do that.
10Include necessary changes from previous commits.
11
12Upstream-Status: Backport [https://git.busybox.net/busybox/commit/?id=bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7]
13CVE: CVE-2011-5325
14bug: 8411
15Signed-off-by: Radovan Scasny <radovan.scasny@siemens.com>
16Signed-off-by: Andrej Valek <andrej.valek@siemens.com>
17
18diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src
19index 942e755..e1a8a75 100644
20--- a/archival/libarchive/Kbuild.src
21+++ b/archival/libarchive/Kbuild.src
22@@ -12,6 +12,8 @@ COMMON_FILES:= \
23 data_extract_all.o \
24 data_extract_to_stdout.o \
25 \
26+ unsafe_symlink_target.o \
27+\
28 filter_accept_all.o \
29 filter_accept_list.o \
30 filter_accept_reject_list.o \
31diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c
32index 1830ffb..b828b65 100644
33--- a/archival/libarchive/data_extract_all.c
34+++ b/archival/libarchive/data_extract_all.c
35@@ -128,10 +128,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
36 res = link(hard_link, dst_name);
37 if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) {
38 /* shared message */
39- bb_perror_msg("can't create %slink "
40- "%s to %s", "hard",
41- dst_name,
42- hard_link);
43+ bb_perror_msg("can't create %slink '%s' to '%s'",
44+ "hard", dst_name, hard_link
45+ );
46 }
47 /* Hardlinks have no separate mode/ownership, skip chown/chmod */
48 goto ret;
49@@ -178,15 +177,17 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
50 case S_IFLNK:
51 /* Symlink */
52 //TODO: what if file_header->link_target == NULL (say, corrupted tarball?)
53- res = symlink(file_header->link_target, dst_name);
54- if (res != 0
55- && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
56- ) {
57- /* shared message */
58- bb_perror_msg("can't create %slink "
59- "%s to %s", "sym",
60- dst_name,
61- file_header->link_target);
62+ if (!unsafe_symlink_target(file_header->link_target)) {
63+ res = symlink(file_header->link_target, dst_name);
64+ if (res != 0
65+ && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
66+ ) {
67+ /* shared message */
68+ bb_perror_msg("can't create %slink '%s' to '%s'",
69+ "sym",
70+ dst_name, file_header->link_target
71+ );
72+ }
73 }
74 break;
75 case S_IFSOCK:
76diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c
77new file mode 100644
78index 0000000..ee46e28
79--- /dev/null
80+++ b/archival/libarchive/unsafe_symlink_target.c
81@@ -0,0 +1,48 @@
82+/* vi: set sw=4 ts=4: */
83+/*
84+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
85+ */
86+#include "libbb.h"
87+#include "bb_archive.h"
88+
89+int FAST_FUNC unsafe_symlink_target(const char *target)
90+{
91+ const char *dot;
92+
93+ if (target[0] == '/') {
94+ const char *var;
95+unsafe:
96+ var = getenv("EXTRACT_UNSAFE_SYMLINKS");
97+ if (var) {
98+ if (LONE_CHAR(var, '1'))
99+ return 0; /* pretend it's safe */
100+ return 1; /* "UNSAFE!" */
101+ }
102+ bb_error_msg("skipping unsafe symlink to '%s' in archive,"
103+ " set %s=1 to extract",
104+ target,
105+ "EXTRACT_UNSAFE_SYMLINKS"
106+ );
107+ /* Prevent further messages */
108+ setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0);
109+ return 1; /* "UNSAFE!" */
110+ }
111+
112+ dot = target;
113+ for (;;) {
114+ dot = strchr(dot, '.');
115+ if (!dot)
116+ return 0; /* safe target */
117+
118+ /* Is it a path component starting with ".."? */
119+ if ((dot[1] == '.')
120+ && (dot == target || dot[-1] == '/')
121+ /* Is it exactly ".."? */
122+ && (dot[2] == '/' || dot[2] == '\0')
123+ ) {
124+ goto unsafe;
125+ }
126+ /* NB: it can even be trailing ".", should only add 1 */
127+ dot += 1;
128+ }
129+}
130\ No newline at end of file
131diff --git a/archival/unzip.c b/archival/unzip.c
132index 9037262..270e261 100644
133--- a/archival/unzip.c
134+++ b/archival/unzip.c
135@@ -335,6 +335,44 @@ static void unzip_create_leading_dirs(const char *fn)
136 free(name);
137 }
138
139+static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn)
140+{
141+ char *target;
142+
143+ if (zip->fmt.ucmpsize > 0xfff) /* no funny business please */
144+ bb_error_msg_and_die("bad archive");
145+
146+ if (zip->fmt.method == 0) {
147+ /* Method 0 - stored (not compressed) */
148+ target = xzalloc(zip->fmt.ucmpsize + 1);
149+ xread(zip_fd, target, zip->fmt.ucmpsize);
150+ } else {
151+#if 1
152+ bb_error_msg_and_die("compressed symlink is not supported");
153+#else
154+ transformer_state_t xstate;
155+ init_transformer_state(&xstate);
156+ xstate.mem_output_size_max = zip->fmt.ucmpsize;
157+ /* ...unpack... */
158+ if (!xstate.mem_output_buf)
159+ WTF();
160+ target = xstate.mem_output_buf;
161+ target = xrealloc(target, xstate.mem_output_size + 1);
162+ target[xstate.mem_output_size] = '\0';
163+#endif
164+ }
165+ if (!unsafe_symlink_target(target)) {
166+//TODO: libbb candidate
167+ if (symlink(target, dst_fn)) {
168+ /* shared message */
169+ bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
170+ "sym", dst_fn, target
171+ );
172+ }
173+ }
174+ free(target);
175+}
176+
177 static void unzip_extract(zip_header_t *zip, int dst_fd)
178 {
179 transformer_state_t xstate;
180@@ -813,7 +851,7 @@ int unzip_main(int argc, char **argv)
181 }
182 check_file:
183 /* Extract file */
184- if (stat(dst_fn, &stat_buf) == -1) {
185+ if (lstat(dst_fn, &stat_buf) == -1) {
186 /* File does not exist */
187 if (errno != ENOENT) {
188 bb_perror_msg_and_die("can't stat '%s'", dst_fn);
189@@ -834,6 +872,7 @@ int unzip_main(int argc, char **argv)
190 goto do_open_and_extract;
191 printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
192 my_fgets80(key_buf);
193+//TODO: redo lstat + ISREG check! user input could have taken a long time!
194
195 switch (key_buf[0]) {
196 case 'A':
197@@ -842,7 +881,8 @@ int unzip_main(int argc, char **argv)
198 do_open_and_extract:
199 unzip_create_leading_dirs(dst_fn);
200 #if ENABLE_FEATURE_UNZIP_CDF
201- dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode);
202+ if (!S_ISLNK(file_mode))
203+ dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode);
204 #else
205 dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC);
206 #endif
207@@ -852,10 +892,18 @@ int unzip_main(int argc, char **argv)
208 ? " extracting: %s\n"
209 : */ " inflating: %s\n", dst_fn);
210 }
211- unzip_extract(&zip, dst_fd);
212- if (dst_fd != STDOUT_FILENO) {
213- /* closing STDOUT is potentially bad for future business */
214- close(dst_fd);
215+#if ENABLE_FEATURE_UNZIP_CDF
216+ if (S_ISLNK(file_mode)) {
217+ if (dst_fd != STDOUT_FILENO) /* no -p */
218+ unzip_extract_symlink(&zip, dst_fn);
219+ } else
220+#endif
221+ {
222+ unzip_extract(&zip, dst_fd);
223+ if (dst_fd != STDOUT_FILENO) {
224+ /* closing STDOUT is potentially bad for future business */
225+ close(dst_fd);
226+ };
227 }
228 break;
229
230diff --git a/coreutils/link.c b/coreutils/link.c
231index ac3ef85..aab249d 100644
232--- a/coreutils/link.c
233+++ b/coreutils/link.c
234@@ -32,9 +32,8 @@ int link_main(int argc UNUSED_PARAM, char **argv)
235 argv += optind;
236 if (link(argv[0], argv[1]) != 0) {
237 /* shared message */
238- bb_perror_msg_and_die("can't create %slink "
239- "%s to %s", "hard",
240- argv[1], argv[0]
241+ bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
242+ "hard", argv[1], argv[0]
243 );
244 }
245 return EXIT_SUCCESS;
246diff --git a/include/bb_archive.h b/include/bb_archive.h
247index 2b9c5f0..1e4da3c 100644
248--- a/include/bb_archive.h
249+++ b/include/bb_archive.h
250@@ -196,6 +196,7 @@ void seek_by_jump(int fd, off_t amount) FAST_FUNC;
251 void seek_by_read(int fd, off_t amount) FAST_FUNC;
252
253 const char *strip_unsafe_prefix(const char *str) FAST_FUNC;
254+int unsafe_symlink_target(const char *target) FAST_FUNC;
255
256 void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC;
257 const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC;
258diff --git a/libbb/copy_file.c b/libbb/copy_file.c
259index 23c0f83..be90066 100644
260--- a/libbb/copy_file.c
261+++ b/libbb/copy_file.c
262@@ -371,7 +371,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
263 int r = symlink(lpath, dest);
264 free(lpath);
265 if (r < 0) {
266- bb_perror_msg("can't create symlink '%s'", dest);
267+ /* shared message */
268+ bb_perror_msg("can't create %slink '%s' to '%s'",
269+ "sym", dest, lpath
270+ );
271 return -1;
272 }
273 if (flags & FILEUTILS_PRESERVE_STATUS)
274diff --git a/testsuite/tar.tests b/testsuite/tar.tests
275index 9f7ce15..b7cd74c 100755
276--- a/testsuite/tar.tests
277+++ b/testsuite/tar.tests
278@@ -10,9 +10,6 @@ unset LC_COLLATE
279 unset LC_ALL
280 umask 022
281
282-rm -rf tar.tempdir 2>/dev/null
283-mkdir tar.tempdir && cd tar.tempdir || exit 1
284-
285 # testing "test name" "script" "expected result" "file input" "stdin"
286
287 testing "Empty file is not a tarball" '\
288@@ -53,6 +50,7 @@ dd if=/dev/zero bs=512 count=20 2>/dev/null | tar xvf - 2>&1; echo $?
289 "" ""
290 SKIP=
291
292+mkdir tar.tempdir && cd tar.tempdir || exit 1
293 # "tar cf test.tar input input_dir/ input_hard1 input_hard2 input_hard1 input_dir/ input":
294 # GNU tar 1.26 records as hardlinks:
295 # input_hard2 -> input_hard1
296@@ -64,7 +62,6 @@ SKIP=
297 # We also don't use "hrw-r--r--" notation for hardlinks in "tar tv" listing.
298 optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES
299 testing "tar hardlinks and repeated files" '\
300-rm -rf input_* test.tar 2>/dev/null
301 >input_hard1
302 ln input_hard1 input_hard2
303 mkdir input_dir
304@@ -95,10 +92,11 @@ drwxr-xr-x input_dir
305 " \
306 "" ""
307 SKIP=
308+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
309
310+mkdir tar.tempdir && cd tar.tempdir || exit 1
311 optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES
312 testing "tar hardlinks mode" '\
313-rm -rf input_* test.tar 2>/dev/null
314 >input_hard1
315 chmod 741 input_hard1
316 ln input_hard1 input_hard2
317@@ -128,10 +126,11 @@ Ok: 0
318 " \
319 "" ""
320 SKIP=
321+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
322
323+mkdir tar.tempdir && cd tar.tempdir || exit 1
324 optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES
325 testing "tar symlinks mode" '\
326-rm -rf input_* test.tar 2>/dev/null
327 >input_file
328 chmod 741 input_file
329 ln -s input_file input_soft
330@@ -159,10 +158,11 @@ lrwxrwxrwx input_file
331 " \
332 "" ""
333 SKIP=
334+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
335
336+mkdir tar.tempdir && cd tar.tempdir || exit 1
337 optional FEATURE_TAR_CREATE FEATURE_TAR_LONG_OPTIONS
338 testing "tar --overwrite" "\
339-rm -rf input_* test.tar 2>/dev/null
340 ln input input_hard
341 tar cf test.tar input_hard
342 echo WRONG >input
343@@ -174,12 +174,13 @@ Ok
344 " \
345 "Ok\n" ""
346 SKIP=
347+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
348
349+mkdir tar.tempdir && cd tar.tempdir || exit 1
350 test x"$SKIP_KNOWN_BUGS" = x"" && {
351 # Needs to be run under non-root for meaningful test
352 optional FEATURE_TAR_CREATE
353 testing "tar writing into read-only dir" '\
354-rm -rf input_* test.tar 2>/dev/null
355 mkdir input_dir
356 >input_dir/input_file
357 chmod 550 input_dir
358@@ -201,7 +202,9 @@ dr-xr-x--- input_dir
359 "" ""
360 SKIP=
361 }
362+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
363
364+mkdir tar.tempdir && cd tar.tempdir || exit 1
365 # Had a bug where on extract autodetect first "switched off" -z
366 # and then failed to recognize .tgz extension
367 optional FEATURE_TAR_CREATE FEATURE_SEAMLESS_GZ GUNZIP
368@@ -217,7 +220,9 @@ Ok
369 " \
370 "" ""
371 SKIP=
372+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
373
374+mkdir tar.tempdir && cd tar.tempdir || exit 1
375 # Do we detect XZ-compressed data (even w/o .tar.xz or txz extension)?
376 # (the uuencoded hello_world.txz contains one empty file named "hello_world")
377 optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_XZ
378@@ -236,7 +241,9 @@ AAAEWVo=
379 ====
380 "
381 SKIP=
382+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
383
384+mkdir tar.tempdir && cd tar.tempdir || exit 1
385 # On extract, everything up to and including last ".." component is stripped
386 optional FEATURE_TAR_CREATE
387 testing "tar strips /../ on extract" "\
388@@ -255,7 +262,9 @@ Ok
389 " \
390 "" ""
391 SKIP=
392+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
393
394+mkdir tar.tempdir && cd tar.tempdir || exit 1
395 # attack.tar.bz2 has symlink pointing to a system file
396 # followed by a regular file with the same name
397 # containing "root::0:0::/root:/bin/sh":
398@@ -270,6 +279,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
399 testing "tar does not extract into symlinks" "\
400 >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
401 " "\
402+tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
403 0
404 " \
405 "" "\
406@@ -281,12 +291,15 @@ l4/V8LDoe90yiWJhOJvIypgEfxdyRThQkBVn/bI=
407 ====
408 "
409 SKIP=
410+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
411+
412+mkdir tar.tempdir && cd tar.tempdir || exit 1
413 # And same with -k
414 optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
415 testing "tar -k does not extract into symlinks" "\
416 >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
417 " "\
418-tar: can't open 'passwd': File exists
419+tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
420 0
421 " \
422 "" "\
423@@ -298,7 +311,9 @@ l4/V8LDoe90yiWJhOJvIypgEfxdyRThQkBVn/bI=
424 ====
425 "
426 SKIP=
427+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
428
429+mkdir tar.tempdir && cd tar.tempdir || exit 1
430 optional UNICODE_SUPPORT FEATURE_TAR_GNU_EXTENSIONS FEATURE_SEAMLESS_BZ2 FEATURE_TAR_AUTODETECT
431 testing "Pax-encoded UTF8 names and symlinks" '\
432 tar xvf ../tar.utf8.tar.bz2 2>&1; echo $?
433@@ -309,17 +324,45 @@ rm -rf etc usr
434 ' "\
435 etc/ssl/certs/3b2716e5.0
436 etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
437+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
438 etc/ssl/certs/f80cc7f6.0
439 usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt
440 0
441 etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
442-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
443 etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
444 " \
445 "" ""
446 SKIP=
447+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
448
449-
450-cd .. && rm -rf tar.tempdir || exit 1
451+mkdir tar.tempdir && cd tar.tempdir || exit 1
452+optional UUDECODE FEATURE_SEAMLESS_BZ2 FEATURE_TAR_AUTODETECT
453+testing "Symlink attack: create symlink and then write through it" '\
454+exec 2>&1
455+uudecode -o input && tar xvf input; echo $?
456+ls /tmp/bb_test_evilfile
457+ls bb_test_evilfile
458+ls symlink/bb_test_evilfile
459+' "\
460+anything.txt
461+symlink
462+tar: skipping unsafe symlink to '/tmp' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
463+symlink/bb_test_evilfile
464+0
465+ls: /tmp/bb_test_evilfile: No such file or directory
466+ls: bb_test_evilfile: No such file or directory
467+symlink/bb_test_evilfile
468+" \
469+"" "\
470+begin-base64 644 tar_symlink_attack.tar.bz2
471+QlpoOTFBWSZTWZgs7bQAALT/hMmQAFBAAf+AEMAGJPPv32AAAIAIMAC5thlR
472+omAjAmCMADQT1BqNE0AEwAAjAEwElTKeo9NTR6h6gaeoA0DQNLVdwZZ5iNTk
473+AQwCAV6S00QFJYhrlfFkVCEDEGtgNVqYrI0uK3ggnt30gqk4e1TTQm5QIAKa
474+SJqzRGSFLMmOloHSAcvLiFxxRiQtQZF+qPxbo173ZDISOAoNoPN4PQPhBhKS
475+n8fYaKlioCTzL2oXYczyUUIP4u5IpwoSEwWdtoA=
476+====
477+"
478+SKIP=
479+cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null
480
481 exit $FAILCOUNT