Featured image of post Полномочия определяются тем, чем вы владеете, а не тем, кто вы: архитектура безопасности на основе мандатов (Capability-Based Security)

Полномочия определяются тем, чем вы владеете, а не тем, кто вы: архитектура безопасности на основе мандатов (Capability-Based Security)

Строгое архитектурное исследование мандатной безопасности (Capability-Based Security): разрезание матрицы доступа Батлера Лэмпсона по столбцам (ACL) и по строкам (Capabilities), проблема запутанного посредника Норма Харди (1988), объектно-мандатая модель (OCap) Марка Миллера, редукция прав через фасеты, отзыв через Caretaker Ределла и мембраны, а также современные реализации в seL4, FreeBSD Capsicum, CHERI/Morello и WASI.

В современных операционных системах, облачных инфраструктурах и веб-приложениях катастрофические уязвимости безопасности следуют удивительно повторяющемуся шаблону. Межсайтовая подделка запросов (CSRF), серверная подделка запросов (SSRF), SQL- и командные инъекции, побеги из контейнеров и атаки на цепочки поставок ПО в экосистемах npm или PyPI на первый взгляд относятся к совершенно разным уровням программного стека. Однако под поверхностными деталями скрывается один и тот же фундаментальный архитектурный изъян.

Этот изъян заключается в полувековой зависимости индустрии от списков контроля доступа (Access Control Lists, ACL) в сочетании с фоновыми (окружающими) полномочиями (Ambient Authority) — парадигмы проектирования, в которой решение о доступе зависит от того, кто вы (идентичность субъекта), а не от того, чем вы владеете (предъявленный мандат), и в которой обозначение ресурса (Designation) через подделываемый строковый путь или URL оторвано от полномочий (Authority).

В этой статье мы систематически разберем альтернативную парадигму, устраняющую эту структурную уязвимость на корню: безопасность на основе мандатов / возможностей (Capability-Based Security). Начав с математической декомпозиции матрицы контроля доступа Батлера Лэмпсона $M[S, O]$ (1971), мы проследим классическую проблему запутанного посредника (Confused Deputy Problem) Норма Харди (1988), сформулируем объектно-мандатную модель (OCap) Марка Миллера и принцип наименьших полномочий (POLA), построим конкретные шаблоны кода для редукции прав (фасеты) и мгновенного или транзитивного отзыва (Caretaker Ределла и мембраны), а также рассмотрим современные промышленные реализации в формально верифицированном микроядре seL4, FreeBSD Capsicum, аппаратных указателях CHERI / ARM Morello и WebAssembly WASI.


1. Две парадигмы полномочий: удостоверения личности против сервисного ключа парковщика (Valet Key)

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

Парадигма A: Удостоверения личности и дверные ведомости (Модель ACL)

Представьте себе строго охраняемое корпоративное здание, где у каждой двери стоит охранник со списком сотрудников, допущенных в комнату 302 (список контроля доступа, ACL). Когда вы просите открыть комнату 302, охранник проверяет ваш пропуск (Identity / uid) и сверяет ваше имя со списком.

Допустим, вы хотите поручить стажеру (вспомогательной программе или сторонней библиотеке) принести один-единственный файл Q3_Budget.pdf из шкафа в комнате 302. Поскольку стажера нет в списке, вам приходится одолжить ему собственный пропуск (аналог setuid в UNIX или запуска кода от имени вашего пользователя). В тот момент, когда стажер входит в комнату 302 с вашим пропуском, он автоматически получает фоновые полномочия (Ambient Authority) над всеми документами в этой комнате, включая базу зарплат Payroll_Master.db. Хуже того, если злоумышленник подменит записку стажера и обманом заставит его вынести Payroll_Master.db, охранник спокойно пропустит его, ведь на стажере надет ваш пропуск!

