Переопределить функции libc, вызываемые из другой функции libc, с помощью LD_PRELOAD - PullRequest
5 голосов
/ 30 сентября 2011

У меня есть проект, целью которого является запуск php-cgi chrooted для массового виртуального хостинга (более 10 тыс. Виртуальных хостов), причем каждый виртуальный хост имеет свой собственный chroot под Ubuntu Lucid x86_64.

Я бы хотел избежать создания необходимой среды внутри каждого chroot для таких вещей, как / dev / null, / dev / zero, locales, icons ... и всего, что может понадобиться модулям php, считая, что они работают вне chroot.

Цель состоит в том, чтобы php-cgi работал внутри chroot, но предоставил ему доступ к файлам вне chroot , если эти файлы (для большинства из них) открыты в режиме чтения.только режим и в разрешенном списке (/ dev / log, / dev / zero, / dev / null, путь к локали ...)

Кажется очевидным, что способ создать (или использовать, если он существует)) модуль ядра, который может перехватывать и перенаправлять доверенные пути open () вне chroot.Но я не думаю, что это самый простой способ:

  • Я никогда не делал модуль ядра, поэтому неправильно оцениваю сложность.
  • Кажется, что есть несколько системных вызововперехватить файл "open" (open, connect, mmap ...), но я думаю, что есть общая функция ядра для всего, что связано с открытием файла.

Я хочу минимизировать количествопатчи к php или его модулю, чтобы свести к минимуму объем работы, необходимый каждый раз, когда я буду обновлять нашу платформу до последней стабильной версии PHP (и, таким образом, обновлять вышедшие версии PHP чаще и быстрее), поэтому я считаю, что лучше исправлять поведениеPHP извне (поскольку у нас есть особые настройки, поэтому исправление PHP и предложение исправления для upstream не имеет значения).

Вместо этого я сейчас пытаюсь найти решение для пользовательского пространства: перехватить функции libc с помощью LD_PRELOAD, который работаетв большинстве случаев это действительно быстро реализуется, но я столкнулся с проблемой, которую не могу решить в одиночку.(Идея состоит в том, чтобы поговорить с демоном, работающим вне chroot, и получить от него дескриптор файла, используя ioctl SENDFD и RECVFD).

Когда я вызываю syslog () (сначала без openlog ()), syslog () вызывает метод connect (), чтобы открыть файл .

Пример:

folays@phenix:~/ldpreload$ strace logger test 2>&1 | grep connect
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0

Пока все хорошо, я пытался подключить функцию connect () в libc, безуспешно.Я также попытался установить некоторые флаги для dlopen () внутри функции _init () моей библиотеки предварительной загрузки, чтобы проверить, могут ли некоторые из них выполнить эту работу без успеха

Вот соответствующий код моей предварительной загрузкибиблиотека:

void __attribute__((constructor)) my_init(void)
{
  printf("INIT preloadz %s\n", __progname);
  dlopen(getenv("LD_PRELOAD"), RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL |
                               RTLD_NOW);
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  printf("HOOKED connect\n");
  int (*f)() = dlsym(RTLD_NEXT, "connect");
  int ret = f(sockfd, addr, addrlen);
  return ret;
}

int __connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  printf("HOOKED __connect\n");
  int (*f)() = dlsym(RTLD_NEXT, "connect");
  int ret = f(sockfd, addr, addrlen);
  return ret;
}

Но функция connect () в libc по-прежнему имеет приоритет над моей:

folays@phenix:~/ldpreload$ LD_PRELOAD=./lib-preload.so logger test
INIT preloadz logger
[...] no lines with "HOOKED connect..." [...]
folays@phenix:~/ldpreload$

