xref: /openbsd-src/lib/libc/stdlib/realpath.c (revision 47ebaa3eeca22f2628945bf28e386658126fb0b6)
1 /*	$OpenBSD: realpath.c,v 1.25 2019/05/30 13:22:48 deraadt Exp $ */
2 /*
3  * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The names of the authors may not be used to endorse or promote
14  *    products derived from this software without specific prior written
15  *    permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <limits.h>
35 #include <syslog.h>
36 #include <stdarg.h>
37 
38 /* A slightly modified copy of this file exists in libexec/ld.so */
39 
40 /*
41  * char *realpath(const char *path, char resolved[PATH_MAX]);
42  *
43  * Find the real name of path, by removing all ".", ".." and symlink
44  * components.  Returns (resolved) on success, or (NULL) on failure,
45  * in which case the path which caused trouble is left in (resolved).
46  */
47 static char *
48 urealpath(const char *path, char *resolved)
49 {
50 	const char *p;
51 	char *q;
52 	size_t left_len, resolved_len, next_token_len;
53 	unsigned symlinks;
54 	int serrno, mem_allocated;
55 	ssize_t slen;
56 	int trailingslash = 0;
57 	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
58 
59 	if (path == NULL) {
60 		errno = EINVAL;
61 		return (NULL);
62 	}
63 
64 	if (path[0] == '\0') {
65 		errno = ENOENT;
66 		return (NULL);
67 	}
68 
69 	serrno = errno;
70 
71 	if (resolved == NULL) {
72 		resolved = malloc(PATH_MAX);
73 		if (resolved == NULL)
74 			return (NULL);
75 		mem_allocated = 1;
76 	} else
77 		mem_allocated = 0;
78 
79 	symlinks = 0;
80 	if (path[0] == '/') {
81 		resolved[0] = '/';
82 		resolved[1] = '\0';
83 		if (path[1] == '\0')
84 			return (resolved);
85 		resolved_len = 1;
86 		left_len = strlcpy(left, path + 1, sizeof(left));
87 	} else {
88 		if (getcwd(resolved, PATH_MAX) == NULL) {
89 			if (mem_allocated)
90 				free(resolved);
91 			else
92 				strlcpy(resolved, ".", PATH_MAX);
93 			return (NULL);
94 		}
95 		resolved_len = strlen(resolved);
96 		left_len = strlcpy(left, path, sizeof(left));
97 	}
98 	if (left_len >= sizeof(left)) {
99 		errno = ENAMETOOLONG;
100 		goto err;
101 	}
102 
103 	/*
104 	 * Iterate over path components in `left'.
105 	 */
106 	while (left_len != 0) {
107 		/*
108 		 * Extract the next path component and adjust `left'
109 		 * and its length.
110 		 */
111 		p = strchr(left, '/');
112 
113 		next_token_len = p ? (size_t) (p - left) : left_len;
114 		memcpy(next_token, left, next_token_len);
115 		next_token[next_token_len] = '\0';
116 
117 		if (p != NULL) {
118 			left_len -= next_token_len + 1;
119 			memmove(left, p + 1, left_len + 1);
120 		} else {
121 			left[0] = '\0';
122 			left_len = 0;
123 		}
124 
125 		if (resolved[resolved_len - 1] != '/') {
126 			if (resolved_len + 1 >= PATH_MAX) {
127 				errno = ENAMETOOLONG;
128 				goto err;
129 			}
130 			resolved[resolved_len++] = '/';
131 			resolved[resolved_len] = '\0';
132 		}
133 		if (next_token[0] == '\0')
134 			continue;
135 		else if (strcmp(next_token, ".") == 0)
136 			continue;
137 		else if (strcmp(next_token, "..") == 0) {
138 			/*
139 			 * Strip the last path component except when we have
140 			 * single "/"
141 			 */
142 			if (resolved_len > 1) {
143 				resolved[resolved_len - 1] = '\0';
144 				q = strrchr(resolved, '/') + 1;
145 				*q = '\0';
146 				resolved_len = q - resolved;
147 			}
148 			continue;
149 		}
150 
151 		/*
152 		 * Append the next path component and readlink() it. If
153 		 * readlink() fails we still can return successfully if
154 		 * it exists but isn't a symlink, or if there are no more
155 		 * path components left.
156 		 */
157 		resolved_len = strlcat(resolved, next_token, PATH_MAX);
158 		if (resolved_len >= PATH_MAX) {
159 			errno = ENAMETOOLONG;
160 			goto err;
161 		}
162 		slen = readlink(resolved, symlink, sizeof(symlink));
163 		if (slen < 0) {
164 			switch (errno) {
165 			case EINVAL:
166 				/* not a symlink, continue to next component */
167 				continue;
168 			case ENOENT:
169 				if (p == NULL) {
170 					errno = serrno;
171 					return (resolved);
172 				}
173 				/* FALLTHROUGH */
174 			default:
175 				goto err;
176 			}
177 		} else if (slen == 0) {
178 			errno = EINVAL;
179 			goto err;
180 		} else if (slen == sizeof(symlink)) {
181 			errno = ENAMETOOLONG;
182 			goto err;
183 		} else {
184 			if (symlinks++ > SYMLOOP_MAX) {
185 				errno = ELOOP;
186 				goto err;
187 			}
188 
189 			symlink[slen] = '\0';
190 			if (symlink[0] == '/') {
191 				resolved[1] = 0;
192 				resolved_len = 1;
193 			} else {
194 				/* Strip the last path component. */
195 				q = strrchr(resolved, '/') + 1;
196 				*q = '\0';
197 				resolved_len = q - resolved;
198 			}
199 
200 			/*
201 			 * If there are any path components left, then
202 			 * append them to symlink. The result is placed
203 			 * in `left'.
204 			 */
205 			if (p != NULL) {
206 				if (symlink[slen - 1] != '/') {
207 					if (slen + 1 >= sizeof(symlink)) {
208 						errno = ENAMETOOLONG;
209 						goto err;
210 					}
211 					symlink[slen] = '/';
212 					symlink[slen + 1] = 0;
213 				}
214 				left_len = strlcat(symlink, left, sizeof(symlink));
215 				if (left_len >= sizeof(symlink)) {
216 					errno = ENAMETOOLONG;
217 					goto err;
218 				}
219 			}
220 			left_len = strlcpy(left, symlink, sizeof(left));
221 		}
222 	}
223 
224 	/*
225 	 * Remove trailing slash except when the resolved pathname
226 	 * is a single "/".
227 	 */
228 	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
229 		resolved[resolved_len - 1] = '\0';
230 	return (resolved);
231 
232 err:
233 	if (mem_allocated)
234 		free(resolved);
235 	return (NULL);
236 }
237 
238 /*
239  * Copyright (c) 2019 Bob Beck <beck@openbsd.org>
240  *
241  * Permission to use, copy, modify, and/or distribute this software for any
242  * purpose with or without fee is hereby granted, provided that the above
243  * copyright notice and this permission notice appear in all copies.
244  *
245  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
246  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
247  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
248  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
249  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
250  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
251  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
252  */
253 
254 int __realpath(const char *pathname, char *resolved);
255 PROTO_NORMAL(__realpath);
256 
257 /*
258  * wrapper for kernel __realpath
259  */
260 
261 char *
262 realpath(const char *path, char *resolved)
263 {
264 	char pbuf[PATH_MAX], rbuf[PATH_MAX], expected[PATH_MAX];
265 	struct syslog_data sdata = SYSLOG_DATA_INIT;
266 	int usererrno = 0, kernelerrno = 0, trailingslash = 0, save_errno;
267 	int kernelonly = (getenv("USE_KERNEL_REALPATH") != NULL);
268 	ssize_t i;
269 
270 	rbuf[0] = pbuf[0] = expected[0] = '\0';
271 
272 	if (!kernelonly) {
273 		memset(expected, 0, sizeof(expected));
274 		if (urealpath(path, expected) == NULL) {
275 			usererrno = errno;
276 			expected[0] = '\0';
277 		}
278 	}
279 
280 	if (path == NULL) {
281 		kernelerrno = EINVAL;
282 		goto out;
283 	}
284 	if (path[0] == '\0') {
285 		kernelerrno = ENOENT;
286 		goto out;
287 	}
288 	if (strlcat(pbuf, path, sizeof(pbuf)) >= sizeof(pbuf)) {
289 		kernelerrno = ENAMETOOLONG;
290 		goto out;
291 	}
292 
293 	if (pbuf[strlen(pbuf) - 1] == '/')
294 		trailingslash = 1;
295 
296 	if (__realpath(pbuf, rbuf) == -1)
297 		kernelerrno = errno;
298 
299 	/*
300 	 * XXX XXX XXX
301 	 *
302 	 * The old userland implementation strips trailing slashes.
303 	 * According to Dr. POSIX, realpathing "/bsd" should be fine,
304 	 * realpathing "/bsd/" should return ENOTDIR.
305 	 *
306 	 * Similar, but *different* to the above, The old userland
307 	 * implementation allows for realpathing "/nonexistent" but
308 	 * not "/nonexistent/", Both those should return ENOENT
309 	 * according to POSIX.
310 	 *
311 	 * This hack should go away once we decide to match POSIX.
312 	 * which we should as soon as is convenient.
313 	 */
314 	if (kernelerrno == ENOTDIR) {
315 		/* Try again without the trailing slash. */
316 		kernelerrno = 0;
317 		for (i = strlen(pbuf); i > 1 && pbuf[i - 1] == '/'; i--)
318 			pbuf[i - 1] = '\0';
319 		rbuf[0] = '\0';
320 		if (__realpath(pbuf, rbuf) == -1)
321 			kernelerrno = errno;
322 	}
323 
324 out:
325 	if (!kernelonly) {
326 		/* syslog if kernel and userland are different */
327 		save_errno = errno;
328 		if (strcmp(rbuf, expected) != 0 || (usererrno == 0 &&
329 		    kernelerrno != 0))
330 			syslog_r(LOG_CRIT | LOG_CONS, &sdata,
331 			    "realpath '%s' -> '%s' errno %d, "
332 			    "expected '%s' errno %d", path, rbuf,
333 			    kernelerrno, expected, usererrno);
334 		errno = save_errno;
335 
336 		/* use userland result */
337 		if (usererrno) {
338 			errno = usererrno;
339 			return NULL;
340 		}
341 		else
342 			errno = 0;
343 		if (resolved == NULL)
344 			resolved = strdup(expected);
345 		else if (strlcpy(resolved, expected, PATH_MAX) >= PATH_MAX) {
346 			errno = ENAMETOOLONG;
347 			return NULL;
348 		}
349 
350 	} else {
351 		/* use kernel result */
352 		if (kernelerrno) {
353 			errno = kernelerrno;
354 			return NULL;
355 		}
356 		else
357 			errno = 0;
358 		if (resolved == NULL)
359 			resolved = strdup(rbuf);
360 		else if (strlcpy(resolved, rbuf, PATH_MAX) >= PATH_MAX) {
361 			errno = ENAMETOOLONG;
362 			return NULL;
363 		}
364 	}
365 	return (resolved);
366 }
367