Парадигма B: Сервисный ключ парковщика / Valet Key (Мандатная модель Capability)

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

  1. Единство обозначения и полномочий (Unification of Designation and Authority): Ключ одновременно указывает, какой именно автомобиль нужно завести, и уполномочивает владельца ключа запустить двигатель. Машине все равно, кто держит ключ; обладание неподделываемым токеном является необходимым и достаточным доказательством прав.
  2. Редукция / ослабление прав (Attenuation / Least Privilege): В отличие от мастер-ключа, ключ парковщика не открывает багажник или бардачок и ограничивает максимальную скорость. Он предоставляет ровно тот минимум полномочий, который необходим для парковки.
  3. Децентрализованное делегирование: При смене дежурства первый парковщик просто передает ключ второму без редактирования центральной базы данных.

В информатике парадигма A — это модель ACL, а парадигма B — модель Capability-Based Security.


2. Матрица контроля доступа Батлера Лэмпсона $M[S, O]$ и два способа её декомпозиции

В 1971 году лауреат премии Тьюринга Батлер Лэмпсон опубликовал работу “Protection”, предложив матрицу контроля доступа (Access Control Matrix) в качестве универсальной математической абстракции механизмов защиты. Состояние защиты системы задается тройкой $(S, O, M)$:

  • Множество субъектов $S = \{s_1, s_2, \dots, s_m\}$: Активные сущности, инициирующие операции (пользователи, процессы, потоки, программные модули).
  • Множество объектов $O = \{o_1, o_2, \dots, o_n\}$: Защищаемые ресурсы (файлы, сегменты памяти, сокеты IPC, устройства), обычно $S \subseteq O$.
  • Универсальное множество прав $\mathcal{R} = \{\mathsf{read}, \mathsf{write}, \mathsf{execute}, \mathsf{append}, \mathsf{grant}, \mathsf{own}\}$: Дискретные операции над объектами.
  • Матрица контроля доступа $M : S \times O \to 2^{\mathcal{R}}$: Каждая ячейка $M[s, o] \subseteq \mathcal{R}$ определяет точное подмножество операций, которые субъект $s \in S$ вправе выполнять над объектом $o \in O$.

Рассмотрим систему с тремя субъектами $S = \{\text{Alice}, \text{Compiler}, \text{Logger}\}$ и тремя файлами $O = \{\texttt{src.f}, \texttt{/SYSX/STAT}, \texttt{/SYSX/BILL}\}$:

$$ M[S, O] = \begin{array}{c|ccc} & \texttt{src.f} & \texttt{/SYSX/STAT} & \texttt{/SYSX/BILL} \\ \hline \text{Alice} & \{\mathsf{read}, \mathsf{write}\} & \emptyset & \emptyset \\ \text{Compiler} & \{\mathsf{read}\} & \{\mathsf{append}\} & \{\mathsf{read}, \mathsf{write}\} \\ \text{Logger} & \emptyset & \{\mathsf{read}, \mathsf{write}\} & \emptyset \end{array} $$

Поскольку в реальных ОС матрица $M[S, O]$ огромна и крайне разрежена, архитекторы вынуждены разлагать её вдоль одной из двух осей: разрезать по столбцам (прикрепляя списки к объектам) или разрезать по строкам (раздавая мандаты субъектам).

  flowchart TD
    ACM["Матрица контроля доступа Батлера Лэмпсона M[S, O]<br/>Строки: Субъекты S / Столбцы: Объекты O"]
    ACM -->|"Разрез по столбцам (По объектам)"| ACL["Списки контроля доступа (ACL)<br/>Каждый объект o хранит пары (Субъект s, Права R)"]
    ACM -->|"Разрез по строкам (По субъектам)"| CAP["Мандаты / Capabilities (C-Lists / OCap)<br/>Каждый субъект s хранит неподделываемые дескрипторы Cap(o, R)"]

    ACL --> ACL_PROP["1. Отделяет обозначение ('/etc/passwd') от полномочий (фоновый uid)<br/>2. Процесс несет ВСЕ свои привилегии одновременно (Ambient Authority)<br/>3. Структурно уязвим к проблеме запутанного посредника (CSRF / SSRF)"]
    CAP --> CAP_PROP["1. Объединяет обозначение (объект o) и полномочия (права R) в одном дескрипторе<br/>2. Нет обозначения без полномочий (полная изоляция пространств имен)<br/>3. Обеспечивает гранулярный принцип наименьших привилегий (POLA)"]

