aboutsummaryrefslogtreecommitdiffstats
path: root/meta/recipes-core/busybox/busybox/CVE-2011-5325-fix.patch
blob: a8d7e4bd506815715dc7822fe929ff72d45cd655 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
From 3e1e224fd031ae3927acda70f6e1fa55193e5b68 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 20 Feb 2018 15:57:45 +0100
Subject: [PATCH] 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>

Upstream-Status: Backport from 1.28.2 [https://git.busybox.net/busybox/commit/?h=1_28_stable&id=37277a23fe48b13313f5d96084d890ed21d5fd8b]

Signed-off-by: Martin Jansa <Martin.Jansa@gmail.com>
---
 archival/libarchive/data_extract_all.c      | 50 +++++++++-------
 archival/libarchive/unsafe_symlink_target.c | 63 +++++++++------------
 archival/tar.c                              |  2 +
 archival/unzip.c                            | 29 ++++++----
 include/bb_archive.h                        | 23 +++++---
 testsuite/tar.tests                         | 10 ++--
 6 files changed, 95 insertions(+), 82 deletions(-)

diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c
index b828b656d..dad8d7d87 100644
--- a/archival/libarchive/data_extract_all.c
+++ b/archival/libarchive/data_extract_all.c
@@ -108,9 +108,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
 			}
 		}
 		else if (existing_sb.st_mtime >= file_header->mtime) {
-			if (!(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
-			 && !S_ISDIR(file_header->mode)
-			) {
+			if (!S_ISDIR(file_header->mode)) {
 				bb_error_msg("%s not created: newer or "
 					"same age file exists", dst_name);
 			}
@@ -126,7 +124,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
 	/* Handle hard links separately */
 	if (hard_link) {
 		res = link(hard_link, dst_name);
-		if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) {
+		if (res != 0) {
 			/* shared message */
 			bb_perror_msg("can't create %slink '%s' to '%s'",
 					 "hard", dst_name, hard_link
@@ -166,10 +164,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
 	}
 	case S_IFDIR:
 		res = mkdir(dst_name, file_header->mode);
-		if ((res == -1)
+		if ((res != 0)
 		 && (errno != EISDIR) /* btw, Linux doesn't return this */
 		 && (errno != EEXIST)
-		 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
 		) {
 			bb_perror_msg("can't make dir %s", dst_name);
 		}
@@ -177,27 +174,38 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
 	case S_IFLNK:
 		/* Symlink */
 //TODO: what if file_header->link_target == NULL (say, corrupted tarball?)
-		if (!unsafe_symlink_target(file_header->link_target)) {
-			res = symlink(file_header->link_target, dst_name);
-			if (res != 0
-				&& !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
-			) {
-						/* shared message */
-						bb_perror_msg("can't create %slink '%s' to '%s'",
-							"sym",
-							dst_name, file_header->link_target
-						);
-			}
-		}
+
+		/* To avoid a directory traversal attack via symlinks,
+		 * do not restore symlinks with ".." components
+		 * or symlinks starting with "/", unless a magic
+		 * envvar is set.
+		 *
+		 * For example, consider a .tar created via:
+		 *  $ tar cvf bug.tar anything.txt
+		 *  $ ln -s /tmp symlink
+		 *  $ tar --append -f bug.tar symlink
+		 *  $ rm symlink
+		 *  $ mkdir symlink
+		 *  $ tar --append -f bug.tar symlink/evil.py
+		 *
+		 * This will result in an archive that contains:
+		 *  $ tar --list -f bug.tar
+		 *  anything.txt
+		 *  symlink [-> /tmp]
+		 *  symlink/evil.py
+		 *
+		 * Untarring bug.tar would otherwise place evil.py in '/tmp'.
+		 */
+		create_or_remember_symlink(&archive_handle->symlink_placeholders,
+				file_header->link_target,
+				dst_name);
 		break;
 	case S_IFSOCK:
 	case S_IFBLK:
 	case S_IFCHR:
 	case S_IFIFO:
 		res = mknod(dst_name, file_header->mode, file_header->device);
-		if ((res == -1)
-		 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)
-		) {
+		if (res != 0) {
 			bb_perror_msg("can't create node %s", dst_name);
 		}
 		break;
diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c
index ee46e28f8..8dcafeaa1 100644
--- a/archival/libarchive/unsafe_symlink_target.c
+++ b/archival/libarchive/unsafe_symlink_target.c
@@ -5,44 +5,37 @@
 #include "libbb.h"
 #include "bb_archive.h"
 
-int FAST_FUNC unsafe_symlink_target(const char *target)
+void FAST_FUNC create_or_remember_symlink(llist_t **symlink_placeholders,
+		const char *target,
+		const char *linkname)
 {
-	const char *dot;
-
-	if (target[0] == '/') {
-		const char *var;
-unsafe:
-		var = getenv("EXTRACT_UNSAFE_SYMLINKS");
-		if (var) {
-			if (LONE_CHAR(var, '1'))
-				return 0; /* pretend it's safe */
-			return 1; /* "UNSAFE!" */
-		}
-		bb_error_msg("skipping unsafe symlink to '%s' in archive,"
-			" set %s=1 to extract",
-			target,
-			"EXTRACT_UNSAFE_SYMLINKS"
+	if (target[0] == '/' || strstr(target, "..")) {
+		llist_add_to(symlink_placeholders,
+			xasprintf("%s%c%s", linkname, '\0', target)
+		);
+		return;
+	}
+	if (symlink(target, linkname) != 0) {
+		/* shared message */
+		bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
+			"sym", linkname, target
 		);
-		/* Prevent further messages */
-		setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0);
-		return 1; /* "UNSAFE!" */
 	}
+}
 
-	dot = target;
-	for (;;) {
-		dot = strchr(dot, '.');
-			if (!dot)
-				return 0; /* safe target */
+void FAST_FUNC create_symlinks_from_list(llist_t *list)
+{
+	while (list) {
+		char *target;
 
-			/* Is it a path component starting with ".."? */
-			if ((dot[1] == '.')
-				&& (dot == target || dot[-1] == '/')
-					/* Is it exactly ".."? */
-				&& (dot[2] == '/' || dot[2] == '\0')
-			) {
-				goto unsafe;
-			}
-			/* NB: it can even be trailing ".", should only add 1 */
-			dot += 1;
+		target = list->data + strlen(list->data) + 1;
+		if (symlink(target, list->data)) {
+			/* shared message */
+			bb_error_msg_and_die("can't create %slink '%s' to '%s'",
+				"sym",
+				list->data, target
+			);
+		}
+		list = list->link;
 	}
-}
\ No newline at end of file
+}
diff --git a/archival/tar.c b/archival/tar.c
index 7598b71e3..bde86330c 100644
--- a/archival/tar.c
+++ b/archival/tar.c
@@ -1252,6 +1252,8 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
 	while (get_header_tar(tar_handle) == EXIT_SUCCESS)
 		bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */
 
+	create_symlinks_from_list(tar_handle->symlink_placeholders);
+
 	/* Check that every file that should have been extracted was */
 	while (tar_handle->accept) {
 		if (!find_list_entry(tar_handle->reject, tar_handle->accept->data)
diff --git a/archival/unzip.c b/archival/unzip.c
index 270e261b7..2f53ab7a4 100644
--- a/archival/unzip.c
+++ b/archival/unzip.c
@@ -335,7 +335,10 @@ static void unzip_create_leading_dirs(const char *fn)
 	free(name);
 }
 
-static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn)
+#if ENABLE_FEATURE_UNZIP_CDF
+static void unzip_extract_symlink(llist_t **symlink_placeholders,
+		zip_header_t *zip,
+		const char *dst_fn)
 {
 	char *target;
 
@@ -361,17 +364,12 @@ static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn)
 		target[xstate.mem_output_size] = '\0';
 #endif
 	}
-	if (!unsafe_symlink_target(target)) {
-//TODO: libbb candidate
-		if (symlink(target, dst_fn)) {
-			/* shared message */
-			bb_perror_msg_and_die("can't create %slink '%s' to '%s'",
-				"sym", dst_fn, target
-			);
-		}
-	}
+	create_or_remember_symlink(symlink_placeholders,
+			target,
+			dst_fn);
 	free(target);
 }
+#endif
 
 static void unzip_extract(zip_header_t *zip, int dst_fd)
 {
@@ -464,6 +462,9 @@ int unzip_main(int argc, char **argv)
 	llist_t *zaccept = NULL;
 	llist_t *zreject = NULL;
 	char *base_dir = NULL;
+#if ENABLE_FEATURE_UNZIP_CDF
+	llist_t *symlink_placeholders = NULL;
+#endif
 	int i, opt;
 	char key_buf[80]; /* must match size used by my_fgets80 */
 	struct stat stat_buf;
@@ -894,8 +895,8 @@ int unzip_main(int argc, char **argv)
 			}
 #if ENABLE_FEATURE_UNZIP_CDF
 			if (S_ISLNK(file_mode)) {
-				if (dst_fd != STDOUT_FILENO) /* no -p */
-					unzip_extract_symlink(&zip, dst_fn);
+				if (dst_fd != STDOUT_FILENO) /* not -p? */
+					unzip_extract_symlink(&symlink_placeholders, &zip, dst_fn);
 			} else
 #endif
 			{
@@ -931,6 +932,10 @@ int unzip_main(int argc, char **argv)
 		total_entries++;
 	}
 
+#if ENABLE_FEATURE_UNZIP_CDF
+	create_symlinks_from_list(symlink_placeholders);
+#endif
+
 	if (listing && quiet <= 1) {
 		if (!verbose) {
 			//	"  Length      Date    Time    Name\n"
diff --git a/include/bb_archive.h b/include/bb_archive.h
index 1e4da3c33..436eb0fe3 100644
--- a/include/bb_archive.h
+++ b/include/bb_archive.h
@@ -64,6 +64,9 @@ typedef struct archive_handle_t {
 	/* Currently processed file's header */
 	file_header_t *file_header;
 
+	/* List of symlink placeholders */
+	llist_t *symlink_placeholders;
+
 	/* Process the header component, e.g. tar -t */
 	void FAST_FUNC (*action_header)(const file_header_t *);
 
@@ -119,15 +122,14 @@ typedef struct archive_handle_t {
 #define ARCHIVE_RESTORE_DATE        (1 << 0)
 #define ARCHIVE_CREATE_LEADING_DIRS (1 << 1)
 #define ARCHIVE_UNLINK_OLD          (1 << 2)
-#define ARCHIVE_EXTRACT_QUIET       (1 << 3)
-#define ARCHIVE_EXTRACT_NEWER       (1 << 4)
-#define ARCHIVE_DONT_RESTORE_OWNER  (1 << 5)
-#define ARCHIVE_DONT_RESTORE_PERM   (1 << 6)
-#define ARCHIVE_NUMERIC_OWNER       (1 << 7)
-#define ARCHIVE_O_TRUNC             (1 << 8)
-#define ARCHIVE_REMEMBER_NAMES      (1 << 9)
+#define ARCHIVE_EXTRACT_NEWER       (1 << 3)
+#define ARCHIVE_DONT_RESTORE_OWNER  (1 << 4)
+#define ARCHIVE_DONT_RESTORE_PERM   (1 << 5)
+#define ARCHIVE_NUMERIC_OWNER       (1 << 6)
+#define ARCHIVE_O_TRUNC             (1 << 7)
+#define ARCHIVE_REMEMBER_NAMES      (1 << 8)
 #if ENABLE_RPM
-#define ARCHIVE_REPLACE_VIA_RENAME  (1 << 10)
+#define ARCHIVE_REPLACE_VIA_RENAME  (1 << 9)
 #endif
 
 
@@ -196,7 +198,10 @@ 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;
-int unsafe_symlink_target(const char *target) FAST_FUNC;
+void create_or_remember_symlink(llist_t **symlink_placeholders,
+		const char *target,
+		const char *linkname) FAST_FUNC;
+void create_symlinks_from_list(llist_t *list) FAST_FUNC;
 
 void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC;
 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 127eeaaee..21cef49fe 100755
--- a/testsuite/tar.tests
+++ b/testsuite/tar.tests
@@ -279,7 +279,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
 testing "tar does not extract into symlinks" "\
 >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
 " "\
-tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
+tar: can't create symlink 'passwd' to '/tmp/passwd'
 0
 " \
 "" "\
@@ -299,7 +299,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2
 testing "tar -k does not extract into symlinks" "\
 >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$?
 " "\
-tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
+tar: can't create symlink 'passwd' to '/tmp/passwd'
 0
 " \
 "" "\
@@ -324,11 +324,11 @@ rm -rf etc usr
 ' "\
 etc/ssl/certs/3b2716e5.0
 etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
-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
 etc/ssl/certs/f80cc7f6.0
 usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt
 0
 etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
+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
 etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem
 " \
 "" ""
@@ -346,9 +346,9 @@ ls symlink/bb_test_evilfile
 ' "\
 anything.txt
 symlink
-tar: skipping unsafe symlink to '/tmp' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract
 symlink/bb_test_evilfile
-0
+tar: can't create symlink 'symlink' to '/tmp'
+1
 ls: /tmp/bb_test_evilfile: No such file or directory
 ls: bb_test_evilfile: No such file or directory
 symlink/bb_test_evilfile
-- 
2.17.1