Проблемы с переносом функций в ядре linux

Я написал LKM, который реализует Trusted Path Execution (TPE) в ваше ядро:

https://github.com/cormander/tpe-lkm

Я нахожусь в случайном ядре OOPS (описываю в конце этого вопроса), когда я определяю WRAP_SYSCALLS в 1, и я нахожусь в своем конце, пытаясь отследить его.

Немного фона:

Поскольку структура LSM не экспортирует свои символы, мне нужно было сделать креатив с тем, как я вставляю проверку TPE в запущенное ядро. Я написал функцию find_symbol_address(), которая дает мне адрес любой функции, которая мне нужна, и она работает очень хорошо. Я могу вызвать такие функции:

int (*my_printk)(const char *fmt, ...);
my_printk = find_symbol_address("printk");
(*my_printk)("Hello, world!\n");

И он отлично работает. Я использую этот метод для поиска функций security_file_mmap, security_file_mprotect и security_bprm_check.

Затем я перезаписываю эти функции с помощью asm-перехода к моей функции, чтобы выполнить проверку TPE. Проблема в том, что загруженный в настоящий момент LSM больше не будет выполнять код для привязки к этой функции, потому что он был полностью захвачен.

Вот пример того, что я делаю:

int tpe_security_bprm_check(struct linux_binprm *bprm) {

    int ret = 0;

    if (bprm->file) {
            ret = tpe_allow_file(bprm->file);
            if (IS_ERR(ret))
                    goto out;
    }

#if WRAP_SYSCALLS
    stop_my_code(&cs_security_bprm_check);

    ret = cs_security_bprm_check.ptr(bprm);

    start_my_code(&cs_security_bprm_check);
#endif

    out:

    return ret;
}

Обратите внимание на раздел раздела #if WRAP_SYSCALLS (по умолчанию он задан как 0). Если установлено значение 1, вызван LSM-крючок, потому что я пишу исходный код обратно через ASM-переход и вызываю эту функцию, но я запускаю случайное ядро ​​OOPS с "недопустимым кодом операции":

invalid opcode: 0000 [#1] SMP 
RIP: 0010:[<ffffffff8117b006>]  [<ffffffff8117b006>] security_bprm_check+0x6/0x310

Я не знаю, в чем проблема. Я пробовал несколько разных типов методов блокировки (см. Внутреннюю часть start/stop_my_code для деталей) безрезультатно. Чтобы запустить ядро ​​OOPS, напишите простой цикл bash while, который бесконечно запускает запрограммированную команду "ls". Через минуту или около того, это произойдет.

Я тестирую это на ядре RHEL6, также работает на Ubuntu 10.04 LTS (2.6.32 x86_64).

Пока этот метод был самым успешным до сих пор, я попробовал другой метод простого копирования функции ядра на указатель, созданный с помощью kmalloc, но когда я пытаюсь его выполнить, я получаю: Ядро попыталось выполнить NX-защищенную страницу - попытка использования? (uid: 0). Если кто-нибудь скажет мне, как это сделать в поле kmalloc и присвоить его как исполняемый файл, это также поможет мне решить указанную выше проблему.

Любая помощь приветствуется!

Ответ 1

1. Кажется, начало security_bprm_check() не восстанавливается полностью до вызова функции. Oops происходит в security_bprm_check+0x6, т.е. Сразу после прыжка, который вы разместили там, поэтому, похоже, какая-то часть прыжка все еще там в тот момент. Я не могу сейчас сказать, почему это может произойти.

Взгляните на реализацию Kernel Probes (KProbes) на x86, это может дать вам несколько советов. Подробнее см. Также описание KProbes. KProbes необходимо исправлять и восстанавливать почти произвольные фрагменты кода ядра безопасным способом выполнения своей работы.

2.Накопите другой подход, который вы упомянули, относительно копирования функции. Ниже приведена небольшая взлома, и разработчики ядра будут недовольны разработчиками, но если нет другого способа, это может помочь.

Вы можете выделить память для копирования функций из той же области, где выделена память для кода модулей ядра. Эта область должна быть выполнена по умолчанию. Опять же, KProbes используют этот трюк для выделения своих буферов для обхода.

Память выделяется функцией module_alloc() и освобождается module_free(). Эти функции, конечно, не экспортируются, но вы можете найти их адреса так же, как и для security_file_mmap() и т.д. Просто любопытство, вы используете kallsyms_on_each_symbol(), правильно?

Если вы выделяете память таким образом, это также может помочь избежать другой не столь очевидной проблемы. На x86-64 области адресов памяти, доступные для kmalloc и для кода модулей, расположены довольно далеко друг от друга (см. Documentation/x86/x86_64/mm.txt), вне досягаемости любого относительного скачка. Если память сопоставляется с областью адресов модулей, вы можете использовать почти относительные прыжки и вызовы для вызова скопированных функций. Аналогичную проблему с RIP-относительной адресацией также можно избежать.

РЕДАКТИРОВАТЬ: Обратите внимание, что на x86, если вы скопируете часть кода в другую область памяти и хотите запустить ее, некоторые изменения в этом коде могут быть необходимы. По крайней мере, вам необходимо исправить относительные вызовы и переходы, которые передают управление вне скопированного кода (например, вызовы другой функции и т.д.), А также инструкции с относительной адресацией RIP.

Кроме того, в коде могут быть другие структуры, которые необходимо скопировать. Например, компилятор мог оптимизировать некоторые или даже все операторы switch для перехода через таблицу. То есть адреса блоков кода для каждого case хранятся в таблице в памяти, а переменная switch - это индекс в эту таблицу. Таким образом, вместо многих сравнений ваш модуль будет выполнять что-то вроде jmp <table_start>(%reg, N) (N - размер указателя, в байтах). То есть, просто переход к адресу, который находится в соответствующем элементе таблицы. Поскольку такие таблицы создаются для кода перед его копированием, может потребоваться исправление, в противном случае такие переходы вернут выполнение исходного кода, а не скопированного.