2.1 Разрезание по столбцам: списки контроля доступа (ACL)

Разрезая матрицу $M[S, O]$ вертикально по столбцам, мы связываем с каждым объектом $o \in O$ список непустых элементов его столбца:

$$ \text{ACL}(o) = \bigl\{ (s, M[s, o]) \;\big|\; s \in S,\; M[s, o] \neq \emptyset \bigr\} $$

Права UNIX/POSIX, Windows NTFS DACL и политики облачного IAM построены по столбцам. Это порождает разделение обозначения и полномочий: программа указывает объект с помощью публичной строки вроде "/SYSX/BILL", а монитор обращений ядра проверяет фоновую идентичность (uid) вызывающего процесса. В результате процесс при каждом системном вызове автоматически применяет объединение всех прав своей строки $\bigcup_{o \in O} M[s, o]$ — фоновые полномочия (Ambient Authority).

2.2 Разрезание по строкам: мандаты / Capabilities (Деннис и Ван Хорн, 1966)

В 1966 году Джек Деннис и Эрл Ван Хорн предложили двойственную декомпозицию: горизонтальное разрезание $M[S, O]$ по строкам. Каждому субъекту $s \in S$ сопоставляется пространство мандатов (CSpace или C-List):

$$ \text{CSpace}(s) = \bigl\{ \text{Cap}(o, R) \;\big|\; o \in O,\; R = M[s, o] \neq \emptyset \bigr\} $$

Каждый элемент $c = \text{Cap}(o, R)$ представляет собой мандат (Capability) — неподделываемый токен полномочий, неразрывно связывающий обозначение (прямой указатель или дескриптор объекта $o$) и полномочия (разрешенное подмножество прав $R \subseteq \mathcal{R}$). Если субъект $s$ не владеет мандатом на объект $o$, он не может даже назвать или адресовать $o$: Нет обозначения без полномочий; нет полномочий без обозначения.

Архитектурное измерениеСписки контроля доступа (ACL — по столбцам)Мандаты / Capabilities (OCap — по строкам)
Декомпозиция $M[S, O]$Хранится по столбцам при каждом объекте $o \in O$Хранится по строкам у каждого субъекта $s \in S$
Философия авторизацииКто вы (Who You Are): проверка uid вызывающегоЧем вы владеете (What You Hold): предъявленный токен-мандат
Обозначение и полномочияРазделены (Подделываемый строковый путь + Фоновый uid)Объединены (Дескриптор именует $o$ и дает права $R$)
Модель полномочийФоновые полномочия: все права uid активны всегдаИзбирательные полномочия: активен лишь переданный мандат
Уязвимость к Confused DeputyСтруктурно уязвима (CSRF, SSRF, setuid-эскалация)Структурно иммунна (Клиент обязан передать готовый дескриптор)
Примеры системUNIX/POSIX ACL, Windows NTFS DACL, AWS IAMKeyKOS, seL4, FreeBSD Capsicum, Fuchsia Zircon, CHERI, WASI

3. Проблема запутанного посредника (Confused Deputy): классика Норма Харди (1988)

Почему разделение обозначения и полномочий так опасно? Компьютерный архитектор Норм Харди дал исчерпывающее объяснение в октябре 1988 года в журнале ACM SIGOPS Operating Systems Review в статье “The Confused Deputy (or why capabilities might have been invented)", описав реальную катастрофу середины 1970-х годов в коммерческой ОС Tymshare.

