Взаимодействие вилки и памяти пользовательского пространства, отображенной в ядре


Рассмотрим драйвер Linux, который использует get_user_pages (или get_page) для отображения страниц из вызывающего процесса. Физический адрес страниц затем передается аппаратному устройству. И процесс, и устройство могут читать и записывать на страницы до тех пор, пока стороны не решат прекратить общение. В частности, связь может продолжать использовать страницы после того, как системный вызов, вызывающий get_user_pages, возвратится. Системный вызов фактически устанавливает общую зону памяти между процессом и аппаратное устройство .

Меня беспокоит, что произойдет, если процесс вызовет fork (это может быть из другого потока, и может произойти либо во время выполнения syscall, который вызывает get_user_pages, либо позже). В частности, если родитель записывает в разделяемую область памяти после форка, что я знаю о базовом физическом адресе (предположительно измененном из-за копирования при записи)? Я хочу понять:

  1. что нужно сделать ядру, чтобы защититься от потенциально ненадлежащий процесс (я не хочу создавать дырубезопасности !);
  2. Каким ограничениям должен подчиняться процесс, чтобыфункциональность нашего драйвера работала правильно (то есть физическая память остается отображенной по тому же адресу в Родительском процессе).

      В идеале, я бы хотел, чтобы общий случай, когда дочерний процесс вообще не использует наш драйвер (он, вероятно, вызывает exec почти сразу), работал.
  3. В идеале, родитель процесс не должен предпринимать никаких специальных шагов при выделении памяти, так как у нас есть существующий код, который передает драйверу буфер, выделенный стеком.
  4. я знаю о madvise с MADV_DONTFORK, и было бы нормально, чтобы память исчезла из пространства дочернего процесса, но это не применимо к буферу, выделенному стеком.
  5. "Не используйте вилку, пока у вас есть активное соединение с нашим водителем" было бы раздражающим, но приемлемым в крайнем случае, если пункт 1 удовлетворенный.
Я хочу, чтобы мне указали на документацию или исходный код. Я посмотрел, в частности, на драйверы устройств Linux , но не нашел решения этой проблемы. RTFS, применяемые даже к соответствующей части исходного кода ядра, немного перегружают.

Версия ядра не полностью исправлена, но является последней (скажем, ≥2.6.26). Мы нацелены только на платформы Arm (пока что однопроцессорные, но многоядерные только за углом), если это вопросы.

2 3

2 ответа:

A fork() не будет мешать get_user_pages(): get_user_pages() даст вам struct page.

Вы должны были бы kmap() его, прежде чем получить к нему доступ, и это отображение выполняется в пространстве ядра, а не в пространстве пользователя.

EDIT: get_user_pages() коснитесь таблицы страниц, но вы не должны беспокоиться об этом (он просто убедитесь, что страницы отображаются в пользовательском пространстве), и возвращает-EFAULT, если у него были какие-либо проблемы с этим.

Если вы разветвляете (), то до тех пор, пока не будет выполнено копирование на запись, ребенок сможет увидеть, что страница. Как только копирование на запись будет сделано (потому что дочерний / драйвер / родитель написал на страницу через отображение пространства пользователя - не kernel kmap () драйвер имеет), эта страница больше не будет общей. Если вы все еще держите kmap() на странице (в коде драйвера), вы не сможете узнать, держите ли вы родительскую страницу или страницу ребенка.

1) это не дыра в безопасности, потому что как только вы execve (), все это исчезает.

2) Когда вы fork () вы хотите, чтобы оба процесса были идентичны (Это вилка !!). Я думаю, что ваш дизайн должен позволить и родителю, и ребенку получить доступ к драйверу. Execve() все смоет.

Как насчет добавления некоторых функций в пользовательское пространство, например:

 f = open("/dev/your_thing")
 mapping = mmap(f, ...)

Когда mmap() вызывается на вашем устройстве, вы устанавливаете отображение памяти со специальными флагами: http://os1a.cs.columbia.edu/lxr/source/include/linux/mm.h#071

У вас есть несколько интересных вещей, таких как:

#define VM_SHARED       0x00000008
#define VM_LOCKED       0x00002000
#define VM_DONTCOPY     0x00020000      /* Do not copy this vma on fork */

VM_SHARED отключит копирование на писать VM_LOCKED отключит обмен на этой странице VM_DONTCOPY скажет ядру не копировать область vma на fork, хотя я не думаю, что это хорошая идея

Короткий ответ-использовать madvise(addr, len, MADV_DONTFORK) на любых буферах пользовательского пространства, которые вы предоставляете своему водителю. Это говорит ядру, что отображение не должно быть скопировано от родителя к потомку, и поэтому нет никакой коровы.

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

Update : буфер в стеке проблематичен, я не уверен, что вы можете сделайте его безопасным в целом.

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

Другой способ избежать коровы-создать совместное отображение, но вы не можете сопоставить свой стек общим по очевидным причинам.

Это означает, что вы рискуете коровой, если раскошелитесь. Даже если ребенок" просто " исполняет, он все равно может коснуться страницы стека и вызвать корову, что приведет к тому, что родитель получит другую страницу, что плохо. Один незначительный момент в вашу пользу заключается в том, что код, использующий буфер на стеке, должен беспокоиться только о коде, который он вызывает раздвоение, т. е. вы не можете использовать буфер на стеке после того, как функция возвратилась. Таким образом, вам нужно только проверить своих абонентов, и если они никогда не раскошеливаются, вы в безопасности, но это все еще может быть невыполнимо и хрупко, если код когда-либо изменения. Я думаю, что вы действительно хотите, чтобы вся память, предоставленная вашему драйверу, исходила из пользовательского распределителя в пользовательском пространстве. Это не должно быть так навязчиво. Распределитель может либо mmap ваше устройство напрямую, как предлагалось в другом ответе, либо просто использовать анонимный mmap, madvise(DONTFORK), и, вероятно, mlock(), чтобы избежать подкачки.