Как преобразовать относительный путь в абсолютный путь в 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;
}