3.1 Анатомия катастрофы с компилятором /SYSX/FORT

В системе Tymshare компилятор Фортрана находился по пути /SYSX/FORT:

  1. Требование сбора статистики: Администраторы хотели, чтобы при каждой компиляции /SYSX/FORT дописывал статистическую запись в файл /SYSX/STAT.
  2. Предоставление фоновых полномочий: Для этого исполняемому файлу /SYSX/FORT были даны фоновые права на запись во весь каталог /SYSX (аналог бита setuid в UNIX).
  3. Пользовательское имя выходного файла: Компилятор принимал от пользователя строковый аргумент с путем файла для записи отладочного листинга (например, FORT(SRC="prog.f", OUT="listing.txt")).
  4. Главная ценность в /SYSX/BILL: В том же каталоге /SYSX хранилась главная база данных биллинга клиентов /SYSX/BILL.

Однажды пользователь запустил /SYSX/FORT и передал строку "/SYSX/BILL" в качестве имени файла для отладочного вывода. Когда компилятор вызвал open("/SYSX/BILL", WRITE), монитор ACL операционной системы проверил: «Имеет ли процесс /SYSX/FORT право записи в /SYSX?» Ответ был «Да» — и компилятор послушно перезаписал главную базу биллинга компании /SYSX/BILL диагностическими сообщениями Фортрана!

  flowchart TD
    subgraph ACL_WORLD["Модель ACL + Фоновые полномочия (Запутанный посредник)"]
        U1["Злоумышленник<br/>(Нет доступа к /SYSX/BILL)"] -->|"1. Передает строку пути OUT='/SYSX/BILL'"| D1["Посредник: Компилятор /SYSX/FORT<br/>(Фоновые права: запись в /SYSX/*)"]
        D1 -->|"2. open('/SYSX/BILL', WRITE)<br/>Неявно использует свои фоновые полномочия"| OS1["Монитор обращений ОС (Проверка ACL)"]
        OS1 -->|"3. У компилятора есть права на /SYSX -> РАЗРЕШЕНО!"| BILL1["База биллинга /SYSX/BILL<br/>[УНИЧТОЖЕНА / ПЕРЕЗАПИСАНА!]"]
    end

    subgraph CAP_WORLD["Мандатная модель (Единство обозначения и полномочий)"]
        U2["Пользователь<br/>(Владеет мандатами только на свои файлы)"] -->|"1. Передает выходной мандат cap_out<br/>(Не может передать мандат на /SYSX/BILL, так как не владеет им!)"| D2["Посредник: Компилятор /SYSX/FORT<br/>(Наделен ТОЛЬКО мандатом cap_stat)"]
        D2 -->|"2a. write(cap_out, debug_output)<br/>Использует полномочия, явно переданные пользователем"| OUT2["Выходной файл listing.txt"]
        D2 -->|"2b. append(cap_stat, usage_stats)<br/>Использует приватный мандат, выданный при установке"| STAT2["Файл статистики /SYSX/STAT"]
    end

3.2 Почему компилятор «запутался»? И современные потомки этой уязвимости

Харди назвал /SYSX/FORT запутанным посредником (Confused Deputy), поскольку он одновременно действовал от лица двух доверителей (системного администратора и пользователя), но из-за разделения строкового пути и полномочий ОС применила высокие привилегии компилятора к вредоносному пути пользователя. В мандатной архитектуре эта ошибка невозможна: компилятор наделяется только приватным мандатом $c_{\text{stat}} = \text{Cap}(\texttt{/SYSX/STAT}, \{\mathsf{append}\})$, а пользователь обязан сам открыть выходной файл и передать готовый мандат записи $c_{\text{out}} = \text{Cap}(f_{\text{out}}, \{\mathsf{write}\})$.

