Как определить, находится ли путь внутри каталога? (POSIX) - PullRequest
6 голосов
/ 21 августа 2011

В C, используя вызовы POSIX, как я могу определить, находится ли путь внутри целевого каталога?

Например, веб-сервер имеет корневой каталог в /srv, для демона это getcwd(). При анализе запроса на /index.html возвращается содержимое /srv/index.html.

Как отфильтровать запросы на пути за пределами /srv?

/../etc/passwd, /valid/../../etc/passwd, и т.д.

Разделение пути на / и отклонение любого массива, содержащего .., прервет допустимый доступ /srv/valid/../index.html.

Есть ли канонический способ сделать это с помощью системных вызовов? Или мне нужно вручную пройти путь и посчитать глубину каталога?

Ответы [ 3 ]

6 голосов
/ 21 августа 2011

Всегда есть realpath:

Функция realpath () должна извлекать из пути, на который указывает * file_name *, абсолютный путь, который разрешает ту же запись каталога, разрешение которой не включает «.» , '..', или символические ссылки.

Затем сравните то, что дает realpath, с нужным корневым каталогом и посмотрите, совпадают ли они.

Вы также можете очистить имя файла вручную, расширив двойные точки перед тем, как добавить "/srv". Разбейте входящий путь на слэши и пройдитесь по нему по частям. Если вы получите ".", тогда снимите его и продолжайте; если вы получите "..", то удалите его и предыдущий компонент (следя за тем, чтобы не пройти мимо первой записи в вашем списке); если вы получите что-то еще, просто перейдите к следующему компоненту. Затем вставьте то, что осталось, вместе с косой чертой между компонентами и добавьте "/srv/". Поэтому, если кто-то даст вам "/valid/../../etc/passwd", вы получите "/srv/etc/passwd", а "/where/is/../pancakes/house" - "/srv/where/pancakes/house".

Таким образом, вы не сможете выйти наружу "/srv" (кроме как через символические ссылки, конечно), и входящий "/../.." будет таким же, как "/" (как в обычной файловой системе). Но вы все равно хотите использовать realpath, если вы беспокоитесь о символике в "/srv".

Работа с компонентом имени пути по компонентам также позволит вам разорвать связь между макетом, который вы представляете внешнему миру, и фактическим макетом файловой системы; "/this/that/other/thing" не нужно отображать где-либо на фактический файл "/srv/this/that/other/thing", путь может быть просто ключом в какой-то базе данных или каким-либо путем к пространству имен для вызова функции.

2 голосов
/ 01 сентября 2011

Чтобы определить, находится ли файл F в каталоге D, сначала stat D, чтобы определить номер его устройства и номер inode (члены st_dev и st_ino из struct stat).

Затем stat F, чтобы определить, является ли это каталогом. Если нет, вызовите basename, чтобы определить имя каталога, в котором он находится. Установите G на имя этого каталога. Если F уже был каталогом, установите G = F.

Теперь, F находится в D, если и только если G в D. Затем у нас есть цикл.

while (1) {
  if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) {
    return 1; // F was within D
  } else if (0 == strcmp("/", G) {
    return 0; // F was not within D.
  }
  G = dirname(G);
}

Функция samefile проста:

int samefile(dev_t ddev, ino_t dino, const char *path) {
  struct stat st;
  if (0 == stat(path, &st)) {
    return ddev == st.st_dev && dino == st.st_no;
  } else {
    throw ...; // or return error value (but also change the caller to detect it)
  }
}

Это будет работать на файловых системах POSIX. Но многие файловые системы не POSIX. Проблемы, на которые стоит обратить внимание:

  1. Файловые системы, в которых устройство / индекс не являются уникальными. Некоторые файловые системы FUSE являются примерами этого; они иногда составляют номера инодов, когда базовые файловые системы их не имеют. Они не должны повторно использовать номера инодов, но в некоторых файловых системах FUSE есть ошибки.
  2. Сломанные реализации NFS. В некоторых системах все файловые системы NFS имеют одинаковый номер устройства. Если они проходят через номер инода, как он существует на сервере, это может вызвать проблему (хотя я никогда не видел, чтобы это происходило на практике).
  3. Точки монтирования Linux. Если /a - это связывающее монтирование /b, то /a/1 правильно отображается внутри /a, но с реализацией выше, /b/1 также представляется внутри /a. Я думаю, что это, вероятно, правильный ответ. Однако, если это не тот результат, который вы предпочитаете, это легко исправить, изменив регистр return 1 на вызов strcmp(), чтобы сравнить и пути. Однако, чтобы это работало, вам нужно начать с вызова realpath на F и D. Вызов realpath может быть довольно дорогим (поскольку может потребоваться несколько раз ударить по диску).
  4. Особый путь //foo/bar. POSIX позволяет именам путей, начинающимся с //, быть особенными, что не совсем точно определено. На самом деле я забыл точный уровень гарантии семантики, предоставляемой POSIX. Я думаю, что POSIX позволяет //foo/bar и //baz/ugh ссылаться на один и тот же файл. Проверка устройства / индекса должна по-прежнему выполнять правильную функцию, но вы можете обнаружить, что это не так (т. Е. Вы можете обнаружить, что //foo/bar и //baz/ugh могут ссылаться на один и тот же файл, но иметь разные номера устройства / индекса).

В этом ответе предполагается, что мы начинаем с абсолютного пути для F и D. Если это не гарантировано, вам может потребоваться выполнить какое-то преобразование, используя realpath() и getcwd(). Это будет проблемой, если имя текущего каталога длиннее PATH_MAX (что, безусловно, может произойти).

0 голосов
/ 21 августа 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...