1 |
jpp |
1.1 |
diff --git a/libarchive/archive_write_disk.c b/libarchive/archive_write_disk.c |
2 |
|
|
index f61ae17..2acf847 100644 |
3 |
|
|
--- a/libarchive/archive_write_disk.c |
4 |
|
|
+++ b/libarchive/archive_write_disk.c |
5 |
|
|
@@ -218,6 +218,7 @@ struct archive_write_disk { |
6 |
|
|
#define MINIMUM_DIR_MODE 0700 |
7 |
|
|
#define MAXIMUM_DIR_MODE 0775 |
8 |
|
|
|
9 |
|
|
+static int check_path_for_symlinks(char *path, int *error_number, struct archive_string *error_string, int flags); |
10 |
|
|
static int check_symlinks(struct archive_write_disk *); |
11 |
|
|
static int create_filesystem_object(struct archive_write_disk *); |
12 |
|
|
static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); |
13 |
|
|
@@ -876,7 +877,7 @@ edit_deep_directories(struct archive_write_disk *a) |
14 |
|
|
a->restore_pwd = -1; |
15 |
|
|
|
16 |
|
|
/* If path is short, avoid the open() below. */ |
17 |
|
|
- if (strlen(tail) <= PATH_MAX) |
18 |
|
|
+ if (strlen(tail) < PATH_MAX) |
19 |
|
|
return; |
20 |
|
|
|
21 |
|
|
/* Try to record our starting dir. */ |
22 |
|
|
@@ -885,7 +886,7 @@ edit_deep_directories(struct archive_write_disk *a) |
23 |
|
|
return; |
24 |
|
|
|
25 |
|
|
/* As long as the path is too long... */ |
26 |
|
|
- while (strlen(tail) > PATH_MAX) { |
27 |
|
|
+ while (strlen(tail) >= PATH_MAX) { |
28 |
|
|
/* Locate a dir prefix shorter than PATH_MAX. */ |
29 |
|
|
tail += PATH_MAX - 8; |
30 |
|
|
while (tail > a->name && *tail != '/') |
31 |
|
|
@@ -1081,6 +1082,10 @@ create_filesystem_object(struct archive_write_disk *a) |
32 |
|
|
const char *linkname; |
33 |
|
|
mode_t final_mode, mode; |
34 |
|
|
int r; |
35 |
|
|
+ /* these for check_path_for_symlinks */ |
36 |
|
|
+ char *linkname_copy; /* non-const copy of linkname */ |
37 |
|
|
+ struct archive_string error_string; |
38 |
|
|
+ int error_number; |
39 |
|
|
|
40 |
|
|
/* We identify hard/symlinks according to the link names. */ |
41 |
|
|
/* Since link(2) and symlink(2) don't handle modes, we're done here. */ |
42 |
|
|
@@ -1089,6 +1094,18 @@ create_filesystem_object(struct archive_write_disk *a) |
43 |
|
|
#if !HAVE_LINK |
44 |
|
|
return (EPERM); |
45 |
|
|
#else |
46 |
|
|
+ archive_string_init(&error_string); |
47 |
|
|
+ linkname_copy = strdup(linkname); |
48 |
|
|
+ if (linkname_copy == NULL) { |
49 |
|
|
+ return (EPERM); |
50 |
|
|
+ } |
51 |
|
|
+ r = check_path_for_symlinks(linkname_copy, &error_number, &error_string, a->flags); |
52 |
|
|
+ free(linkname_copy); |
53 |
|
|
+ if (r != ARCHIVE_OK) { |
54 |
|
|
+ archive_set_error(&a->archive, error_number, "%s", error_string.s); |
55 |
|
|
+ /* EPERM is more appropriate than error_number for our callers */ |
56 |
|
|
+ return (EPERM); |
57 |
|
|
+ } |
58 |
|
|
r = link(linkname, a->name) ? errno : 0; |
59 |
|
|
/* |
60 |
|
|
* New cpio and pax formats allow hardlink entries |
61 |
|
|
@@ -1421,97 +1438,213 @@ current_fixup(struct archive_write_disk *a, const char *pathname) |
62 |
|
|
* recent paths. |
63 |
|
|
*/ |
64 |
|
|
/* TODO: Extend this to support symlinks on Windows Vista and later. */ |
65 |
|
|
-static int |
66 |
|
|
-check_symlinks(struct archive_write_disk *a) |
67 |
|
|
+ |
68 |
|
|
+/* |
69 |
|
|
+ * Checks the given path to see if any elements along it are symlinks. Returns |
70 |
|
|
+ * ARCHIVE_OK if there are none, otherwise puts an error in errmsg. |
71 |
|
|
+ */ |
72 |
|
|
+static int check_path_for_symlinks(char *path, int *error_number, struct archive_string *error_string, int flags) |
73 |
|
|
{ |
74 |
|
|
#if !defined(HAVE_LSTAT) |
75 |
|
|
/* Platform doesn't have lstat, so we can't look for symlinks. */ |
76 |
|
|
- (void)a; /* UNUSED */ |
77 |
|
|
+ (void)path; /* UNUSED */ |
78 |
|
|
+ (void)error_number; /* UNUSED */ |
79 |
|
|
+ (void)error_string; /* UNUSED */ |
80 |
|
|
+ (void)flags; /* UNUSED */ |
81 |
|
|
return (ARCHIVE_OK); |
82 |
|
|
#else |
83 |
|
|
- char *pn, *p; |
84 |
|
|
+ int res = ARCHIVE_OK; |
85 |
|
|
+ char *tail; |
86 |
|
|
+ char *head; |
87 |
|
|
+ int last; |
88 |
|
|
char c; |
89 |
|
|
int r; |
90 |
|
|
struct stat st; |
91 |
|
|
+ int restore_pwd; |
92 |
|
|
+ |
93 |
|
|
+ /* Nothing to do here if name is empty */ |
94 |
|
|
+ if(path[0] == '\0') |
95 |
|
|
+ return (ARCHIVE_OK); |
96 |
|
|
|
97 |
|
|
/* |
98 |
|
|
* Guard against symlink tricks. Reject any archive entry whose |
99 |
|
|
* destination would be altered by a symlink. |
100 |
|
|
+ * |
101 |
|
|
+ * Walk the filename in chunks separated by '/'. For each segment: |
102 |
|
|
+ * - if it doesn't exist, continue |
103 |
|
|
+ * - if it's symlink, abort or remove it |
104 |
|
|
+ * - if it's a directory and it's not the last chunk, cd into it |
105 |
|
|
+ * As we go: |
106 |
|
|
+ * head points to the current (relative) path |
107 |
|
|
+ * tail points to the temporary \0 terminating the segment we're currently examining |
108 |
|
|
+ * c holds what used to be in *tail |
109 |
|
|
+ * last is 1 if this is the last tail |
110 |
|
|
*/ |
111 |
|
|
- /* Whatever we checked last time doesn't need to be re-checked. */ |
112 |
|
|
- pn = a->name; |
113 |
|
|
- p = a->path_safe.s; |
114 |
|
|
- while ((*pn != '\0') && (*p == *pn)) |
115 |
|
|
- ++p, ++pn; |
116 |
|
|
- c = pn[0]; |
117 |
|
|
- /* Keep going until we've checked the entire name. */ |
118 |
|
|
- while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { |
119 |
|
|
+ restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC); |
120 |
|
|
+ if (restore_pwd < 0) |
121 |
|
|
+ return (ARCHIVE_FATAL); |
122 |
|
|
+ head = path; |
123 |
|
|
+ tail = path; |
124 |
|
|
+ last = 0; |
125 |
|
|
+ |
126 |
|
|
+ /* TODO: reintroduce a safe cache here? */ |
127 |
|
|
+ |
128 |
|
|
+ /* Keep going until we've checked the entire name. |
129 |
|
|
+ * head, tail, path all alias the same string, which is |
130 |
|
|
+ * temporarily zeroed at tail, so be careful restoring the |
131 |
|
|
+ * stashed (c=tail[0]) for error messages. |
132 |
|
|
+ * Exiting the loop with break is okay; continue is not. |
133 |
|
|
+ */ |
134 |
|
|
+ while (!last) { |
135 |
|
|
+ /* Skip the separator we just consumed, plus any adjacent ones */ |
136 |
|
|
+ while (*tail == '/') |
137 |
|
|
+ ++tail; |
138 |
|
|
/* Skip the next path element. */ |
139 |
|
|
- while (*pn != '\0' && *pn != '/') |
140 |
|
|
- ++pn; |
141 |
|
|
- c = pn[0]; |
142 |
|
|
- pn[0] = '\0'; |
143 |
|
|
+ while (*tail != '\0' && *tail != '/') |
144 |
|
|
+ ++tail; |
145 |
|
|
+ /* is this the last path component? */ |
146 |
|
|
+ last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0'); |
147 |
|
|
+ /* temporarily truncate the string here */ |
148 |
|
|
+ c = tail[0]; |
149 |
|
|
+ tail[0] = '\0'; |
150 |
|
|
/* Check that we haven't hit a symlink. */ |
151 |
|
|
- r = lstat(a->name, &st); |
152 |
|
|
+ r = lstat(head, &st); |
153 |
|
|
if (r != 0) { |
154 |
|
|
+ tail[0] = c; |
155 |
|
|
/* We've hit a dir that doesn't exist; stop now. */ |
156 |
|
|
if (errno == ENOENT) |
157 |
|
|
break; |
158 |
|
|
+ /* Treat any other error as fatal - best to be paranoid here */ |
159 |
|
|
+ if(error_number) *error_number = errno; |
160 |
|
|
+ if(error_string) |
161 |
|
|
+ archive_string_sprintf(error_string, |
162 |
|
|
+ "Could not stat %s", |
163 |
|
|
+ path); |
164 |
|
|
+ res = (ARCHIVE_FATAL); |
165 |
|
|
+ break; |
166 |
|
|
+ } else if (S_ISDIR(st.st_mode)) { |
167 |
|
|
+ if (!last) { |
168 |
|
|
+ if (chdir(head) != 0) { |
169 |
|
|
+ tail[0] = c; |
170 |
|
|
+ if(error_number) *error_number = errno; |
171 |
|
|
+ if(error_string) |
172 |
|
|
+ archive_string_sprintf(error_string, |
173 |
|
|
+ "Could not chdir %s", |
174 |
|
|
+ path); |
175 |
|
|
+ res = (ARCHIVE_FATAL); |
176 |
|
|
+ break; |
177 |
|
|
+ } |
178 |
|
|
+ /* Our view is now from inside this dir: */ |
179 |
|
|
+ head = tail + 1; |
180 |
|
|
+ } |
181 |
|
|
} else if (S_ISLNK(st.st_mode)) { |
182 |
|
|
- if (c == '\0') { |
183 |
|
|
+ if (last) { |
184 |
|
|
/* |
185 |
|
|
* Last element is symlink; remove it |
186 |
|
|
* so we can overwrite it with the |
187 |
|
|
* item being extracted. |
188 |
|
|
*/ |
189 |
|
|
- if (unlink(a->name)) { |
190 |
|
|
- archive_set_error(&a->archive, errno, |
191 |
|
|
- "Could not remove symlink %s", |
192 |
|
|
- a->name); |
193 |
|
|
- pn[0] = c; |
194 |
|
|
- return (ARCHIVE_FAILED); |
195 |
|
|
+ if (unlink(head)) { |
196 |
|
|
+ tail[0] = c; |
197 |
|
|
+ if(error_number) *error_number = errno; |
198 |
|
|
+ if(error_string) |
199 |
|
|
+ archive_string_sprintf(error_string, |
200 |
|
|
+ "Could not remove symlink %s", |
201 |
|
|
+ path); |
202 |
|
|
+ res = (ARCHIVE_FAILED); |
203 |
|
|
+ break; |
204 |
|
|
} |
205 |
|
|
- a->pst = NULL; |
206 |
|
|
/* |
207 |
|
|
* Even if we did remove it, a warning |
208 |
|
|
* is in order. The warning is silly, |
209 |
|
|
* though, if we're just replacing one |
210 |
|
|
* symlink with another symlink. |
211 |
|
|
*/ |
212 |
|
|
+ tail[0] = c; |
213 |
|
|
+ /* FIXME: not sure how important this is to restore |
214 |
|
|
if (!S_ISLNK(a->mode)) { |
215 |
|
|
- archive_set_error(&a->archive, 0, |
216 |
|
|
- "Removing symlink %s", |
217 |
|
|
- a->name); |
218 |
|
|
+ if(error_number) *error_number = 0; |
219 |
|
|
+ if(error_string) |
220 |
|
|
+ archive_string_sprintf(error_string, |
221 |
|
|
+ "Removing symlink %s", |
222 |
|
|
+ path); |
223 |
|
|
} |
224 |
|
|
+ */ |
225 |
|
|
/* Symlink gone. No more problem! */ |
226 |
|
|
- pn[0] = c; |
227 |
|
|
- return (0); |
228 |
|
|
- } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { |
229 |
|
|
+ res = (ARCHIVE_OK); |
230 |
|
|
+ break; |
231 |
|
|
+ } else if (flags & ARCHIVE_EXTRACT_UNLINK) { |
232 |
|
|
/* User asked us to remove problems. */ |
233 |
|
|
- if (unlink(a->name) != 0) { |
234 |
|
|
- archive_set_error(&a->archive, 0, |
235 |
|
|
- "Cannot remove intervening symlink %s", |
236 |
|
|
- a->name); |
237 |
|
|
- pn[0] = c; |
238 |
|
|
- return (ARCHIVE_FAILED); |
239 |
|
|
+ if (unlink(head) != 0) { |
240 |
|
|
+ tail[0] = c; |
241 |
|
|
+ if(error_number) *error_number = 0; |
242 |
|
|
+ if(error_string) |
243 |
|
|
+ archive_string_sprintf(error_string, |
244 |
|
|
+ "Cannot remove intervening symlink %s", |
245 |
|
|
+ path); |
246 |
|
|
+ res = (ARCHIVE_FAILED); |
247 |
|
|
+ break; |
248 |
|
|
} |
249 |
|
|
- a->pst = NULL; |
250 |
|
|
} else { |
251 |
|
|
- archive_set_error(&a->archive, 0, |
252 |
|
|
- "Cannot extract through symlink %s", |
253 |
|
|
- a->name); |
254 |
|
|
- pn[0] = c; |
255 |
|
|
- return (ARCHIVE_FAILED); |
256 |
|
|
+ tail[0] = c; |
257 |
|
|
+ if(error_number) *error_number = 0; |
258 |
|
|
+ if(error_string) |
259 |
|
|
+ archive_string_sprintf(error_string, |
260 |
|
|
+ "Cannot extract through symlink %s", |
261 |
|
|
+ path); |
262 |
|
|
+ res = (ARCHIVE_FAILED); |
263 |
|
|
+ break; |
264 |
|
|
} |
265 |
|
|
} |
266 |
|
|
+ /* be sure to always maintain this */ |
267 |
|
|
+ tail[0] = c; |
268 |
|
|
+ } |
269 |
|
|
+ /* Catches loop exits via break */ |
270 |
|
|
+ tail[0] = c; |
271 |
|
|
+#ifdef HAVE_FCHDIR |
272 |
|
|
+ /* If we changed directory above, restore it here. */ |
273 |
|
|
+ if (restore_pwd >= 0) { |
274 |
|
|
+ r = fchdir(restore_pwd); |
275 |
|
|
+ if (r != 0) { |
276 |
|
|
+ if(error_number) *error_number = 0; |
277 |
|
|
+ if(error_string) |
278 |
|
|
+ archive_string_sprintf(error_string, |
279 |
|
|
+ "Cannot extract through symlink %s", |
280 |
|
|
+ path); |
281 |
|
|
+ } |
282 |
|
|
+ close(restore_pwd); |
283 |
|
|
+ restore_pwd = -1; |
284 |
|
|
+ if (r != 0) { |
285 |
|
|
+ res = (ARCHIVE_FATAL); |
286 |
|
|
+ } |
287 |
|
|
} |
288 |
|
|
- pn[0] = c; |
289 |
|
|
- /* We've checked and/or cleaned the whole path, so remember it. */ |
290 |
|
|
- archive_strcpy(&a->path_safe, a->name); |
291 |
|
|
- return (ARCHIVE_OK); |
292 |
|
|
+#endif |
293 |
|
|
+ /* TODO: reintroduce a safe cache here? */ |
294 |
|
|
+ return res; |
295 |
|
|
#endif |
296 |
|
|
} |
297 |
|
|
|
298 |
|
|
+/* |
299 |
|
|
+ * Check a->name for symlinks, returning ARCHIVE_OK if its clean, otherwise |
300 |
|
|
+ * calls archive_set_error and returns ARCHIVE_{FATAL,FAILED} |
301 |
|
|
+ */ |
302 |
|
|
+static int |
303 |
|
|
+check_symlinks(struct archive_write_disk *a) |
304 |
|
|
+{ |
305 |
|
|
+ struct archive_string error_string; |
306 |
|
|
+ int error_number; |
307 |
|
|
+ int rc; |
308 |
|
|
+ archive_string_init(&error_string); |
309 |
|
|
+ rc = check_path_for_symlinks(a->name, &error_number, &error_string, a->flags); |
310 |
|
|
+ if (rc != ARCHIVE_OK) { |
311 |
|
|
+ archive_set_error(&a->archive, error_number, "%s", error_string.s); |
312 |
|
|
+ } |
313 |
|
|
+ archive_string_free(&error_string); |
314 |
|
|
+ a->pst = NULL; /* to be safe */ |
315 |
|
|
+ return rc; |
316 |
|
|
+} |
317 |
|
|
+ |
318 |
|
|
+ |
319 |
|
|
#if defined(_WIN32) || defined(__CYGWIN__) |
320 |
|
|
/* |
321 |
|
|
* 1. Convert a path separator from '\' to '/' . |