?

Log in

No account? Create an account
Previous Entry —>другу Next Entry
COMEFROM memory protection
bartson
dmytrish
Недавно стукнула мені в голову одна ідейка щодо memory protection у операційних системах. Мабуть, я перевинайшов якийсь велосипед або в цій ідеї є якась принципова хріновість, — це я і хочу зрозуміти за допомогою шановної публіки.

Як відомо, в юнісксоподібних (включно із NT) системах пам’ять процесів захищається через створення таблиць віртуальної пам’яті, специфічних для кожного процесу та завантаження цих таблиць (із вимиванням кеша та іншими неприємними побічними ефектами) при кожному перемиканні контексту. В результаті якщо код/дані замаплені у віртуальну пам’ять процеса, вони доступні, якщо ні — їх там не існує.

Тому мікроядра примушені ходити довгою дорогою: «відіслали повідомлення в ядро, syscall» — «ядро копіює його собі і кладе в чергу, yield процеса або context switch на процес-адресат» — «повернулись у процес-адресат, скопіювали собі, обробили запит, відіслали відповідь, syscall» — «ядро копіює відповідь собі і кладе у чергу повідомлень, yield або виклик адресанта, копіювання в віртуальну пам’ять адресанта, context switch» — «адресант приймає відповідь».

Просто віртуальну пам’ять я б назвав GOTO memory protection: вона заснована на тому, *куди* направлений стрибок коду або звернення за даними, незалежно від того, звідки переходять або звертаються.

А що ж протилежністю GOTO? Правильно, інструкція COMEFROM, яка існує в деяких езотеричних мовах і досі здавалась мені абсолютно безглуздою. Як вона працює: COMEFROM 0xdeadbeef в будь-якому місці програми створює стрибок із 0xdeadbeef у це місце.

Ідея полягає в тому, щоб перевіряти *звідки* прийшов control flow або звернення за даними, і для певних ділянок дозволяти такі звернення. для інших ні. Таким чином, можна в одному адресному просторі тримати багато доменів коду-даних, у яких немає прямого доступу один до одного, але можуть бути публічні інтерфейси.

Тобто, якщо ми хочемо файлову систему як сервіс у юзерспейсі, ми створюємо в фізичній пам’яті домен коду «інтерфейс», доступ до якого можливий із будь-якої адреси, домен «код реалізації» та «дані реалізації», доступ до якого можливий лише лише із фізичної пам’яті ділянки «інтерфейс». Далі ми мапимо ці домени в адресний простір усіх зацікавлених процесів. Коли процесу необхідно, для прикладу, прочитати файл, він просто робить виклик функції із «інтерфейсу», вона валідує дані і викликає функцію із «реалізації». Прямий виклик неможливий і призводить до PageFault/DomainFault. Спроба записати дані із стороннього коду в «дані реалізації» також повинна закінчуватись DomainFault.

Таким чином, сервіс, подібний до мікроядерного, стає просто спільним кодом усіх клієнтів, які для нього виглядають як треди, які працюють із спільним станом. І виклик цього сервісу перетворюється в два виклики функцій в юзерспейсі. Проблеми синхронізації спільних даних, звичайно, гидкі, але сішників це не зупиняє і інструментарій є (м’ютекси-семафори-бар’єри-conditionals).

Найбільша проблема цієї ідеї — що DomainFault має реалізовуватись у залізі додатково до PageFault та всієї існуючої віртуальної пам’яті.
Крім того, неясно, наскільки витратна така модель по пам’яті. В принципі, для кожної ділянки фізичної пам’яті (сторінки або сегмента?) було б достатньо вказати, чи публічний доступ до неї, чи тільки із якогось одного діапазону/сегмента.

P.S. В певному розумінні ця ідея є ідеєю security capabilities. Код, який прийшов із «інтерфейсу», має capability доступу до даних та коду реалізації, будь-який інший виклик — ні.

  • 1
Это ж централизация получается, нет разве? А если multicore?

В каком смысле централизация?

Вот если multicore — хороший вопрос, но умудряются же даже треды с шареной памятью разбрасывать на разные ядра одновременно. Правда, как тогда порядок доступа к памяти контролируется, я слабо представляю.

> Таким чином, сервіс, подібний до мікроядерного, стає просто спільним кодом усіх клієнтів, які для нього виглядають як треди, які працюють із спільним станом. І виклик цього сервісу перетворюється в два виклики функцій в юзерспейсі.

Це саме так і працює у Windows NT. Наприклад підсистема може працювати в режимі ядра або в режимі користувача в залежності від кількісті переключень стеків. Такими техніками організовується і завантаження одного модуля у всі віртуалні простори одразу. Це ж просто області пам'яті Area, Section, разом з Acl, а Acl як відомо можно поставити на будь-який об'єкт від семафора і потока і до памяті і порта.

Пост насправді про те, що добре було би логічно вибудувати схему Acl для рівней довіри та доступу. Так воно вже все зроблено, є там сервіс Se який контролює усі токени аж до X.509 та Kerberos.

Edited at 2015-03-26 04:53 pm (UTC)

Гаразд, а як контролюється доступ з юзерського коду? Що відбувається, якщо юзерський код намагається прочитати дані із модуля?

