Как преобразовать относительный путь в абсолютный путь в C?

Я пытаюсь создать приложение suid, которое будет выполнять только скрипты ruby, расположенные в ограниченной папке. Я попытался сделать это с помощью realpath(3), но он возвращает только первый сегмент пути. Ниже мой код...

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#define SUEXEC_STR_LEN 2048
#define RUBY_APP "/usr/bin/ruby"
#define DIRECTORY_SEPARATOR "/"

static void safepath(const char *path_in, char * path_out, int outlen) {
    realpath(path_in, path_out);
}

int main ( int argc, char *argv[] )
{
    char cmd[SUEXEC_STR_LEN];
    char path_out[SUEXEC_STR_LEN];
    char path_in[SUEXEC_STR_LEN];

    char *cp = &cmd[0];

    strncpy(cp, RUBY_APP, SUEXEC_STR_LEN - 1);

    strncpy(path_in, DIRECTORY_SEPARATOR, SUEXEC_STR_LEN - 1);
    strncat(path_in,argv[1],SUEXEC_STR_LEN - 1);

    safepath(path_in,path_out,SUEXEC_STR_LEN - 1);

    printf("path_in=%s path_out=%s\n",path_in,path_out);

    setuid( 0 );
    // system( cmd );

    return 0;
}

Это пример результата, который я получаю

root@server01:/root/src# ./a.out foo/bar/../test
path_in=/foo/bar/../test path_out=/foo

Это результат, который я хочу

root@server01:/root/src# ./a.out foo/bar/../test
path_in=/foo/bar/../test path_out=/foo/test

3 ответа

Вы должны проверить realpath()возвращаемое значение. Как описано в его справочной странице,

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Если ошибки нет, realpath() возвращает указатель на resolved_path.

В противном случае он возвращает указатель NULL, а содержимое массива resolved_path не определено. Глобальная переменная errno установлена ​​для указания ошибки.

Также в разделе ОШИБКИ его справочной страницы,

ENOENT Именованный файл не существует.

Таким образом, если действительно нет /foo/test в вашей файловой системе, realpath() должен вернуться NULL и вывод не определен.

Итак, вот рабочий набросок того, как вы можете сделать это в C на Linux. Это быстрый взлом, который я не представляю как примерный код, эффективный и т. Д. Он (ab) использует PATH_MAX, использует "плохие" строковые функции и может привести к утечке памяти, съесть вашу кошку и иметь угловые случаи, которые приводят к сбою и т. д. Когда он ломается, вы можете оставить обе части.

Основная идея состоит в том, чтобы пройти по заданному пути, разбив его на "слова", используя "/" в качестве разделителя. Затем просмотрите список, помещая "слова" в стек, но игнорируя, если он пуст или ".", И выталкивая, если "..", затем сериализуя стек, начиная с нижней части и накапливая строку с косыми чертами между,

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <linux/limits.h>

typedef struct stack_s {
    char *data[PATH_MAX];
    int   top;
} stack_s;

void stack_push(stack_s *s, char *c) {
    s->data[s->top++] = c;
}

char *stack_pop(stack_s *s) {
    if( s->top <= 0 ) {
        return NULL;
    }
    s->top--;
    return s->data[s->top];
}

// DANGER! DANGER! Returns malloc()ed pointer that you must free()
char *stack_serialize(stack_s *s) {
    int i;
    char *buf;
    int len=1;

    for(i=0; i<s->top; i++) {
        len += strlen(s->data[i]);
        len++; // For a slash
    }
    buf = malloc(len);
    *buf = '\0';
    for(i=0; i<s->top-1; i++) {
        strcat(buf, s->data[i]);
        strcat(buf, "/");
    }
    strcat(buf, s->data[i]);
    return buf;
}

// DANGER! DANGER! Returns malloc()ed pointer that you must free()
char *semicanonicalize(char *src) {
    char *word[PATH_MAX] = {NULL};
    int   w=0;
    int   n_words;

    char *buf;
    int   len;
    char *p, *q;

    stack_s dir_stack = {{NULL},0};

    // Make a copy of the input string:
    len = strlen(src);
    buf = strdup(src);

    // Replace slashes with NULs and record the start of each "word"
    q = buf+len;
    word[0]=buf;
    for(p=buf,w=0; p<q; p++) {
        if(*p=='/') {
            *p = '\0';
            word[++w] = p+1;
        }
    }
    n_words=w+1;

    // We push w[0] unconditionally to preserve slashes and dots at the
    // start of the source path:
    stack_push(&dir_stack, word[0]);

    for(w=1; w<n_words; w++) {
        len = strlen(word[w]);
        if( len == 0 ) {
            // Must've hit a double slash
            continue;
        }
        if( *word[w] == '.' ) {
            if( len == 1 ) {
                // Must've hit a dot
                continue;
            }
            if( len == 2 && *(word[w]+1)=='.' ) {
                // Must've hit a '..'
                (void)stack_pop(&dir_stack);
                continue;
            }
        }
        // If we get to here, the current "word" isn't "", ".", or "..", so
        // we push it on the stack:
        stack_push(&dir_stack, word[w]);
    }

    p = stack_serialize(&dir_stack);
    free(buf);
    return p;
}


