Pull to refresh

Comments 42

В высшей мере интересно, спасибо! Реквестирую больше статей о ядерной разработке в Linux.
Интересно, есть ли у linux сообщества разработчиков общая база знаний и актуальной информации? Или всё надо находить по статьям в интернете? Что-то помимо /usr/src/linux/Documentation/
Последний раз когда я узнавал, книги были сильно устаревшими (была какая-то одна акутальная, но на немецком языке), а статьи быстро устаревали, потому что разработчики ядра не сильно парились по обратной совместимости внутри ядра. То есть чтобы быть в теме нужно постоянно чем-то около ядерным заниматься.

Для загрузки модуля еще можно использовать modprobe

если только он лежит в /lib/modules/`uname -r`
А как перехватывает системные вызовы strace?
https://habrahabr.ru/post/215577/

Вот мне стало интересно и я скачал исходники strace. Он работает через ptrace (это набор функций под каждую архитектуру в ядре) далее он обращается к ptrace_traceme, в тех же самых security_hook_heads

Он использует ptrace, т. е. это в userspace — мы можем оставновить процесс перед входом в системный вызов и после завершения, посмотреть на состояние и даже изменить его, но свой код в привелигированном режиме ptrace нам выполнить не даст.
самая большая проблема с LSM, а также подсистемами трассировки/профилирования — это то, что количество вещей, которые можно сделать исключительно на них, ограничено фантазией разработчиков ядра :)

в некоторых случаях приходится править уже код ядра, и только потом пытаться отправить изменения в апстрим.
В крайней мере заинтересовал макрос MODULE_LICENCE. В большинстве источников указывается «напишите GPL или свою лицензию». В гораздо меньшем упоминается «ограничение возможностей взаимодействия с ядром». Еще в одном месте упоминается «загрузка такого модуля опозорит ядро» (loading xxx.ko will taint the kernel) в логах.

А как на самом деле работает этот механизм и на что влияет? Или все ограничивается ворнингом?
если найдёте какую-то информацию на этот счёт, отпишитесь пожалуйста, тоже интересно.
Если не ошибаюсь, то в Fedora не даёт репортить баги ядра если в нём загружены модули с другими лицензиями. В частности, наличие модулей vbox* в отчёте об ошибке блокирует их отправку в bagzilla-у.
Если мне не изменяет память, то если модуль вызывает методы из других GPL-модулей (например, platform_driver_unregister или irq_of_parse_and_map) и в нём нет макроса MODULE_LICENSE(«GPL»), то Linux отругается и не даст загрузить этот модуль.
Модуль ядра представляет собой объектный файл. При загрузке его в ядро командой insmod или modprobe происходит системный вызов. Ядро загружает объектный код модуля и само производит линковку. Линковка производится для вызываемых функций в модуле, которые в ядре помечаются как экспортируемые. Помечаются следующей строчкой: EXPORT_SYMBOL(<function_name>). Так вот, есть ещё директива EXPORT_SYMBOL_GPL. Делает она тоже самое за одним нюансом: эту функцию могут вызывать только модули под лицензией GPL. Если у вас модуль под другой лицензией и при этом в его коде вызывается функция ядра, экспортируемая через EXPORT_SYMBOL_GPL, то ядро просто не загрузит модуль, не произведёт линковку.
Уточнение. Сейчас проверил, модуль даже не соберётся имея лицензию отличную от GPL и попытавшись вызвать функцию EXPORT_SYMBOL_GPL. Вот пример:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/reboot.h>

MODULE_LICENSE("I hate GPL!");

int init(void)
{
   int *i = kmalloc(sizeof(int), GFP_KERNEL); //exported with EXPORT_SYMBOL

   kernel_restart(NULL); // exported with EXPORT_SYMBOL_GPL

   return *i;
}
module_init(init);


И результат попытки сборки:
make -C /lib/modules/4.8.14-300.fc25.x86_64/build M=/home/work/
make[1]: Entering directory '/usr/src/kernels/4.8.14-300.fc25.x86_64'
  CC [M]  /home/work/workspace/work/module/gpl_test.o
  Building modules, stage 2.
  MODPOST 1 modules
