#glibc #dlopen
#glibc #dlopen
Вопрос:
Я хочу перехватить весь доступ к файловой системе, который происходит внутри dlopen(). Поначалу это казалось бы LD_PRELOAD
или -Wl,-wrap,
было бы жизнеспособным решением, но у меня возникли проблемы с их работой по некоторым техническим причинам:
-
ld.so уже сопоставил свои собственные символы к моменту обработки LD_PRELOAD. Для меня не критично перехватывать начальную загрузку, но
_dl_*
рабочие функции в это время разрешены, поэтому будущие вызовы проходят через них. Я думаю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
, потому что это для минимальных ядер «вычислительного узла», которые не поддерживают такие вещи.
Комментарии:
1. Это не ответ на ваш вопрос, поэтому я не публикую его как один, но в целом вы не можете сделать это надежно: можно получить доступ к файловой системе, вызвав системный вызов напрямую, не проходя через интерфейс динамической библиотеки. Если у вас нет абсолютного контроля над тем, как была скомпилирована библиотека, которую вы пытаетесь загрузить, вам может не повезти. Такие программы, как fakeroot, которые используют этот метод, большую часть времени работают нормально, а в некоторых ситуациях ужасно терпят неудачу.
2. Тем не менее, вы можете заставить это работать, запустив код вашей динамической библиотеки в ее собственном процессе и используя
ptrace
для перехвата сами системные вызовы. Я сделал это с большим успехом, и это позволяет полностью избежать всей ерунды с разделяемой библиотекой. Но это требует, чтобы вы полностью перепроектировали свою логику, чтобы иметь главный процесс, который выполняет ptrace, и подчиненный процесс, который выполняет динамическую библиотеку.3. Ну, мне нужно
dlopen
/dlsym
для правильной работы, но для доступа к файловой системе по-другому. В частности, в средах высокой производительности, таких как Blue Gene, все операции, связанные с файловым дескриптором ядра, отправляются с узлов ввода-вывода вычислительных узлов. Это вызывает серьезную проблему конкуренции при высоком параллелизме узлов. Например, загрузка приложения на Python, которое ссылается на несколько скомпилированных разделяемых библиотек, занимает около 4 часов на 65 тыс. ядер. Излишне говорить, что люди не в восторге от сжигания четверти миллиона часов ядра для загрузки своей программы.4. Чтобы исправить это, я реализовал интерфейс ввода-вывода (
open
,read
,mmap
, и т.д.) с использованием коллективов MPI. Это нормально для загрузки байт-кода Python, но разделяемые библиотеки должны пройтиdlopen
, и у меня возникли проблемы с вызовом моей реализации внутриdlopen
.5. Я подозреваю, что вам придется написать свою собственную реализацию dlopen(). Это ужас. (Мы сделали это там, где я работаю на своей повседневной работе.) Я был бы склонен попробовать трюк с ptrace; это не так много кода, и это позволит вам запускать стандартную версию кода, включая стандартную dlopen() , но ваш сервер мониторинга следит за процессом и переопределяетвызовы файловой системы делают свое дело. Тем не менее, это замедляет системные вызовы, но если вы привязаны к процессору, это может не быть проблемой. Смотрите quequero.org/Intercepting_with_ptrace() .
Ответ №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_open
(in libc.so.6
), но это не может сработать по очевидной причине: это то ld.so
, что open
s 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
, in dl-load.c
). Как только вы найдете интересное CALL
, mprotect
страница, которая его содержит, будет доступна для записи, и исправьте адрес вашего собственного посредника (который, в свою очередь, может вызвать __libc_open
, если это необходимо), затем mprotect
верните его обратно. Любое будущее dlopen()
теперь будет проходить через ваш interposer.
Комментарии:
1. Первая идея полезна, но переключение на
PLT
вызовы вdlopen()
привело к сбоям segfaults, поэтому мы рассмотрим второй вариант…