Возникли проблемы при переносе функций в ядре Linux - PullRequest
11 голосов
/ 22 июня 2011

Я написал 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;
}

Обратите внимание на раздел между #, если 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 Ответ

9 голосов
/ 22 июня 2011

1. Кажется, начало security_bprm_check() восстанавливается не полностью до вызова функции.Упс происходит в 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-относительной адресацией также избегают таким способом.

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

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

...