Читал исходники ядра. Много думал...
А дело тут вот в чем. Как бы «всем хорошо известно» и по-умолчанию предполагалось, что ядро содержит массив указателей на системные вызовы sys_call_table.
Поколения куул-хацкеров и прочих честных разработчиков разработали 100500 примерно четыре метода разыскивания адреса этой таблицы в памяти и замены соответствующего сисколла на свой. И ладно бы только куул-хацкеры! Честные коммерсанты, которые по разным причинам не хотят открывать код своих ядерных модулей, делают то же самое. И все бы хорошо, но в коде ядра 5.15-107, например, мы видим (см. arch/x86/entry/syscall_64.c)
#define __SYSCALL(nr, sym) extern long __x64_##sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
#undef __SYSCALL
#define __SYSCALL(nr, sym) __x64_##sym,
asmlinkage const sys_call_ptr_t sys_call_table[] = {
#include <asm/syscalls_64.h>
};
И все хорошо, как и ожидалось.
А в 5.15-158, к примеру, картинка уже другая:
/*
* The sys_call_table[] is no longer used for system calls, but
* kernel/trace/trace_syscalls.c still wants to know the system
* call address.
*/
#define __SYSCALL(nr, sym) __x64_##sym,
const sys_call_ptr_t sys_call_table[] = {
#include <asm/syscalls_64.h>
};
#undef __SYSCALL
#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
{
switch (nr) {
#include <asm/syscalls_64.h>
default: return __x64_sys_ni_syscall(regs);
}
};
Поясняю. Теперь вместо вызова по индексу в массиве имеем «тупо свитч». Кстати, этот переход между способами вызова сисколла не однократный. В 6.1 опять используется традиционная схема. Вот мне интересно, кто-нибудь кроме меня в этом деле ковырялся? Как нам теперь сисколлы-то перехватывать надежным способом?
И да, «писать свой LSM» не предлагать. Не потому, что хотим что-то скрыть, а потому, что не можем или не хотим поддерживать свой форк ядра. Да и у клиентов будет стоять тот линукс, который будет стоять. А LSMы нонче модулями не грузятся.