Самые разрушительные классы современных уязвимостей — это прямые воплощения запутанного посредника:

  • Cross-Site Request Forgery (CSRF): Браузер выступает запутанным посредником, автоматически прикрепляя ваши фоновые полномочия (сессионные cookie сайта bank.example) к запросу, инициированному страницей evil.example. CSRF-токены решают проблему, требуя от самой формы предъявить неподделываемый мандат.
  • Server-Side Request Forgery (SSRF): Облачный веб-сервер запрашивает переданный пользователем URL ("http://169.254.169.254/latest/meta-data/"), используя собственные фоновые сетевые права внутри облачной VPC для кражи ключей IAM.
  • Атаки на цепочки поставок (npm / PyPI): Крошечная библиотека форматирования строк может прочитать ~/.ssh/id_rsa, потому что в модели POSIX ACL любая функция внутри процесса наследует все фоновые права вашего uid.

4. Объектно-мандатная модель (OCap), редукция прав и отзыв мандатов

В своей докторской диссертации 2006 года в Университете Джонса Хопкинса (“Robust Composition”) Марк С. Миллер доказал, что объектно-ориентированные языки с безопасной работой с памятью уже содержат точный механизм для реализации принципа наименьших полномочий (POLA) на уровне отдельных объектов: саму объектную ссылку.

4.1 Три инварианта OCap и три закона приобретения мандатов

Чтобы язык с безопасной памятью (например, Hardened JavaScript / SES, язык E или компоненты Wasm) стал полноценной системой Object-Capability (OCap), среда выполнения должна обеспечивать три инварианта:

  1. Невозможность подделки ссылок: Нельзя сконструировать ссылку из целого числа или с помощью адресной арифметики.
  2. Отсутствие изменяемого глобального состояния (Нулевые фоновые полномочия): Глобальная область видимости не должна содержать изменяемых переменных или фоновых модулей ввода-вывода (fs, net), а базовые прототипы (Object.prototype) должны быть глубоко заморожены (Object.freeze).
  3. Абсолютная инкапсуляция: Приватные переменные лексического замыкания или поля #private недоступны извне через рефлексию.

В графе ссылок $G = (V, E)$ новая дуга $(A \to C)$ может появиться только тремя путями: 1. Порождение (Parenthood, new C()), 2. Наделение при создании (Endowment в замыкании) или 3. Знакомство (Introduction, когда посредник $B$, владеющий $(B \to A)$ и $(B \to C)$, передает $C$ как аргумент сообщения для $A$):

$$ \text{Закон знакомства (Law of Introduction)}: \quad (B \to A) \;\land\; (B \to C) \;\;\Longrightarrow\;\; B \text{ может установить } (A \to C) $$

4.2 Редукция прав через фасеты и отзыв через Caretaker Ределла и мембраны

Вопреки распространенному мифу, мандаты легко ограничиваются и отзываются, что еще в 1974 году показал Дэвид Ределл с помощью шаблона Caretaker (Смотритель) и обобщил Марк Миллер с помощью отзываемых мембран (Revocable Membranes):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. Ослабляющий фасет (Attenuating Facet): чтение только ключей с заданным префиксом
function makeScopedReadFacet(fullStorageCap, allowedPrefix) {
  return Object.freeze({
    read(key) {
      if (typeof key !== "string" || !key.startsWith(allowedPrefix)) {
        throw new Error(`Отказ в доступе: ключ должен начинаться с '${allowedPrefix}'`);
      }
      return fullStorageCap.read(key);
    }
    // методы write() и deleteAll() полностью отсутствуют в фасете!
  });
}

// 2. Шаблон Caretaker Ределла (1974) для мгновенного отзыва мандата за O(1)
function makeCaretaker(targetCap) {
  let currentTarget = targetCap; // Приватный изменяемый указатель внутри замыкания
  const caretaker = Object.freeze({
    read(...args) {
      if (currentTarget === null) throw new Error("RevokedCapabilityError: Доступ отозван");
      return currentTarget.read(...args);
    },
    write(...args) {
      if (currentTarget === null) throw new Error("RevokedCapabilityError: Доступ отозван");
      return currentTarget.write(...args);
    }
  });
  const revoker = Object.freeze({
    revoke() { currentTarget = null; } // Разрывает связь и освобождает объект для GC
  });
  return Object.freeze({ caretaker, revoker });
}