Як ACL контролюють доступ до пам’яті? Через traps процесора?

щось не розумію, що ви намагаєтесь поліпшити і якою ціною ви отримуєте це поліпшення. (тобто, я так розумію, що аналіз адреси, звідки був виклик публічного інтерфейсу дозволить використати ту саму таблицю для всіх процесів; це так? але які загрози тоді виникають?)

Намагаюсь уникнути зайвого перемикання контексту при тому, щоб сервіс-абстракція мав свою власну пам’ять, яку не може перезаписати клієнт-процес.

зрозумів. Тобто, процесс, який отримує запит на доступ до пам’яті, дивиться, хто питає, і тільки тоді може дати доступ - так? Схоже, це virtual memory mapping in software. Але тут виникає питання. Якщо .so використовується різними процесами, і вже .so запитує щось, то яке значення має COMEFROM?

Edited at 2015-03-26 07:35 pm (UTC)

Ні, сервіс, який викликається процесом, знаходиться в самому процесі (сервіс є лише спільним кодом і даними для багатьох процесів), але пам’ять сервіса захищена від чужого коду. Ідея в тому, щоб залізо відстежувало, чи до коду/даних сервіса звертався його інтерфейс (якому сервіс довіряє і який виставляє для доступу будь-звідки) і він сам, чи довільний код (якому загалом довіряти не можна; не можна, щоб якийсь із клієнтів міг обрушити сервіс і перезаписати дані).

тут є багато підводних каменів, до речі... :)

по-перше, у наївній реалізації (що є у Java, наприклад) - така нібито проста штука, як колбек стрімко перетворюється на privilege escalation...

по-друге... а, там багато таких "по-друге". непроста штука воно.

Заради виявлення підводних каменів і був написаний пост :)

Щодо колбека не зовсім ясно: якщо керування уже передали в колбек, %ip уже знаходиться в іншій фізичній ділянці, тільки що якщо колбеку передадуть захищені дані, то це все закінчиться DomainFault. Але компрометації даних я не бачу.

Що варто почитати на тему security capabilities для чайників?

Дуже важко зрозуміти вашу ідею, бо ви використовуєте дивні терміни. Що таке домен? Якщо у процесорі немає call (є непрямий jump), які виклики інтерфейсу?

Якщо я правильно зрозумів, з кожною областю пам’яті, де зберігається код, пов’язана своя карта віртуальної пам’яті. Нехай з областями А і Б пов’язані різні карти. Якщо стрибнути з А в Б, доведеться перемкнути карту. Тобто завантаження адреса карти у відповідний регістр вбудовано у процесор. Це настільки важливо?

call є. Віртуальна пам’ять є і працює як звичайно.

Додається новий шар контролю capabilities: різні ділянки фізичної пам’яті можуть бути замаплені в один віртуальний простір, але деякі ділянки фізичної пам’яті перевіряють, хто до них звертається, дозволяючи це лише іншій ділянці (домену) і видаючи DomainFault при доступі (передачі керування/зверненні до даних) з будь-якої іншої. Перемикання віртуальної пам’яті не відбувається, відбувається або виклик/звернення (без жодного оверхеду) або DomainFault, при цьому фізична пам’ять одночасто стирчить у віртуальні простори багатьох процесів, але захищена від доступу не через інтерфейсну ділянку.

але деякі ділянки фізичної пам’яті перевіряють, хто до них звертається, дозволяючи це лише іншій ділянці (домену)

Це і є оверхед, для цього і потрібна карта пам’яті. Поясніть докладно, як «перевіряють», і побачите.

Скажімо, по аналогії із GDT/IDT створюється Domain Descriptor Table, що зберігає для кожного домену початок і кінець фізичної ділянки пам’яті (2 по 4/8 байт). Кожній сторінці віртуальної пам’яті приписаний індекс у цій таблиці (нехай він займає до 16 біт, 32 тисяч ділянок буде достатньо, сподіваюсь). Коли відбувається звернення за логічною адресою, процесор дізнається в TLB або в Page Directory її фізичну адресу та, якщо індекс DDT ненульовий, іде в DDT і дізнається адреси початку і кінця дозволеної фізичної пам’яті. Порівняння фізичної адреси caller'а із двома іншими адресами (і то, якщо сторінка непублічна) — це зовсім не затратний процес. Оверхед я бачу хіба що в cache miss при зверненні до DDT (але з Page Directory/Page Tables проблема аналогічна і вирішується translation lookaside buffer) та в додаткових 2 байтах індекса DDT для кожної сторінки (хоча, якщо використовуються huge pages по 4 Mb, це взагалі перестає бути проблемою).

P.S. Ще можна видавати права на доступ до портів I/O конкретним доменам, і таким чином, драйвер будь-чого житиме в одному адресному просторі із юзерським процесом, який сам не матиме прав на доступ до портів.

Edited at 2015-04-23 02:24 pm (UTC)

Intel SGX в нових x86 - дуже схоже.

Ні, Intel SGX більше про захищену ділянку, окрему від ОС, наскільки я розумію.

Ця ідея більше про те, як зробити мікроядро та зробити більшість системних сервісів shared кодом без доступу кожного процеса до даних сервісів.

  • 1