Как перехватить доступ к файловой системе внутри dlopen()?

Я хочу перехватить все обращения к файловой системе, происходящие внутри dlopen(). Сначала казалось, что LD_PRELOAD или -Wl,-wrap, были бы жизнеспособными решениями, но у меня возникли проблемы с их работой по некоторым техническим причинам:

  • ld.so уже сопоставил свои собственные символы к моменту обработки LD_PRELOAD. Для меня не критично перехватывать первоначальную загрузку, но _dl_* worker функции в это время разрешаются, поэтому будущие вызовы идут через них. Я думаю, что LD_PRELOAD слишком поздно.

  • Каким-то образом malloc обходит описанную выше проблему, потому что malloc() внутри ld.so не имеет функционального free(), он просто вызывает memset().

  • Рабочие функции файловой системы, например. __libc_read(), содержащиеся в ld.so, являются статическими, поэтому я не могу их перехватить с помощью -Wl,-wrap,__libc_read.

Все это может означать, что мне нужно создать свой собственный ld.so непосредственно из исходного кода, а не связывать его с оболочкой. Проблема в том, что и libc, и rtld-libc созданы из одного и того же источника. Я знаю, что макрос IS_IN_rtld определяется при построении rtld-libc, но как я могу гарантировать наличие только одной копии статических структур данных при экспорте функции открытого интерфейса? (Это вопрос системы сборки glibc, но я не нашел документации по этим деталям.)

Есть ли лучший способ попасть внутрь dlopen()?

Примечание. Я не могу использовать специфичное для Linux решение, такое как FUSE, потому что оно предназначено для ядер с минимальным «вычислительным узлом», которые не поддерживают такие вещи.


person Jed    schedule 08.10.2011    source источник
comment
Это не ответ на ваш вопрос, поэтому я не публикую его как один, но в целом вы не можете сделать это надежно: можно получить доступ к файловой системе, вызвав системный вызов напрямую, не проходя через интерфейс динамической библиотеки . Если у вас нет абсолютного контроля над тем, как была скомпилирована библиотека, которую вы пытаетесь загрузить, вам может не повезти. Такие программы, как fakeroot, использующие этот метод, в большинстве случаев работают нормально, а в некоторых ситуациях дают сбой.   -  person David Given    schedule 09.10.2011
comment
Тем не менее, вы можете заставить это работать, запустив код динамической библиотеки в отдельном процессе и используя ptrace для перехвата самих системных вызовов. Я сделал это с большим успехом, и он полностью избегает всей ерунды с общей библиотекой. Но это требует, чтобы вы полностью перепроектировали свою логику, чтобы иметь главный процесс, который выполняет функции ptrace, и подчиненный процесс, который выполняет функции динамической библиотеки.   -  person David Given    schedule 09.10.2011
comment
Что ж, мне нужно, чтобы dlopen/dlsym функционировали должным образом, но чтобы обращаться к файловой системе по-другому. В частности, в средах HPC, таких как Blue Gene, все операции, связанные с файловым дескриптором ядра, отправляются с узлов ввода-вывода вычислительных узлов. Это вызывает серьезную проблему конкуренции при высокой параллелизации узлов. Например, загрузка приложения Python, которое ссылается на несколько скомпилированных общих библиотек, занимает около 4 часов на 65 000 ядер. Излишне говорить, что люди не в восторге от того, что тратят четверть миллиона часов ядра на загрузку своей программы.   -  person Jed    schedule 09.10.2011
comment
Чтобы исправить это, я реализовал интерфейс ввода-вывода (open, read, mmap и т. д.), используя коллективы MPI. Это нормально для загрузки байт-кода Python, но общие библиотеки должны пройти через dlopen, и у меня возникли проблемы с вызовом моей реализации внутри dlopen.   -  person Jed    schedule 09.10.2011
comment
Я подозреваю, что вам придется написать собственную реализацию dlopen(). Это ужас. (Мы сделали это в том месте, где я работаю по основной работе.) Я был бы склонен попробовать трюк с ptrace; это не так много кода, и он позволит вам запускать стандартную версию кода, включая стандартную dlopen(), но ваш сервер мониторинга наблюдает за процессом и переопределяет вызовы файловой системы, чтобы делать свое дело. Однако это замедляет системные вызовы, но если вы привязаны к процессору, это может не быть проблемой. См. quequero.org/Intercepting_with_ptrace%28%29.   -  person David Given    schedule 09.10.2011
comment
Хм, спасибо. К сожалению, ptrace недоступен в Blue Gene, так что это не вариант. Я вернусь к созданию собственного ld.so с подключенным dlopen для вызова моей реализации.   -  person Jed    schedule 09.10.2011


Ответы (1)


казалось бы, LD_PRELOAD или -Wl,-wrap были бы жизнеспособными решениями

Решение --wrap не может быть жизнеспособным: оно работает только во время (статической) ссылки, а ваши ld.so, libc.so.6 и libdl.so.2 уже связаны, так что теперь слишком поздно использовать --wrap.

LD_PRELOAD мог бы сработать, но... ld.so считает, что dlopen() вызывает open() внутреннюю деталь реализации. Таким образом, он просто вызывает внутреннюю функцию __open, минуя PLT и вашу возможность вставить open вместе с ней.

Каким-то образом malloc обходит проблему

Это потому, что libc поддерживает пользователей, которые реализуют свои собственные malloc (например, в целях отладки). Таким образом, вызов, например. calloc из dlopen проходит через PLT и вставляется через LD_PRELOAD.

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

Что будет делать восстановленный ld.so? Я думаю, вы хотите, чтобы он вызывал __libc_openlibc.so.6), но это не может работать по очевидной причине: это ld.so, что opens libc.so.6 в первую очередь (при запуске процесса).

Вы можете перестроить ld.so, заменив вызов __open вызовом open. Это заставит ld.so пройти через PLT и подвергнуть его интерпозиции LD_PRELOAD.

Если вы пойдете по этому пути, я предлагаю вам не перезаписывать систему ld.so новой копией (слишком велика вероятность совершить ошибку и сделать систему не загружаемой). Вместо этого установите его, например. /usr/local/my-ld.so, а затем свяжите свои двоичные файлы с -Wl,--dynamic-linker=/usr/local/my-ld.so.

Другая альтернатива: исправление во время выполнения. Это немного хак, но вы можете (как только вы получите управление в main) просто сканировать .text из ld.so и искать CALL __open инструкции. Если ld.so не раздет, то можно найти как внутренний __open, так и функции, которые нужно пропатчить (например, open_verify в dl-load.c). Как только вы найдете интересную CALL, mprotect страницу, содержащую ее, доступную для записи, и вставите адрес вашего собственного интерпозера (который, в свою очередь, может вызвать __libc_open, если потребуется), затем mprotect вернете ее обратно. Любые будущие dlopen() теперь будут проходить через ваш промежуточный модуль.

person Employed Russian    schedule 17.10.2011
comment
Первая идея полезна, но переход на PLT вызовы в dlopen() приводил к segfault, так что рассмотрим второй вариант... - person Aron Ahmadia; 18.10.2011