Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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.