int main(void)
{
    char *in[] = { "/home/emmet/../foo//./bar/quux/../.",
                   "../home/emmet/../foo//./bar/quux/../.",
                   "./home/emmet/../foo//./bar/quux/../.",
                   "home/emmet/../foo//./bar/quux/../."
    };
    char *out;
    for(int i=0; i<4; i++) {
        out = semicanonicalize(in[i]);
        printf("%s \t->\t %s\n", in[i], out);
        free(out);
    }
    return 0;
}

Это код, который я использовал в качестве решения проблемы. В нем могут остаться некоторые ошибки, и он не проверяет выходной аргумент, чтобы избежать ошибок и других уродливостей, но, похоже, выполняет свою работу.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <linux/limits.h>

#define SUEXEC_STR_LEN 2048
#define RUBY_APP "/usr/bin/ruby"
#define DIRECTORY_SEPARATOR "/"
#define RUBY_EXT ".rb"

#define SERVICES_BASE_PATH "/path/to/ruby/services"

static inline int isDirSeparator(const char c) { return (c == '/' || c == '\\'); }

static void safepath(const char *path_in, char * path_out, int outlen)
{
    char *dirs[PATH_MAX];
    int depth = 0;
    char *dstptr = path_out;
    const char *srcptr = path_in;

    *dstptr++ = DIRECTORY_SEPARATOR[0];
    dirs[0] = dstptr;
    dirs[1] = NULL;
    depth++;

    while (1) {
        if ((srcptr[0] == '.') && isDirSeparator(srcptr[1])) {
            srcptr += 2;
        } else if (srcptr[0] == '.' && srcptr[1] == '.' && isDirSeparator(srcptr[2])) {
            if (depth > 1) {
                dirs[depth] = NULL;
                depth--;
                dstptr = dirs[depth-1];
            } else {
                dstptr = dirs[0];
            }
            srcptr += 3;
        } else if (srcptr[0] == '.' && srcptr[1] == '.' && srcptr[2] == 0) {
            if (depth == 1) {
                srcptr += 2;
            } else {
                depth--;
                dstptr = dirs[depth-1];
                srcptr += 2;
            }
        } else {
            while (!isDirSeparator(srcptr[0]) && srcptr[0]) {
                *dstptr++ = *srcptr++;
            }
            if (srcptr[0] == 0) {
                if (dstptr != dirs[0] && isDirSeparator(dstptr[-1])) {
                    dstptr[-1] = 0;
                }
                dstptr[0] = 0;
                return;
            } else if (isDirSeparator(srcptr[0])) {
                if (dstptr == dirs[0]) {
                    srcptr++;
                } else {
                    *dstptr++ = *srcptr++;
                    dirs[depth] = dstptr;
                    depth++;
                }
                while (isDirSeparator(srcptr[0]) && srcptr[0]) {
                    srcptr++;
                }
            } else {
                path_out[0] = 0;
                return;
            }
        }
    }
}

int main ( int argc, char *argv[] )
{
    int ret;
    char cmd[SUEXEC_STR_LEN];
    char path_out[SUEXEC_STR_LEN];
    char path_in[SUEXEC_STR_LEN];

    char *cp = &cmd[0];


    if (argc < 2) {
        fprintf(stderr,"usage: %s <service>\n",argv[0]);
        return 1;
    }
    strncpy(cp, RUBY_APP, SUEXEC_STR_LEN - 1);

    strncpy(path_in, DIRECTORY_SEPARATOR, SUEXEC_STR_LEN - 1);
    strncat(path_in,argv[1],SUEXEC_STR_LEN - 1);

    safepath(path_in,path_out,SUEXEC_STR_LEN - 1);

    //printf("path_in=%s path_out=%s\n",path_in,path_out);

    strncat(cmd," ",SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));

    strncat(cmd,SERVICES_BASE_PATH,SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));
    strncat(cmd,path_out,SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));
    strncat(cmd,RUBY_EXT,SUEXEC_STR_LEN - 1);

    setuid( 0 );
    ret = system( cmd );
    if (ret == -1) {
        return ret;
    }
    ret =  WEXITSTATUS(ret);
    return ret;
}
Другие вопросы по тегам