Intereting Posts

Как ядро ​​уведомляется, когда процессу нужны новые страницы для фреймов стека?

Я понимаю, что MMU на чипе принимает виртуальные адреса и преобразует их в адрес физической памяти. MMU выполняет следующие действия:

(1) Конкурирует таблицу страниц, относящуюся к процессу

(2) Если страница, соответствующая виртуальному адресу, находится в резидентном наборе, то переводит адрес в физический адрес

(3) Если страница, соответствующая виртуальному адресу, НЕ находится в резидентном наборе, то генерирует ошибку страницы, которую обрабатывает ядро

Теперь я понимаю, что создание и удаление страниц для нескольких разделов процесса требуют системных вызовов, таких как brk() , sbrk() , mmap() и munmap() . Таким образом, Kernel всегда будет иметь возможность обновлять таблицу страниц процесса, когда будут сделаны эти системные вызовы.

Однако работающий процесс может увеличить область стека, попросив уменьшить указатель стека %rsp на 10 000, что может потребовать нескольких страниц выделения, чтобы учесть увеличение глубины стека.

Если мое понимание MMU правильное выше, то в случае изменения %rsp MMU не будет генерировать ошибку страницы (потому что адрес не находится в таблице процессов для начала). Что делает MMU в этой ситуации, чтобы уведомить ядро?

Доступ к немаркированным страницам происходит с ошибкой. Как вы думаете, что они могли бы сделать?


Я нашел упоминание, что Ульрих удалил MAP_GROWSDOWN из glibc. Поэтому конкретный вопрос, вероятно, имеет только академический интерес.

EDIT: Оказывается, мой ответ ниже был совершенно устаревшим. Вероятно, лучше начать с чтения более позднего LWN на StackSmash . Хотя это не совсем объясняет мне все.


Если вы читаете документацию ядра для MAP_GROWSDOWN , она задокументирована как одна страница защиты. Чтобы он работал надежно, он подразумевает, что язык должен внедрять стековые датчики, то есть гарантировать пошаговое касание страниц, чтобы страница защиты всегда попадала первым. Поэтому описание в этом вопросе о распределении нескольких страниц сразу кажется запутанным.

LLVM на Linux не реализовал стековые пробники – это TODO для безопасного для памяти языка Rust. Есть упоминание о том, что GCC делает, но только если вы пройдете -fstack-check . «К сожалению, -fstack-check на самом деле не подходит для наших целей» по нескольким причинам .

https://github.com/rust-lang/rust/issues/16012

Публикация Ульриха немного запутанна против страницы руководства. Страница руководства описывает страницу защиты, которая обычно означает отображение PROT_NONE (всегда-ошибка). Однако сообщение Ульриха сформулировано так, что MAP_GROWSDOWN не выделяет страницу защиты. Страница, на которой будет храниться, просто не выделяется, и ядро ​​обнаруживает неисправности, вызванные им для специальной обработки. Это создает проблему, заключающуюся в том, что сопоставление может быть легко распределено по тому же адресу, что и несуществующая страница защиты, побеждая систему.

Я мог бы описать это ошибочно, поскольку я не читал код, поэтому, пожалуйста, прочитайте оригинальную публикацию.

https://readlist.com/lists/vger.kernel.org/linux-kernel/106/533375.html

 + Why: The two flags cannot be used securely because using them means that + collisions with other allocations cannot be avoided. Assume a stack + of size S is allocated using mmap with the MAP_GROWSDOWN flag set. + The address is determined by the kernel to be A. To make the + MAP_GROWSDOWN flag useful no guard page(s) can be allocated just + below the stack (ie, just below address A). This means the + address space just below A is unallocated. + + Now assume a second, unrelated mmap call allocates a block in the + range [B, B+T), B+T < A. Nothing will prevent the growing-down + stack from sooner or later reaching that block. At this point + the stack will overwrite the memory in that second block and vice + versa. + + The only way to prevent this is to change _every_ allocation via + mmap to include guard pages at either end. This is completely + inpractical, expensive, and not a full protection any way. 

Вне MAP_GROWSDOWN: стек MAP_ANONYMOUS, прочитайте дружественную man-страницу для mmap() . Начальный стек, отображаемый ядром, имеет (защищенную страницу PROT_NONE). EDIT: это было добавлено в 2010 году . Стеки для потоков за пределами первого настраиваются полностью пользовательским пространством. Когда вы касаетесь любой страницы PROT_NONE, ядро ​​доставляет SIGSEGV SIGBUS и пользовательское пространство могут справиться с этим, как ему нравится.

Примечание. Но как вы можете запустить обработчик сигнала, если у вас нет доступного стека? Ответ: вы зарезервируете заранее, используя sigaltstack() .

(Я предполагаю, что ядро ​​не реализует MAP_GROWSDOWN в первоначально выделенном стеке, поскольку Ульрих сформулировал бы его более четко и срочно, если бы это было так).