Если мандат возвращает дочерние мандаты (например, каталог возвращает подкаталоги), отзываемая мембрана с помощью Proxy и WeakMap транзитивно оборачивает все возвращаемые значения и аргументы, мгновенно отключая весь подграф при вызове revoke().


5. Промышленные реализации: seL4, FreeBSD Capsicum, CHERI и WebAssembly WASI

Прежде всего устраним путаницу в терминах: capabilities(7) в ядре Linux (CAP_NET_ADMIN и др.) НЕ являются объектными мандатами! Они лишь дробят фоновые привилегии root на ~41 битовый флаг потока без привязки к конкретным объектам. Истинные промышленные мандатные архитектуры воплощены в четырех системах:

  1. Формально верифицированное микроядро seL4: Первое в мире ядро ОС с полным машинно-проверенным математическим доказательством корректности (в Isabelle/HOL). В seL4 нет фоновых полномочий: доступ ко всем ресурсам (потокам, адресным пространствам, портам IPC, физическим страницам) осуществляется исключительно через мандаты в CSpace (таблицах CNode) потока с редукцией прав через seL4_CNode_Mint и транзитивным отзывом через дерево деривации CDT (seL4_CNode_Revoke).
  2. FreeBSD Capsicum: Разработанный в Кембриджском университете и Google механизм Capsicum превращает файловые дескрипторы UNIX (fd) в настоящие мандаты. После вызова cap_enter() ядро необратимо блокирует любые системные вызовы к глобальным пространствам имен (open(), connect(), bind() возвращают ECAPMODE). Процесс может работать только с уже открытыми дескрипторами (и относительными путями внутри них через openat(dir_fd, ...) без выхода по ".."), сужая более 60 битов прав вызовом cap_rights_limit(fd, &rights).
  3. Мандаты в кремнии: CHERI и ARM Morello: Совместный проект SRI International и Кембриджского университета, реализованный компанией ARM в процессоре Morello и в CHERIoT-RISC-V, заменяет 64-битные указатели на 128-битные аппаратные мандаты с 1-битным внеполосным тегом достоверности (Tag Bit):
$$ \underbrace{\text{Tag (1 bit)}}_{\text{Аппаратный бит подлинности}} \;\Big|\; \underbrace{\text{Permissions (18 bits)}}_{\text{Load, Store, Exec, Cap-RW}} \;\Big|\; \underbrace{\text{Object Type (18 bits)}}_{\text{Аппаратная печать}} \;\Big|\; \underbrace{\text{Compressed Bounds (28 bits)}}_{\text{Сжатые границы [Base, Top)}} \;\Big|\; \underbrace{\text{Address Cursor (64 bits)}}_{\text{64-битный виртуальный адрес}} $$

При каждом обращении к памяти процессор за один такт проверяет вхождение адреса в интервал [Base, Top) и биты разрешений. Попытка перезаписать хотя бы один байт мандата в RAM обычной целочисленной инструкцией автоматически сбрасывает бит Tag в 0, делая поддельный указатель непригодным для разыменования и физически искореняя переполнения буфера и ROP-атаки. 4. WebAssembly (WASI) и криптографические Macaroons: Модули WASI запускаются с нулевыми фоновыми полномочиями и имеют доступ только к явно переданным хостом дескрипторам открытых каталогов. В распределенных облачных средах токены Macaroons от Google используют вложенные цепочки HMAC-подписей, позволяя клиенту самостоятельно ослаблять полномочия токена добавлением контекстных ограничений (Caveats) без обращения к центральному серверу.

comments powered by Disqus