Глядя на код syslog () (apt-get source libc6,glibc-2.13 / misc / syslog.c), кажется, он вызывает openlog_internal, который, в свою очередь, вызывает __connect (), в строке 386 misc / syslog.c:

            if (LogFile != -1 && !connected)
            {
                    int old_errno = errno;
                    if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
                        == -1)
                    {

Ну, objdump показывает мне соединение и__connect в таблице динамических символов libc:

folays@phenix:~/ldpreload$ objdump -T /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 connect
00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 __connect

Но в записях динамического перемещения нет символа connect, поэтому я предполагаю, что это объясняет, почему я не могу успешно переопределить метод connect (), используемый openlog_internal (), онвероятно, не использует динамическое перемещение символов и, вероятно, имеет адрес функции __connect () в hard (относительное смещение -fPIC?).

folays@phenix:~/ldpreload$ objdump -R /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
folays@phenix:~/ldpreload$ 

connect - это слабый псевдоним __connect:

 eglibc-2.13/socket/connect.c:weak_alias (__connect, connect)

gdb по-прежнему может использовать точку останова на символе libc connect библиотеки libc:

folays@phenix:~/ldpreload$ gdb logger
(gdb) b connect
Breakpoint 1 at 0x400dc8
(gdb) r test
Starting program: /usr/bin/logger 

Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
        in ../sysdeps/unix/syscall-template.S
(gdb) c 2
Will ignore next crossing of breakpoint 1.  Continuing.

Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) bt
#0  connect () at ../sysdeps/unix/syscall-template.S:82
#1  0x00007ffff7b28974 in openlog_internal (ident=<value optimized out>, logstat=<value optimized out>, logfac=<value optimized out>) at ../misc/syslog.c:386
#2  0x00007ffff7b29187 in __vsyslog_chk (pri=<value optimized out>, flag=1, fmt=0x40198e "%s", ap=0x7fffffffdd40) at ../misc/syslog.c:274
#3  0x00007ffff7b293af in __syslog_chk (pri=<value optimized out>, flag=<value optimized out>, fmt=<value optimized out>) at ../misc/syslog.c:131

Конечно, я мог бы полностью пропустить эту конкретную проблему, выполнив openlog () сам, но я думаю,что я столкнусь с тем же типом проблемы с некоторыми другими функциями.

Я не очень понимаю, почему openlog_internal не использует динамическое перемещение символов для вызова __connect (), и если вообще возможно перехватить это __connect () вызов с использованием простого механизма LD_PRELOAD.

Другие способы, которыми я вижу, как это можно сделать:

  • Загрузите libc.so из LD_PRELOAD с помощью dlopen, получите адрес __connect библиотеки libc с помощью dlsym (), а затем исправьте функцию (в соответствии с ASM), чтобы заставить работать ловушку. Это кажется излишним и склонным к ошибкам.
  • Используйте измененный пользовательский libc для PHP, чтобы исправить эти проблемы непосредственно у источника (функции open / connect / mmap ...)
  • Код LKM, чтобы перенаправить доступ к файлу, где я хочу. Плюсы: нет необходимости в ioctl (SENDFD) и нет демона вне chroot.

Я был бы очень признателен, если бы это было возможно, узнать, как я мог бы перехватить вызов __connect (), выданный openlog_internal, предложениями или ссылками на документацию ядра, связанную с перехватом syscall и перенаправлением.

Мой поиск в Google, связанный с "системными вызовами хуков", обнаружил множество ссылок на LSM , но, похоже, он позволяет только спискам ACL отвечать "да" или "нет", но перенаправления путей open () нет.

Спасибо за чтение.

1 Ответ

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

Это определенно невозможно с LD_PRELOAD без создания собственного сильно модифицированного libc, и в этом случае вы могли бы просто поместить хаки перенаправления прямо внутрь. Не обязательно звонить на open, connect и т. Д. Вообще. Вместо этого могут быть вызовы аналогичной скрытой функции, связанной во время создания библиотеки (не динамически перебираемой) или даже встроенные системные вызовы, и это, конечно, может непредсказуемо измениться с версией.

Ваши варианты - это либо модуль ядра, либо, возможно, использование ptrace для всего, что находится внутри "chroot", и изменение аргументов для системных вызовов всякий раз, когда процесс трассировки встречает тот, который требует исправления. Ни то, ни другое не звучит просто ...

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

...