Lua-скриптинг в strace (Виктор Крапивенский, OSSDEVCONF-2017)
- Докладчик
- Виктор Крапивенский
strace — инструмент для отслеживания взаимодействия пользовательских процессов и ядра Linux: системных вызовов, сигналов и изменений состояния процесса. Ранее, в рамках одного из проектов GSoC 2016, в strace была добавлена функция fault injection, которая позволяет подменять результаты системных вызовов.
В рамках проекта GSoC 2017 в strace появилась поддержка Lua-скриптинга, которая позволяет не только производить фильтрацию и подмену системных вызовов с большей гибкостью, но и производить success injection с сохранением семантики системного вызова, которая, в частности, может заключаться в записи определённых данных в адресное пространство процесса.
Содержание
Видео
Посмотрели доклад? Понравился? Напишите комментарий! Не согласны? Тем более напишите.
Презентация
Thesis
Обзор
В рамках одного из проектов GSoC 2016 в strace была добавлена поддержка подмена кодов возврата системных вызовов.
Была реализована возможность подменять коды возврата произвольного подмножества системных вызовов; при этом, логика ограничивалась лишь счётчиком — можно было подменять результаты всех системных вызовов, только N-ного, либо N-ного и далее каждого K-того.
При этом, не было возможности производить success injection с сохранением семантики системного вызова, которая, в частности, может заключаться в записи определённых данных в адресное пространство процесса.
В этом году, strace получил поддержку Lua(JIT)-скриптинга, что открывает новые возможности в этом направлении.
Lua, LuaJIT и FFI
Lua — легковесный встраиваемый скриптовый язык программирования.
LuaJIT — Just-In-Time компилятор для Lua, называемый «одной из самых производительных реализаций динамических языков программирования».
LuaJIT также предоставляет набор библиотек и расширений, отсутствующих в стандартной поставке Lua.
Наиболее важна для strace библиотека ffi, которая позволяет взаимодействовать с кодом на Си и включает в себя парсер деклараций типов и констант, поддерживающий, за небольшими незначительными исключениями, C99 в полной мере.
<source lang="C"> ffi.cdef[[ typedef struct foo { int a, b; } foo_t; // Declare a struct and typedef. int dofoo(foo_t *f, int n); /* Declare an external C function. */ ]] </source> Библиотека ffi также даёт возможность создавать объекты, соответствующие известным типам данных Си, и производить с ними различные манипуляции.
Существуют также отдельные реализации библиотеки ffi для обычной реализации Lua. Они также поддерживаются strace.
Реализация
strace «скармливает» библиотеке ffi декларацию типа struct tcb — типа, поля которого совпадают с первыми полями структуры, в которой strace хранит информацию о текущем состоянии системного вызова для каждого процесса:
<source lang="C"> /* Trace control block */ struct tcb {
int pid; /* Tracee's PID */ unsigned long u_error; /* Error code */ kernel_ulong_t scno; /* System call number */ kernel_ulong_t u_arg[/* MAX_ARGS */];/* System call arguments */ kernel_ulong_t u_rval; /* Return value */ unsigned int currpers; /* Current personality */
</source>
А также декларации ещё нескольких необходимых структур и типов: Также, strace, через модуль strace.C, предоставляет функции и константы, необходимые для обеспечения функциональности скриптинга.
После этого загружается библиотека, написанная на Lua, предоставляющая удобные обёртки над этими функциями и константами.
Основное взаимодействие с strace осуществляется через функции strace.next_sc, strace.C.monitor и strace.C.monitor_all.
strace хранит множество тех системных вызовов, которые нужно возвратить из strace.next_sc при входе в системный вызов, и множество тех, которые нужно возвратить по выходу из него.
Изначально оба этих множества пусты; библиотека или пользователь могут выбрать несколько системных вызовов по номеру для мониторинга с помощью strace.C.monitor, или же потребовать информировать скрипт обо всех системных вызовах с помощью strace.C.monitor_all.
Библиотека предоставляет более удобный интерфейс информирования о системных вызовах, а именно возможность повесить функцию-обработчик на вход либо выход из системного вызова по его номеру, имени, либо классу системных вызовов.
Примеры
Подсчёт количества порождённых процессов:
<source lang="C"> n = 0 assert(strace.hook({'clone', 'fork', 'vfork'}, 'exiting', function(tcp)
if tcp.u_rval = -1 then n = n + 1 end
end)) strace.at_exit(function() print('Processes spawned:', n) end) </source> Использование препроцессора для извлечения констант из заголовочных файлов:
<source lang="C"> ffi = require 'ffi' f = assert(io.popen( grep -v '^#' #define _GNU_SOURCE #include <fcntl.h> enum { f_setpipe_sz = F_SETPIPE_SZ }; EOF, 'r')) ffi.cdef(f:read('*a')) f:close() assert(strace.hook({'fcntl', 'fcntl64'}, 'entering', function(tcp)
if tcp.u_arg[1] == ffi.C.f_setpipe_sz then assert(strace.inject_error('EPERM')) end
end)) </source> Использование препроцессора для извлечения определений структур из заголовочных файлов:
$ uname Linux $ strace -l pretend-win.lua -e trace=none uname Windows +++ exited with 0 +++ $ cat pretend-win.lua ffi = require 'ffi' f = assert(io.popen([[cpp - «EOF | grep -v '^#'
<source lang="C">
- include <sys/utsname.h>
EOF]], 'r')) ffi.cdef(f:read('*a')) f:close() assert(strace.hook('uname', 'exiting', function(tcp)
if tcp.u_rval == -1 then return end local u = assert(strace.read_obj(tcp.u_arg[0], 'struct utsname')) local s = 'Windows' assert(ffi.sizeof(u.sysname) >= #s + 1) ffi.copy(u.sysname, s) assert(strace.write_obj(tcp.u_arg[0], u))
end)) </source>
Текущее состояние
Правки пока не приняты в strace; текущее состояние проекта можно отслеживать в репозитории [1].
Примечания и ссылки
Plays:132 Comments:0