FATAL: modpost: GPL-incompatible module gpl_test.ko uses
GPL-only symbol 'kernel_restart'
scripts/Makefile.modpost:91: recipe for target '__modpost' failed


Посмотреть на объявление kernel_restart можно здесь.
Небольшое уточнение для педантов 8-)
лицензия должна включать GPL — например, «Dual BSD/GPL»
по ссылке есть список лицензий, которые кернел считает совместимыми. Как они сами говорят — "… but when running with Linux it is the GPL that is relevant"
а такой способ позволяет перехватывать вызовы fork & mmap? Какие ограничения у данного подхода? Потому что я пробовал метод с заменой таблицы модулей, там в случае перехвата fork & mmap у меня был Kernel Panic, но потом мне объяснили, почему это плохо и я стал смотреть в сторону systemtap, но он большой и разбираться было лень, поэтому пока забил.
Очень интересно.
Хук будет вызываться перед системным вызовом я так понял. А возможен ли вызов хука после системного вызова, а не до?
В таком виде — нет.
Если я правильно понимаю код ядра, то этот хук он на самом деле «паразитирует» на задаче SELinux`а проверить права процесса на действие _перед самим действием_.
Есть интересное следствие — если из хука вернуть не 0 а ошибку, то действие не будет выполнено.
Спасибо за статью. Можно ли так же хукнуть системные вызовы без пересборки самого ядра, а используя insmod?
Ядро >=4.2 (или пропатчено), включены CONFIG_SECURITY_PATH, CONFIG_SECURITY_NETWORK, CONFIG_SECURITY_NETWORK_XFRM, CONFIG_KEYS, CONFIG_AUDIT — можно пользоваться в своём модуле «искаропки».
Используя LSM вы не перехватываете системные вызовы по нескольким причинам. Во-первых, прежде чем вызвать ваш LSM обработчик сначала производится стандартная проверка прав доступа. Так, если у пользователя не хватает прав на создание директории, то системный вызов сразу вернёт код ошибки пользовательскому процессу. Ваш обработчик при этом не вызовется. Во-вторых, не для всех системных вызовов есть соответствующий LSM hook.
Предназначение LSM несколько в другом — в возможности реализации дополнительных проверок прав доступа поверх стандартных. Более того, c некоторых пор LSM стал стековым интерфейсом, то есть приобрёл возможность подключать сразу несколько модулей безопасности (например SELinux, Yama). Это означает, что если ваш модуль работает после SELinux, а он решает не давать доступа на mkdir, то ваш обработчик не будет вызываться.
На мой взгляд, если нужен именно перехват всех системных вызовов, то можно было бы использовать Systemtap. Вот, кстати стандартный примерчик https://sourceware.org/systemtap/examples/process/syscalls_by_proc.stp
#! /usr/bin/env stap

global syscalls

probe nd_syscall.* {
   syscalls[execname()]++
}

probe end {
   printf ("%-10s %-s\n", "#SysCalls", "Process Name")
   foreach (proc in syscalls-)
      printf("%-10d %-s\n", syscalls[proc], proc)
}

Приведённый скрипт на systemtap просто подсчитывал количество системных вызовов, сделанных каждым процессом. Вот ещё один простенький пример. Он отслеживает начало обработки системного вызова ядром и завершение обработки. Выводит информацию о процессе, который его инициировал, аргументы системного вызова и код результата его обработки. if можно расскомментировать, если нужно отслеживать только какой-то конкретный процесс.

syscall_test.stp:
#!/usr/bin/stp

probe nd_syscall.* {
#if (pid() == target()) {
	printf("<syscall %s: %s(%s)\n", execname(), name, argstr)
#}
}

probe nd_syscall.*.return {
#if (pid() == target()) {
	printf(">syscall ret %s: %s result: %s\n", execname(), name, retstr)
#}
}


Запускается так:
$ sudo stap ./syscall_test.stp


Вот пример вывода (ядро скомпилировано с поддержкой многоядерности, поэтому системные вызовы обрабатываются не последовательно):
<syscall konsole: write(95, "\0", 1)

<syscall ksmserver: ioctl(28, 21531, 0x7fffab1c9ec4)
>syscall ret konsole: write result: 1
>syscall ret ksmserver: ioctl result: 0

<syscall ksmserver: read(28, 0x5626d6785938, 40)
>syscall ret ksmserver: read result: 40

<syscall konsole: lseek(94, -2147482516, SEEK_SET)

<syscall ksmserver: poll(0x5626d66df110, 64, -1)
>syscall ret konsole: lseek result: -22 (EINVAL)

<syscall konsole: write(2, "HistoryFile::add.seek: Invalid argument\n", 40)
>syscall ret konsole: write result: 40

<syscall konsole: lseek(93, 22447548, SEEK_SET)
>syscall ret konsole: lseek result: 22447548

<syscall konsole: write(93, "l\004\0\200", 4)
>syscall ret konsole: write result: 4
Очень интересно, спасибо!

Любопытно, а virtualbox при установке своих модулей тоже так же перекомпилирует ядро?
Очевидно нет. Они пытаются делать модули так чтобы они подходили под разные ядра, но на новых ядрах они регулярно ломаются.
UFO just landed and posted this here
Ребят, много таких, кто не просто прочитал статью, но и прошёлся по мануалу?

Интересно, у всех ли всё получилось, и если не получилось, то почему?
Если что-то не так — будем разбираться, править.
А почему максимальный приоритет выставлялся для make?
всегда думал, что максимум CPU в подобных случаях поедает компилятор,
или измененный приоритет make из гнома наследуется и на него как дочернего?
Да, там порождается куча процессов, и приоритет будет наследоваться от make
Я вообще ни разу не системный программист, но статья понравилась своей простотой и ясностью. Проделал все тоже самое на Debian 8.6. Взял ядро 4.9 и все прекрасно получилось:)
спасибо за статью. прямо как-будто читаю Шрайбера с недокументированными возможностями вин2к и волшебным int 2e…
После успешной сборки нужно установить всё то, что мы собрали. Это требует root-прав.
Установка заголовков:
sudo make headers_install

Это нужно только для сборки libc, при обычной сборке ядра/модулей этот шаг не нужен.
Согласен, но заголовки также нужны и для модулей virtual-box, я решил что туториал это не усложнит, но пользу принесёт
Ну и пример в дефолте. дичь.
>Makefile:13: *** missing separator.
Ну ладно, выровнял через пробел и;

>arch/x86/Makefile:173: CONFIG_X86_X32 enabled but no binutils support
>Makefile:670: Cannot use CONFIG_CC_STACKPROTECTOR_REGULAR: -fstack-protector not supported by compiler
>make[1]: *** Нет правила для сборки цели «стол/test». Останов.

Класс, чё тут сказать.
это при сборке конфига, который собирается defconfig-ом?
лечится вот этим https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1574982/comments/15
Спасибо за статью!
Подписался. Продолжайте писать в таком жанре.

Как уже написали выше, LSM — это не про перехват системных вызовов, а про реализацию политик по работе с объектами ядра. Например, хук inode_mkdir вызывается не только в системном вызове mkdir. С недавнего времени можно писать программы-обработчики хуков LSM на BPF без необходимости писать модули, см. Security Auditing and Enforcement using eBPF, KP Singh


Если же вам хочется просто посмотреть на все системные вызовы (хотя и без возможности вернуть ошибку), то это еще проще, например, смотрим на тот же mkdir при помощи bpftrace:


# bpftrace -e 't:*:sys_enter_mkdir {printf("%s: %s\n", comm, str(uptr(args->pathname)));}'
Attaching 1 probe...
mkdir: /tmp/123
mkdir: /tmp/1234
...

Здесь мы подсосались к tracepoint sys_enter_mkdir — вход в системный вызов mkdir. В момент срабатывания мы печатаем comm — имя процесса и args->pathname — один из аргументов системного вызова, который мы читаем при помощи str — вытянуть строку. Для чтения из pathname мы делаем uptr, так как строка лежит в пространстве пользователя. Hint: bpftrace -vl 't:*:*' покажет все доступные вам tracepoints + их аргументы.

omg, не заметил, что это статья 2016 года :facepalm: Но мой комментарий выше следует считать актуальным

Sign up to leave a comment.

Articles

Change theme settings