1 /*
2 * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * Pathname canonicalization for Unix file systems
28 */
29
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <errno.h>
36 #include <limits.h>
37 #include <unistd.h>
38 #if !defined(_ALLBSD_SOURCE)
39 #include <alloca.h>
40 #endif
41
42
43 /* Note: The comments in this file use the terminology
44 defined in the java.io.File class */
45
46
47 // BEGIN Android-added: Remove consecutive duplicate path separators "//". b/267617531
48 // and the trailing path separator `/` if it's not root fs.
removeDupSeparator(char * path)49 char* removeDupSeparator(char *path)
50 {
51 if (path == NULL || *path == '\0') {
52 return NULL;
53 }
54
55 char *in = path;
56 char *out = path;
57 char prevChar = 0;
58 int n = 0;
59 for (; *in != '\0'; in++) {
60 // Remove duplicate path separators
61 if (!(*in == '/' && prevChar == '/')) {
62 *(out++) = *in;
63 n++;
64 }
65 prevChar = *in;
66 }
67 *out = '\0';
68
69 // Remove the trailing path separator, except when path equals `/`
70 if (prevChar == '/' && n > 1) {
71 *(--out) = '\0';
72 }
73
74 return path;
75 }
76 // END Android-added: Remove consecutive duplicate path separators "//". b/267617531
77
78 /* Check the given name sequence to see if it can be further collapsed.
79 Return zero if not, otherwise return the number of names in the sequence. */
80
81 static int
collapsible(char * names)82 collapsible(char *names)
83 {
84 char *p = names;
85 int dots = 0, n = 0;
86
87 while (*p) {
88 if ((p[0] == '.') && ((p[1] == '\0')
89 || (p[1] == '/')
90 || ((p[1] == '.') && ((p[2] == '\0')
91 || (p[2] == '/'))))) {
92 dots = 1;
93 }
94 n++;
95 while (*p) {
96 if (*p == '/') {
97 // Android-changed: Remove consecutive duplicate path separators "//". b/267617531
98 // p++
99 while (*p == '/') {
100 p++;
101 }
102 break;
103 }
104 p++;
105 }
106 }
107 return (dots ? n : 0);
108 }
109
110
111 /* Split the names in the given name sequence,
112 replacing slashes with nulls and filling in the given index array */
113
114 static void
splitNames(char * names,char ** ix)115 splitNames(char *names, char **ix)
116 {
117 char *p = names;
118 int i = 0;
119
120 while (*p) {
121 ix[i++] = p++;
122 while (*p) {
123 if (*p == '/') {
124 // Android-changed: Remove consecutive duplicate path separators "//". b/267617531
125 // *p++ = '\0';
126 while (*p == '/') {
127 *p++ = '\0';
128 }
129 break;
130 }
131 p++;
132 }
133 }
134 }
135
136
137 /* Join the names in the given name sequence, ignoring names whose index
138 entries have been cleared and replacing nulls with slashes as needed */
139
140 static void
joinNames(char * names,int nc,char ** ix)141 joinNames(char *names, int nc, char **ix)
142 {
143 int i;
144 char *p;
145
146 for (i = 0, p = names; i < nc; i++) {
147 if (!ix[i]) continue;
148 if (i > 0) {
149 p[-1] = '/';
150 }
151 if (p == ix[i]) {
152 p += strlen(p) + 1;
153 } else {
154 char *q = ix[i];
155 while ((*p++ = *q++));
156 }
157 }
158 *p = '\0';
159 }
160
161
162 /* Collapse "." and ".." names in the given path wherever possible.
163 A "." name may always be eliminated; a ".." name may be eliminated if it
164 follows a name that is neither "." nor "..". This is a syntactic operation
165 that performs no filesystem queries, so it should only be used to cleanup
166 after invoking the realpath() procedure. */
167
168 static void
collapse(char * path,bool isAtLeastTargetSdk35)169 collapse(char *path,
170 // Android-added: Remove parent directory /.. at the rootfs. http://b/312399441
171 bool isAtLeastTargetSdk35)
172 {
173 // Android-changed: Remove consecutive duplicate path separators "//". b/267617531
174 removeDupSeparator(path);
175
176 // Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441
177 bool isPathAbsolute = (path[0] == '/');
178 char *names = isPathAbsolute ? path + 1 : path; /* Preserve first '/' */
179 int nc;
180 char **ix;
181 int i, j;
182 // Android-removed: unused variables.
183 // char *p, *q;
184
185 nc = collapsible(names);
186 if (nc < 2) return; /* Nothing to do */
187 ix = (char **)alloca(nc * sizeof(char *));
188 splitNames(names, ix);
189
190 for (i = 0; i < nc; i++) {
191 int dots = 0;
192
193 /* Find next occurrence of "." or ".." */
194 do {
195 char *p = ix[i];
196 // Android-changed: null pointer check.
197 // if (p[0] == '.') {
198 if (p != NULL && p[0] == '.') {
199 if (p[1] == '\0') {
200 dots = 1;
201 break;
202 }
203 if ((p[1] == '.') && (p[2] == '\0')) {
204 dots = 2;
205 break;
206 }
207 }
208 i++;
209 } while (i < nc);
210 if (i >= nc) break;
211
212 /* At this point i is the index of either a "." or a "..", so take the
213 appropriate action and then continue the outer loop */
214 if (dots == 1) {
215 /* Remove this instance of "." */
216 ix[i] = 0;
217 }
218 else {
219 /* If there is a preceding name and at the rootfs, remove both that name and this
220 instance of ".."; otherwise, leave the ".." as is */
221 for (j = i - 1; j >= 0; j--) {
222 if (ix[j]) break;
223 }
224 if (j < 0) {
225 // Android-added: Remove parent directory /.. at the rootfs. http://b/312399441
226 if (isPathAbsolute && isAtLeastTargetSdk35) {
227 ix[i] = 0;
228 }
229 continue;
230 }
231 ix[j] = 0;
232 ix[i] = 0;
233 }
234 /* i will be incremented at the top of the loop */
235 }
236
237 joinNames(names, nc, ix);
238 }
239
240
241 /* Convert a pathname to canonical form. The input path is assumed to contain
242 no duplicate slashes. On Solaris we can use realpath() to do most of the
243 work, though once that's done we still must collapse any remaining "." and
244 ".." names by hand. */
245
246 // Android-changed: hidden to avoid conflict with libm (b/135018555)
247 __attribute__((visibility("hidden")))
248 int
canonicalize(char * original,char * resolved,int len,bool isAtLeastTargetSdk35)249 canonicalize(char *original, char *resolved, int len,
250 // Android-added: Remove parent directory /.. at the rootfs. http://b/312399441
251 bool isAtLeastTargetSdk35)
252 {
253 if (len < PATH_MAX) {
254 errno = EINVAL;
255 return -1;
256 }
257
258 // Android-changed: Avoid crash in getCanonicalPath() due to a long path. b/266432364
259 // if (strlen(original) > PATH_MAX) {
260 if (strlen(original) >= PATH_MAX) {
261 errno = ENAMETOOLONG;
262 return -1;
263 }
264
265 /* First try realpath() on the entire path */
266 if (realpath(original, resolved)) {
267 /* That worked, so return it */
268 // Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441
269 // collapse(resolved);
270 collapse(resolved, isAtLeastTargetSdk35);
271 return 0;
272 }
273 else {
274 // Android-changed: Avoid crash in getCanonicalPath(). b/266432364
275 if (errno == EINVAL || errno == ELOOP || errno == ENAMETOOLONG || errno == ENOMEM) {
276 return -1;
277 }
278
279 /* Something's bogus in the original path, so remove names from the end
280 until either some subpath works or we run out of names */
281 char *p, *end, *r = NULL;
282 // Android-changed: Avoid crash in getCanonicalPath() due to a long path. b/266432364
283 char path[PATH_MAX];
284
285 strncpy(path, original, sizeof(path));
286 // Android-changed: Avoid crash in getCanonicalPath() due to a long path. b/266432364
287 if (path[PATH_MAX - 1] != '\0') {
288 errno = ENAMETOOLONG;
289 return -1;
290 }
291 end = path + strlen(path);
292
293 for (p = end; p > path;) {
294
295 /* Skip last element */
296 while ((--p > path) && (*p != '/'));
297 if (p == path) break;
298
299 /* Try realpath() on this subpath */
300 *p = '\0';
301 r = realpath(path, resolved);
302 *p = (p == end) ? '\0' : '/';
303
304 if (r != NULL) {
305 /* The subpath has a canonical path */
306 break;
307 }
308 // Android-changed: Added ENOTCONN case (b/26645585, b/26070583)
309 else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES || errno == ENOTCONN) {
310 /* If the lookup of a particular subpath fails because the file
311 does not exist, because it is of the wrong type, or because
312 access is denied, then remove its last name and try again.
313 Other I/O problems cause an error return. */
314
315 /* NOTE: ENOTCONN seems like an odd errno to expect, but this is
316 the behaviour on linux for fuse filesystems when the fuse device
317 associated with the FS is closed but the filesystem is not
318 unmounted. */
319 continue;
320 }
321 else {
322 return -1;
323 }
324 }
325
326 size_t nameMax;
327 if (r != NULL) {
328 /* Append unresolved subpath to resolved subpath */
329 int rn = strlen(r);
330 if (rn + (int)strlen(p) >= len) {
331 /* Buffer overflow */
332 errno = ENAMETOOLONG;
333 return -1;
334 }
335
336 // Android-changed: Avoid crash in getCanonicalPath() due to a long path. b/266432364
337 nameMax = pathconf(r, _PC_NAME_MAX);
338
339 if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) {
340 /* Avoid duplicate slashes */
341 p++;
342 }
343 strcpy(r + rn, p);
344 // Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441
345 // collapse(r);
346 collapse(r, isAtLeastTargetSdk35);
347 }
348 else {
349 /* Nothing resolved, so just return the original path */
350 // Android-changed: Avoid crash in getCanonicalPath() due to a long path. b/266432364
351 nameMax = pathconf("/", _PC_NAME_MAX);
352 strcpy(resolved, path);
353 // Android-changed: Remove parent directory /.. at the rootfs. http://b/312399441
354 // collapse(resolved);
355 collapse(resolved, isAtLeastTargetSdk35);
356 }
357
358 // BEGIN Android-added: Avoid crash in getCanonicalPath() due to a long path. b/266432364
359 // Ensure resolve path length is "< PATH_MAX" and collapse() did not overwrite
360 // terminating null byte
361 char resolvedPath[PATH_MAX];
362 strncpy(resolvedPath, resolved, sizeof(resolvedPath));
363 if (resolvedPath[PATH_MAX - 1] != '\0') {
364 errno = ENAMETOOLONG;
365 return -1;
366 }
367
368 // Ensure resolve path does not contain any components who length is "> NAME_MAX"
369 // If pathconf call failed with -1 or returned 0 in case of permission denial
370 if (nameMax < 1) {
371 nameMax = NAME_MAX;
372 }
373
374 char *component;
375 char *rest = resolvedPath;
376 while ((component = strtok_r(rest, "/", &rest))) {
377 if (strlen(component) > nameMax) {
378 errno = ENAMETOOLONG;
379 return -1;
380 }
381 }
382
383 return 0;
384 // END Android-added: Avoid crash in getCanonicalPath() due to a long path. b/266432364
385 }
386
387 }
388