|
|
Message-ID: <97dd3cf7c69673e5962e9ccd46ea5131@ispras.ru>
Date: Mon, 23 Nov 2020 05:03:25 +0300
From: Alexey Izbyshev <izbyshev@...ras.ru>
To: musl@...ts.openwall.com
Subject: Re: realpath without procfs -- should be ready for inclusion
On 2020-11-23 01:56, Rich Felker wrote:
> I originally considered keeping the procfs based version and only
> using the new one as a fallback, but I discovered there are cases
> (involving chroot, namespaces, etc.) where the answer from procfs is
> wrong and validating it requires basically the same procedure as
> implementing it manually (walking and performing readlink on each path
> component).
>
Pity that the simple and fast procfs-based implementation goes away. Do
you have any specific example of a wrong answer from procfs at hand, or
at least a more specific direction to look at than just
"chroot/namespaces"?
> #define _GNU_SOURCE
> #include <stdlib.h>
> #include <limits.h>
> #include <errno.h>
> #include <unistd.h>
> #include <string.h>
>
> static inline int at_dotdot(const char *end, size_t len)
> {
> if (len<2) return 0;
> if (len>2 && end[-3]!='/') return 0;
> return end[-1]=='.' && end[-2]=='.';
> }
>
> char *realpath(const char *restrict filename, char *restrict resolved)
> {
> char stack[PATH_MAX];
> char buf[resolved ? 1 : PATH_MAX];
> char *output = resolved ? resolved : buf;
> size_t p, q, l, cnt=0;
>
> l = strnlen(filename, sizeof stack + 1);
Why + 1?
> if (!l) {
> errno = ENOENT;
> return 0;
> }
> if (l >= sizeof stack) goto toolong;
> p = sizeof stack - l - 1;
> q = 0;
> memcpy(stack+p, filename, l+1);
>
> while (stack[p]) {
> int up = 0;
> if (stack[p] == '/') {
> q=0;
> output[q++] = '/';
> p++;
> /* Initial // is special. */
> if (stack[p] == '/' && stack[p+1] != '/') {
> output[q++] = '/';
> }
> while (stack[p] == '/') p++;
> continue;
> }
> char *z = __strchrnul(stack+p, '/');
> l = z-(stack+p);
> if (l==1 && stack[p]=='.') {
> p += l;
> while (stack[p] == '/') p++;
> continue;
> }
> if (at_dotdot(stack+p+l, l)) {
> if (q && !at_dotdot(output+q, q)) {
> while(q && output[q-1]!='/') q--;
> if (q>1 && (q>2 || output[0]!='/')) q--;
> p += l;
> while (stack[p] == '/') p++;
> continue;
> }
> up = 1;
> }
> if (q && output[q-1] != '/') {
> if (!p) goto toolong;
> stack[--p] = '/';
> l++;
> }
> if (q+l >= PATH_MAX) goto toolong;
> memcpy(output+q, stack+p, l);
> output[q+l] = 0;
> p += l;
> if (up) goto notlink;
> ssize_t k = readlink(output, stack, p);
> if (k==-1) {
> if (errno == EINVAL) {
> notlink:
> q += l;
> while (stack[p] == '/') p++;
> continue;
> }
> return 0;
> }
> if (k==p) goto toolong;
> if (++cnt == SYMLOOP_MAX) {
> errno = ELOOP;
> return 0;
> }
> p -= k;
> memmove(stack+p, stack, k);
This isn't always correct if the symlink resolves to "/" because
stack[p] might be '/', so two slashes will be preserved in the output.
For example, "root/home" resolves to "//home" (where "root" -> "/").
> }
>
> output[q] = 0;
>
> if (output[0] != '/') {
> if (!getcwd(stack, sizeof stack)) return 0;
> l = strlen(stack);
> /* Cancel any initial .. components. */
> p = 0;
> while (q-p>=2 && at_dotdot(output+p+2, p+2)) {
This doesn't check that output+p+2 is the end of a path element, which
is the prerequisite for at_dotdot(). So, for example, "..." resolves
incorrectly.
> while(l>1 && stack[l-1]!='/') l--;
> if (l>1) l--;
> p += 2;
> if (p<q) p++;
> }
> if (q-p && stack[l-1]!='/') output[--p] = '/';
> if (l + (q-p) + 1 >= PATH_MAX) goto toolong;
> memmove(output + l, output + p, q - p + 1);
> memcpy(output, stack, l);
> }
>
> return resolved ? resolved : strdup(output);
>
> toolong:
> errno = ENAMETOOLONG;
> return 0;
> }
>
Alexey
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.