Это вы из проверки внутри if (error_code & PF_USER) сделали вывод? Но стек необязательно двигать командой entry. Можно тупо sub esp, 100500. Эта проверка о том, что обращаться к стеку ниже текущего указателя стека вообще-то нельзя, но если надо — то можно, но недалеко (64К).
mayorovp не так же. Вот кусок обработчика страничного сбоя для linux x86:
vma = find_vma(mm, address);
if (unlikely(!vma)) {
bad_area(regs, error_code, address);
return;
}
if (likely(vma->vm_start <= address))
goto good_area;
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
bad_area(regs, error_code, address);
return;
}
if (error_code & PF_USER) {
/*
* Accessing the stack below %sp is always a bug.
* The large cushion allows instructions like enter
* and pusha to work. ("enter $65535, $31" pushes
* 32 pointers and then decrements %sp by 65535.)
*/
if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
bad_area(regs, error_code, address);
return;
}
}
if (unlikely(expand_stack(vma, address))) {
bad_area(regs, error_code, address);
return;
}
В переводе на русский: для сбойного адреса ищется первая VMA, конечный адрес которой больше чем адрес сбоя. Если такая VMA найдена, и она расширяется вниз (VM_GROWSDOWN) — то это область стека. Попытаться её расширить функцией expand_stack.
Т.е. сбой по любому адресу в пустом месте перед стековой VMA вызовет попытку расширения стека.
Для ядра компилятор генерирует код по-другому?
Это не о ядерном коде вообще. Ядерные стеки имеют фиксированный размер и никогда не расширяются.
Принцип эксплуатации этого бородатого бага основан на увеличении объема стека без записи в него. Это делается по-разному в разных ОС, например, с помощью рекурсивного вызова процедуры, или многомегабайтными аргументами командной строки. Указатель стека перемещается на его начало (нижний адрес), стек резко наращивается, и – хоп, – указатель стека оказывается уже за сторожевой страницей. Доступа к самой сторожевой странице не происходит, ошибки нет. Получается, что область стека перекрывается с динамической областью какого-то процесса.
Вот тут уже можно пошалить. Данные из стека перезаписывают содержимое указателя из чужого процесса, либо наоборот – наш процесс перезаписывает один из чужих возвратных указателей, содержащихся в стеке. Так или иначе, это потенциально позволяет запустить свой код с повышенными правами.
У вас здесь написана ерунда, потому что вы смешали в кучу пользовательский и ядерный стек.
Смотрите: сторожевая страница находится на границе пользовательского стека.
Указатель стека перемещается на его начало (нижний адрес), стек резко наращивается, и – хоп, – указатель стека оказывается уже за сторожевой страницей. Доступа к самой сторожевой странице не происходит, ошибки нет. Получается, что область стека перекрывается с динамической областью какого-то процесса.
Здесь речь идёт о переносе указателя стека через сторожевую страницу в пользовательском процессе. В одном пользовательском процессе. По ту сторону сторожевой страницы находится виртуальная память этого же самого процесса, не какого-нибудь другого.
Вот тут уже можно пошалить. Данные из стека перезаписывают содержимое указателя из чужого процесса, либо наоборот – наш процесс перезаписывает один из чужих возвратных указателей, содержащихся в стеке. Так или иначе, это потенциально позволяет запустить свой код с повышенными правами.
Пошалить в пределах своего процесса можно было всегда, но повлиять на какой-нибудь чужой процесс так нельзя.
В статьях на которые приведены ссылки идёт речь о переполнении ядерного стека.
Обычно этого достаточно, чтобы компилятор начал сборку проекта
Этого достаточно, если Makefile проекта написан так, чтобы использовать переменную окружения CROSS_COMPILE. Вот как, например, Makefile ядра или U-Boot. С произвольным проектом это не работает.
В этом месте википедии всё нормально написано. Тем не менее, начиная с заголовка HOW2 в вашей статье описывается не кросс-компиляция, а нативная компиляция внутри эмулятора.
Ещё один вопрос: если у вас есть gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf, зачем танцы с ARM в QEMU?
Building a native compiler
For a native build, the default configuration is to perform a 3-stage bootstrap of the compiler when ‘make’ is invoked.
It can be disabled with the --disable-bootstrap parameter to ‘configure'
Building a cross compiler
When building a cross compiler, it is not generally possible to do a 3-stage bootstrap of the compiler.
Я имею в виду вот что: над списком, второй пункт которого я процитировал, вы написали: «Из выше сказанного. В результате, что мы имеем:». Но до этого вы ни разу не упоминали детали реализации такого низкого уровня. Я не вижу, как этот пункт вытекает из сказанного вами выше.
Если вы хотели сказать, что для того, чтобы сделать CAS нужно загрузить старое значение из памяти, что даёт два обращения к памяти, а чтобы сделать xadd старое значение загружать не нужно, и обращение к памяти нужно только одно, то можно было так и сказать.
Если вы используете типичную нагрузку CAS идиомы, предполагая нормальный snoop-base когерентности кэша (подслушивание или snooping, это часто употребимая реализация когерентности в многоядерных системах), то нагрузка может вызывать read-to-share транзакцию, чтобы получить основную строку кэша в S или состояние E. CAS, который имеет эффективную память семантик в отношении протоколов когерентности кэшей, может вызвать другую транзакцию шины, чтобы обновить линию для M состояния. Таким образом, в самом худшем случае идиома может подвергнуть шину двум транзакциям, но реализация XADD будет стремиться провести передачу линии непосредственно в M состоянии. В процессе вы могли бы спекулировать значениями и получать короткий путь, который пытается получить «голый» CAS без предварительных загрузок. К тому же, это возможно для сложных реализаций процессора для выполнения согласованных спекуляций и целевого исследования линии в M состоянии. Наконец, в некоторых случаях можно успешно вставить инструкцию предвыборка-для-записи (PREFETCHW) до нагрузки, чтобы избежать транзакции обновления. Но этот подход должен быть применен с тщательностью, так как в некоторых случаях это может принести больше вреда, чем пользы. Учитывая все это, XADD, где это возможно, имеет преимущество.
Вы не могли бы перевести этот текст до конца на русский? Действительно ли сказанное здесь вытекает из вышесказанного?
Моё мнение — код, приводимый в качестве примера должен быть настолько близок к идеалу, насколько возможно, как по содержанию, так и по форме. Потому что, по сравнению с «продакшн кодом» который где-то просто работает, из этого кода существенно большее количество людей извлечёт что-то для себя, начиная от использованных приёмов и заканчивая скопипащенными строчками.
static ssize_t fw_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset)
{
printk("Reading from device, return total number of messages\n");
return sprintf(buffer, "%u", accepted_num + dropped_num);
}
При использовании printk строка формата должна начинаться с одного из макросов KERN_*, определяющих уровень важности сообщения, или с KERN_CONT, если нужно продолжить с последним использованным уровнем важности.
Но это несущественная ошибка, в отличие от sprintf в пользовательский буфер.
Что будет, если пользователь передаст в параметре buffer невалидный пользовательский адрес?
Или что будет, если пользователь передаст в параметре buffer виртуальный адрес где-нибудь в области данных ядра?
В первом случае приложение будет завершено с SIGSEGV и сообщением ядра о недопустимом обращении ядра к виртуальному адресу, хотя достаточно было бы одного SIGSEGV.
Во втором случае память ядра будет перезаписана вашей строкой, что вряд ли закончится хорошо.
Для безопасного копирования данных в/из пространства пользователя есть функции copy_to_user и copy_from_user, а так же strlen_user/strnlen_user/strncpy_from_user.
Кроме того, вы проигнорировали параметр length, из-за чего в следующем фрагменте пользовательского кода происходит переполнение буфера msg:
Это вы из проверки внутри if (error_code & PF_USER) сделали вывод? Но стек необязательно двигать командой entry. Можно тупо sub esp, 100500. Эта проверка о том, что обращаться к стеку ниже текущего указателя стека вообще-то нельзя, но если надо — то можно, но недалеко (64К).
В переводе на русский: для сбойного адреса ищется первая VMA, конечный адрес которой больше чем адрес сбоя. Если такая VMA найдена, и она расширяется вниз (VM_GROWSDOWN) — то это область стека. Попытаться её расширить функцией expand_stack.
Т.е. сбой по любому адресу в пустом месте перед стековой VMA вызовет попытку расширения стека.
Это не о ядерном коде вообще. Ядерные стеки имеют фиксированный размер и никогда не расширяются.
У вас здесь написана ерунда, потому что вы смешали в кучу пользовательский и ядерный стек.
Смотрите: сторожевая страница находится на границе пользовательского стека.
Здесь речь идёт о переносе указателя стека через сторожевую страницу в пользовательском процессе. В одном пользовательском процессе. По ту сторону сторожевой страницы находится виртуальная память этого же самого процесса, не какого-нибудь другого.
Пошалить в пределах своего процесса можно было всегда, но повлиять на какой-нибудь чужой процесс так нельзя.
В статьях на которые приведены ссылки идёт речь о переполнении ядерного стека.
в отличие от expand и unexpand, tr заменит 8 идущих подряд пробелов на 8 табов.
Этого достаточно, если Makefile проекта написан так, чтобы использовать переменную окружения CROSS_COMPILE. Вот как, например, Makefile ядра или U-Boot. С произвольным проектом это не работает.
Ещё один вопрос: если у вас есть gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf, зачем танцы с ARM в QEMU?
Собственно, это была вовсе и не кросскомпиляция.
А если RTFM?
LL/SC круче CAS, поскольку оно не страдает от проблемы ABA.
Если вы хотели сказать, что для того, чтобы сделать CAS нужно загрузить старое значение из памяти, что даёт два обращения к памяти, а чтобы сделать xadd старое значение загружать не нужно, и обращение к памяти нужно только одно, то можно было так и сказать.
Ну и про грамматику уже вам сказали.
Вы не могли бы перевести этот текст до конца на русский? Действительно ли сказанное здесь вытекает из вышесказанного?
С чего бы это вдруг?
$ dpkg -L libc6-dev-i386 | grep '\.a'
/usr/lib32/libanl.a
/usr/lib32/libBrokenLocale.a
/usr/lib32/libc.a
/usr/lib32/libc_nonshared.a
/usr/lib32/libcrypt.a
/usr/lib32/libdl.a
/usr/lib32/libg.a
/usr/lib32/libieee.a
/usr/lib32/libm.a
/usr/lib32/libmcheck.a
/usr/lib32/libnsl.a
/usr/lib32/libresolv.a
/usr/lib32/librt.a
/usr/lib32/libutil.a
/usr/lib32/libpthread_nonshared.a
/usr/lib32/libpthread.a
Не может быть двух разных соединений. Пакетов-то может быть сколько угодно.
Моё мнение — код, приводимый в качестве примера должен быть настолько близок к идеалу, насколько возможно, как по содержанию, так и по форме. Потому что, по сравнению с «продакшн кодом» который где-то просто работает, из этого кода существенно большее количество людей извлечёт что-то для себя, начиная от использованных приёмов и заканчивая скопипащенными строчками.
При использовании printk строка формата должна начинаться с одного из макросов KERN_*, определяющих уровень важности сообщения, или с KERN_CONT, если нужно продолжить с последним использованным уровнем важности.
Но это несущественная ошибка, в отличие от sprintf в пользовательский буфер.
Что будет, если пользователь передаст в параметре buffer невалидный пользовательский адрес?
Или что будет, если пользователь передаст в параметре buffer виртуальный адрес где-нибудь в области данных ядра?
В первом случае приложение будет завершено с SIGSEGV и сообщением ядра о недопустимом обращении ядра к виртуальному адресу, хотя достаточно было бы одного SIGSEGV.
Во втором случае память ядра будет перезаписана вашей строкой, что вряд ли закончится хорошо.
Для безопасного копирования данных в/из пространства пользователя есть функции copy_to_user и copy_from_user, а так же strlen_user/strnlen_user/strncpy_from_user.
Кроме того, вы проигнорировали параметр length, из-за чего в следующем фрагменте пользовательского кода происходит переполнение буфера msg:
Второй параметр этой функции тоже имеет атрибут __user.
Правильность использования атрибута __user можно проверить, передав параметр C=1 команде make собирающей модуль.