KasperskyOS Community Edition 1.0

Содержание

Что нового

В KasperskyOS Community Edition 1.0 появились следующие возможности и доработки:

  • Добавлена поддержка аппаратной платформы Raspberry Pi 4 Model B.
  • Добавлена поддержка SD-карты для аппаратной платформы Raspberry Pi 4 Model B.
  • Добавлена поддержка Ethernet для аппаратной платформы Raspberry Pi 4 Model B.
  • Добавлена поддержка портов ввода-вывода GPIO для аппаратной платформы Raspberry Pi 4 Model B.
  • Добавлены сетевые сервисы DHCP, DNS, NTP и примеры работы с ними.
  • Добавлена библиотека для работы с протоколом MQTT и примеры ее использования.
В начало

О KasperskyOS Community Edition

KasperskyOS Community Edition (CE) — общедоступная версия KasperskyOS, предназначенная для освоения основных принципов разработки приложений под KasperskyOS. KasperskyOS Community Edition позволит вам увидеть, как концепции, заложенные в KasperskyOS, работают на практике. KasperskyOS Community Edition включает в себя примеры приложений с исходным кодом, подробные пояснения, а также инструкции и инструменты для сборки приложений.

KasperskyOS Community Edition пригодится вам для:

  • изучения принципов и приемов разработки "secure by design" на практических примерах;
  • изучения KasperskyOS как возможной платформы для реализации своих проектов;
  • прототипирования решений (прежде всего, Embedded/IoT) на основе KasperskyOS;
  • портирования приложений/компонентов/драйверов на KasperskyOS;
  • изучения вопросов безопасности в разработке ПО.

Для получения KasperskyOS Community Edition перейдите по ссылке.

Помимо этой документации, также рекомендуем изучить материалы раздела сайта KasperskyOS для разработчиков.

В начало

Об этом документе

Руководство разработчика KasperskyOS Community Edition адресовано специалистам, которые осуществляют разработку безопасных решений на базе KasperskyOS.

Руководство предназначено специалистам, обладающим следующими навыками: знание языков C/C++, опыт разработки под POSIX-совместимые системы, знакомство с GNU Binary Utilities (далее также "binutils").

Вы можете применять информацию в этом руководстве для выполнения следующих задач:

  • установка и удаление KasperskyOS Community Edition;
  • использование KasperskyOS Community Edition.

Основные понятия

Ниже приведены часто используемые термины, связанные с KasperskyOS Community Edition:

  • KasperskyOS – микроядерная операционная система для построения безопасных программно-аппаратных систем.
  • Kaspersky Security System – технология, позволяющая создавать декларативное описание политики безопасности решения, а также генерировать на основе этого описания модуль безопасности Kaspersky Security Module.
  • Kaspersky Security Module – модуль ядра, дающий разрешение или запрет на каждое IPC-взаимодействие в решении.
  • Решение – ядро KasperskyOS, модуль безопасности Kaspersky Security Module, а также прикладное и системное ПО, интегрированные для работы в составе программно-аппаратного комплекса.
  • Сущность – запущенная на исполнение программа в KasperskyOS.
  • Служба – набор связанных по смыслу методов, доступных через механизм IPC (например, служба ядра для выделения памяти или служба сущности-драйвера для работы с блочным устройством).
  • Дескриптор (handle) – идентификатор ресурса (например, участка памяти, порта или сетевого интерфейса). Дескриптор используется для доступа к ресурсу.
  • IPC – механизм, использующийся сущностями для взаимодействия друг с другом и с ядром.
  • Политика безопасности решения – логика управления IPC-взаимодействиями в решении, реализуемая модулем безопасности Kaspersky Security Module.
В начало

Системные требования

Для установки KasperskyOS Community Edition и запуска примеров под QEMU необходимы:

  1. Операционная система: Debian GNU/Linux "Buster" версии 10.7 x64.
  2. Процессор: процессор с архитектурой x86-64 (для большей производительности требуется поддержка аппаратной виртуализации).
  3. Оперативная память: для комфортной работы с инструментами сборки рекомендуется иметь не менее 4 Гб оперативной памяти.
  4. Дисковое пространство: не менее 3 ГБ свободного пространства в разделе /opt (в зависимости от разрабатываемого решения);

Для запуска примеров на аппаратной платформе Raspberry Pi, необходимо использовать модель Raspberry Pi 4 Model B с объемом оперативной памяти равным 2, 4, или 8 Гб.

В начало

Комплект поставки

В комплект поставки KasperskyOS Community Edition входят:

  • deb-пакет для установки KasperskyOS Community Edition, содержащий:
    • образ ядра операционной системы KasperskyOS;
    • компоненты KasperskyOS Community Edition;
    • набор инструментов для разработки решения (компилятор NK, компилятор GCC, отладчик GDB, набор утилит binutils, эмулятор QEMU и сопутствующие инструменты);
    • лицензионное соглашение;
    • файл с информацией о стороннем коде (Legal Notices).
  • Информация о версии (Release Notes);
  • Руководство разработчика KasperskyOS Community Edition (онлайн-документация).

Следующие компоненты, входящие в комплект поставки KasperskyOS Community Edition, являются "Runtime компонентами" в соответствии с условиями лицензионного соглашения:

  • Образ ядра операционной системы KasperskyOS.

Остальные части комплекта поставки не являются "Runtime компонентами". Условия и возможности использования каждого компонента могут быть дополнительно указаны в разделе "Информация о стороннем коде".

В начало

Включенные сторонние библиотеки и приложения

Для упрощения процесса разработки приложений в состав KasperskyOS Community Edition также включены следующие сторонние библиотеки и приложения:

  • Boost (v.1.71) – собрание библиотек классов, использующих функциональность языка C++ и предоставляющих удобный кроссплатформенный высокоуровневый интерфейс для лаконичного кодирования различных повседневных подзадач программирования (работа с данными, алгоритмами, файлами, потоками и т. п.)

    Документация: https://www.boost.org/doc/

  • Arm Mbed TLS (v.2.16) – реализация протоколов TLS и SSL, а также соответствующих криптографических алгоритмов и необходимого кода поддержки.

    Документация: https://tls.mbed.org/kb

  • Civetweb (v.1.11) – простой в использовании, мощный, встраиваемый веб-сервер на C / C ++ с дополнительной поддержкой CGI, SSL и Lua.

    Документация: http://civetweb.github.io/civetweb/UserManual.html

  • Eclipse Mosquitto (v1.6.4) – брокер сообщений, реализующий протокол MQTT.

    Документация: https://mosquitto.org/documentation/

Также см. Информация о стороннем коде.

В начало

Ограничения и известные проблемы

Поскольку KasperskyOS Community Edition предназначен для обучения, мы интегрировали в пакет ряд ограничений:

  1. Не поддерживается симметричная многопроцессорность (SMP). Используется только одно ядро процессора.
  2. Не поддерживается динамическая загрузка библиотек.
  3. Максимальное поддерживаемое количество запущенных приложений (сущностей): 32.
  4. При завершении работы сущности любым способом (например, return из основного потока исполнения) выделенные сущностью ресурсы не освобождаются, а сама сущность переводится в "спящее" состояние. Сущности не могут быть запущены повторно.
  5. Не поддерживается запуск двух и более сущностей с одинаковым EDL-описанием.
  6. Система останавливается, если не осталось работающих сущностей или если один из потоков драйверной сущности завершился (штатным или нештатным образом).
В начало

Основные понятия KasperskyOS

Решение на базе KasperskyOS

Решение на базе KasperskyOS состоит из ядра, модуля безопасности, а также прикладного и системного ПО, интегрированных для работы в составе программно-аппаратного комплекса. Процессы в KasperskyOS называются сущностями. Ядро гарантирует, что сущности изолированы и могут взаимодействовать только через ядро (с помощью системных вызовов). Каждая сущность в решении имеет статическое описание, определяющее интерфейсы, доступные другим сущностям. Для описания интерфейсов используются специально разработанные языки – EDL, CDL и IDL.

Кибериммунный подход

Для разработки безопасных решений на базе KasperskyOS используется кибериммунный подход. Этот подход основан на выборе способа разбиения системы на сущности и задании определенных правил их взаимодействия (т.н. политика безопасности решения). Политика безопасности реализуется модулем безопасности Kaspersky Security Module, который входит в решение.

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

Подробнее о кибериммунном подходе

Kaspersky Security System

Технология Kaspersky Security System позволяет разрабатывать и реализовывать разнообразные политики безопасности. При этом можно комбинировать несколько моделей безопасности, добавлять свои модели и гибко настраивать правила взаимодействия сущностей. Для формального описания политики безопасности решения используется специально разработанный язык PSL. На основе PSL-описания генерируется модуль безопасности Kaspersky Security Module для использования в конкретном решении.

KasperskyOS Community Edition

KasperskyOS Community Edition содержит инструменты для разработки безопасных решений на базе KasperskyOS, включая:

  • образ ядра операционной системы KasperskyOS;
  • компилятор NK, предназначенный для генерации модуля безопасности Kaspersky Security Module и вспомогательного транспортного кода;
  • прочие инструменты для разработки решения (компилятор GCC, отладчик GDB, набор утилит binutils, эмулятор QEMU и сопутствующие инструменты);
  • набор библиотек, обеспечивающих частичную совместимость со стандартом POSIX;
  • компоненты KasperskyOS Community Edition;
  • документацию;
  • примеры простейших решений на базе KasperskyOS.
В начало

Кибериммунитет

Концепция кибериммунитета основана на следующих понятиях:

  • цели и предположения безопасности;
  • понятия модели MILS (домен безопасности, ядро разделения и монитор обращений);
  • доверенная вычислительная база (TCB).

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

Цели и предположения безопасности

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

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

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

Понятия модели MILS

В рамках модели MILS (Multiple Independent Levels of Security) безопасная информационная система состоит из изолированных доменов безопасности и ядра разделения, контролирующего взаимодействия доменов друг с другом. Ядро разделения обеспечивает изоляцию доменов и управление информационными потоками между ними.

Каждая попытка взаимодействия между доменами безопасности проверяется на соответствие определенным правилам, которые задаются политикой безопасности системы. Если взаимодействие запрещено текущей политикой, оно не пропускается (блокируется). Политику безопасности в архитектуре MILS реализует отдельный компонент – монитор обращений. Для каждого взаимодействия доменов безопасности монитор обращений возвращает решение (булево значение), соответствует ли это взаимодействие политике безопасности. Ядро разделения вызывает монитор каждый раз, когда один домен обращается к другому.

Доверенная вычислительная база (TCB)

Доверенная вычислительная база (Trusted Computing Base, TCB) – совокупность всего программного кода, уязвимость в котором приводит к невозможности выполнения информационной системой заданных целей безопасности. В модели MILS ядро разделения и монитор обращений составляют основу доверенной вычислительной базы.

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

Кибериммунная система

Информационная система является кибериммунной (или обладает кибериммунитетом), если она разделена на изолированные домены безопасности, все взаимодействия между которыми независимо контролируются, и предоставляет:

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

Кибериммунный подход

Кибериммунный подход – это способ построения кибериммунных систем.

Кибериммунный подход основан на:

  • разбиении системы на изолированные домены безопасности;
  • независимом контроле всех взаимодействий между доменами безопасности на соответствие заданной политике безопасности;
  • обеспечении надежности доверенной кодовой базы.

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

Преимущества кибериммунного подхода

Кибериммунный подход позволяет:

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

Изоляция и взаимодействие сущностей

Кибериммунная система состоит из изолированных частей (доменов безопасности в терминах MILS), которые могут взаимодействовать друг с другом только через ядро разделения, т.е. контролируемым образом. В KasperskyOS реализацией доменов безопасности являются сущности.

В этом разделе

Сущности

Взаимодействие сущностей (IPC)

Описания интерфейсов сущности (EDL, CDL, IDL)

IPC-транспорт

В начало

Сущности

Каждый процесс в KasperskyOS – это субъект в политике безопасности решения. При запуске процесса ядро KasperskyOS ассоциирует с ним контекст, необходимый для его исполнения, а модуль Kaspersky Security Module – контекст безопасности, необходимый для контроля его взаимодействий с другими процессами.

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

С точки зрения ядра KasperskyOS, сущность – это процесс, имеющий отдельное адресное пространство и один или несколько потоков исполнения. Ядро гарантирует изоляцию адресных пространств сущностей. Сущность может реализовывать интерфейсы, а другие сущности – вызывать методы этих интерфейсов через ядро.

С точки зрения модуля безопасности Kaspersky Security Module, сущность является субъектом, с которым могут взаимодействовать другие субъекты (сущности). Возможные виды взаимодействий задаются описанием интерфейсов сущности, которые должны соответствовать реализации. Описания интерфейсов позволяют модулю безопасности проверять каждое взаимодействие сущностей на соответствие политике безопасности решения.

Дополнительная информация по сущностям

Для модуля безопасности Kaspersky Security Module ядро является таким же субъектом, как и сущности. Сущности могут вызывать методы ядра, и эти взаимодействия контролируются аналогично вызовам методов других сущностей. Поэтому далее мы будем говорить, что ядро является отдельной сущностью с точки зрения Kaspersky Security Module.

В начало

Взаимодействие сущностей (IPC)

В KasperskyOS есть только один способ взаимодействия сущностей – с помощью синхронного обмена IPC-сообщениями: запросом и ответом. В каждом взаимодействии выделяются две роли: клиент (сущность, инициирующая взаимодействие) и сервер (сущность, обрабатывающая обращение). При этом сущность, являющаяся клиентом в одном взаимодействии, может выступать как сервер в другом.

Клиент и сервер используют три системных вызова: Call(), Recv() и Reply():

  1. Клиент направляет серверу сообщение-запрос. Для этого один из потоков исполнения клиента выполняет вызов Call() и блокируется до получения ответа от сервера или ядра (например, в случае ошибки).
  2. Серверный поток, выполнивший вызов Recv(), находится в ожидании сообщений. При получении запроса этот поток разблокируется, обрабатывает запрос и отправляет сообщение-ответ с помощью вызова Reply().
  3. При получении сообщения-ответа (или ошибки) клиентский поток разблокируется и продолжает исполнение.

Таким образом, ядро KasperskyOS является ядром разделения в терминах модели MILS, поскольку все взаимодействия сущностей проходят через него.

Обмен сообщениями как вызов метода

Отправка IPC-запроса сущности-серверу представляет собой обращение к одному из интерфейсов, реализуемых сервером. IPC-запрос содержит входные аргументы вызываемого метода, а также идентификатор реализации интерфейса (RIID) и идентификатор вызываемого метода (MID). Получив запрос, сущность-сервер использует эти идентификаторы, чтобы найти реализацию метода. Сервер вызывает реализацию метода, передав в нее входные аргументы из IPC-запроса. Обработав запрос, сущность-сервер отправляет клиенту ответ, содержащий выходные аргументы метода.

Модуль безопасности Kaspersky Security Module может анализировать все компоненты IPC-сообщения, чтобы вынести решение, соответствует ли это сообщение политике безопасности системы.

IPC‑каналы

Чтобы две сущности могли обмениваться сообщениями, между ними должен быть установлен IPC-канал (далее также канал или соединение). Канал задает роли сущностей, т.е. имеет "клиентскую" и "серверную" стороны. При этом одна сущность может иметь несколько каналов, в которых она является клиентом, и несколько каналов, где она – сервер.

В KasperskyOS предусмотрено два способа создания IPC-каналов:

  1. Статический способ предполагает создание канала в момент запуска сущностей (при старте решения). Статическое создание каналов выполняется инициализирующей сущностью Einit.
  2. Динамический способ позволяет уже запущенным сущностям установить канал между собой.
В начало

Описания интерфейсов сущности (EDL, CDL, IDL)

Для контроля взаимодействий между сущностями необходимо, чтобы структура пересылаемых IPC-сообщений была прозрачна для модуля безопасности. В KasperskyOS это достигается с помощью статического декларирования интерфейсов сущностей. Для этого используются специальные языки: Entity Definition Language (EDL), Component Definition Language (CDL) и Interface Definition Language (IDL). Если IPC-сообщение не соответствует описанию интерфейса, оно будет отклонено модулем безопасности.

Описание интерфейсов сущности определяет допустимые структуры IPC-сообщений. Так задается явная связь между реализацией каждого метода и тем, как вызов этого метода представлен для модуля безопасности. Почти все инструменты сборки решения явно или косвенно используют описания интерфейсов сущностей.

Виды статических описаний

Описание интерфейсов сущности строится по модели "сущность-компонент-интерфейс":

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

Пример

Ниже приведены статические описания решения, состоящего из сущности Client, не реализующей ни одного интерфейса, и сущности Server, реализующей интерфейс FileOps.

Client.edl

// Статическое описание состоит только из имени сущности

entity Client

Server.edl

// Сущность Server содержит экземпляр компонента Operations

entity Server

components {

OpsComp: Operations

}

Operations.cdl

// Компонент Operations реализует интерфейс FileOps

component Operations

interfaces {

FileOpsImpl: FileOps

}

FileOps.idl

package FileOps

// Объявление пользовательского типа String

typedef array <UInt8, 256> String;

// Интерфейс FileOps содержит единственный метод Open с входным аргументом name и выходным аргументом h

interface {

Open(in String name, out UInt32 h);

}

См. подробнее о синтаксисе статических описаний.

В начало

IPC-транспорт

Чтобы реализовать взаимодействие сущностей, необходим транспортный код, отвечающий за корректное создание IPC-сообщений, их упаковку, отправку, распаковку и диспетчеризацию. Разработчику решения под KasperskyOS нет необходимости самостоятельно писать транспортный код. Вместо этого можно использовать специальные инструменты и библиотеки, поставляемые в составе KasperskyOS Community Edition.

Транспортный код для разрабатываемых компонентов

Разработчик новых компонентов для KasperskyOS может сгенерировать транспортный код на основе статических описаний этого компонента. Для этого в составе KasperskyOS Community Edition поставляется компилятор NK. Компилятор NK позволяет генерировать транспортные методы и типы для использования как на клиентской стороне, так и на серверной.

Транспортный код для поставляемых компонентов

Функциональность большинства компонентов, поставляемых в составе KasperskyOS Community Edition, может быть использована в решении как локально, т.е. путем статической компоновки с разрабатываемым кодом, так и через IPC.

Для вынесения компонента в отдельную серверную сущность и использования его по IPC поставляются следующие транспортные библиотеки:

  • Клиентская библиотека компонента преобразует локальные вызовы в IPC-запросы к сущности драйвера.
  • Серверная библиотека компонента принимает IPC-запросы к сущности драйвера и преобразует их в локальные вызовы.

Чтобы использовать компонент по IPC, достаточно его реализацию скомпоновать с серверной библиотекой, а клиентскую сущность скомпоновать с клиентской библиотекой.

Интерфейс клиентской библиотеки не отличается от интерфейса самого компонента. Таким образом, для перехода на использование компонента по IPC (вместо статической компоновки) нет необходимости вносить изменения в код клиентской сущности.

Подробнее см. "IPC и транспорт".

В начало

Контроль взаимодействий

В кибериммунной системе каждая попытка взаимодействия между изолированными частями системы (доменами безопасности в терминах MILS) проверяется на соответствие определенным правилам, которые задаются политикой безопасности системы. Если взаимодействие запрещено текущей политикой, оно не пропускается. Ниже мы рассмотрим, как данный принцип реализован в KasperskyOS.

В этом разделе

Модуль безопасности Kaspersky Security Module

Язык описания политик PSL

Управление доступом к ресурсам

В начало

Модуль безопасности Kaspersky Security Module

Технология Kaspersky Security System позволяет под каждое разрабатываемое решение сгенерировать код модуля безопасности Kaspersky Security Module, реализующего заданную политику безопасности. Модуль безопасности контролирует все взаимодействия между сущностями, т.е. является монитором безопасности в терминах MILS.

Контроль IPC-сообщений

Ядро обращается к модулю безопасности Kaspersky Security Module каждый раз, когда одна сущность отправляет сообщение другой сущности. Модуль безопасности проверяет, что структура сообщения соответствует описанию вызываемого метода. Если это так, модуль безопасности вызывает все связанные с этим сообщением правила и выносит решение: "разрешено" или "запрещено". Ядро применяет полученное решение, т.е. доставляет сообщение, только если это разрешено.

Таким образом, код, вычисляющий решения (Policy Decision Point) отделен от кода, применяющего их (Policy Enforcement Point).

IPC-ответы подлежат контролю, как и IPC-запросы. Это может использоваться, например, чтобы гарантировать целостность ответов.

Ядро доставляет IPC-сообщение, только если Kaspersky Security Module разрешает доставку

Контроль запуска сущностей

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

Интерфейс безопасности

Сущности могут напрямую обращаться к Kaspersky Security Module, с помощью интерфейса безопасности, чтобы сообщить о своем состоянии или о контексте какого-либо события. При этом модуль безопасности применяет все правила, связанные с вызываемым методом интерфейса безопасности. Далее сущность может сама использовать полученное от модуля безопасности решение. Интерфейс безопасности используется, например, для реализации сущностей, управляющих доступом к ресурсам.

В начало

Язык описания политик PSL

Важнейшая часть технологии Kaspersky Security System – это язык PSL (Policy Specification Language). Он позволяет формально, близко к терминам самой задачи, описать политику безопасности решения. На основе полученного psl-описания генерируется код модуля безопасности Kaspersky Security Module под конкретное решение. Для этого используется компилятор NK, поставляемый в составе KasperskyOS Community Edition. Таким образом, psl-описание решения является связующим звеном между неформальным описанием политики и ее реализацией.

Язык PSL позволяет использовать различные структуры данных и комбинировать несколько моделей безопасности.

В начало

Управление доступом к ресурсам

Виды ресурсов

В KasperskyOS есть два вида ресурсов:

  • Системные ресурсы, которыми управляет ядро. К ним относятся, например, процессы, регионы памяти, прерывания.
  • Пользовательские ресурсы, которыми управляют процессы. Примеры пользовательских ресурсов: файлы, устройства ввода-вывода, накопители данных.

Дескрипторы

Как системные, так и пользовательские ресурсы идентифицируются дескрипторами (англ. handles). Получая дескриптор, процесс получает доступ к ресурсу, который этот дескриптор идентифицирует. Процессы могут передавать дескрипторы другим процессам. Один и тот же ресурс может идентифицироваться несколькими дескрипторами, которые используют разные процессы.

Идентификаторы безопасности (SID)

Для системных и пользовательских ресурсов ядро KasperskyOS назначает идентификаторы безопасности. Идентификатор безопасности (англ. Security Identifier, SID) – это глобальный уникальный идентификатор ресурса (то есть у ресурса есть только один SID, а дескрипторов может быть несколько). Модуль безопасности Kaspersky Security Module идентифицирует ресурсы по их SID.

При передаче IPC-сообщения, содержащего дескрипторы, ядро так изменяет это сообщение, что на этапе проверки модулем безопасности оно содержит значения SID вместо дескрипторов. Когда IPC-сообщение будет доставлено получателю, оно будет содержать дескрипторы.

У ядра так же, как и у ресурсов, есть SID.

Контекст безопасности

Технология Kaspersky Security System позволяет применять механизмы безопасности, которые принимают на вход значения SID. При применении таких механизмов модуль безопасности Kaspersky Security Module различает ресурсы (и ядро KasperskyOS) и связывает с ними контексты безопасности. Контекст безопасности представляет собой данные, ассоциированные с SID, которые используются модулем безопасности для принятия решений.

Содержимое контекста безопасности зависит от используемых моделей безопасности. Контекст безопасности может содержать, например, состояние ресурса, уровни целостности субъектов и/или объектов доступа. Если контекст безопасности хранит состояние ресурса, это позволяет, например, разрешить выполнять действия над ресурсом, если только этот ресурс находится в каком-либо конкретном состоянии.

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

Управление доступом к ресурсам ядром KasperskyOS

Ядро KasperskyOS управляет доступом к ресурсам одновременно двумя взаимодополняющими способами: выполняя решения модуля безопасности Kaspersky Security Module и реализуя механизм безопасности на основе мандатных ссылок (англ. Object Capability, OCap).

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

Управление доступом к ресурсам поставщиками ресурсов

Процессы, которые управляют пользовательскими ресурсами и доступом к этим ресурсам для других процессов, являются поставщиками ресурсов. (Поставщиками ресурсов являются, например, драйверы.) Поставщики управляют доступом к ресурсам двумя взаимодополняющими способами: выполняя решения модуля безопасности Kaspersky Security Module и используя механизм OCap, который реализуется ядром KasperskyOS.

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

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

В начало

Надежность TCB

Кибериммунная система должна предоставлять гарантии надежности всей доверенной вычислительной базы (TCB). Такие гарантии могут быть предоставлены, только если доверенная вычислительная база достаточно компактна. Ниже мы рассмотрим, как это требование реализуется в решениях на базе KasperskyOS.

В этом разделе

Микроядерная архитектура

Надежность доверенных компонентов

В начало

Микроядерная архитектура

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

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

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

Вызов служб ядра KasperskyOS (например, для создания потока или выделения памяти) выполняется с помощью того же механизма IPC и того же системного вызова Call(), что и вызов методов другой сущности. С этой точки зрения ядро KasperskyOS предстает отдельной сущностью, которая реализует интерфейсы, описанные на языке IDL.

В начало

Надежность доверенных компонентов

Доверенная вычислительная база решения может включать в себя, помимо микроядра KasperskyOS и модуля безопасности, разнообразные доверенные компоненты. В зависимости от целей и предположений безопасности, к доверенным компонентам могут относиться, например, драйверы устройств или поставщики ресурсов. Архитектура и набор инструментов KasperskyOS позволяют повысить надежность доверенных компонентов.

Вынос доверенного компонента в отдельную сущность

Разработчик решения может повысить надежность TCB, уменьшив объем доверенных компонентов. Для этого их следует отделить от остального (недоверенного) кода, т.е. вынести в отдельные сущности. В составе KasperskyOS Community Edition поставляются транспортные библиотеки и инструменты генерации транспортного кода, позволяющие почти любой компонент реализовать как отдельную сущность, все взаимодействия с которой контролируются.

Создание дубликатов компонента

Ещё один способ повысить надежность TCB – это ограничение влияния недоверенных компонентов на доверенные путем разделения их информационных потоков. Для этого один и тот же компонент может быть независимо использован в составе нескольких сущностей. Например, за реализации файловых систем и сетевого стека в KasperskyOS отвечает компонент VFS. Если включить экземпляры VFS в разные сущности, каждая из них будет работать с собственной реализацией файловой системы и/или сетевого стека. Таким образом достигается разделение информационных потоков доверенных и недоверенных сущностей и, соответственно, увеличение надежности TCB.

Способ разделения пользовательского кода на доверенный и недоверенный зависит от целей и предположений безопасности конкретного решения.

В начало

Образ решения на базе KasperskyOS

Загружаемый образ конечного решения на базе KasperskyOS содержит:

  • образ ядра KasperskyOS;
  • модуль безопасности;
  • сущность Einit, которая запускает остальные сущности;
  • все сущности, входящие в решение (включая драйверы, служебные сущности и сущности, реализующие бизнес-логику).

Все сущности расположены в образе файловой системы ROMFS.

Сборка образа решения

Образ решения собирается при помощи специального скрипта, который поставляется в составе KasperskyOS Community Edition.

Образ ядра KasperskyOS, а также служебные сущности и сущности-драйверы поставляются в составе KasperskyOS Community Edition. Модуль безопасности и сущность Einit собираются под каждое конкретное решение.

Запуск решения

Запуск решения происходит следующим образом:

  1. Загрузчик загружает ядро KasperskyOS.
  2. Ядро находит и загружает модуль безопасности.
  3. Ядро запускает сущность Einit.
  4. Сущность Einit запускает все остальные сущности, входящие в решение.
В начало

Начало работы

Этот раздел содержит информацию, необходимую для начала работы с KasperskyOS Community Edition.

В этом разделе

Установка и удаление

Настройка среды разработки

Сборка и запуск примеров

В начало

Установка и удаление

Установка

KasperskyOS Community Edition поставляется в виде deb-пакета. Для установки KasperskyOS Community Edition мы рекомендуем использовать установщик пакетов gdebi.

Для развертывания пакета с помощью gdebi запустите с root-правами команду:

$ gdebi <путь-к-deb-пакету>

Пакет будет установлен в директорию /opt/KasperskyOS-Community-Edition-<version>.

Для удобства работы вы можете добавить путь к бинарным файлам инструментов KasperskyOS Community Edition в переменную PATH, это позволит работать с утилитами через терминал из любой директории:

$ export PATH=$PATH:/opt/KasperskyOS-Community-Edition-<version>/toolchain/bin

Удаление

Для удаления KasperskyOS Community Edition выполните с root-правами команду:

$ sudo apt-get remove --purge kasperskyos-community-edition

При этом будут удалены все установленные файлы в директории /opt/KasperskyOS-Community-Edition-<version>.

В начало

Настройка среды разработки

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

Настройка редактора кода

Для упрощения процесса разработки решений на базе KasperskyOS перед началом работы рекомендуется:

  • Установить в редакторе кода расширения и плагины для используемых языков программирования (C и/или C++).
  • Добавить заголовочные файлы, поставляемые в KasperskyOS Community Edition, в проект разработки.

    Заголовочные файлы расположены в следующей директории: /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

Пример настройки Visual Studio Code

Например, работа с исходным кодом при разработке под KasperskyOS может проводиться в Visual Studio Code.

Для более удобной навигации по коду проекта, включая системный API, необходимо выполнить следующие действия:

  1. Создайте новую рабочую область (workspace) или откройте существующую рабочую область в Visual Studio Code.

    Рабочая область может быть открыта неявно, с помощью пунктов меню File > Open folder.

  2. Убедитесь, что расширение C/C++ for Visual Studio Code установлено.
  3. В меню View выберите пункт Command Palette.
  4. Выберите пункт C/C++: Edit Configurations (UI).
  5. В поле Include path добавьте путь /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.
  6. Закройте окно C/C++ Configurations.
В начало

Сборка примеров

Сборка примеров осуществляется с помощью системы сборки CMake, входящей в состав KasperskyOS Community Edition.

Код примеров и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples

Сборку примеров нужно выполнять в домашней директории, поэтому директорию с примером, который требуется собрать, нужно скопировать из /opt/KasperskyOS-Community-Edition-<version>/examples в домашнюю директорию.

Сборка примеров для запуска на QEMU

Чтобы выполнить сборку примера, перейдите в директорию с примером и выполните команду:

$ sudo ./cross-build.sh

В результате работы скрипта cross-build.sh создается образ решения на базе KasperskyOS, который включает пример. Файл образа решения kos-qemu-image сохраняется в директории <название примера>/build/einit.

Сборка примеров для запуска на Raspberry Pi 4 B

Чтобы выполнить сборку примера:

  1. Перейдите в директорию с примером.
  2. Откройте файл скрипта cross-build.sh в текстовом редакторе.
  3. В последней строке скрипта замените команду make sim на команду make kos-image.
  4. Сохраните файл скрипта, а затем выполните команду:

    $ sudo ./cross-build.sh

В результате работы скрипта cross-build.sh создается образ решения на базе KasperskyOS, который включает пример. Файл образа решения kos-image сохраняется в директории <название примера>/build/einit.

В начало

Запуск примеров на QEMU

Запуск примеров на QEMU в Linux с графической оболочкой

Запуск примера на QEMU в Linux с графической оболочкой осуществляется скриптом cross-build.sh, который также выполняет сборку примера. Чтобы запустить скрипт, перейдите в директорию с примером и выполните команду:

$ sudo ./cross-build.sh

Запуск некоторых примеров требует использования дополнительных параметров QEMU. Команды для запуска таких примеров приведены в описаниях этих примеров.

Запуск примеров на QEMU в Linux без графической оболочки

Чтобы запустить пример на QEMU в Linux без графической оболочки, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -serial stdio -kernel kos-qemu-image

В начало

Запуск примеров на Raspberry Pi 4 B

Коммутация компьютера и Raspberry Pi 4 B

Чтобы видеть вывод примеров на компьютере, выполните следующие действия:

  1. Соедините пины преобразователя USB-UART на базе FT232 с соответствующими GPIO-пинами Raspberry Pi 4 B (см. рис. ниже).

    RPI_USB

    Схема соединения преобразователя USB-UART и Raspberry Pi 4 B

  2. Соедините USB-порт компьютера и преобразователь USB-UART.
  3. Установите PuTTY или другую аналогичную программу для чтения данных из COM-порта. Настройте параметры следующим образом: bps = 115200, data bits = 8, stop bits = 1, parity = none, flow control = none.

Чтобы компьютер и Raspberry Pi 4 B могли взаимодействовать через сеть Ethernet, выполните следующие действия:

  1. Соедините сетевые карты компьютера и Raspberry Pi 4 B с коммутатором или друг с другом.
  2. Выполните настройку сетевой карты компьютера, чтобы ее IP-адрес был в одной подсети с IP-адресом сетевой карты Raspberry Pi 4 B (параметры сетевой карты Raspberry Pi 4 B задаются в файле dhcpcd.conf, который находится по пути <название примера>/resources/...).

Подготовка загрузочной SD-карты для Raspberry Pi 4 B

Загрузочную SD-карту для Raspberry Pi 4 B можно подготовить автоматически и вручную.

Чтобы подготовить загрузочную SD-карту автоматически, подключите SD-карту к компьютеру и выполните следующие команды:

# Следующая команда создает файл образа загрузочного

# носителя (*.img).

$ sudo /opt/KasperskyOS-Community-Edition-<version>/examples/rpi4_prepare_fs_image.sh

# В следующей команде path_to_img – путь к файлу образа

# загрузочного носителя (этот путь выводится по окончании

# выполнения предыдущей команды), [X] – последний символ

# в имени блочного устройства для SD-карты.

$ sudo dd bs=4M if=path_to_img of=/dev/sd[X] conv=fsync

Чтобы подготовить загрузочную SD-карту вручную, выполните следующие действия:

  1. Выполните сборку загрузчика U-Boot для платформы ARMv7, который будет автоматически запускать пример. Для этого выполните следующие команды:

    $ sudo apt install gcc-arm-linux-gnueabi gcc-arm-linux-gnueabihf git bison flex

    $ git clone https://github.com/u-boot/u-boot.git u-boot-armv7

    $ cd u-boot-armv7 && git checkout tags/v2020.10

    $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- rpi_4_32b_defconfig

    # В меню, которое появится при выполнении следующей команды, включите

    # флаг Enable a default value for bootcmd. В поле bootcmd value введите

    # значение fatload mmc 0 ${loadaddr} kos-image; bootelf ${loadaddr}.

    # Затем выйдите из меню, сохранив параметры.

    $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

    $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- u-boot.bin

  2. Отформатируйте SD-карту. Для этого подключите SD-карту к компьютеру и выполните следующие команды:

    $ wget https://downloads.raspberrypi.org/raspbian_lite_latest

    $ unzip raspbian_lite_latest

    # В следующей команде [X] – последний символ в имени блочного устройства

    # для SD-карты.

    $ sudo dd bs=4M if=$(ls *raspbian*lite.img) of=/dev/sd[X] conv=fsync

  3. Скопируйте загрузчик U-Boot на SD-карту, выполнив следующие команды:

    # В следующих командах путь ~/mnt/fat32 используется для примера. Вы

    # можете использовать другой путь.

    $ mkdir -p ~/mnt/fat32

    # В следующей команде [X] – последний буквенный символ в имени блочного

    # устройства для раздела на отформатированной SD-карте.

    $ sudo mount /dev/sd[X]1 ~/mnt/fat32/

    $ sudo cp u-boot.bin ~/mnt/fat32/u-boot.bin

  4. Скопируйте конфигурационный файл для загрузчика U-Boot на SD-карту. Для этого перейдите в директорию /opt/KasperskyOS-Community-Edition-<version>/examples и выполните следующие команды:

    $ sudo cp config.txt ~/mnt/fat32/config.txt

    $ sync

    $ sudo umount ~/mnt/fat32

Запуск примера на Raspberry Pi 4 B

Чтобы запустить пример на Raspberry Pi 4 B, выполните следующие действия:

  1. Перейдите в директорию с примером и соберите пример.
  2. Скопируйте на загрузочную SD-карту образ решения на базе KasperskyOS. Для этого подключите загрузочную SD-карту к компьютеру и выполните следующие команды:

    # В следующей команде [X] – последний буквенный символ в имени блочного

    # устройства для раздела на загрузочной SD-карте.

    # В следующих командах путь ~/mnt/fat32 используется для примера. Вы

    # можете использовать другой путь.

    $ sudo mount /dev/sd[X]1 ~/mnt/fat32/

    $ sudo cp build/einit/kos-image ~/mnt/fat32/kos-image

    $ sync

    $ sudo umount ~/mnt/fat32

  3. Подключите загрузочную SD-карту к Raspberry Pi 4 B.
  4. Подайте питание на Raspberry Pi 4 B и дождитесь, пока запустится пример.

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

В начало

Часть 1. Простейшее приложение (POSIX)

В состав KasperskyOS Community Edition входит набор библиотек (libc, libm, libpthread), которые обеспечивают частичную совместимость разрабатываемых приложений с набором стандартов POSIX.

В этой части руководства рассматриваются:

  • вывод строки на экран с помощью fprintf();
  • использование компонента VFS для работы с сетью и файловыми системами;
  • создание инициализирующей сущности Einit;
  • ограничения поддержки POSIX.

Чтобы сделать изложение более простым, пример в этой части руководства собирается без модуля ksm.module. Поэтому при запуске примера выдается предупреждение WARNING! Booting an insecure kernel!. Политика безопасности решения, использование политик безопасности и сборка модуля ksm.module рассматриваются в третьей части руководства.

В этом разделе справки

Пример hello

VFS: работа с файлами и сетью

Ограничения поддержки POSIX

В начало

Пример hello

По сложившейся традиции в мире разработки ПО, первое, с чего стоит начать знакомство с любой технологией – это поприветствовать с помощью нее мир. Не будем нарушать эту традицию в KasperskyOS и начнем с примера, выводящего на экран строку Hello world!.

KasperskyOS позволяет разрабатывать решения как на языке C, так и на C++.

Код hello.c выглядит привычным и простым для разработчика на языке C – он полностью совместим с POSIX:

hello.c

#include <stdio.h>

#include <stdlib.h>

int main(int argc, const char *argv[])

{

fprintf(stderr,"Hello world!\n");

return EXIT_SUCCESS;

}

Чтобы запустить файл Hello в KasperskyOS, понадобится несколько дополнительных действий.

Разработка приложений под KasperskyOS имеет следующие особенности:

Во-первых, каждая сущность (так в KasperskyOS называются приложения и соответствующие им процессы) должна быть статически описана. Описание содержится в файлах с расширениями edl, cdl и idl, которые используются для сборки решения. Минимально возможное описание сущности – EDL-файл, в котором указано имя сущности. Все сущности, разрабатываемые в первой части руководства, имеют минимальное статическое описание (только EDL-файл с именем сущности).

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

EDL-описание сущности Hello

Статическое описание сущности Hello состоит из единственного файла Hello.edl, в котором необходимо прописать имя сущности:

Hello.edl

/* После ключевого слова "entity" указано имя сущности. */

entity Hello

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

Во второй части руководства показаны примеры более сложных EDL-описаний, а также появляются CDL- и IDL-описания.

Создание инициализирующей сущности Einit

При загрузке KasperskyOS ядро запускает сущность с именем Einit. Сущность Einit запускает все остальные сущности, входящие в решение, то есть служит инициализирующей сущностью.

В составе пакета инструментов KasperskyOS Community Edition поставляется утилита einit, которая позволяет сгенерировать код инициализирующей сущности (einit.c) на основе init-описания. В приведенном ниже примере файл с init-описанием называется init.yaml, хотя может иметь любое имя.

Подробнее см. "Запуск сущностей".

Для того чтобы сущность Hello запустилась после загрузки операционной системы, достаточно указать ее имя в файле init.yaml и собрать из него сущность Einit.

init.yaml

entities:

# Запустить сущность "Hello".

- name: Hello

Схема сборки примера hello

Общая схема сборки примера hello выглядит следующим образом:

Сборка и запуск примера hello

См. "Сборка и запуск примеров".

В начало

VFS: обзор

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

vfs_overview

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

  • одну или несколько файловых систем;
  • сетевой стек;
  • сетевой стек и сетевой драйвер.

Компонент VFS можно использовать как напрямую (путем статической компоновки), так и через IPC (как отдельную сущность). Использование функциональности VFS по IPC позволяет разработчику решения:

  • контролировать вызовы методов для работы с сетью и файловыми системами через политику безопасности решения;
  • соединить несколько клиентских сущностей с одной сущностью VFS;
  • соединить одну клиентскую сущность с двумя сущностями VFS для раздельной работы с сетью и файловыми системами.
В начало

Сборка сущности VFS

В составе KasperskyOS Community Edition не поставляется готовый образ сущности, содержащей компонент VFS. Разработчик решения может самостоятельно собрать одну или несколько сущностей, включив в каждую из них именно ту функциональность VFS, которая необходима в решении.

См. также примеры multi_vfs_dhcpcd, multi_vfs_dns_client и multi_vfs_ntpd в составе KasperskyOS Community Edition.

Сборка сетевой VFS

Для сборки "сетевой" сущности VFS, содержащей сетевой драйвер, необходимо файл с функцией main() скомпоновать с библиотеками vfs_server, vfs_net и dnet_implementation:

CMakeLists.txt (фрагмент)

target_link_libraries (Net1Vfs ${vfs_SERVER_LIB}

${vfs_NET_LIB}

${dnet_IMPLEMENTATION_LIB})

set_target_properties (Net1Vfs PROPERTIES ${blkdev_ENTITY}_REPLACEMENT "")

vfs_net1

Использование "сетевой" сущности VFS, скомпонованной с сетевым драйвером

Чтобы использовать сетевой драйвер по IPC (как отдельную сущность), необходимо вместо dnet_implementation использовать библиотеку dnet_client:

CMakeLists.txt (фрагмент)

target_link_libraries (Net2Vfs ${vfs_SERVER_LIB}

${vfs_NET_LIB}

${dnet_CLIENT_LIB})

set_target_properties (Net2Vfs PROPERTIES ${blkdev_ENTITY}_REPLACEMENT "")

vfs_net2

Использование "сетевой" сущности VFS и сетевого драйвера в виде отдельной сущности

Некоторые сетевые функции, а также вывод в stdout используют файловые операции. Для корректной работы этих функций требуется при сборке добавить библиотеку vfs_implementation вместо vfs_net.

Сборка файловой VFS

Для сборки "файловой" сущности VFS необходимо файл с функцией main() скомпоновать с библиотеками vfs_server и vfs_fs, а также библиотеками реализации файловых систем:

CMakeLists.txt (фрагмент)

target_link_libraries (VfsFs

${vfs_SERVER_LIB}

${LWEXT4_LIB}

${vfs_FS_LIB})

set_target_properties (VfsFs PROPERTIES ${blkdev_ENTITY}_REPLACEMENT ${ramdisk_ENTITY})

В этом примере сущность VFS подготовлена для соединения с сущностью ramdisk-драйвера.

Драйвер блочного устройства не может быть скомпонован с VFS и всегда используется через IPC:

vfs_fs

Использование "файловой" сущности VFS и драйвера в виде отдельной сущности

При необходимости можно собрать сущность VFS, содержащую как сетевой стек, так и файловые системы. Для этого необходимо использовать библиотеки vfs_server, vfs_implementation, dnet_implementation (или dnet_client), а также библиотеки реализации файловых систем.

В начало

Сущность Env

Служебная сущность Env предназначена для передачи запускаемым сущностям аргументов и переменных окружения. При запуске каждая сущность автоматически отправляет запрос сущности Env и получает необходимые данные.

Включение сущности Env в решение позволяет монтировать файловые системы при запуске VFS, соединять одну клиентскую сущность с двумя сущностями VFS и решать многие другие задачи.

Чтобы использовать сущность Env в своем решении, необходимо:

1. Разработать код сущности Env, используя макросы из env/env.h.

2. Собрать образ сущности, скомпоновав ее с библиотекой env_server.

3. В init-описании указать, что необходимо запустить сущность Env и соединить с ней выбранные сущности (Env при этом является сервером). Имя канала задается макросом ENV_SERVICE_NAME, объявленным в файле env/env.h.

4. Включить образ сущности Env в образ решения.

Код сущности Env

В коде сущности Env используются следующие макросы и функции, объявленные в файле env/env.h:

  • ENV_REGISTER_ARGS(name,argarr) – сущности с именем name будут переданы аргументы из массива argarr;
  • ENV_REGISTER_VARS(name,envarr) – сущности с именем name будут переданы переменные окружения из массива envarr;
  • ENV_REGISTER_PROGRAM_ENVIRONMENT(name,argarr,envarr) – сущности с именем name будут переданы как аргументы, так и переменные окружения;
  • envServerRun() – инициализировать серверную часть сущности, чтобы она могла отвечать на запросы.

Пример:

env.c

#include <env/env.h>

#include <stdlib.h>

int main(int argc, char** argv)

{

const char* NetVfsArgs[] = {

"-l", "devfs /dev devfs 0",

"-l", "romfs /etc romfs 0"

};

ENV_REGISTER_ARGS("VFS", NetVfsArgs);

envServerRun();

return EXIT_SUCCESS;

}

Пример init.yaml для использования сущности Env

В следующем примере сущность Client будет соединена с сущностью Env, образ которой находится в папке env:

init.yaml

entities:

- name: env.Env

- name: Client

connections:

- target: env.Env

id: {var: ENV_SERVICE_NAME, include: env/env.h}

В начало

Соединение сущности-клиента с одной и двумя сущностями VFS

Вызовы сетевых и файловых POSIX-функций можно перенаправить в два отдельных компонента VFS, соединив сущность-клиент с двумя разными сущностями VFS. Если такое разделение информационных потоков не требуется (например, если клиент работает только с сетью), достаточно соединить сущность-клиент с одной сущностью VFS.

Соединение с одной сущностью VFS

Имя IPC-канала между сущностью-клиентом и сущностью VFS должно задаваться макросом _VFS_CONNECTION_ID, объявленным в файле vfs/defs.h. При этом как "сетевые", так и "файловые" вызовы будут направляться в эту сущность VFS.

Пример:

init.yaml

- name: ClientEntity

connections:

- target: VfsEntity

id: {var: _VFS_CONNECTION_ID, include: vfs/defs.h}

- name: VfsEntity

Соединение с двумя сущностями VFS

Пусть сущность-клиент соединена с двумя разными сущностями VFS – назовем их "сетевая" VFS и "файловая" VFS.

Чтобы "сетевые" вызовы из сущности-клиента направлялись только в "сетевую" VFS, используется переменная окружения _VFS_NETWORK_BACKEND:

  • Для "сетевой" сущности VFS: _VFS_NETWORK_BACKEND=server:<имя IPC-канала до "сетевой" VFS>
  • Для сущности-клиента: _VFS_NETWORK_BACKEND=client: <имя IPC-канала до "сетевой" VFS>

Для направления "файловых" вызовов используется аналогичная переменная окружения _VFS_FILESYSTEM_BACKEND:

  • Для "файловой" сущности VFS: _VFS_FILESYSTEM_BACKEND=server:<имя IPC-канала до "файловой" VFS>
  • Для сущности-клиента: _VFS_FILESYSTEM_BACKEND=client: <имя IPC-канала до "файловой" VFS>

В результате функции для работы с сетью и файлами будут направляться в две разные сущности VFS.

В следующем примере сущность Client соединена с двумя сущностями VFS – "сетевой" VfsFirst и "файловой" VfsSecond:

init.yaml

entities:

- name: Env

- name: Client

connections:

- target: Env

id: {var: ENV_SERVICE_NAME, include: env/env.h}

- target: VfsFirst

id: VFS1

- target: VfsSecond

id: VFS2

- name: VfsFirst

connections:

- target: Env

id: {var: ENV_SERVICE_NAME, include: env/env.h}

- name: VfsSecond

connections:

- target: Env

id: {var: ENV_SERVICE_NAME, include: env/env.h}

Код сущности Env:

env.c

#include <env/env.h>

#include <stdlib.h>

int main(void)

{

const char* vfs_first_args[] = { "_VFS_NETWORK_BACKEND=server:VFS1" };

ENV_REGISTER_VARS("VfsFirst", vfs_first_args);

const char* vfs_second_args[] = { "_VFS_FILESYSTEM_BACKEND=server:VFS2" };

ENV_REGISTER_VARS("VfsSecond", vfs_second_args);

const char* client_envs[] = { "_VFS_NETWORK_BACKEND=client:VFS1", "_VFS_FILESYSTEM_BACKEND=client:VFS2" };

ENV_REGISTER_VARS("Client", client_envs);

envServerRun();

return EXIT_SUCCESS;

}

См. также примеры multi_vfs_dhcpcd, multi_vfs_dns_client и multi_vfs_ntpd в составе KasperskyOS Community Edition.

В начало

Монтирование файловых систем при запуске VFS

Компонент VFS по умолчанию обеспечивает доступ к:

  • Файловой системе RAMFS. По умолчанию RAMFS монтирована в корневой каталог.
  • Объектному ROMFS-хранилищу. Хранилище содержит неисполняемые (в т.ч. конфигурационные) файлы, добавленные при сборке в образ решения. По умолчанию файловая система ROMFS не монтирована, но доступ к хранилищу возможен косвенным образом – например, через аргумент -f.

Если требуется монтировать другие файловые системы, это можно сделать как после запуска VFS, используя вызов mount(), так и непосредственно в момент запуска сущности VFS, передав ей следующие аргументы и переменные окружения:

  • -l <запись в формате fstab>

    Аргумент -l позволяет монтировать файловую систему.

  • -f <путь к файлу fstab>

    Аргумент -f позволяет передать файл с записями в формате fstab для монтирования файловых систем. Файл будет искаться в ROMFS-хранилище. Если переменная UMNAP_ROMFS определена, то файл будет искаться на файловой системе, смонтированной с помощью переменной ROOTFS.

  • UNMAP_ROMFS

    Если переменная UNMAP_ROMFS определена, то ROMFS-хранилище будет удалено. Это позволяет сэкономить память и изменить поведение при использовании аргумента -f.

  • ROOTFS = <запись в формате fstab>

    Переменная ROOTFS позволяет монтировать файловую систему в корневой каталог. В комбинации с переменной UNMAP_ROMFS и аргументом -f позволяет искать fstab-файл на монтированной файловой системе, а не в ROMFS-хранилище.

Пример:

env.c

#include <env/env.h>

#include <stdlib.h>

int main(int argc, char** argv)

{

/* Для сущности Vfs1 будут монтированы файловые системы devfs и romfs. */

const char* Vfs1Args[] = {

"-l", "devfs /dev devfs 0",

"-l", "romfs /etc romfs 0"

};

ENV_REGISTER_ARGS("Vfs1", Vfs1Args);

/* Для сущности Vfs2 будут монтированы файловые системы, заданные через файл /etc/dhcpcd.conf, который расположен в ROMFS-хранилище. */

const char* Vfs2Args[] = { "-f", "/etc/dhcpcd.conf" };

ENV_REGISTER_ARGS("Vfs2", Vfs2Args);

/* Для сущности Vfs3 в корневой каталог будет монтирована файловая система ext2, на которой будет найден файл /etc/fstab для монтирования дополнительных файловых систем. ROMFS-хранилище будет удалено. */

const char* Vfs3Args[] = { "-f", "/etc/fstab" };

const char* Vfs3Envs[] = {

"ROOTFS=ramdisk0,0 / ext2 0",

"UNMAP_ROMFS=1"

};

ENV_REGISTER_PROGRAM_ENVIRONMENT("Vfs3", Vfs3Args, Vfs3Envs);

envServerRun();

return EXIT_SUCCESS;

}

См. также примеры net_with_separate_vfs, net2_with_separate_vfs, multi_vfs_dhcpcd, multi_vfs_dns_client и multi_vfs_ntpd в составе KasperskyOS Community Edition.

В начало

Ограничения поддержки POSIX

В KasperskyOS ограниченно реализован интерфейс POSIX с ориентацией на стандарт POSIX.1-2008 (без поддержки XSI). Прежде всего ограничения связаны с обеспечением безопасности.

Ограничения затрагивают:

  • взаимодействие между процессами;
  • взаимодействие между потоками выполнения (threads) посредством сигналов;
  • стандартный ввод-вывод;
  • асинхронный ввод-вывод;
  • использование робастных мьютексов;
  • работу с терминалом;
  • использование оболочки;
  • манипуляции с дескрипторами файлов.

Ограничения представлены:

  • нереализованными интерфейсами;
  • интерфейсами, которые реализованы с отклонениями от стандарта POSIX.1-2008;
  • интерфейсами-заглушками, которые не выполняют никаких действий, кроме присвоения переменной errno значения ENOSYS и возвращения значения -1.

В KasperskyOS сигналы не могут прервать системные вызовы Call(), Recv(), Reply(), которые обеспечивают работу библиотек, реализующих интерфейс POSIX.

Ядро KasperskyOS не посылает сигналы.

Ограничения взаимодействия между процессами

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

fork()

Создать новый (дочерний) процесс.

Заглушка

unistd.h

pthread_

atfork()

Зарегистрировать обработчики, которые вызываются перед и после создания дочернего процесса.

Не реализован

pthread.h

wait()

Ожидать остановки или завершения дочернего процесса.

Заглушка

sys/wait.h

waitid()

Ожидать изменения состояния дочернего процесса.

Не реализован

sys/wait.h

waitpid()

Ожидать остановки или завершения дочернего процесса.

Заглушка

sys/wait.h

execl()

Запустить исполняемый файл.

Заглушка

unistd.h

execle()

Запустить исполняемый файл.

Заглушка

unistd.h

execlp()

Запустить исполняемый файл.

Заглушка

unistd.h

execv()

Запустить исполняемый файл.

Заглушка

unistd.h

execve()

Запустить исполняемый файл.

Заглушка

unistd.h

execvp()

Запустить исполняемый файл.

Заглушка

unistd.h

fexecve()

Запустить исполняемый файл.

Заглушка

unistd.h

setpgid()

Перевести процесс в другую группу или создать группу.

Заглушка

unistd.h

setsid()

Создать сессию.

Не реализован

unistd.h

getpgrp()

Получить идентификатор группы для вызывающего процесса.

Не реализован

unistd.h

getpgid()

Получить идентификатор группы.

Заглушка

unistd.h

getppid()

Получить идентификатор родительского процесса.

Не реализован

unistd.h

getsid()

Получить идентификатор сессии.

Заглушка

unistd.h

times()

Получить значения времени для процесса и его потомков.

Заглушка

sys/times.h

kill()

Послать сигнал процессу или группе процессов.

Можно посылать только сигнал SIGTERM. Параметр pid игнорируется.

signal.h

pause()

Ожидать сигнала.

Не реализован

unistd.h

sigpending()

Проверить наличие полученных заблокированных сигналов.

Не реализован

signal.h

sigprocmask()

Получить и изменить набор заблокированных сигналов.

Заглушка

signal.h

sigsuspend()

Ожидать сигнала.

Заглушка

signal.h

sigwait()

Ожидать сигнала из заданного набора сигналов.

Заглушка

signal.h

sigqueue()

Послать сигнал процессу.

Не реализован

signal.h

sigtimedwait()

Ожидать сигнала из заданного набора сигналов.

Не реализован

signal.h

sigwaitinfo()

Ожидать сигнала из заданного набора сигналов.

Не реализован

signal.h

sem_init()

Создать неименованный семафор.

Нельзя создать неименованный семафор для синхронизации между процессами. Если передать функции ненулевое значение через параметр pshared, то она только вернет значение -1 и присвоит переменной errno значение ENOTSUP.

semaphore.h

sem_open()

Создать/открыть именованный семафор.

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

semaphore.h

pthread_

mutexattr_

setpshared()

Задать атрибут мьютекса, который разрешает использование мьютекса несколькими процессами.

Нельзя задать атрибут мьютекса, который разрешает использование мьютекса несколькими процессами. Если передать функции значение PTHREAD_PROCESS_SHARED через параметр pshared, то она только вернет значение ENOSYS.

pthread.h

pthread_

barrierattr_

setpshared()

Задать атрибут барьера, который разрешает использование барьера несколькими процессами.

Нельзя задать атрибут барьера, который разрешает использование барьера несколькими процессами. Если передать функции значение PTHREAD_PROCESS_SHARED через параметр pshared, то она только вернет значение ENOSYS.

pthread.h

pthread_

condattr_

setpshared()

Задать атрибут условной переменной, который разрешает использование условной переменной несколькими процессами.

Нельзя задать атрибут условной переменной, который разрешает использование условной переменной несколькими процессами. Если передать функции значение PTHREAD_PROCESS_SHARED через параметр pshared, то она только вернет значение ENOSYS.

pthread.h

pthread_

rwlockattr_

setpshared()

Задать атрибут объекта блокировки чтения-записи, который разрешает использование объекта блокировки чтения-записи несколькими процессами.

Нельзя задать атрибут объекта блокировки чтения-записи, который разрешает использование объекта блокировки чтения-записи несколькими процессами. Если передать функции значение PTHREAD_PROCESS_SHARED через параметр pshared, то она только вернет значение ENOSYS.

pthread.h

pthread_

spin_init()

Создать спин-блокировку.

Нельзя создать спин-блокировку для синхронизации между процессами. Если передать функции значение PTHREAD_PROCESS_SHARED через параметр pshared, то это значение будет проигнорировано.

pthread.h

shm_open()

Создать или открыть объект разделяемой памяти.

Не реализован

sys/mman.h

mmap()

Отобразить в память.

Нельзя выполнить отображение в память для взаимодействия между процессами. Если передать функции значение MAP_SHARED через параметр flags, то это значение будет проигнорировано. Кроме того, через параметр prot нельзя передавать сочетания флагов PROT_WRITE|PROT_EXEC и PROT_READ|PROT_WRITE|PROT_EXEC. В этом случае функция только возвращает значение MAP_FAILED и присваивает переменной errno значение ENOMEM.

sys/mman.h

mprotect()

Задать права доступа к памяти.

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

sys/mman.h

pipe()

Создать неименованный канал.

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

unistd.h

mkfifo()

Создать специальный файл FIFO (именованный канал).

Заглушка

sys/stat.h

mkfifoat()

Создать специальный файл FIFO (именованный канал).

Не реализован

sys/stat.h

Ограничения взаимодействия между потоками выполнения посредством сигналов

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

pthread_kill()

Послать сигнал потоку выполнения.

Нельзя послать сигнал потоку выполнения. Если передать функции номер сигнала через параметр sig, то она только вернет значение ENOSYS.

signal.h

pthread_sigmask()

Получить и изменить набор заблокированных сигналов.

Заглушка

signal.h

siglongjmp()

Восстановить состояние потока управления и маску сигналов.

Не реализован

setjmp.h

sigsetjmp()

Сохранить состояние потока управления и маску сигналов.

Не реализован

setjmp.h

Ограничения стандартного ввода-вывода

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

dprintf()

Выполнить форматированный вывод в файл.

Не реализован

stdio.h

fmemopen()

Использовать память как поток данных (stream).

Не реализован

stdio.h

open_memstream()

Использовать динамически выделенную память как поток данных (stream).

Не реализован

stdio.h

vdprintf()

Выполнить форматированный вывод в файл.

Не реализован

stdio.h

Ограничения асинхронного ввода-вывода

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

aio_cancel()

Отменить запросы ввода-вывода, которые ожидают обработки.

Не реализован

aio.h

aio_error()

Получить ошибку операции асинхронного ввода-вывода.

Не реализован

aio.h

aio_fsync()

Запросить выполнение операций ввода-вывода.

Не реализован

aio.h

aio_read()

Запросить чтение из файла.

Не реализован

aio.h

aio_return()

Получить статус операции асинхронного ввода-вывода.

Не реализован

aio.h

aio_suspend()

Ожидать выполнения операций асинхронного ввода-вывода.

Не реализован

aio.h

aio_write()

Запросить запись в файл.

Не реализован

aio.h

lio_listio()

Запросить выполнение набора операций ввода-вывода.

Не реализован

aio.h

Ограничения использования робастных мьютексов

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

pthread_mutex_consistent()

Вернуть робастный мьютекс в консистентное состояние.

Не реализован

pthread.h

pthread_mutexattr_getrobust()

Получить атрибут робастности мьютекса.

Не реализован

pthread.h

pthread_mutexattr_setrobust()

Задать атрибут робастности мьютекса.

Не реализован

pthread.h

Ограничения работы с терминалом

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

ctermid()

Получить путь к файлу управляющего терминала.

Функция только возвращает или передает через параметр s пустую строку.

stdio.h

tcsetattr()

Задать параметры терминала.

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

termios.h

tcdrain()

Ожидать завершения вывода.

Функция только возвращает значение -1.

termios.h

tcflow()

Приостановить или возобновить прием или передачу данных.

Приостановка вывода и запуск приостановленного вывода не поддерживаются.

termios.h

tcflush()

Очистить очередь ввода или очередь вывода, или обе эти очереди.

Функция только возвращает значение -1.

termios.h

tcsendbreak()

Разорвать соединение с терминалом на заданное время.

Функция только возвращает значение -1.

termios.h

ttyname()

Получить путь к файлу терминала.

Функция только возвращает нулевой указатель.

unistd.h

ttyname_r()

Получить путь к файлу терминала.

Функция только возвращает значение ошибки.

unistd.h

tcgetpgrp()

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

Функция только возвращает значение -1.

unistd.h

tcsetpgrp()

Задать идентификатор группы процессов, использующих терминал.

Функция только возвращает значение -1.

unistd.h

tcgetsid()

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

Функция только возвращает значение -1.

termios.h

Ограничения работы с оболочкой

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

popen()

Создать дочерний процесс для выполнения команды и канал с этим процессом.

Функция только присваивает переменной errno значение ENOSYS и возвращает значение NULL.

stdio.h

pclose()

Закрыть канал с дочерним процессом, созданным функцией popen(), и ожидать завершения этого дочернего процесса.

Функцию нельзя использовать, так как ее входным параметром является дескриптор потока данных, возвращаемый функцией popen(), которая не может вернуть ничего, кроме значения NULL.

stdio.h

system()

Создать дочерний процесс для выполнения команды.

Заглушка

stdlib.h

wordexp()

Раскрыть строку как в оболочке.

Не реализован

wordexp.h

wordfree()

Освободить память, выделенную для результатов вызова интерфейса wordexp().

Не реализован

wordexp.h

Ограничения манипуляций с дескрипторами файлов

Интерфейс

Назначение

Реализация

Заголовочный файл по стандарту POSIX.1-2008

dup()

Сделать копию дескриптора открытого файла.

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

fcntl.h

dup2()

Сделать копию дескриптора открытого файла.

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

fcntl.h

В начало

Часть 2. Взаимодействие сущностей

В предыдущей части руководства показано, как построить взаимодействие с сущностями, поставляемыми в составе KasperskyOS Community Edition. Для этого достаточно добавить несколько строк в файл init.yaml и подключить клиентскую библиотеку сущности (vfs_remote).

Но как самостоятельно создать серверную сущность (то есть приложение, предоставляющее функциональность другим, клиентским, сущностям)? Для этого потребуется использовать IPC-транспорт, вспомогательные утилиты и библиотеки, поставляемые в составе KasperskyOS Community Edition.

В этой части руководства рассматриваются:

  • механизм взаимодействия сущностей в KasperskyOS;
  • утилиты и библиотеки, реализующие транспорт;
  • пошаговые действия для обмена IPC-сообщениями.

Чтобы сделать изложение более простым, примеры в этой части руководства собираются без модуля ksm.module. Поэтому при запуске примеров выдается предупреждение WARNING! Booting an insecure kernel!. Политика безопасности решения, использование политик безопасности и сборка модуля ksm.module рассматриваются в третьей части руководства.

В этом разделе справки

Инструменты IPC-транспорта

Пример echo

В начало

Инструменты IPC-транспорта

Чтобы организовать взаимодействие сущностей, не требуется "с нуля" реализовывать корректную упаковку и распаковку сообщений. Кроме того, не обязательно писать отдельный код для создания IPC-каналов.

Для решения этих и других проблем организации IPC-транспорта в KasperskyOS есть специальный набор инструментов:

  • Транспорт NkKosTransport
  • EDL-, CDL- и IDL-описания
  • Компилятор NK
  • Init-описание и утилита einit
  • Локатор сервисов (Service Locator)

Совместное использование этих инструментов показано в примере echo.

Транспорт NkKosTransport

Транспорт NkKosTransport является удобной надстройкой над системными вызовами Call, Recv и Reply. Он позволяет работать отдельно с фиксированной частью и ареной сообщений.

Для вызова транспорта используются функции nk_transport_call(), nk_transport_recv() и nk_transport_reply().

Пример:

/* Функция nk_transport_recv () выполняет системный вызов Recv.

* Полученный от клиента запрос помещается в req (фиксированная часть ответа) и

* req_arena (арена ответа). */

nk_transport_recv(&transport.base, (struct nk_message *)&req, &req_arena);

Структура NkKosTransport и методы работы с ней объявлены в файле transport-kos.h:

#include <coresrv/nk/transport-kos.h>

Подробнее о фиксированной части и арене см. "Структура IPC-сообщения".

Подробнее об использовании NkKosTransport см. "Транспорт NkKosTransport".

EDL-, CDL- и IDL-описания

Для описания интерфейсов, которые реализуют серверные сущности, используются языки EDL, CDL и IDL.

Подробнее см. "Описание сущностей, компонентов и интерфейсов (EDL, CDL, IDL)".

Файлы описаний (*.edl, *.cdl и *.idl) при сборке обрабатываются компилятором NK. В результате создаются файлы *.edl.h, *.cdl.h, и *.idl.h, содержащие транспортные методы и типы, используемые как на клиенте, так и на сервере.

Компилятор NK

На основе EDL-, CDL- и IDL-описаний компилятор NK (nk-gen-c) генерирует набор транспортных методов и типов. Транспортные методы и типы нужны для формирования, отправки, приема и обработки IPC-сообщений

Важнейшие транспортные методы:

  • Интерфейсные методы. При вызове на клиенте интерфейсного метода серверу отправляется IPC-запрос для вызова соответствующего метода.
  • Dispatch-методы (диспетчеры). При получении запроса сервер вызывает диспетчер, который в свою очередь вызывает реализацию соответствующего метода.

Важнейшие транспортные типы:

  • Типы, определяющие структуру фиксированной части сообщения. Передаются в интерфейсные методы, диспетчеры и функции транспорта (nk_transport_recv(), nk_transport_reply()).
  • Типы прокси-объектов. Прокси-объект используется как аргумент интерфейсного метода.

Подробнее см. "Сгенерированные методы и типы".

Init-описание и утилита einit

Утилита einit позволяет автоматизировать создание кода инициализирующей сущности Einit. Эта сущность первой запускается при загрузке KasperskyOS и запускает остальные сущности, а также создает каналы (соединения) между ними.

Чтобы сущность Einit при запуске создала соединение между сущностями A и B, в файле init.yaml нужно указать:

init.yaml

# Запустить B

- name: B

# Запустить A

- name: A

  connections:

  # Создать соединение с серверной сущностью B.

  - target: B

  # Имя нового соединения: some_connection_to_B

    id: some_connection_to_B

Подробнее см. "Запуск сущностей".

Локатор сервисов (Service Locator)

Локатор сервисов – библиотека, содержащая следующие функции:

  • ServiceLocatorConnect() – позволяет узнать клиентский IPC-дескриптор канала с указанным именем.
  • ServiceLocatorRegister() – позволяет узнать серверный IPC-дескриптор канала с указанным именем.
  • ServiceLocatorGetRiid() – позволяет узнать RIID (порядковый номер реализации интерфейса) по имени реализации интерфейса.

Значения IPC-дескриптора и RIID используются при инициализации транспорта NkKosTransport.

Чтобы использовать локатор сервисов, нужно в коде сущности подключить файл sl_api.h:

#include <coresrv/sl/sl_api.h>

Подробнее см. "Локатор сервисов (Service Locator)".

В начало

Пример echo

Пример echo демонстрирует использование IPC-транспорта.

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

В этом разделе

О примере echo

Реализация сущности Client в примере echo

Реализация сущности Server в примере echo

Файлы описаний в примере echo

Сборка и запуск примера echo

В начало

О примере echo

Пример echo описывает простейший случай взаимодействия двух сущностей:

  1. Сущность Client передает сущности Server число (value).
  2. Сущность Server изменяет это число и передает новое число (result) сущности Client.
  3. Сущность Client выводит число result на экран.

Чтобы организовать такое взаимодействие сущностей, потребуется:

  1. Соединить сущности Client и Server, используя init-описание.
  2. Реализовать на сервере интерфейс с единственным методом Ping, который имеет один входной аргумент – исходное число (value) и один выходной аргумент – измененное число (result).

    Описание метода Ping на языке IDL:

    Ping(in UInt32 value, out UInt32 result);

  3. Создать файлы статических описаний на языках EDL, CDL и IDL. С помощью компилятора NK сгенерировать файлы, содержащие транспортные методы и типы (прокси-объект, диспетчеры и т.д.).
  4. В коде сущности Client инициализировать все необходимые объекты (транспорт, прокси-объект, структуру запроса и др.) и вызвать интерфейсный метод.
  5. В коде сущности Server подготовить все необходимые объекты (транспорт, диспетчер компонента и диспетчер сущности и др.), принять запрос от клиента, обработать его и отправить ответ.

Пример echo состоит из следующих исходных файлов:

  • client/src/client.c – реализация сущности Client;
  • server/src/server.c – реализация сущности Server;
  • resources/Server.edl, resources/Client.edl, resources/Ping.cdl, resources/Ping.idl – статические описания;
  • init.yaml – init-описание.
В начало

Реализация сущности Client в примере echo

В коде сущности Client используются транспортные типы и методы, которые будут сгенерированы во время сборки решения компилятором NK на основе IDL-описания интерфейса Ping.

Однако чтобы получить необходимые для реализации сущности описания типов и сигнатуры методов, вы можете воспользоваться компилятором NK непосредственно после создания EDL-описания сущности, CDL-описаний компонентов и IDL-описаний используемых интерфейсов взаимодействия. В результате необходимые типы и сигнатуры методов будут объявлены в сгенерированных файлах *.h.

В реализации сущности Client необходимо:

  1. Получить клиентский IPC-дескриптор соединения (канала), используя функцию локатора сервисов ServiceLocatorConnect().

    На вход нужно передать имя IPC-соединения server_connection, заданное ранее в файле init.yaml.

  2. Инициализировать транспорт NkKosTransport, передав полученный IPC-дескриптор в функцию NkKosTransport_Init().
  3. Получить идентификатор необходимого интерфейса (RIID), используя функцию локатора сервисов ServiceLocatorGetRiid().
  4. На вход нужно передать полное имя реализации интерфейса Ping. Оно состоит из имен экземпляра компонента (Echo.ping) и интерфейса (ping), заданных ранее в Server.edl и Ping.cdl.
  5. Инициализировать прокси-объект, передав транспорт и идентификатор интерфейса в функцию Ping_proxy_init().
  6. Подготовить структуры запроса и ответа.
  7. Вызвать интерфейсный метод Ping_Ping(), передав прокси-объект, а также указатели на запрос и ответ.

client.c

#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>

/* Файлы, необходимые для инициализации транспорта. */

#include <coresrv/nk/transport-kos.h>

#include <coresrv/sl/sl_api.h>

/* Описание интерфейса сервера, по которому обращается клиентская сущность. */

#include <echo/Ping.idl.h>

#include <assert.h>

#define EXAMPLE_VALUE_TO_SEND 777

/* Точка входа в клиентскую сущность. */

int main(int argc, const char *argv[])

{

NkKosTransport transport;

struct echo_Ping_proxy proxy;

int i;

fprintf(stderr, "Hello I'm client\n");

/* Получаем клиентский IPC-дескриптор соединения с именем

* "server_connection". */

Handle handle = ServiceLocatorConnect("server_connection");

assert(handle != INVALID_HANDLE);

/* Инициализируем IPC-транспорт для взаимодействия с серверной сущностью. */

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

/* Получаем Runtime Interface ID (RIID) для интерфейса echo.Ping.ping.

* Здесь ping – имя экземпляра компонента echo.Ping,

* echo.Ping.ping - имя реализации интерфейса Ping. */

nk_iid_t riid = ServiceLocatorGetRiid(handle, "echo.Ping.ping");

assert(riid != INVALID_RIID);

/* Инициализируем прокси-объект, указав транспорт (&transport)

* и идентификатор интерфейса сервера (riid). Каждый метод

* прокси-объекта будет реализован как отправка запроса серверу. */

echo_Ping_proxy_init(&proxy, &transport.base, riid);

/* Структуры запроса и ответа */

echo_Ping_Ping_req req;

echo_Ping_Ping_res res;

/* Тестовый цикл. */

req.value = EXAMPLE_VALUE_TO_SEND;

for (i = 0; i < 10; ++i)

{

/* Вызываем интерфейсный метод Ping.

* Серверу будет отправлен запрос для вызова метода Ping интерфейса

* ping_comp.ping_impl с аргументом value. Вызывающий поток блокируется

* до момента получения ответа от сервера. */

if (echo_Ping_Ping(&proxy.base, &req, NULL, &res, NULL) == rcOk)

{

/* Выводим значение result, содержащееся в ответе

* (result - выходной аргумент метода Ping). */

fprintf(stderr, "result = %d\n", (int) res.result);

/* Помещаем полученное значение result в аргумент value

* для повторной отправки серверу в следующей итерации. */

req.value = res.result;

}

else

fprintf(stderr, "Failed to call echo.Ping.Ping()\n");

}

return EXIT_SUCCESS;

}

В начало

Реализация сущности Server в примере echo

В коде сущности Server используются транспортные типы и методы, которые будут сгенерированы во время сборки решения компилятором NK на основе EDL-описания сущности Server.

Однако чтобы получить необходимые для реализации сущности типы и сигнатуры методов, вы можете воспользоваться компилятором NK непосредственно после создания EDL-описания сущности, CDL-описаний компонентов и IDL-описаний используемых интерфейсов взаимодействия. В результате необходимые типы и сигнатуры методов будут объявлены в сгенерированных файлах *.h.

В реализации сущности server необходимо:

  1. Реализовать метод Ping().

    Сигнатура реализации метода Ping() должна в точности совпадать с сигнатурой интерфейсного метода Ping_Ping(), который объявлен в файле Ping.idl.h.

  2. Получить серверный IPC-дескриптор соединения (канала), используя функцию локатора сервисов ServiceLocatorRegister().

    На вход нужно передать имя IPC-соединения server_connection, заданное ранее в файле init.yaml.

  3. Инициализировать транспорт NkKosTransport, передав полученный IPC-дескриптор в функцию NkKosTransport_Init().
  4. Подготовить структуры запроса и ответа.
  5. Инициализировать dispatch-метод (диспетчер) компонента Ping, используя функцию Ping_component_init().
  6. Инициализировать dispatch-метод (диспетчер) сущности Server, используя функцию Server_entity_init().
  7. Получить запрос, вызвав nk_transport_recv().
  8. Обработать полученный запрос, вызвав диспетчер Server_entity_dispatch().

    Диспетчер вызовет необходимую реализацию метода на основе полученного от клиента идентификатора интерфейса (RIID).

  9. Отправить ответ сущности Client, вызвав nk_transport_reply().

server.c

#include <stdio.h>

#include <stdlib.h>

#include <stdbool.h>

/* Файлы, необходимые для инициализации транспорта. */

#include <coresrv/nk/transport-kos.h>

#include <coresrv/sl/sl_api.h>

/* Описания сущности-сервера на языке EDL. */

#include <echo/Server.edl.h>

#include <assert.h>

/* Тип объекта реализующего интерфейс. */

typedef struct IPingImpl {

struct echo_Ping base; // базовый интерфейс объекта

int step; // дополнительные параметры

} IPingImpl;

/* Реализация метода Ping. */

static nk_err_t Ping_impl(struct echo_Ping *self,

const echo_Ping_req *req,

const struct nk_arena* req_arena,

echo_Ping_res* res,

struct nk_arena* res_arena)

{

IPingImpl *impl = (IPingImpl *)self;

/* Значение value, пришедшее в запросе от клиента, инкрементируем на

* величину шага step и помещаем в аргумент result, который будет

* отправлен клиенту в составе ответа от сервера. */

res->Ping.result = req->Ping.value + impl->step;

return NK_EOK;

}

/* Конструктор объекта IPing.

* step - шаг, то есть число, на которое будет увеличиваться входящее значение. */

static struct echo_Ping *CreateIPingImpl(int step)

{

/* Таблица реализаций методов интерфейса IPing. */

static const struct echo_Ping_ops ops = {

.Ping = Ping_impl

};

/* Объект, реализующий интерфейс. */

static struct IPingImpl impl = {

.base = {&ops}

};

impl.step = step;

return &impl.base;

}

/* Точка входа в сервер. */

int main(void)

{

NkKosTransport transport;

ServiceId iid;

/* Получаем серверный IPC-дескриптор соединения "server_connection". */

Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);

assert(handle != INVALID_HANDLE);

/* Инициализируем транспорт до клиента. */

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

/* Подготавливаем структуры запроса к сущности server: фиксированную

* часть и арену. Поскольку ни у одного из методов сущности server нет

* аргументов типа sequence, используются только фиксированные части

* запроса и ответа. Арены фактически не используются. Однако в серверные

* методы транспорта (nk_transport_recv, nk_transport_reply) и

* dispatch-метод server_entity_dispatch необходимо передать валидные

* арены запроса и ответа. */

echo_Server_entity_req req;

char req_buffer[echo_Server_entity_req_arena_size];

struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer));

/* Подготавливаем структуры ответа: фиксированную часть и арену. */

echo_Server_entity_res res;

char res_buffer[echo_Server_entity_res_arena_size];

struct nk_arena res_arena = NK_ARENA_INITIALIZER(res_buffer, res_buffer + sizeof(res_buffer));

/* Инициализируем диспетчер компонента ping. 3 – величина шага,

* то есть число, на которое будет увеличиваться входящее значение. */

echo_Ping_component component;

echo_Ping_component_init(&component, CreateIPingImpl(3));

/* Инициализируем диспетчер сущности server. */

echo_Server_entity entity;

echo_Server_entity_init(&entity, &component);

fprintf(stderr, "Hello I'm server\n");

/* Реализация цикла обработки запросов. */

do

{

/* Сбрасываем буферы с запросом и ответом. */

nk_req_reset(&req);

nk_arena_reset(&req_arena);

nk_arena_reset(&res_arena);

/* Ожидаем поступление запроса к сущности-серверу. */

if (nk_transport_recv(&transport.base, &req.base_, &req_arena) != NK_EOK) {

fprintf(stderr, "nk_transport_recv error\n");

} else {

/* Обрабатываем полученный запрос, вызывая реализацию Ping_impl

* запрошенного интерфейсного метода Ping. */

echo_Server_entity_dispatch(&entity, &req.base_, &req_arena, &res.base_, &res_arena);

}

/* Отправка ответа. */

if (nk_transport_reply(&transport.base, &res.base_, &res_arena) != NK_EOK) {

fprintf(stderr, "nk_transport_reply error\n");

}

}

while (true);

return EXIT_SUCCESS;

}

В начало

Файлы описаний в примере echo

Описание сущности Client

Сущность Client не реализует ни одного интерфейса, поэтому в ее EDL-описании достаточно объявить имя сущности:

Client.edl

/* Описание сущности Client. */

entity echo.Client

Описание сущности Server

Описание сущности Server должно содержать информацию о том, что он реализует интерфейс Ping. С помощью статических описаний нужно "поместить" реализацию интерфейса Ping в новый компонент (например, Ping), а экземпляр этого компонента – в сущность Server.

Сущность Server содержит экземпляр компонента Ping:

Server.edl

/* Описание сущности Server. */

entity echo.Server

/* Server - именованный экземпляр компонента echo.Ping. */

components {

Server: echo.Ping

}

Компонент Ping содержит реализацию интерфейса Ping:

Ping.cdl

/* Описание компонента Ping. */

component echo.Ping

/* ping - реализация интерфейса Ping. */

interfaces {

ping: echo.Ping

}

Пакет Ping содержит объявление интерфейса Ping:

Ping.idl

/* Описание пакета Ping. */

package echo.Ping

interface {

Ping(in UInt32 value, out UInt32 result);

}

Init-описание

Чтобы сущность Client могла вызвать метод сущности Server, между ними требуется создать соединение (IPC-канал).

Для этого в init-описании укажите, что необходимо запустить сущности Client и Server, а также соединить их:

init.yaml

entities:

- name: echo.Client

connections:

- target: echo.Server

id: server_connection

- name: Server

Поскольку сущность Server указана как - target, она будет выступать в роли серверной сущности, т.е. принимать запросы сущности Client и отвечать на них.

Созданный IPC-канал имеет имя server_connection. С помощью локатора сервисов можно получить дескриптор этого канала.

В начало

Сборка и запуск примера echo

См. "Сборка и запуск примеров".

Схема сборки примера echo выглядит следующим образом:

В начало

Часть 3. Политика безопасности решения

В предыдущих частях руководства показано, как реализовать взаимодействие между сущностями. При этом для простоты все решения собирались без модуля безопасности (ksm.module), которым представлена подсистема Kaspersky Security System.

Между тем, именно подсистема Kaspersky Security System контролирует обращения сущностей друг к другу и другие события. Это значит, что решение можно разделить на сущности, управлять их взаимодействиями и, как следствие, повысить безопасность решения.

В этой части руководства рассматриваются:;

  • синтаксис языка PSL;
  • модели безопасности, поддерживаемые в Kaspersky Security System;
  • пример политики безопасности решения.

В этом разделе справки

Общие сведения об описании политики безопасности решения

Синтаксис языка PSL

Модели безопасности

Пример ping

Тестирование политики безопасности решения на языке Policy Assertion Language (PAL)

В начало

Общие сведения об описании политики безопасности решения

В упрощенном представлении описание политики безопасности решения состоит из привязок, которые ассоциируют описания событий безопасности с вызовами методов, предоставляемых объектами моделей безопасности. Объект модели безопасности – это экземпляр класса, определение которого является формальным представлением модели безопасности (в PSL-файле). Формальные представления моделей безопасности содержат сигнатуры методов моделей безопасности, которые определяют допустимость взаимодействий сущностей между собой и с ядром KasperskyOS. Эти методы делятся на два вида:

  • Правила моделей безопасности – это методы моделей безопасности, возвращающие результат "разрешено" или "запрещено". Правила моделей безопасности могут изменять контексты безопасности (о контексте безопасности см. "Управление доступом к ресурсам").
  • Выражения моделей безопасности – это методы моделей безопасности, возвращающие значения, которые могут использоваться как входные данные для других методов моделей безопасности.

Объект модели безопасности предоставляет методы, специфичные для одной модели безопасности, и хранит параметры, используемые этими методами (например, начальное состояние конечного автомата или размер контейнера для каких-либо данных). Один объект может применяться для множества ресурсов. При этом этот объект будет независимо использовать контексты безопасности этих ресурсов. Также несколько объектов одной или разных моделей безопасности может применяться для одного и того же ресурса. В этом случае разные объекты будут использовать контекст безопасности одного ресурса без взаимного влияния.

События безопасности – это сигналы об инициации взаимодействий сущностей между собой и с ядром KasperskyOS. К событиям безопасности относятся следующие события:

  • отправка IPC-запросов клиентами;
  • отправка IPC-ответов серверами или ядром;
  • инициация запусков сущностей ядром или сущностями;
  • запуск ядра;
  • обращения сущностей к модулю безопасности Kaspersky Security Module через интерфейс безопасности.

События безопасности обрабатываются модулем безопасности.

Модели безопасности

В составе KasperskyOS SDK поставляются PSL-файлы, которые описывают следующие модели безопасности:

  • Base – методы, реализующие простейшую логику;
  • Pred – методы, реализующие операции сравнения;
  • Bool – методы, реализующие логические операции;
  • Math – методы, реализующие операции целочисленной арифметики;
  • Struct – методы, обеспечивающие доступ к структурным элементам данных (например, доступ к параметрам интерфейсных методов, передаваемых в IPC-сообщениях);
  • Regex – методы для валидации текстовых данных по регулярным выражениям;
  • HashSet – методы для работы с одномерными таблицами, ассоциированными с ресурсами;
  • StaticMap – методы для работы с двумерными таблицами типа "ключ–значение", ассоциированными с ресурсами;
  • Flow – методы для работы с конечными автоматами, ассоциированными с ресурсами.

Обработка событий безопасности модулем безопасности Kaspersky Security Module

Модуль безопасности Kaspersky Security Module вызывает все методы (правила и выражения) моделей безопасности, связанные с произошедшим событием безопасности. Если все правила вернули результат "разрешено", модуль безопасности возвращает решение "разрешено". Если хотя бы одно правило вернуло результат "запрещено", модуль безопасности возвращает решение "запрещено".

Если хотя бы один метод, связанный с произошедшим событием безопасности, не может быть корректно выполнен, модуль безопасности возвращает решение "запрещено".

Если с произошедшим событием безопасности не связано ни одно правило, модуль безопасности возвращает решение "запрещено". То есть все взаимодействия компонентов решения между собой и с ядром KasperskyOS, которые явно не разрешены политикой безопасности решения, запрещены (принцип Default Deny).

Аудит безопасности

Аудит безопасности (далее также аудит) представляет собой следующую последовательность действий. Модуль безопасности Kaspersky Security Module сообщает ядру KasperskyOS сведения о решениях, принятых этим модулем. Затем ядро передает эти данные системной программе Klog, которая декодирует их и передает системной программе KlogStorage (передача данных осуществляется через IPC). Последняя выводит полученные данные через стандартный вывод или сохраняет в файл.

Данные аудита безопасности (далее данные аудита) – это сведения о решениях модуля безопасности Kaspersky Security Module, которые включают сами решения ("разрешено" или "запрещено"), описания событий безопасности, результаты вызовов методов моделей безопасности, а также данные о некорректности IPC-сообщений.

В начало

Синтаксис языка PSL

Базовые правила

  1. Декларации могут располагаться в файле в любом порядке.
  2. Одна декларация может быть записана в одну или несколько строк. Вторая и последующие строки декларации должны быть записаны с отступами относительно первой строки. Закрывающая фигурная скобка, которая завершает декларацию, может быть записана на уровне первой строки.
  3. В многострочной декларации используются отступы разных размеров, чтобы отразить вложенность конструкций, составляющих эту декларацию. Строки многострочной конструкции, заключенные в фигурные скобки, и открывающая фигурная скобка должны быть записаны с отступом относительно первой строки этой конструкции. Закрывающая фигурная скобка многострочной конструкции может быть записана с отступом или на уровне первой строки конструкции.
  4. Поддерживаются однострочные и многострочные комментарии:

    /* Это комментарий

    И это тоже */

    // Ещё один комментарий

Типы деклараций

В языке PSL есть следующие типы деклараций:

  • описание глобальных параметров политики безопасности решения;
  • включение PSL-файлов;
  • включение EDL-файлов;
  • создание объектов моделей безопасности;
  • привязка методов моделей безопасности к событиям безопасности;
  • описание профилей аудита безопасности.

В этом разделе

Описание глобальных параметров политики безопасности решения

Включение PSL-файлов

Включение EDL-файлов

Создание объектов моделей безопасности

Привязка методов моделей безопасности к событиям безопасности

Описание профилей аудита безопасности

Типы данных в языке PSL

Пример простейшей политики безопасности решения

В начало

Описание глобальных параметров политики безопасности решения

Глобальными являются следующие параметры политики безопасности решения:

  • Execute-интерфейс, через который ядро KasperskyOS обращается к модулю безопасности Kaspersky Security Module, чтобы сообщить о запуске ядра или об инициации запуска сущностей ядром или другими сущностями. Чтобы задать этот интерфейс, нужно использовать декларацию:

    execute: kl.core.Execute

    В настоящее время в KasperskyOS поддерживается только один execute-интерфейс Execute, определенный в файле kl/core/Execute.idl. (Этот интерфейс состоит из одного метода main, который не имеет параметров и не выполняет никаких действий. Метод main зарезервирован для возможного использования в будущем.)

  • [Опционально] Глобальный профиль аудита безопасности и начальный уровень аудита безопасности. Чтобы задать эти параметры, нужно использовать декларацию:

    audit default = <имя профиля аудита безопасности> <уровень аудита безопасности>

    Пример:

    audit default = global 0

    По умолчанию в качестве глобального используется пустой профиль аудита безопасности empty, описанный в файле toolchain/include/nk/base.psl из состава KasperskyOS SDK, и уровень аудита безопасности 0.

В начало

Включение PSL-файлов

Для включения PSL-файла нужно использовать декларацию:

use <ссылка на PSL-файл._>

Ссылка на PSL-файл представляет собой путь к PSL-файлу (без расширения и точки перед ним) относительно директории, которая включена в набор директорий, где компилятор nk-psl-gen-c ищет PSL-, IDL-, CDL-, EDL-файлы. (Этот набор директорий задается параметрами скрипта makekss вида "-I <путь к файлам>".) В качестве разделителя в описании пути используется точка. Декларация завершается последовательностью символов ._.

Пример:

use policy_parts.flow_part._

Эта декларация включает файл flow_part.psl, который находится в директории policy_parts. Директория policy_parts должна находиться в одной из директорий, где компилятор nk-psl-gen-c выполняет поиск PSL-, IDL-, CDL-, EDL-файлов. Например, директория policy_parts может располагаться в одной директории с PSL-файлом, содержащим эту декларацию.

Включение PSL-файла с формальным представлением модели безопасности

Чтобы использовать методы требуемой модели безопасности, нужно включить PSL-файл с формальным представлением этой модели. PSL-файлы с формальными представлениями моделей безопасности находятся в <KOS_KASPERSKY> SDK по пути:

toolchain/include/nk

Пример:

/* Включение файла base.psl с формальным представлением модели

* безопасности Base */

use nk.base._

/* Включение файла flow.psl с формальным представлением модели

* безопасности Flow */

use nk.flow._

/* Компилятор nk-psl-gen-c должен быть настроен на поиск

* PSL-, IDL-, CDL-, EDL-файлов в директории toolchain/include. */

В начало

Включение EDL-файлов

Чтобы включить EDL-файл для ядра KasperskyOS, нужно использовать декларацию:

use EDL kl.core.Core

Чтобы включить EDL-файл для прикладной или системной программы (или для драйвера), нужно использовать декларацию:

use EDL <ссылка на EDL-файл>

Ссылка на EDL-файл представляет собой путь к EDL-файлу (без расширения и точки перед ним) относительно директории, которая включена в набор директорий, где компилятор nk-psl-gen-c ищет PSL-, IDL-, CDL-, EDL-файлы. (Этот набор директорий задается параметрами скрипта makekss вида "-I <путь к файлам>".) В качестве разделителя в описании пути используется точка.

Пример:

/* Включение файла UART.edl, который находится

* в KasperskyOS SDK по пути sysroot-*-kos/include/kl/drivers. */

use EDL kl.drivers.UART

/* Компилятор nk-psl-gen-c должен быть настроен на поиск

* PSL-, IDL-, CDL-, EDL-файлов в директории sysroot-*-kos/include. */

Компилятор nk-psl-gen-c находит IDL-, CDL-файлы через EDL-файлы, так как EDL-файлы содержат ссылки на соответствующие CDL-файлы, а CDL-файлы содержат ссылки на соответствующие CDL-, IDL-файлы.

В начало

Создание объектов моделей безопасности

Для вызова методов требуемой модели безопасности, нужно создать объект этой модели безопасности.

Чтобы создать объект модели безопасности, нужно использовать декларацию:

policy object <имя объекта модели безопасности : название модели безопасности> {

[параметры объекта модели безопасности]

}

Параметры объекта модели безопасности специфичны для модели безопасности. Описание параметров и примеры создания объектов разных моделей безопасности приведены в разделе "Модели безопасности".

В начало

Привязка методов моделей безопасности к событиям безопасности

Чтобы выполнить привязку методов моделей безопасности к событию безопасности, нужно использовать декларацию:

<вид события безопасности> [селекторы события безопасности] {

[профиль аудита безопасности]

<вызываемые правила моделей безопасности>

}

Вид события безопасности

Чтобы задать вид события безопасности, используются следующие спецификаторы:

  • request – отправка IPC-запросов;
  • response – отправка IPC-ответов;
  • error – отправка IPC-ответов, содержащих сведения об ошибках;
  • security – попытки обращений сущностей к модулю безопасности Kaspersky Security Module через интерфейс безопасности;
  • execute – инициация запусков сущностей или запуск ядра KasperskyOS.

При взаимодействии сущностей с модулем безопасности применяется механизм, отличный от IPC. Но при разработке политики безопасности решения на обращения сущностей к модулю безопасности можно смотреть как на передачу IPC-сообщений, так как сущности действительно передают модулю безопасности сообщения (в этих сообщениях не указывается приемник).

Для запуска сущностей не используется механизм IPC. Но когда инициируется запуск сущности, ядро обращается к модулю безопасности, сообщая сведения об инициаторе запуска и запускаемой сущности. Поэтому с точки зрения автора политики безопасности решения можно считать, что запуск сущности – это передача IPC-сообщения от инициатора запуска к запускаемой сущности. Также при запуске ядра можно считать, что ядро отправляет IPC-сообщение самому себе.

Селекторы события безопасности

Селекторы события безопасности позволяют уточнить описание события безопасности заданного вида. Используются следующие селекторы:

  • src=<имя класса сущностей/ядро> – сущности заданного класса или ядро KasperskyOS являются источниками IPC-сообщений;
  • dst=<имя класса сущностей/ядро> – сущности заданного класса или ядро являются приемниками IPC-сообщений;
  • interface=<имя интерфейса> – описывает следующие события безопасности:
    • клиенты пытаются обратиться к серверам или ядру через интерфейс с заданным именем;
    • сущности обращаются к модулю безопасности Kaspersky Security Module через интерфейс безопасности с заданным именем;
    • серверы или ядро отправляют клиентам результаты обработки обращений через интерфейс с заданным именем;
  • endpoint=<квалифицированное имя службы> – описывает следующие события безопасности:
    • клиенты пытаются использовать службу серверов или ядра с заданным именем;
    • серверы или ядро отправляют клиентам результаты использования службы с заданным именем;
  • method=<имя метода> – описывает следующие события безопасности:
    • клиенты пытаются обратиться к серверам или ядру, вызывая метод службы с заданным именем;
    • сущности обращаются к модулю безопасности, вызывая метод интерфейса безопасности с заданным именем;
    • серверы или ядро отправляют клиентам результаты вызова метода службы с заданным именем;
    • ядро сообщает о своем запуске модулю безопасности, вызывая метод execute-интерфейса с заданным именем;
    • ядро инициирует запуски сущностей, вызывая метод execute-интерфейса с заданным именем;
    • сущности инициируют запуски других сущностей, в результате чего ядро вызывает метод execute-интерфейса с заданным именем.

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

Классы сущностей, интерфейсы, службы, методы должны называться так, как они называются в IDL-, CDL-, EDL-описаниях. Ядро должно называться kl.core.Core.

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

Можно использовать комбинации селекторов. При этом селекторы можно разделять запятыми.

На использование селекторов есть ограничения. Для событий безопасности вида execute нельзя использовать селекторы interface и endpoint. Для событий безопасности вида security нельзя использовать селекторы dst и endpoint.

Также есть ограничения на комбинации селекторов. Для событий безопасности видов request, response и error селектор method можно использовать только совместно с одним или обоими селекторами endpoint и interface. (Селекторы method, endpoint и interface должны быть согласованы, то есть метод должен соответствовать службе или интерфейсу, служба должна соответствовать интерфейсу.) Для событий безопасности вида request селектор endpoint можно использовать только совместно с селектором dst. Для событий безопасности видов response и error селектор endpoint можно использовать только совместно с селектором src.

Профиль аудита безопасности

Профиль аудита безопасности задается конструкцией audit <имя профиля аудита безопасности>. Если профиль аудита безопасности не задан, используется глобальный профиль аудита безопасности.

Вызываемые правила моделей безопасности

Вызываемые правила моделей безопасности задаются списком из конструкций следующего вида:

[имя объекта модели безопасности.]<имя правила модели безопасности> <параметр>

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

[имя объекта модели безопасности.]<имя выражения модели безопасности> <параметр>

Также в качестве входных данных для методов моделей безопасности (правил и выражений) могут использоваться параметры интерфейсных методов. (О получении доступа к параметрам интерфейсных методов см. "Модель безопасности Struct"). Кроме этого, входными данными для методов моделей безопасности могут быть значения SID сущностей и ядра KasperskyOS, которые задаются ключевыми словами src_sid и dst_sid. Первое означает SID сущности (или ядра), которая является источником IPC-сообщения. Второе означает SID сущности (или ядра), которая является приемником IPC-сообщения (при обращениях к модулю безопасности Kaspersky Security Module dst_sid использовать нельзя).

Для вызовов некоторых правил и выражений моделей безопасности можно не указывать имя объекта модели безопасности или использовать операторы. Подробнее о методах моделей безопасности см. "Модели безопасности".

Вложенные конструкции для привязки методов моделей безопасности к событиям безопасности

В одной декларации можно выполнить привязку методов моделей безопасности к разным события безопасности одного вида. Для этого нужно использовать match-секции, которые представляют собой конструкции вида:

match <селекторы события безопасности> {

[профиль аудита безопасности]

<вызываемые правила моделей безопасности>

}

Match-секции могут быть вложены в другую match-секцию. Match-секция использует одновременно свои селекторы события безопасности и селекторы события безопасности уровня декларации и всех match-секций, которые "оборачивают" эту match-секцию. Также match-секция применяет по умолчанию профиль аудита безопасности своего контейнера (match-секции предыдущего уровня или уровня декларации), но можно задать отдельный профиль аудита безопасности для match-секции.

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

choice <вызов выражения модели безопасности, проверяющего выполнение условий> {

"<условие 1>" : [{] // Условная секция 1

[профиль аудита безопасности]

<вызываемые правила моделей безопасности>

[}]

"<условие 2>" : ... // Условная секция 2

...

_ : ... // Условная секция, если ни одно условие не выполняется.

}

Конструкцию choice можно использовать внутри match-секции. Условная секция использует селекторы события безопасности и профиль аудита безопасности своего контейнера, но можно задать отдельный профиль аудита безопасности для условной секции.

Если при обработке события безопасности выполняется сразу несколько условий, описанных в конструкции choice, то срабатывает только одна условная секция, соответствующая первому в списке подходящему условию.

В качестве выражения, проверяющего выполнение условий в конструкции choice, можно использовать только те выражения, которые предназначены специально для этого. Некоторые модели безопасности содержат такие выражения (подробнее см. "Модели безопасности").

В начало

Описание профилей аудита безопасности

Для выполнения аудита безопасности нужно ассоциировать объекты моделей безопасности с профилем (профилями) аудита безопасности. Профиль аудита безопасности (далее также профиль аудита) объединяет в себе конфигурации аудита безопасности (далее также конфигурации аудита), каждая из которых задает объекты моделей безопасности, покрываемые аудитом, а также условия выполнения аудита. Можно задать глобальный профиль аудита (подробнее см. "Описание глобальных параметров политики безопасности решения") и/или назначить профиль (профили) аудита на уровне деклараций привязки методов моделей безопасности к событиям безопасности, и/или назначить профиль (профили) аудита на уровне match-секций или choice-секций (подробнее см. "Привязка методов моделей безопасности к событиям безопасности").

Независимо от того, используются профили аудита или нет, данные аудита содержат сведения о решениях "запрещено", которые приняты модулем безопасности Kaspersky Security Module при некорректности IPC-сообщений и обработке событий безопасности, не связанных ни с одним правилом моделей безопасности.

Чтобы описать профиль аудита безопасности, нужно использовать декларацию:

audit profile <имя профиля аудита безопасности> =

{ <уровень аудита безопасности>:

// Описание конфигурации аудита безопасности

{ <имя объекта модели безопасности>:

{ kss : <условия выполнения аудита безопасности, связанные с результатами

вызовов правил модели безопасности>

[, условия выполнения аудита безопасности, специфичные для модели безопасности]

}

[,]...

...

}

[,]...

...

}

Уровень аудита безопасности

Уровень аудита безопасности (далее уровень аудита) является глобальным параметром политики безопасности решения и представляет собой беззнаковое целое число, которое задает активную конфигурацию аудита безопасности. (Слово "уровень" здесь означает вариант конфигурации и не предполагает обязательной иерархии.) Уровень аудита можно изменять в процессе работы модуля безопасности Kaspersky Security Module. Для этого используется специальный метод модели безопасности Base, вызываемый при обращении сущностей к модулю безопасности через интерфейс безопасности (подробнее см. "Модель безопасности Base"). Начальный уровень аудита задается совместно с назначением глобального профиля аудита (подробнее см. "Описание глобальных параметров политики безопасности решения"). В качестве глобального можно явно назначить пустой профиль аудита empty.

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

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

Другим примером применения уровней аудита является возможность переключать аудит с одной подсистемы на другую (например, переключить аудит, связанный с драйверами, на аудит, связанный с прикладными программами, или аудит, связанный с сетевой подсистемой, на аудит, связанный с графической подсистемой).

Имя объекта модели безопасности

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

Сведения о решениях модуля безопасности Kaspersky Security Module, содержащиеся в данных аудита, включают как общее решение модуля безопасности, так и результаты вызовов отдельных методов моделей безопасности, покрытых аудитом. Чтобы сведения о решении модуля безопасности попали в данные аудита, нужно, чтобы по крайней мере один метод, вызванный при обработке события безопасности, был покрыт аудитом.

Имена объектов моделей безопасности, как и имена методов, предоставляемых этими объектами, попадают в данные аудита.

Условия выполнения аудита безопасности

Условия выполнения аудита безопасности задаются отдельно для каждого объекта модели безопасности.

Чтобы задать условия выполнения аудита, связанные с результатами вызовов правил моделей безопасности, нужно использовать следующие конструкции:

  • ["granted"] – аудит выполняется, если вызванное правило вернуло результат "разрешено";
  • ["denied"] – аудит выполняется, если вызванное правило вернуло результат "запрещено";
  • ["granted", "denied"] – аудит выполняется, если вызванное правило вернуло результат "разрешено" или "запрещено";
  • [] – аудит не выполняется независимо от того, какой результат вернуло вызванное правило.

Условия выполнения аудита, связанные с результатами вызовов правил, не применяются к выражениям. Эти условия должны быть заданы (любой возможной конструкцией), даже если модель безопасности содержит только выражения, поскольку этого требует синтаксис языка PSL.

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

Профиль аудита безопасности для тракта аудита безопасности

Тракт аудита безопасности включает ядро, а также сущности Klog и KlogStorage, которые соединены IPC-каналами по схеме "ядро – Klog – KlogStorage". Методы моделей безопасности, которые связаны с передачей данных аудита через этот тракт, не должны покрываться аудитом. В противном случае это приведет к лавинообразному росту данных аудита, так как передача данных будет порождать новые данные.

Чтобы "подавить" аудит, заданный профилем более широкой области действия (например, глобальным или профилем на уровне декларации привязки методов моделей безопасности к события безопасности), нужно назначить пустой профиль аудита empty на уровне декларации привязки методов моделей безопасности к событиям безопасности или на уровне match-секции либо choice-секции.

В начало

Типы данных в языке PSL

Типы данных, поддерживаемые в языке PSL, приведены в таблице ниже.

Типы данных в языке PSL

Обозначения типов

Описание типов

UInt8, UInt16, UInt32, UInt64

Беззнаковое целое число

SInt8, SInt16, SInt32, SInt64

Знаковое целое число

Boolean

Логический тип

Логический тип включает два значения: true и false.

Text

Текстовый тип

()

Тип Unit

Тип Unit включает одно неизменяемое значение. Используется как заглушка в случаях, когда синтаксис языка PSL требует указать какие-либо данные, но фактически эти данные не требуются. Например, тип Unit можно использовать, чтобы объявить метод, который не имеет параметров (аналогично тому, как тип void используется в C/C++).

"<тип>"

Текстовый литерал

Текстовый литерал включает одно неизменяемое текстовое значение.

Примеры определений текстовых литералов:

""

"granted"

<тип>

Целочисленный литерал

Целочисленный литерал включает одно неизменяемое целочисленное значение.

Примеры определений числовых литералов:

12

-5

0xFFFF

<тип 1 | тип 2> [|]...

Вариантный тип

Вариантный тип объединяет два и более типов и может выступать в роли любого из них.

Примеры определений вариантных типов:

Boolean | ()

UInt8 | UInt16 | UInt32 | UInt64

"granted" | "denied"

{ [имя поля : тип поля]

[,] ...

...

}

Словарь

Словарь состоит из полей одного или нескольких типов. Словарь может быть пустым.

Примеры определений словарей:

{}

{ handle : Handle

, rights : UInt32

}

[[тип] [,] ...]

Кортеж

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

Примеры определений кортежей:

[]

["granted"]

[Boolean, Boolean]

Set<<тип элементов>>

Множество

Множество включает ноль и более уникальных элементов одного типа.

Примеры определений множеств:

Set<"granted" | "denied">

Set<Text>

List<<тип элементов>>

Список

Список включает ноль и более элементов одного типа.

Примеры определений списков:

List<Boolean>

List<Text | ()>

Map<<тип ключа, тип значения>>

Ассоциативный массив

Ассоциативный массив включает ноль и более записей типа "ключ-значение" с уникальными ключами.

Пример определения ассоциативного массива:

Map<UInt32, UInt32>

Array<<тип элементов, число элементов>>

Массив

Массив включает заданное число элементов одного типа.

Пример определения массива:

Array<UInt8, 42>

Sequence<<тип элементов, число элементов>>

Последовательность

Последовательность включает от ноля до заданного числа элементов одного типа.

Пример определения последовательности:

Sequence<SInt64, 58>

Псевдонимы некоторых типов PSL

В файле nk/base.psl из состава KasperskyOS SDK определены типы данных, которые используются как типы параметров (или структурных элементов параметров) и возвращаемых значений для методов разных моделей безопасности. Псевдонимы и определения этих типов приведены в таблице ниже.

Псевдонимы и определения некоторых типов данных в языке PSL

Псевдоним типа

Определение типа

Unsigned

Беззнаковое целое число

UInt8 | UInt16 | UInt32 | UInt64

Signed

Знаковое целое число

SInt8 | SInt16 | SInt32 | SInt64

Number

Целое число

Unsigned | Signed

ScalarLiteral

Скалярный литерал

() | Boolean | Number

Literal

Литерал

ScalarLiteral | Text

Sid

Тип идентификатора безопасности SID

UInt32

Handle

Тип идентификатора безопасности SID

Sid

HandleDesc

Словарь, содержащий поля для SID и маски прав дескриптора

{ handle : Handle

, rights : UInt32

}

Cases

Тип данных, принимаемых выражениями моделей безопасности, вызываемыми в конструкции choice для проверки выполнения условий

List<Text | ()>

KSSAudit

Тип данных, задающих условия выполнения аудита безопасности

Set<"granted" | "denied">

Отображение типов IDL на типы PSL

Для описания параметров интерфейсных методов используются типы данных языка IDL. Входные данные для методов моделей безопасности имеют типы из языка PSL. Набор типов данных в языке IDL отличается от набора типов данных в языке PSL. Поскольку параметры интерфейсных методов, передаваемые в IPC-сообщениях, могут использоваться как входные данные для методов моделей безопасности, автору политики безопасности решения нужно понимать, как типы IDL отображаются на типы PSL.

Целочисленные типы IDL отображаются на целочисленные типы PSL, а также на вариантные типы PSL, объединяющие эти целочисленные типы (в том числе с другими типами). Например, знаковые целочисленные типы IDL отображаются на тип Signed в PSL, целочисленные типы IDL отображаются на тип ScalarLiteral в PSL.

Тип Handle в IDL отображается на тип HandleDesc в PSL.

Объединения и структуры IDL отображаются на словари PSL.

Массивы и последовательности IDL отображаются на массивы и последовательности PSL соответственно.

Строковые буферы в IDL отображаются на текстовый тип PSL.

В настоящее время байтовые буферы в IDL не отображаются на типы PSL. Соответственно, данные, содержащиеся в байтовых буферах, не могут использоваться как входы для методов моделей безопасности.

В начало

Пример простейшей политики безопасности решения

Ниже приведена простейшая политика безопасности решения вида "все разрешено" для решения, состоящего из пользовательских сущностей Client и Server, а также сущности Einit и ядра KasperskyOS, представленного сущностью kl.core.Core.

Эта политика разрешает:

  • все взаимодействия сущностей (отправку любых запросов и ответов);
  • запуск всех сущностей;

Использование такой политики безопасности недопустимо в реальных решениях. Более сложная политика безопасности решения показана в примере ping.

security.psl

execute: kl.core.Execute

use nk.base._

use EDL Einit

use EDL kl.core.Core

use EDL Client

use EDL Server

/* Запуск сущностей разрешен */

execute {

grant ()

}

/* Отправка и получение запросов, ответов и ошибок разрешены.

Это означает, что любая сущность может вызывать методы других сущностей и ядра. */

request {

grant ()

}

response {

grant ()

}

error {

grant ()

}

/* При обращения к модулю безопасности Kaspersky Security Module всегда

* будет получено решение "разрешено". */

security {

grant ()

}

В начало

Модель безопасности Pred

Модель безопасности Pred позволяет выполнять операции сравнения.

PSL-файл с описанием модели безопасности Pred находится в KasperskyOS SDK по пути:

toolchain/include/nk/basic.psl

Объект модели безопасности Pred

В файле basic.psl содержится декларация, которая создает объект модели безопасности Pred с именем pred. Соответственно, включение файла basic.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Pred по умолчанию.

Объект модели безопасности Pred не имеет параметров и не может быть покрыт аудитом безопасности.

Создавать дополнительные объекты модели безопасности Pred не требуется.

Методы модели безопасности Pred

Модель безопасности Pred содержит выражения, которые выполняют операции сравнения и возвращают значения типа Boolean. Для вызова этих выражений нужно использовать операторы сравнения:

  • <ScalarLiteral> == <ScalarLiteral> – "равно";
  • <ScalarLiteral> != <ScalarLiteral> – "не равно";
  • <Number> < <Number> – "меньше";
  • <Number> <= <Number> – "меньше или равно";
  • <Number> > <Number> – "больше";
  • <Number> >= <Number> – "больше или равно".

Также модель безопасности Pred содержит выражение empty, которое позволяет определить, содержат ли данные свои структурные элементы. Выражение возвращает значения типа Boolean. Если данные не содержат своих структурных элементов (например, множество является пустым), выражение возвращает true, иначе возвращает false. Чтобы вызвать выражение, нужно использовать конструкцию:

pred.empty <Text | Set | List | Map | ()>

В начало

Модель безопасности Bool

Модель безопасности Bool позволяет выполнять логические операции.

PSL-файл с описанием модели безопасности Bool находится в KasperskyOS SDK по пути:

toolchain/include/nk/basic.psl

Объект модели безопасности Bool

В файле basic.psl содержится декларация, которая создает объект модели безопасности Bool с именем bool. Соответственно, включение файла basic.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Bool по умолчанию.

Объект модели безопасности Bool не имеет параметров и не может быть покрыт аудитом безопасности.

Создавать дополнительные объекты модели безопасности Bool не требуется.

Методы модели безопасности Bool

Модель безопасности Bool содержит выражения, которые выполняют логические операции и возвращают значения типа Boolean. Для вызова этих выражений нужно использовать логические операторы:

  • ! <Boolean> – "логическое НЕ";
  • <Boolean> && <Boolean> – "логическое И";
  • <Boolean> || <Boolean> – "логическое ИЛИ";
  • <Boolean> ==> <Boolean> – "импликация" (! <Boolean> || <Boolean>).

Также модель безопасности Bool содержит выражения all, any и cond.

Выражение all выполняет "логическое И" для произвольного числа значений типа Boolean. Возвращает значения типа Boolean. Если передать через параметр пустой список значений ([]), возвращает true. Чтобы вызвать выражение, нужно использовать конструкцию:

bool.all <List<Boolean>>

Выражение any выполняет "логическое ИЛИ" для произвольного числа значений типа Boolean. Возвращает значения типа Boolean. Если передать через параметр пустой список значений ([]), возвращает false. Чтобы вызвать выражение, нужно использовать конструкцию:

bool.any <List<Boolean>>

Выражение cond выполняет тернарную условную операцию. Возвращает значения типа ScalarLiteral. Чтобы вызвать выражение, нужно использовать конструкцию:

bool.cond

{ if : <Boolean> // Условие

, then : <ScalarLiteral> // Значение, возвращаемое при истинности условия

, else : <ScalarLiteral> // Значение, возвращаемое при ложности условия

}

Помимо выражений модель безопасности Bool включает правило assert, которое работает так же, как одноименное правило модели безопасности Base.

В начало

Модель безопасности Math

Модель безопасности Math позволяет выполнять операции целочисленной арифметики.

PSL-файл с описанием модели безопасности Math находится в KasperskyOS SDK по пути:

toolchain/include/nk/basic.psl

Объект модели безопасности Math

В файле basic.psl содержится декларация, которая создает объект модели безопасности Math с именем math. Соответственно, включение файла basic.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Math по умолчанию.

Объект модели безопасности Math не имеет параметров и не может быть покрыт аудитом безопасности.

Создавать дополнительные объекты модели безопасности Math не требуется.

Методы модели безопасности Math

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

  • <Number> + <Number> – "сложение". Возвращает значения типа Number.
  • <Number> - <Number> – "вычитание". Возвращает значения типа Number.
  • <Number> * <Number> – "умножение". Возвращает значения типа Number.

Другая часть включает следующие выражения:

  • neg <Signed> – "изменение знака числа". Возвращает значения типа Singed.
  • abs <Singed> – "получение модуля числа". Возвращает значения типа Singed.
  • sum <List<Number>> – "сложение чисел из списка". Возвращает значения типа Number. Если передать через параметр пустой список значений ([]), возвращает 0.
  • product <List<Number>> – "перемножение чисел из списка". Возвращает значения типа Number. Если передать через параметр пустой список значений ([]), возвращает 1.

Для вызова этих выражений нужно использовать конструкцию:

math.<имя выражения> <параметр>

В начало

Модель безопасности Struct

Модель безопасности Struct позволяет получать доступ к структурным элементам данных.

PSL-файл с описанием модели безопасности Struct находится в KasperskyOS SDK по пути:

toolchain/include/nk/basic.psl

Объект модели безопасности Struct

В файле basic.psl содержится декларация, которая создает объект модели безопасности Struct с именем struct. Соответственно, включение файла basic.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Struct по умолчанию.

Объект модели безопасности Struct не имеет параметров и не может быть покрыт аудитом безопасности.

Создавать дополнительные объекты модели безопасности Struct не требуется.

Методы модели безопасности Struct

Модель безопасности Struct содержит выражения, которые обеспечивают доступ к структурным элементам данных. Для вызова этих выражений нужно использовать следующие конструкции:

  • <{...}>.<имя поля> – "получение доступа к полю словаря". Тип возвращаемых данных соответствует типу поля словаря.
  • <List | Set | Sequence | Array>.[<номер элемента>] – "получение доступа к элементу данных". Тип возвращаемых данных соответствует типу элементов. Нумерация элементов начинается с нуля. При выходе за границы набора данных выражение завершается некорректно, и модуль безопасности Kaspersky Security Module возвращает решение "запрещено".
  • <HandleDesc>.handle – "получение SID". Возвращает значения типа Handle. (О взаимосвязи между дескрипторами и значениями SID см. "Управление доступом к ресурсам").
  • <HandleDesc>.rights – "получение маски прав дескриптора". Возвращает значения типа UInt32.

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

message.<имя параметра интерфейсного метода>

Имя параметра нужно указать в соответствии с IDL-описанием.

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

В начало

Модель безопасности Base

Модель безопасности Base позволяет реализовать простейшую логику.

PSL-файл с описанием модели безопасности Base находится в KasperskyOS SDK по пути:

toolchain/include/nk/base.psl

Объект модели безопасности Base

В файле base.psl содержится декларация, которая создает объект модели безопасности Base с именем base. Соответственно, включение файла base.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Base по умолчанию. Методы этого объекта можно вызывать без указания имени объекта.

Объект модели безопасности Base не имеет параметров.

Объект модели безопасности Base может быть покрыт аудитом безопасности. Условия выполнения аудита, специфичные для модели безопасности Base, отсутствуют.

Необходимость создавать дополнительные объекты модели безопасности Base возникает в следующих случаях:

  • Если нужно по-разному настроить аудит безопасности для разных объектов модели безопасности Base (например, для разных объектов можно применять разные профили аудита или разные конфигурации аудита одного профиля).
  • Если нужно различать вызовы методов, предоставляемых разными объектами модели безопасности Base (поскольку в данные аудита включается как имя метода модели безопасности, так и имя объекта, предоставляющего этот метод, можно понять, что был вызван метод конкретного объекта).

Методы модели безопасности Base

Модель безопасности Base содержит следующие правила:

  • grant ()

    Имеет параметр типа (). Возвращает результат "разрешено".

    Пример:

    /* Клиенту класса foo разрешено обращаться

    * серверу класса bar. */

    request src=foo dst=bar { grant () }

  • assert <Boolean>

    Возвращает результат "разрешено", если через параметр передать значение true. Иначе возвращает результат "запрещено".

    Пример:

    /* Любому клиенту в решении будет разрешено обращаться к серверу класса foo,

    * вызывая метод Send службы net.Net, если через параметр port метода Send

    * будет передаваться значение больше 80. Иначе любому клиенту в решении

    * будет запрещено обращаться к серверу класса foo, вызывая метод Send

    * службы net.Net. */

    request dst=foo endpoint=net.Net method=Send { assert (message.port > 80) }

  • deny <Boolean | ()>

    Возвращает результат "запрещено", если через параметр передать значение true или (). Иначе возвращает результат "разрешено".

    Пример:

    /* Серверу класса foo запрещено отвечать

    * клиенту класса bar. */

    response src=foo dst=bar { deny () }

  • set_level <UInt8>

    Устанавливает уровень аудита безопасности равным значению, переданному через параметр. Возвращает результат "разрешено". (Подробнее об уровне аудита безопасности см. "Описание профилей аудита безопасности".)

    Пример:

    /* Сущность класса foo получит решение "разрешено" от модуля безопасности

    * Kaspersky Security Module, если вызовет метод интерфейса безопасности SetAuditLevel,

    * чтобы изменить уровень аудита безопасности. */

    security src=foo method=SetAuditLevel { set_level (message.audit_level) }

В начало

Модель безопасности Regex

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

PSL-файл с описанием модели безопасности Regex находится в KasperskyOS SDK по пути:

toolchain/include/nk/regex.psl

Объект модели безопасности Regex

В файле regex.psl содержится декларация, которая создает объект модели безопасности Regex с именем re. Соответственно, включение файла regex.psl в описание политики безопасности решения обеспечивает создание объекта модели безопасности Regex по умолчанию.

Объект модели безопасности Regex не имеет параметров.

Объект модели безопасности Regex может быть покрыт аудитом безопасности. При этом можно задавать условия выполнения аудита, специфичные для модели безопасности Regex. Для этого в описании конфигурации аудита нужно использовать следующие конструкции:

  • emit : ["match"] – аудит выполняется, если вызван метод match;
  • emit : ["select"] – аудит выполняется, если вызван метод select;
  • emit : ["match", "select"] – аудит выполняется, если вызван метод match или select;
  • emit : [] – аудит не выполняется.

Необходимость создавать дополнительные объекты модели безопасности Regex возникает в следующих случаях:

  • Если нужно по-разному настроить аудит безопасности для разных объектов модели безопасности Regex (например, для разных объектов можно применять разные профили аудита или разные конфигурации аудита одного профиля).
  • Если нужно различать вызовы методов, предоставляемых разными объектами модели безопасности Regex (поскольку в данные аудита включается как имя метода модели безопасности, так и имя объекта, предоставляющего этот метод, можно понять, что был вызван метод конкретного объекта).

Методы модели безопасности Regex

Модель безопасности Regex содержит следующие выражения:

  • match {text : <Text>, pattern : <Text>}

    Возвращает значение типа Boolen. Если текст text соответствует регулярному выражению pattern, возвращает true. Иначе возвращает false.

    Пример:

    assert (re.match {text : message.text, pattern : "^[0-9]*$"})

  • select {text : <Text>}

    Предназначено для использования в качестве выражения, проверяющего выполнение условий в конструкции choice (о конструкции choice см. "Привязка методов моделей безопасности к событиям безопасности"). Проверяет соответствие текста text регулярным выражениям. В зависимости от результатов этой проверки выполняются различные варианты обработки события безопасности.

    Пример:

    choice (re.select {text : "hello world"}) {

    "^hello .*": grant ()

    ".*world$" : grant ()

    _ : deny ()

    }

В начало

Модель безопасности HashSet

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

PSL-файл с описанием модели безопасности HashSet находится в KasperskyOS SDK по пути:

toolchain/include/nk/hashmap.psl

В этом разделе

Объект модели безопасности HashSet

Правило init модели безопасности HashSet

Правило fini модели безопасности HashSet

Правило add модели безопасности HashSet

Правило remove модели безопасности HashSet

Выражение contains модели безопасности HashSet

В начало

Объект модели безопасности HashSet

Чтобы использовать модель безопасности HashSet, нужно создать объект (объекты) этой модели.

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

Объект модели безопасности HashSet имеет следующие параметры:

  • type Entry – тип значений в таблицах (поддерживаются целочисленные типы, тип Boolean, а также словари и кортежи на базе целочисленных типов и типа Boolean);
  • config – конфигурация пула таблиц:
    • set_size – размер таблицы;
    • pool_size – число таблиц в пуле.

Все параметры объекта модели безопасности HashSet являются обязательными.

Пример:

policy object S : HashSet {

type Entry = UInt32

config =

{ set_size : 5

, pool_size : 2

}

}

Объект модели безопасности HashSet может быть покрыт аудитом безопасности. Условия выполнения аудита, специфичные для модели безопасности HashSet, отсутствуют.

Необходимость создавать несколько объектов модели безопасности HashSet возникает в следующих случаях:

  • Если нужно по-разному настроить аудит безопасности для разных объектов модели безопасности HashSet (например, для разных объектов можно применять разные профили аудита или разные конфигурации аудита одного профиля).
  • Если нужно различать вызовы методов, предоставляемых разными объектами модели безопасности HashSet (поскольку в данные аудита включается как имя метода модели безопасности, так и имя объекта, предоставляющего этот метод, можно понять, что был вызван метод конкретного объекта).
  • Если нужно использовать таблицы разных размеров и/или с разными типами значений.
В начало

Правило init модели безопасности HashSet

init {sid : <Sid>}

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

Возвращает результат "разрешено", если создало ассоциацию таблицы с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • В пуле нет свободных таблиц.
  • Ресурс с идентификатором безопасности sid уже ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности HashSet.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Запуск сущности класса Server будет разрешен, если

* при инициации запуска будет создана ассоциация этой

* сущности с таблицей. Иначе запуск сущности класса

* Server будет запрещен. */

execute dst=Server {

S.init {sid : dst_sid}

}

В начало

Правило fini модели безопасности HashSet

fini {sid : <Sid>}

Удаляет ассоциацию таблицы с ресурсом, который имеет идентификатор безопасности sid (таблица становится свободной).

Возвращает результат "разрешено", если удалило ассоциацию таблицы с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности HashSet.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Правило add модели безопасности HashSet

add {sid : <Sid>, entry : <Entry>}

Добавляет значение entry в таблицу, ассоциированную с ресурсом, который имеет идентификатор безопасности sid.

Возвращает результат "разрешено" в следующих случаях:

  • Правило добавило значение entry в таблицу.
  • В таблице уже содержится значение entry.

Возвращает результат "запрещено" в следующих случаях:

  • Таблица полностью заполнена.
  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности HashSet.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Сущность класса Server получит решение "разрешено"

* от модуля безопасности Kaspersky Security Module, вызывая метод

* интерфейса безопасности Add, если при вызове этого

* метода значение 5 будет добавлено в таблицу,

* ассоциированную с этой сущностью, или уже содержится

* в этой таблице. Иначе сущность класса Server получит

* решение "запрещено" от модуля безопасности, вызывая

* метод интерфейса безопасности Add. */

security src=Server, method=Add {

S.add {sid : src_sid, entry : 5}

}

В начало

Правило remove модели безопасности HashSet

remove {sid : <Sid>, entry : <Entry>}

Удаляет значение entry из таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает результат "разрешено" в следующих случаях:

  • Правило удалило значение entry из таблицы.
  • В таблице нет значения entry.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности HashSet.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Выражение contains модели безопасности HashSet

contains {sid : <Sid>, entry : <Entry>}

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

Возвращает значение типа Boolean. Если значение entry содержится в таблице, возвращает true. Иначе возвращает false.

Выполняется некорректно в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности HashSet.
  • Идентификатор безопасности sid вне допустимого диапазона.

Если выражение выполнено некорректно, модуль безопасности Kaspersky Security Module возвращает решение "запрещено".

Пример:

/* Сущность класса Server получит решение "разрешено"

* от модуля безопасности Kaspersky Security Module, вызывая метод

* интерфейса безопасности Check, если значение 42

* содержится в таблице, ассоциированной с этой сущностью.

* Иначе сущность класса Server получит решение "запрещено"

* от модуля безопасности, вызывая метод интерфейса

* безопасности Check. */

security src=Server, method=Check {

assert(S.contains {sid : src_sid, entry : 42})

}

В начало

Модель безопасности StaticMap

Модель безопасности StaticMap позволяет ассоциировать с ресурсами двумерные таблицы типа "ключ–значение", читать и изменять значения ключей. Например, можно ассоциировать сущность, в контексте которой выполняется драйвер, с регионом памяти MMIO, который разрешено использовать этому драйверу. Для этого потребуется два ключа, значения которых задают начальный адрес и размер региона памяти MMIO. Эту ассоциацию можно использовать, чтобы проверить, может ли драйвер обращаться к региону памяти MMIO, к которому он пытается получить доступ.

Ключи в таблице имеют одинаковый тип и является уникальными и неизменяемыми. Значения ключей в таблице имеют одинаковый тип.

Одновременно существует два экземпляра таблицы: базовый и рабочий. Оба экземпляра инициализируются одинаковыми данными. Изменения заносятся сначала в рабочий экземпляр, а затем могут быть добавлены в базовый экземпляр или, наоборот, заменены прежними значениями из базового экземпляра. Значения ключей могут быть прочитаны как из базового, так и из рабочего экземпляра таблицы.

PSL-файл с описанием модели безопасности StaticMap находится в KasperskyOS SDK по пути:

toolchain/include/nk/staticmap.psl

В этом разделе

Объект модели безопасности StaticMap

Правило init модели безопасности StaticMap

Правило fini модели безопасности StaticMap

Правило set модели безопасности StaticMap

Правило commit модели безопасности StaticMap

Правило rollback модели безопасности StaticMap

Выражение get модели безопасности StaticMap

Выражение get_uncommited модели безопасности StaticMap

В начало

Объект модели безопасности StaticMap

Чтобы использовать модель безопасности StaticMap, нужно создать объект (объекты) этой модели.

Объект модели безопасности StaticMap содержит пул двумерных таблиц типа "ключ–значение", которые имеют одинаковый размер. Ресурс может быть ассоциирован только с одной таблицей из пула таблиц каждого объекта модели безопасности StaticMap.

Объект модели безопасности StaticMap имеет следующие параметры:

  • type Value – тип значений ключей в таблицах (поддерживаются целочисленные типы);
  • config – конфигурация пула таблиц:
    • keys – таблица, содержащая ключи и их значения по умолчанию (ключи имеют тип Key = Text | List<UInt8>);
    • pool_size – число таблиц в пуле.

Все параметры объекта модели безопасности StaticMap являются обязательными.

Пример:

policy object M : StaticMap {

type Value = UInt16

config =

{ keys:

{ "k1" : 0

, "k2" : 1

}

, pool_size : 2

}

}

Объект модели безопасности StaticMap может быть покрыт аудитом безопасности. Условия выполнения аудита, специфичные для модели безопасности StaticMap, отсутствуют.

Необходимость создавать несколько объектов модели безопасности StaticMap возникает в следующих случаях:

  • Если нужно по-разному настроить аудит безопасности для разных объектов модели безопасности StaticMap (например, для разных объектов можно применять разные профили аудита или разные конфигурации аудита одного профиля).
  • Если нужно различать вызовы методов, предоставляемых разными объектами модели безопасности StaticMap (поскольку в данные аудита включается как имя метода модели безопасности, так и имя объекта, предоставляющего этот метод, можно понять, что был вызван метод конкретного объекта).
  • Если нужно использовать таблицы с разными наборами ключей и/или разными типами значений ключей.
В начало

Правило init модели безопасности StaticMap

init {sid : <Sid>}

Ассоциирует свободную таблицу из пула таблиц с ресурсом, который имеет идентификатор безопасности sid. Ключи инициализируются значениями по умолчанию.

Возвращает результат "разрешено", если создало ассоциацию таблицы с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • В пуле нет свободных таблиц.
  • Ресурс с идентификатором безопасности sid уже ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Запуск сущности класса Server будет разрешен, если

* при инициации запуска будет создана ассоциация этой

* сущности с таблицей. Иначе запуск сущности класса

* Server будет запрещен. */

execute dst=Server {

M.init {sid : dst_sid}

}

В начало

Правило fini модели безопасности StaticMap

fini {sid : <Sid>}

Удаляет ассоциацию таблицы с ресурсом, который имеет идентификатор безопасности sid (таблица становится свободной).

Возвращает результат "разрешено", если удалило ассоциацию таблицы с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Правило set модели безопасности StaticMap

set {sid : <Sid>, key : <Key>, value : <Value>}

Задает значение value ключу key в рабочем экземпляре таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает результат "разрешено", если задало значение value ключу key. (Текущее значение ключа будет перезаписано, даже если оно равно новому.)

Возвращает результат "запрещено" в следующих случаях:

  • Ключ key не содержится в таблице.
  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Сущность класса Server получит решение "разрешено"

* от модуля безопасности Kaspersky Security Module, вызывая метод

* интерфейса безопасности Set, если при вызове этого

* метода значение 2 будет задано ключу k1 в рабочем

* экземпляре таблицы, ассоциированной с этой сущностью.

* Иначе сущность класса Server получит решение "запрещено"

* от модуля безопасности, вызывая метод интерфейса

* безопасности Set. */

security src=Server, method=Set {

M.set {sid : src_sid, key : "k1", value : 2}

}

В начало

Правило commit модели безопасности StaticMap

commit {sid : <Sid>}

Копирует значения ключей из рабочего в базовый экземпляр таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает результат "разрешено", если скопировало значения ключей из рабочего в базовый экземпляр таблицы.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Правило rollback модели безопасности StaticMap

rollback {sid : <Sid>}

Копирует значения ключей из базового в рабочий экземпляр таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает результат "разрешено", если скопировало значения ключей из базового в рабочий экземпляр таблицы.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Выражение get модели безопасности StaticMap

get {sid : <Sid>, key : <Key>}

Возвращает значение ключа key из базового экземпляра таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает значение типа Value.

Выполняется некорректно в следующих случаях:

  • Ключ key не содержится в таблице.
  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.

Если выражение выполнено некорректно, модуль безопасности Kaspersky Security Module возвращает решение "запрещено".

Пример:

/* Сущность класса Server получит решение "разрешено"

* от модуля безопасности Kaspersky Security Module, вызывая метод

* интерфейса безопасности Get, если значение ключа k1

* в базовом экземпляре таблицы, ассоциированной с этой

* сущностью, отлично от нуля. Иначе сущность класса

* Server получит решение "запрещено" от модуля

* безопасности, вызывая метод интерфейса

* безопасности Get. */

security src=Server, method=Get {

assert(M.get {sid : src_sid, key : "k1"} != 0)

}

В начало

Выражение get_uncommited модели безопасности StaticMap

get_uncommited {sid: <Sid>, key: <Key>}

Возвращает значение ключа key из рабочего экземпляра таблицы, ассоциированной с ресурсом, который имеет идентификатор безопасности sid.

Возвращает значение типа Value.

Выполняется некорректно в следующих случаях:

  • Ключ key не содержится в таблице.
  • Ресурс с идентификатором безопасности sid не ассоциирован с таблицей из пула таблиц используемого объекта модели безопасности StaticMap.
  • Идентификатор безопасности sid вне допустимого диапазона.

Если выражение выполнено некорректно, модуль безопасности Kaspersky Security Module возвращает решение "запрещено".

В начало

Модель безопасности Flow

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

PSL-файл с описанием модели безопасности Flow находится в KasperskyOS SDK по пути:

toolchain/include/nk/flow.psl

В этом разделе

Объект модели безопасности Flow

Правило init модели безопасности Flow

Правило fini модели безопасности Flow

Правило enter модели безопасности Flow

Правило allow модели безопасности Flow

Выражение query модели безопасности Flow

В начало

Объект модели безопасности Flow

Чтобы использовать модель безопасности Flow, нужно создать объект (объекты) этой модели.

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

Объект модели безопасности Flow имеет следующие параметры:

  • type State – тип, определяющий множество состояний конечного автомата (вариантный тип, объединяющий текстовые литералы);
  • config – конфигурация конечного автомата:
    • states – множество состояний конечного автомата (должно совпадать со множеством состояний, заданных типом State);
    • initial – начальное состояние конечного автомата;
    • transitions – описание допустимых переходов между состояниями конечного автомата.

Все параметры объекта модели безопасности Flow являются обязательными.

Пример:

policy object service_flow : Flow {

type State = "sleep" | "started" | "stopped" | "finished"

config = { states : ["sleep", "started", "stopped", "finished"]

, initial : "sleep"

, transitions : { "sleep" : ["started"]

, "started" : ["stopped", "finished"]

, "stopped" : ["started", "finished"]

}

}

}

finite_state_machine_example

Диаграмма состояний конечного автомата в примере

Объект модели безопасности Flow может быть покрыт аудитом безопасности. При этом можно задавать условия выполнения аудита, специфичные для модели безопасности Flow. Для этого в описании конфигурации аудита нужно использовать следующую конструкцию:

omit : [<"состояние 1">[,] ...] – аудит не выполняется, если конечный автомат находится в одном из перечисленных состояний.

Необходимость создавать несколько объектов модели безопасности Flow возникает в следующих случаях:

  • Если нужно по-разному настроить аудит безопасности для разных объектов модели безопасности Flow (например, для разных объектов можно применять разные профили аудита или разные конфигурации аудита одного профиля).
  • Если нужно различать вызовы методов, предоставляемых разными объектами модели безопасности Flow (поскольку в данные аудита включается как имя метода модели безопасности, так и имя объекта, предоставляющего этот метод, можно понять, что был вызван метод конкретного объекта).
  • Если нужно использовать конечные автоматы с разными конфигурациями.
В начало

Правило init модели безопасности Flow

init {sid : <Sid>}

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

Возвращает результат "разрешено", если создало ассоциацию конечного автомата с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid уже ассоциирован с конечным автоматом используемого объекта модели безопасности Flow.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Запуск сущности класса Server будет разрешен, если

* при инициации запуска будет создана ассоциация этой

* сущности с конечным автоматом. Иначе запуск сущности

* класса Server будет запрещен. */

execute dst=Server {

service_flow.init {sid : dst_sid}

}

В начало

Правило fini модели безопасности Flow

fini {sid : <Sid>}

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

Возвращает результат "разрешено", если удалило ассоциацию конечного автомата с ресурсом.

Возвращает результат "запрещено" в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с конечным автоматом используемого объекта модели безопасности Flow.
  • Идентификатор безопасности sid вне допустимого диапазона.
В начало

Правило enter модели безопасности Flow

enter {sid : <Sid>, state : <State>}

Переводит конечный автомат, ассоциированный с ресурсом, который имеет идентификатор безопасности sid, в состояние state.

Возвращает результат "разрешено", если перевело конечный автомат в состояние state.

Возвращает результат "запрещено" в следующих случаях:

  • Переход в состояние state из текущего состояния не допускается конфигурацией конечного автомата.
  • Ресурс с идентификатором безопасности sid не ассоциирован с конечным автоматом используемого объекта модели безопасности Flow.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Любому клиенту в решении будет разрешено обращаться

* к серверу класса Server, если конечный автомат,

* ассоциированный с этим сервером, будет переведен

* в состояние started при инициации обращения. Иначе

* любому клиенту в решении будет запрещено обращаться

* к серверу класса Server. */

request dst=Server {

service_flow.enter {sid : dst_sid, state : "started"}

}

В начало

Правило allow модели безопасности Flow

allow {sid : <Sid>, states : <Set<State>>}

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

Возвращает результат "разрешено", если состояние конечного автомата входит в набор состояний states.

Возвращает результат "запрещено" в следующих случаях:

  • Состояние конечного автомата не входит в набор состояний states.
  • Ресурс с идентификатором безопасности sid не ассоциирован с конечным автоматом используемого объекта модели безопасности Flow.
  • Идентификатор безопасности sid вне допустимого диапазона.

Пример:

/* Любому клиенту в решении разрешено обращаться к серверу класса

* Server, если конечный автомат, ассоциированный с этим сервером,

* находится в состоянии started или stopped. Иначе любому клиенту

* в решении запрещено обращаться к серверу класса Server. */

request dst=Server {

service_flow.allow {sid : dst_sid, states : ["started", "stopped"]}

}

В начало

Выражение query модели безопасности Flow

query {sid : <Sid>}

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

Выполняется некорректно в следующих случаях:

  • Ресурс с идентификатором безопасности sid не ассоциирован с конечным автоматом используемого объекта модели безопасности Flow.
  • Идентификатор безопасности sid вне допустимого диапазона.

Если выражение выполнено некорректно, модуль безопасности Kaspersky Security Module возвращает решение "запрещено".

Пример:

/* Любому клиенту в решении разрешено

* обращаться к серверу класса ResourceDriver,

* если конечный автомат, ассоциированный с этим

* сервером, находится в состоянии started или

* stopped. Иначе любому клиенту в решении

* запрещено обращаться к серверу класса

*ResourceDriver. */

request dst=ResourceDriver {

choice (service_flow.query {sid : dst_sid}) {

"started" : grant ()

"stopped" : grant ()

_ : deny ()

}

}

В начало

Пример ping

Пример ping демонстрирует использование политики безопасности решения для управления взаимодействиями между сущностями.

В этом разделе

О примере ping

Реализация сущности Client в примере ping

Реализация сущности Server в примере ping

Файлы описаний в примере ping

Политика безопасности решения в примере ping

Сборка и запуск примера ping

В начало

О примере ping

Пример ping включает в себя две сущности: Client и Server.

Сущность Server предоставляет два идентичных метода Ping и Pong, которые получают число и возвращают измененное число:

Ping(in UInt32 value, out UInt32 result);

Pong(in UInt32 value, out UInt32 result);

Сущность Client вызывает оба этих метода в различной последовательности. Если вызов метода запрещен политикой безопасности решения, выводится сообщение Failed to call...

Транспортная часть примера ping практически аналогична таковой для примера echo. Единственное отличие состоит в том, что в примере ping используется два метода (Ping и Pong), а не один.

Поскольку использование IPC-транспорта подробно рассмотрено в комментариях к примеру echo, в примере ping оно рассматривается кратко.

В примере ping реализуется политика безопасности решения (security.psl) на базе модели безопасности модели безопасности Flow.

Состав примера ping

Пример ping состоит из следующих файлов:

  • client/src/client.c
  • resources/edl/Client.edl
  • server/src/server.c
  • resources/edl/Server.edl, resources/cdl/Control.cdl, resources/idl/Connection.idl
  • init.yaml
  • security.psl
В начало

Реализация сущности Client в примере ping

Сущность Client для вызывает методы Ping и Pong в различной последовательности.

Инициализация транспорта до сервера, использование прокси-объекта и интерфейсных методов, а также назначение файла Connection.idl.h рассматриваются более подробно в комментариях к файлу client.c в примере echo.

client.c

#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>

/* Файлы, необходимые для инициализации транспорта. */

#include <coresrv/nk/transport-kos.h>

#include <coresrv/sl/sl_api.h>

/* Описание интерфейса сервера, по которому обращается клиентская сущность. */

#include <ping/Connection.idl.h>

#define EXAMPLE_VALUE_TO_SEND 777

static const char *Tag = "[Client]";

static struct Connection_proxy proxy;

static uint32_t ping(uint32_t value)

{

/* Структуры запроса и ответа */

struct Connection_Ping_req req;

struct Connection_Ping_res res;

req.value = value;

/**

* Вызываем интерфейсный метод Connection_Ping.

* Серверу будет отправлен запрос для вызова метода Ping интерфейса

* control.connectionimpl с аргументом value. Вызывающий поток блокируется

* до момента получения ответа от сервера.

*/

if (Connection_Ping(&proxy.base, &req, NULL, &res, NULL) == rcOk)

{

fprintf(stderr, "%s Ping(%d), result = %d\n", Tag, value, res.result);

value = res.result;

}

else

{

fprintf(stderr, "%s Ping(%d), failed\n", Tag, value);

}

return value;

}

static uint32_t pong(uint32_t value)

{

/* Структуры запроса и ответа */

struct Connection_Pong_req req;

struct Connection_Pong_res res;

req.value = value;

/**

* Вызываем интерфейсный метод Connection_Pong.

* Серверу будет отправлен запрос для вызова метода Pong интерфейса

* controlimpl.connectionimpl с аргументом value. Вызывающий поток блокируется

* до момента получения ответа от сервера.

*/

if (Connection_Pong(&proxy.base, &req, NULL, &res, NULL) == rcOk)

{

fprintf(stderr, "%s Pong(%d), result = %d\n", Tag, value, res.result);

value = res.result;

}

else

{

fprintf(stderr, "%s Pong(%d), failed\n", Tag, value);

}

return value;

}

/* Точка входа в клиентскую сущность. */

int main(int argc, const char *argv[])

{

NkKosTransport transport;

uint32_t value;

int i;

fprintf(stderr, "%s Entity started\n", Tag);

/**

* Получаем клиентский IPC-дескриптор соединения с именем

* "server_connection".

*/

Handle handle = ServiceLocatorConnect("server_connection");

if (INVALID_HANDLE == handle)

{

fprintf(stderr, "%s ServiceLocatorConnect failed\n", Tag);

return EXIT_FAILURE;

}

/* Инициализируем IPC-транспорт для взаимодействия с серверной сущностью. */

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

/* Получаем Runtime Interface ID (RIID) для интерфейса ping.Control.connectionimpl. */

nk_iid_t riid = ServiceLocatorGetRiid(handle, "ping.Control.connectionimpl");

if (INVALID_RIID == riid)

{

fprintf(stderr, "%s ServiceLocatorGetRiid failed\n", Tag);

return EXIT_FAILURE;

}

/**

* Инициализируем прокси-объект, указав транспорт (&transport)

* и идентификатор интерфейса сервера (riid). Каждый метод

* прокси-объекта будет реализован как отправка запроса серверу.

*/

Connection_proxy_init(&proxy, &transport.base, riid);

/* Тестовый цикл. */

value = EXAMPLE_VALUE_TO_SEND;

for (i = 0; i < 5; ++i)

{

value = ping(value);

value = pong(value);

}

value = ping(value);

value = ping(value);

value = pong(value);

value = pong(value);

return EXIT_SUCCESS;

}

В начало

Реализация сущности Server в примере ping

Инициализация транспорта до клиента, подготовка структур запроса и ответа, а также назначение файла Server.edl.h рассматриваются более подробно в комментариях к файлу server.c в примере echo.

server.c

#include <stdio.h>

#include <stdlib.h>

#include <stdbool.h>

/* Файлы, необходимые для инициализации транспорта. */

#include <coresrv/nk/transport-kos.h>

#include <coresrv/sl/sl_api.h>

/* Описания сущности-сервера на языках EDL, CDL, IDL. */

#include <ping/Connection.idl.h>

#include <ping/Control.cdl.h>

#include <ping/Server.edl.h>

#define INCREMENT_STEP 3

static const char *Tag = "[Server]";

/* Тип объекта реализующего интерфейс. */

typedef struct ObjectImpl

{

struct Connection base; /* базовый интерфейс объекта */

int step; /* дополнительные параметры */

} ObjectImpl;

/* Реализация метода Ping. */

static nk_err_t Ping_impl(

struct Connection *self,

const struct Connection_Ping_req *req,

const struct nk_arena *req_arena,

struct Connection_Ping_res *res,

struct nk_arena *res_arena)

{

ObjectImpl *impl = (ObjectImpl *)self;

/**

* Значение value, пришедшее в запросе от клиента, инкрементируем на

* величину шага step и помещаем в аргумент result, который будет

* отправлен клиенту в составе ответа от сервера.

*/

res->result = req->value + (unsigned int)impl->step;

return NK_EOK;

}

/* Реализация метода Pong. */

static nk_err_t Pong_impl(

struct Connection *self,

const struct Connection_Pong_req *req,

const struct nk_arena *req_arena,

struct Connection_Pong_res *res,

struct nk_arena *res_arena)

{

ObjectImpl *impl = (ObjectImpl *)self;

/**

* Значение value, пришедшее в запросе от клиента, инкрементируем на

* величину шага step и помещаем в аргумент result, который будет

* отправлен клиенту в составе ответа от сервера.

*/

res->result = req->value + (unsigned int)impl->step;

return NK_EOK;

}

/**

* Конструктор объекта Ping.

* step - шаг, то есть число, на которое будет увеличиваться входящее значение.

*/

static struct Connection *CreateObjectImpl(int step)

{

/* Таблица реализаций методов интерфейса Connection. */

static const struct Connection_ops ops = {.Ping = Ping_impl, .Pong = Pong_impl};

/* Объект, реализующий интерфейс. */

static struct ObjectImpl impl = {.base = {&ops}};

impl.step = step;

return &impl.base;

}

/* Точка входа в сервер. */

int main(void)

{

NkKosTransport transport;

ServiceId iid;

/* Регистрируем соединение и получаем дескриптор для него. */

Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);

if (INVALID_HANDLE == handle)

{

fprintf(stderr, "%s Failed to register service locator\n", Tag);

return EXIT_FAILURE;

}

/* Инициализируем транспорт до клиента. */

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

/* Подготавливаем структуры запроса: фиксированную часть и арену. */

union Server_entity_req req;

char req_buffer[Server_entity_req_arena_size];

struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer));

/* Подготавливаем структуры ответа: фиксированную часть и арену. */

union Server_entity_res res;

char res_buffer[Server_entity_res_arena_size];

struct nk_arena res_arena = NK_ARENA_INITIALIZER(res_buffer, res_buffer + sizeof(res_buffer));

/**

* Инициализируем диспетчер компонента Control. INCREMENT_STEP – значение "шага",

* которое будет прибавляться к входному аргументу value.

**/

struct Control_component component;

Control_component_init(&component, CreateObjectImpl(INCREMENT_STEP));

/* Инициализируем диспетчер сущности Server. */

struct Server_entity entity;

Server_entity_init(&entity, &component);

fprintf(stderr, "Hello I'm server\n");

/* Реализация цикла обработки запросов. */

do

{

/* Сбрасываем буферы с запросом и ответом. */

nk_req_reset(&req);

nk_arena_reset(&req_arena);

nk_arena_reset(&res_arena);

/* Ожидаем запрос от клиента. */

if (nk_transport_recv(&transport.base, &req.base_, &req_arena) != NK_EOK)

{

fprintf(stderr, "%s nk_transport_recv error\n", Tag);

}

else

{

/**

* Обрабатываем полученный запрос, вызывая реализацию connectionimpl

* запрошенного интерфейсного метода Ping.

*/

Server_entity_dispatch(&entity, &req.base_, &req_arena, &res.base_, &res_arena);

}

/* Отправляем ответ. */

if (nk_transport_reply(&transport.base, &res.base_, &res_arena) != NK_EOK)

{

fprintf(stderr, "%s nk_transport_reply error\n", Tag);

}

} while (true);

return EXIT_SUCCESS;

}

В начало

Файлы описаний в примере ping

Описание сущности Client

Сущность Client не реализует ни одного интерфейса, поэтому в EDL-описании достаточно объявить имя сущности.

Client.edl

/* Описание сущности Client. */

entity ping.Client

Описание сущности Server

Сущность Server реализует интерфейс Connection, содержащий два метода – Ping и Pong. Как и в примере echo, требуется объявить отдельный компонент (например Control), содержащий реализацию интерфейса Connection.

В EDL-описании сущности Server необходимо указать, что она содержит экземпляр компонента Control:

Server.edl

/* Описание сущности Server. */

entity ping.Server

/* controlimpl - именованный экземпляр компонента ping.Control. */

components {

controlimpl : ping.Control

}

В CDL-описании компонента Control необходимо указать, что он содержит реализацию интерфейса Connection:

Control.cdl

/* Описание компонента Control. */

component ping.Control

/* connectionimpl - реализация интерфейса ping.Connection. */

interfaces {

connectionimpl : ping.Connection

}

В пакете Connection необходимо объявить интерфейс Connection, содержащий два метода – Ping и Pong:

Connection.idl

/* Описание пакета Connection. */

package ping.Connection

interface {

Ping(in UInt32 value, out UInt32 result);

Pong(in UInt32 value, out UInt32 result);

}

Init-описание

Чтобы сущность Client могла вызвать метод сущности Server, между ними требуется создать IPC-канал.

init.yaml

entities:

- name: ping.Client

connections:

- target: ping.Server

id: server_connection

- name: ping.Server

Канал имеет имя server_connection. С помощью локатора сервисов можно получить дескриптор этого канала.

В начало

Политика безопасности решения в примере ping

Политика безопасности решения в этом примере разрешает запуск всех сущностей и позволяет любой сущности обращаться к сущностям Core и Server. При этом обращениями к сущности Server управляют методы модели безопасности Flow.

Конечный автомат, описанный в конфигурации объекта request_state модели безопасности Flow, имеет два состояния: ping_next и pong_next. Исходное состояние – ping_next. Разрешены только переходы из ping_next в pong_next и обратно.

При вызове методов Ping и Pong проверяется текущее состояние объекта request_state. В состоянии ping_next разрешен только вызов Ping, при этом состояние изменится на pong_next. Аналогично, в состоянии pong_next разрешен только вызов Pong, при этом состояние изменится на ping_next.

Таким образом, методы Ping и Pong разрешено вызывать только по очереди.

security.psl

/* Политика безопасности решения для демонстрации использования модели

* безопасности Flow в примере ping */

/* Включение PSL-файлов с формальными представлениями моделей безопасности

* Base и Flow */

use nk.base._

use nk.flow._

/* Создание объекта модели безопасности Flow */

policy object request_state : Flow {

type States = "ping_next" | "pong_next"

config = {

states : ["ping_next" , "pong_next"],

initial : "ping_next",

transitions : {

"ping_next" : ["pong_next"],

"pong_next" : ["ping_next"]

}

}

}

/* Запуск любых сущностей разрешен. */

execute {

grant ()

}

/* Любые запросы разрешены. */

request {

grant ()

}

/* Любые ответы разрешены. */

response {

grant ()

}

/* Включение EDL-файлов */

use EDL kl.core.Core

use EDL ping.Client

use EDL ping.Server

use EDL Einit

/* При запуске сущности Server инициировать эту сущность с конечным автоматом */

execute dst=ping.Server {

request_state.init {sid: dst_sid}

}

/* При вызове метода Ping проверить, что конечный автомат находится в состоянии ping_next.

Если это так, разрешить вызов метода Ping и перевести конечный автомат в состояние pong_next. */

request dst=ping.Server, endpoint=controlimpl.connectionimpl, method=Ping {

request_state.allow {sid: dst_sid, states: ["ping_next"]}

request_state.enter {sid: dst_sid, state: "pong_next"}

}

/* При вызове метода Pong проверить, что конечный автомат находится в состоянии pong_next.

Если это так, разрешить вызов метода Pong и перевести конечный автомат в состояние ping_next. */

request dst=ping.Server, endpoint=controlimpl.connectionimpl, method=Pong {

request_state.allow {sid: dst_sid, states: ["pong_next"]}

request_state.enter {sid: dst_sid, state: "ping_next"}

}

В начало

Сборка и запуск примера ping

См. "Сборка и запуск примеров".

В начало

Тестирование политики безопасности решения на языке Policy Assertion Language (PAL)

Базовый сценарий использования Policy Assertion Language (PAL) – это создание тестовых сценариев для проверки политик безопасности решения, написанных с использованием PSL.

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

Общий синтаксис

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

Тестовые сценарии можно объединять в группы. Группа сценариев может также содержать секции setup и finally, которые будут выполняться соответственно перед и после выполнения каждого сценария в этой группе. Это можно использовать для задания общих стартовых условий или для проверки общих инвариантов.

Для объявления группы тестовых сценариев используется блочная декларация assert:

assert "имя группы сценариев" {

setup {}

sequence "имя сценария 1" {}

sequence "имя сценария 2" {}

...

finally {}

}

Синтаксис запросов

Для конфигурирования запросов внутри тестовых сценариев используются декларации следующего вида:

<ожидание> <заголовок> <операция> <селекторы события> {сообщение}

Здесь:

  • <ожидание> – ожидаемое решение: grant, deny или any.

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

    Если ожидаемое решение не указано явно, ожидается решение grant.

  • <заголовок> – опциональный параметр, содержащий текстовый заголовок описываемого запроса.
  • <операция> – один из видов обращения к модулю безопасности: execute, security, request или response.
  • <селекторы события> – допустимые селекторы события совпадают с селекторами, использующимися при привязке методов моделей безопасности к событиям безопасности. Например можно указать сущность-получатель сообщения или вызываемый метод.
  • {сообщение} – параметр, содержащий значение аргументов вызываемого метода. Значение этого параметра должно соответствовать операции и селекторам запроса. По умолчанию передается пустое количество аргументов {}.

Пример:

assert "some tests" {

sequence "first sequence" {

// При вызове сущностью Client метода Ping реализации интерфейса pingComp.pingImpl сущности Server ожидается решение grant

// При этом в аргументе value метода Ping передается значение 100

grant "Client calls Ping" request src=Client dst=Server endpoint=pingComp.pingImpl method=Ping {value: 100}

// Ожидается, что сущности Server запрещено отвечать на запросы сущности Client

deny "Server cannot respond" response src=Server dst=Client

}

}

Сокращенная форма запросов

Для удобства можно также применять следующие сокращенные формы запросов:

  • При выполнении операции execute, можно сохранить дескриптор запускаемой сущности в переменную с помощью оператора <-.
  • Сокращенная форма для операции security: <сущность> ! <путь.к.методу> {сообщение}

    При выполнении разворачивается в полную форму: security src=<сущность> method=<путь.к.методу> {сообщение}

  • Сокращенная форма для операции request: <клиент> ~> <сервер> : <путь.к.имплементации.метода> {сообщение}

    При выполнении разворачивается в полную форму: request src=<клиент> dst=<сервер> endpoint=<путь.к.имплементации> method=<метод> {сообщение}

  • Сокращенная форма для операции response: <клиент> <~ <сервер> : <путь.к.имплементации.метода> {сообщение}

    При выполнении разворачивается в полную форму: response src=<сервер> dst=<клиент> endpoint=<путь.к.имплементации> method=<метод> {сообщение}

Пример:

assert "ping test"{

setup {

// переменная s содержит указатель на запущенный экземпляр сущности Server

s <- execute dst=ping.Server

// переменная c содержит указатель на запущенный экземпляр сущности Client

c <- execute dst=ping.Client

}

sequence "ping ping is denied" {

// При вызове сущностью Client метода Ping реализации интерфейса pingComp.pingImpl сущности Server ожидается решение grant

// При этом в аргументе value метода Ping передается значение 100

c ~> s : pingComp.pingImpl.Ping {value: 100 }

// При повторном вызове ожидается решение deny

deny c ~> s : pingComp.pingImpl.Ping {value: 100 }

}

sequence "normal" {

// При последовательном вызове методов Ping и Pong ожидаются решения grant

c ~> s : pingComp.pingImpl.Ping { value: 100 }

c ~> s : pingComp.pingImpl.Pong { value: 100 }

}

}

Запуск тестов

Чтобы запустить написанные на PAL тестовые сценарии, необходимо использовать флаг --tests run при запуске компилятора nk-psl-gen-c:

$ nk-psl-gen-c --tests run <другие параметры> ./security.psl

Компилятор nk-psl-gen-c сгенерирует код модуля безопасности и код тестовых сценариев, а затем скомпилирует их с помощью gcc и запустит.

Чтобы сгенерировать код тестовых сценариев, но не компилировать и запускать их, необходимо использовать флаг --tests generate.

В начало

Описание сущностей, компонентов и интерфейсов (EDL, CDL, IDL)

Все используемые в решении сущности, компоненты и интерфейсы должны быть статически описаны с помощью языков EDL, CDL и IDL. Соответствующие описания хранятся в файлах *.edl, *.cdl и *.idl.

Файлы описаний используются при сборке решения для следующих целей:

В этом разделе справки

Модель "сущность-компонент-интерфейс"

EDL

CDL

IDL

Типы данных IDL

Работа с ошибками в IDL

Составные имена сущностей, компонентов и интерфейсов

В начало

Модель "сущность-компонент-интерфейс"

Каждая сущность может предоставлять один или более интерфейсов взаимодействия. Несколько интерфейсов могут быть объединены в компонент. Компоненты могут быть вложены в другие компоненты. Все используемые в решении сущности, компоненты и интерфейсы должны быть статически описаны.

В KasperskyOS есть три вида файлов статических описаний:

  • EDL-файл содержит описание сущности на языке Entity Definition Language (далее EDL): ее имя, состав компонентов и интерфейсов, а также другую информацию.
  • CDL-файл содержит описание компонента на языке Component Definition Language (далее CDL). Описание компонента включает в себя его имя, а также набор включенных в него компонентов и реализуемых интерфейсов.
  • IDL-файл содержит описание пакета, содержащего один интерфейс на языке Interface Definition Language (далее IDL). Описание включает в себя имя пакета, объявление входящего в него интерфейса, а также объявление типов и именованных констант.

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

Сущность может не содержать ни одного компонента и не реализовывать ни один интерфейс. В этом случае она не предоставляет функциональности, доступной другим сущностям.

Сущность Server включает в себя экземпляры компонентов Terminal и Serial, содержащих различные реализации интерфейса Console

В начало

EDL

Каждая сущность в KasperskyOS должна быть описана на языке Entity Definition Language в отдельном файле <имя сущности>.edl.

Имя EDL-файла должно совпадать с именем сущности, которую он описывает.

EDL-файл содержит следующие разделы (порядок важен):

  1. Имя сущности. Обязательный раздел, начинающаяся с ключевого слова entity, за которым следует имя сущности.

    Имя сущности должно начинаться с заглавной буквы и не может содержать символ подчеркивания (_).

  2. Интерфейс безопасности, используемый сущностью. Раздел не является обязательным и должен быть добавлен, только если сущность использует интерфейс безопасности. Декларируется ключевым словом security, за которым следует полное имя интерфейса.
  3. Перечень реализаций интерфейсов, предоставляемых сущностью. Не является обязательным и описывается в секции interfaces. Каждая реализация интерфейса указывается отдельной строкой в следующем формате:

    interfaces {

    <имя реализации интерфейса>:<имя интерфейса>

    }

    Сущность может содержать несколько реализаций одного интерфейса. Все реализуемые интерфейсы необходимо описать на языке IDL в IDL-файлах.

    Имя реализации интерфейса не может содержать символ подчеркивания (_).

  4. Список экземпляров компонентов, входящих в сущность. Не является обязательным и описывается в секции components. Каждый экземпляр компонента указывается отдельной строкой в следующем формате:

    components {

    <имя экземпляра компонента>:<имя компонента>

    }

    Для каждого указанного компонента необходимо создать отдельный файл <имя компонента>.cdl, содержащий описание компонента на языке CDL. В сущность можно добавить несколько экземпляров одного и того же компонента, причем каждый может иметь отдельное состояние (см. ниже пример сущности UartDriver).

    Имя экземпляра компонента не может содержать символ подчеркивания (_).

Секции interfaces и components не являются обязательными и добавляются только для серверных сущностей. Реализации интерфейсов, объявленные в секции interfaces, а также реализации, входящие в компоненты из секции components могут использоваться сущностями-клиентами.

EDL поддерживает однострочные и многострочные комментарии в стиле C++:

/* Это комментарий

И это тоже */

// Ещё один комментарий

Примеры EDL-файлов

В простейшем случае сущность не использует интерфейс безопасности и не предоставляет функциональности другим сущностям, подобно сущности hello из примера hello. EDL-описание такой сущности содержит только ключевое слово entity и имя сущности.

Hello.edl

// Имя сущности: Hello

entity Hello

В следующем примере сущность EFoo содержит единственную реализацию интерфейса и не использует интерфейс безопасности.

Efoo.edl

// Имя сущности: Efoo

entity Efoo

// Сущность содержит именованную реализацию интерфейса IFool. Имя реализации: foo.

interfaces {

foo : IFoo

}

В следующем примере сущность UartDriver содержит два экземпляра компонента UartComp – по одному на каждое UART-устройство.

Сущность UartDriver не использует интерфейс безопасности.

UartDriver.edl

// Имя сущности: UartDriver

entity UartDriver

// uart0 и uart1 - имена экземпляров компонента UartComp,

// которые отвечают за два различных устройства

components {

uart0: UartComp

uart1: UartComp

}

В начало

CDL

Каждый используемый в решении компонент должен быть описан на языке CDL в отдельном файле <имя компонента>.cdl.

Имя CDL-файла должно совпадать с именем компонента, который он описывает.

CDL-файл содержит следующие разделы:

  1. Имя компонента. Перед именем компонента ставится ключевое слово component.

    Имя компонента должно начинаться с заглавной буквы и не может содержать символ подчеркивания (_).

  2. Интерфейс безопасности, содержащийся в этом компоненте. Раздел не является обязательным и должен быть добавлен, только если в компоненте содержится интерфейс безопасности. Декларируется ключевым словом security, за которым следует полное имя интерфейса.
  3. Перечень реализаций интерфейсов, которые включены в этот компонент. Интерфейсы декларируются в секции interfaces, внутри которой каждая реализация интерфейса указывается отдельной строкой в следующем формате:

    interfaces {

    <имя реализации интерфейса>:<имя интерфейса>

    }

    Компонент может содержать несколько реализаций одного интерфейса. Все реализуемые интерфейсы необходимо описать на языке IDL в IDL-файлах.

    Имя реализации интерфейса не может содержать символ подчеркивания (_).

  4. Список экземпляров компонентов, вложенных в этот компонент. Раздел не является обязательным и добавляется в том случае, если компонент содержит вложенные компоненты. Компоненты декларируются в секции components, внутри которой каждый экземпляр компонента указывается отдельной строкой в следующем формате:

    components {

    <имя экземпляра компонента>:<имя компонента>

    }

    Для каждого указанного компонента необходимо создать отдельный файл <имя компонента>.cdl, содержащий описание компонента на языке CDL. В компонент можно добавить несколько экземпляров одного и того же компонента, причем каждый может иметь отдельное состояние.

    Имя экземпляра компонента не может содержать символ подчеркивания (_).

СDL поддерживает однострочные и многострочные комментарии в стиле C++.

Примеры CDL-файлов

В простейшем случае компонент содержит единственную реализацию интерфейса, подобно компоненту ping из примера echo.

Ping.cdl

/* Имя компонента: Ping */

component Ping

/* Компонент содержит именованную реализацию интерфейса IPing. Имя реализации: pingimpl.*/

components {

pingimpl: IPing

}

В следующем примере компонент CoFoo содержит реализации двух интерфейсов, объявленных в двух разных пакетах Foo и Baz (т.е. в файлах Foo.idl и Bar.idl):

CoFoo.cdl

/* Имя компонента: CoFoo */

component CoFoo

interfaces {

/* Компонент содержит реализацию интерфейса Foo. Имя реализации: foo.*/

foo: Foo

/* Компонент содержит три разных реализации интерфейса Bar. Имена реализаций: bar1, bar2 и bar3.*/

bar1: Bar

bar2: Bar

bar3: Bar

}

В следующем примере компонент CoFoo содержит единственную реализацию интерфейса, а также вложенный компонент.

CoFoo.cdl

/* Имя компонента: CoFoo */

component CoFoo

interfaces {

/* Компонент содержит реализацию интерфейса Foo. Имя реализации: foo.*/

foo: Foo

}

components {

/* Компонент содержит экземпляр компонента CoBar. Имя экземпляра: bar.*/

bar: CoBar

}

В начало

IDL

Все реализуемые в решении интерфейсы необходимо описать в IDL-файлах.

В каждом IDL-файле может быть объявлен только один интерфейс.

Имя IDL-файла должно совпадать с именем интерфейса, который он описывает.

IDL-файл содержит следующие разделы:

  1. Имя интерфейса.

    Единственный обязательный раздел.

    Имя интерфейса должно начинаться с заглавной буквы и не может содержать символ подчеркивания (_).

    Объявление имени имеет следующий формат:

    package <имя описываемого интерфейса>

    Имя интерфейса может быть составным. В этом случае оно используется для формирования пути к IDL-файлу, например:

    /* Module.idl расположен по следующему пути: a/b/c/Module.idl */

    package a.b.c.Module

  2. Импорт внешних пакетов.

    Инструкция импорта пакета имеет следующий формат:

    import <имя внешнего пакета>

    Позволяет ввести в область видимости IDL-файла элементы внешнего пакета: именованные константы и пользовательские типы, включая структуры и вариантные типы.

  3. Объявление структур, вариантных типов, именованных констант и синонимов.
  4. Объявление методов интерфейса.

    Объявление методов интерфейса имеет следующий синтаксис:

    interface {

    <Объявления методов>

    }

    Внутри фигурных скобок находятся объявления методов, имеющие следующий синтаксис:

    <Имя метода> (<аргументы>);

    Имя метода не может содержать символ подчеркивания (_). Рекомендуется начинать имена методов с заглавной буквы.

    Аргументы разделяются на входные (in) и выходные (out) и перечисляются через запятую. Пример объявления интерфейса:

    interface {

    // Объявление метода Ping

    Ping(in UInt32 value, out UInt32 result);

    }

    Для возврата кодов ошибок, возникающих в результате обработки запроса сущностью-сервером, не рекомендуется использовать out-аргументы. Вместо них рекомендуется использовать специальные error-аргументы. Подробнее см. Работа с ошибками в IDL.

IDL поддерживает однострочные и многострочные комментарии в стиле C++.

Примеры IDL-файлов

В простейшем случае IDL-описание содержит только имя и объявление интерфейса:

Ping.idl

/* Ping — имя интерфейса */

package Ping

/* Объявление методов интерфейса */

interface {

// Объявление метода Ping

Ping(in UInt32 value, out UInt32 result);

}

Пример более сложного IDL-описания:

Foo.idl

// Объявление имени

package Foo

// Инструкции импорта

import Bar1

import Bar2

// Объявление именованной константы

const UInt32 MaxStringSize = 1024;

// Объявление синонима для пользовательского типа

typedef sequence <Char, MaxStringSize> String;

// Объявление структуры

struct BazInfo {

Char x;

array <String, 100> y;

sequence <sequence <UInt32, 100>, 200> y;

}

// Объявление методов интерфейса

interface {

Baz(in BazInfo a, out UInt32 b);

}

В начало

Типы данных IDL

Примитивные типы данных IDL

В IDL поддерживаются следующие примитивные типы данных:

  • SInt8, SInt16, SInt32, SInt64 (знаковое целое);
  • UInt8, UInt16, UInt32, UInt64 (беззнаковое целое);
  • Handle (дескриптор).

Примеры объявления переменных примитивных типов:

SInt32 value;

UInt8 ch;

Handle ResID;

Для примитивных типов, кроме типа Handle, можно объявить именованную константу с помощью ключевого слова const:

const UInt32 c0 = 0;

const UInt32 c1 = 1;

Далее мы рассмотрим сложные (составные) типы данных: union (вариантный тип), struct (структура), array (массив) и sequence (последовательность).

Тип union

Вариантный тип union, подобно обычному объединению в языке C, позволяет хранить значения разных типов в одной области памяти. Однако при передаче IPC-сообщения тип union снабжается дополнительным полем tag, позволяющим определить, какое именно поле объединения используется в переданном значении. Поэтому тип union также называют объединением с тегом. Пример объявления переменной типа union:

union foo {

UInt32 bar;

UInt8 baz;

}

Объявление вариантного типа является объявлением верхнего уровня.

Тип struct

Структуры в языке IDL аналогичны структурам в языке C. Пример объявления структуры:

struct BazInfo {

UInt64 x;

UInt64 y;

}

Объявление структуры является объявлением верхнего уровня.

Тип array

Массив (array) имеет фиксированный размер, задаваемый в момент объявления:

array <ТипЭлемента, ЧислоЭлементов>

В IDL массивы анонимны и могут объявляться непосредственно при объявлении других сложных типов, а также при объявлении интерфейсов.

Для назначения имени объявляемому типу массива используется ключевое слово typedef, например:

typedef array <SInt8, 1024> String;

Тип sequence

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

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

sequence <ТипЭлемента, ЧислоЭлементов>

Пример объявления именованной последовательности:

typedef sequence <SInt8, 1024> String;

Вложенные сложные типы

Сложные типы можно использовать при объявлении других сложных типов. При этом массивы (array) и последовательности (sequence) могут объявляться непосредственно внутри другого составного типа:

struct BazInfo {

UInt64 x;

array <UInt8, 100> y;

sequence <sequence <UInt32, 100>, 200> z;

}

Типы union или struct должны объявляться перед использованием:

union foo {

UInt32 value1;

UInt8 value2;

}

struct bar {

UInt32 a;

UInt8 b;

}

struct BazInfo {

foo x;

bar y;

}

Объявление синонима типа (typedef)

Ключевое слово typedef вводит имя, которое внутри своей области видимости является синонимом для указанного типа, например:

typedef array <UInt32, 20> value;

Конструкции typedef struct и typedef union недопустимы — ввести новое имя для структуры или вариантного типа можно только если они объявлены заранее:

union foo {

UInt32 value1;

UInt8 value2;

}

typedef foo bar;

Объявление typedef не вводит новый тип данных — оно вводит новые имена для уже существующих типов.

В начало

Работа с ошибками в IDL

Out-аргументы в IDL-описании не рекомендуется использовать для возврата сущности-клиенту кодов логических ошибок, возникающих в результате обработки запроса сущностью-сервером. Вместо этого рекомендуется использовать error-аргументы.

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

Использование error-аргументов позволяет:

  • не пересылать все out-аргументы методов в сообщении-ответе от сущности-сервера к сущности-клиенту в случае возврата ошибки. Вместо этого, при необходимости вернуть код ошибки, сообщение-ответ будет содержать только error-аргументы.
  • связывать методы моделей безопасности не только с событиями отправки ответов, но также с событиями возврата ошибок.

Язык IDL и компилятор NK предоставляют два способа использования error-аргументов:

  • Упрощенная обработка ошибок

    Этот способ используется компилятором NK по умолчанию.

    При использовании этого способа методы интерфейсов могут иметь не более одного error-аргумента типа UInt16. Значения возвращаемого error-аргумента метода упаковываются в код возврата соответствующего клиентского интерфейсного метода.

  • Расширенная обработка ошибок

    Для использования этого способа необходимо использовать параметр --extended-errors компилятора NK.

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

Типы и макросы для работы с ошибками содержатся в файле /opt/KasperskyOS-Community-Edition-<version>/toolchain/include/nk/types.h

Упрощенная обработка ошибок

При использовании упрощенной обработки ошибок методы интерфейсов могут иметь не более одного error-аргумента типа UInt16 с именем status.

В случае ошибки:

  • Реализации методов на стороне серверной сущности должны использовать макрос NK_ELOGIC_MAKE() для упаковки кода ошибки и возвращать результат его работы.
  • Соответствующие клиентские интерфейсные методы возвращают код ошибки (логической или транспортной), упакованный в значение типа nk_err_t. Для проверки на наличие в нем логических и транспортных ошибок используются макросы NK_ELOGIC_CHECK() и NK_ETRANSPORT_CHECK() соответственно.

В случае успешной обработки запроса:

  • Реализации методов на стороне серверной сущности должны поместить результаты в соответствующие out-аргументам поля сообщения-ответа и вернуть NK_EOK.
  • Соответствующие клиентские интерфейсные методы также возвращают NK_EOK.

Например, рассмотрим IDL-описание интерфейса, содержащего один метод:

Connection.idl

typedef UInt16 ErrorCode

// пример кода ошибки

const ErrorCode ESendFailed = 10;

interface {

Send(in Handle handle,

in Packet packet,

out UInt32 sentLen,

// метод Send имеет один error-аргумент status типа UInt16

error ErrorCode status

);

}

Реализация серверной сущности:

server.c

...

// реализация метода Send

nk_err_t Send_impl(struct Connection *self,

const struct Connection_Send_req *req,

const struct nk_arena* req_arena,

struct Connection_Send_res* res,

struct nk_arena* res_arena)

{

if (...) {

// в случае успешной обработки запроса помещаем результат в out-аргумент и возвращаем NK_EOK

res->sentLen = value;

return NK_EOK;

} else {

// в случае ошибки возвращаем результат работы макроса NK_ELOGIC_MAKE

return NK_ELOGIC_MAKE(Connection_ESendFailed);

}

Реализация клиентской сущности:

client.c

// значения возвращаемого error-аргумента метода упаковываются в код возврата соответствующего клиентского интерфейсного метода

nk_err_t ret = Connection_Send(ctx,

&req, &req_arena,

&res, NK_NULL);

if (ret == NK_EOK) {

// если ошибок нет, res содержит значения out-аргументов

return res.sentLen;

} else {

if (NK_ELOGIC_CHECK(ret)) {

// обрабатываем логическую ошибку

} else {

// обрабатываем транспортную ошибку

}

}

Расширенная обработка ошибок

При использовании расширенной обработки ошибок методы интерфейсов могут иметь более одного error аргумента любого типа.

В случае ошибки:

  • Реализации методов на стороне серверной сущности должны:
    • использовать макрос nk_err_reset(), чтобы выставить в сообщении-ответе флаг ошибки;
    • поместить коды ошибок в соответствующие error-аргументам поля сообщения-ответа;
    • вернуть NK_EOK.
  • Соответствующие клиентские интерфейсные методы возвращают код транспортной ошибки или NK_EOK, если транспортной ошибки нет. Для проверки наличия в сообщении-ответе логических ошибок используется макрос nk_msg_check_err(). При этом значения возвращаемых error-аргументов метода содержатся во внутренних полях структуры сообщения-ответа.

В случае успешной обработки запроса:

  • Реализации методов на стороне серверной сущности должны поместить результаты в соответствующие out-аргументам поля сообщения-ответа и вернуть NK_EOK.
  • Соответствующие клиентские интерфейсные методы также возвращают NK_EOK.

Например, рассмотрим IDL-описание интерфейса, содержащего один метод:

Connection.idl

typedef UInt16 ErrorCode

interface {

Send(in Handle handle,

in Packet packet,

out UInt32 sentLen,

// метод Send имеет один error-аргумент status типа UInt16

error ErrorCode status

);

}

При этом union-тип, сгенерированный компилятором NK для фиксированной части сообщения-ответа для этого метода, содержит как out-аргументы, так и error-аргументы:

Connection.idl.h

...

struct Connection_Send_res {

union {

struct {

struct nk_message base_;

nk_uint32_t sentLen;

};

struct {

struct nk_message base_;

nk_uint32_t sentLen;

} res_;

struct kl_Connection_Send_err {

struct nk_message base_;

nk_uint16_t status

} err_;

};

};

...

Реализация серверной сущности:

server.c

...

// реализация метода Send

nk_err_t Send_impl(struct Connection *self,

const struct Connection_Send_req *req,

const struct nk_arena* req_arena,

struct Connection_Send_res* res,

struct nk_arena* res_arena)

{

if (...) {

// в случае успешной обработки запроса помещаем результат в out-аргумент и возвращаем NK_EOK

res->sentLen = value;

return NK_EOK;

} else {

// в случае ошибки вызываем макрос nk_err_reset(), чтобы выставить флаг ошибки в сообщении-ответе

nk_err_reset(res)

// заполняем поля err-аргументов в структуре res->err_

res->err_.status = 10;

// возвращаем NK_EOK

return NK_EOK;

}

Реализация клиентской сущности:

client.c

nk_err_t ret = Connection_Send(ctx,

&req, &req_arena,

&res, NK_NULL);

if (ret == NK_EOK) {

if (nk_msg_check_err(res)) {

// обрабатываем логическую ошибку

// значения возвращаемых error-аргументов метода содержится во внутренних полях структуры сообщения-ответа

return res.err_.status;

} else {

// если ошибок нет, res содержит значения out-аргументов

return res.sentLen;

}

} else {

// обрабатываем транспортную ошибку

}

В начало

Составные имена сущностей, компонентов и интерфейсов

Для сущностей, компонентов и интерфейсов допустимо использовать составные имена, содержащие одну или несколько точек. При этом часть имени, следующая за последней точкой, называется кратким именем.

Например, если полное имя сущности – kl.VfsEntity, то краткое имя – VfsEntity. Интерфейс c полным именем main.security.Verify имеет краткое имя Verify.

Если имя несоставное (например, server), то оно является и кратким, и полным именем одновременно.

Имена и расположение файлов описаний

Имя EDL-файла должно совпадать с кратким именем сущности, которую он описывает. При этом составное имя используется для формирования пути к EDL-файлу. Например, сущность kl.VfsEntity должна быть описана в файле kl/VfsEntity.edl.

То же правило действует для компонентов и интерфейсов: имя CDL-файла должно совпадать с кратким именем компонента, а имя IDL-файла должно совпадать с кратким именем интерфейса. При этом составное имя используется для формирования пути к CDL- или IDL-файлу соответственно. Например, компонент net.Updater должен описываться в файле net/Updater.cdl, интерфейс c полным именем main.security.Verify должен описываться в файле main/security/Verify.idl.

Краткие имена сущностей, компонентов и интерфейсов не могут содержать символ подчеркивания (_). В сегментах составного имени символ подчеркивания разрешен.

Имена исполняемых файлов для запуска сущностей

При старте решения сущность Einit запускает другие сущности следующим образом: в ROMFS (в образе решения) выбирается для запуска исполняемый файл с именем, которое совпадает с кратким именем запускаемой сущности. Например, сущности Client и net.Client по умолчанию будут запущены из исполняемого файла с именем Client. Чтобы при старте решения сущность запускалась из исполняемого файла с другим именем, необходимо в init-описании использовать ключевое слово path.

Полные имена в описаниях

В EDL-, CDL- и IDL-описаниях, а также в init-описании и в конфигурации безопасности используются только полные имена сущностей, компонентов и интерфейсов. Для примера возьмем уже упоминавшиеся выше сущность kl.VfsEntity, компонент net.Updater и интерфейс main.security.Verify.

Пример EDL-описания сущности kl.VfsEntity:

VfsEntity.edl

entity kl.VfsEntity

...

Пример CDL-описания компонента net.Updater:

Updater.cdl

component net.Updater

...

Пример Init-описания:

init.yaml

entities:

- name: kl.VfsEntity

...

Пример конфигурации безопасности:

security.psl

...

/* Объявление сущности "kl.VfsEntity". */

use EDL kl.VfsEntity;

...

/* Конфигурирование запросов для вызова метода Check любых реализаций интерфейса "Verify". */

request interface=main.security.Verify, method=Check { grant () }

...

Имена NK-сгенерированных методов и типов

Имена сгенерированных типов и методов построены на основе полных имен сущностей, компонентов и интерфейсов. Например, для компонента net.Updater будет сгенерирована структура net_Updater_component, функция net_Updater_component_init, диспетчер net_Updater_component_dispatch, а также структуры запроса net_Updater_component_req и ответа net_Updater_component_res.

В начало

Запуск сущностей

В этом разделе справки

Сущность Einit

Файл init.yaml

В начало

Сущность Einit

Одной из важнейших сущностей в KasperskyOS является сущность c именем Einit, которая первой запускается ядром операционной системы при загрузке образа. В большинстве решений на базе KasperskyOS сущность Einit запускает все остальные сущности, входящие в решение, то есть служит инициализирующей сущностью.

В составе пакета инструментов KasperskyOS Community Edition поставляется утилита einit, которая позволяет сгенерировать код сущности Einit на языке C на основе файла init.yaml (так называемого init-описания). Сущность Einit, созданная с помощью скрипта einit, выполняет следующие инициализирующие функции:

  • создает все сущности, входящие в решение;
  • создает необходимые соединения (IPC-каналы) между сущностями;
  • копирует в окружение каждой сущности информацию о ее соединениях;
  • запускает сущности.

Стандартным способом использования утилиты einit является интеграция ее вызова в один из шагов сборочного скрипта, в результате которого утилита einit на основе файла init.yaml сгенерирует файл einit.c, содержащий код сущности Einit. На одном из следующих шагов сборочного скрипта необходимо скомпилировать файл einit.c в исполняемый файл сущности Einit и включить в образ решения.

Для сущности Einit не требуется создавать файлы статических описаний. Эти файлы поставляются в составе пакета инструментов KasperskyOS Community Edition и автоматически подключаются при сборке решения. При этом сущность Einit должна быть описана в файле security.psl.

В начало

Файл init.yaml

Файл init.yaml (init-описание) используется утилитой einit для генерации исходного кода инициализирующей сущности Einit. Этот файл содержит данные в формате YAML, которые идентифицируют:

  • сущности, запускаемые при загрузке KasperskyOS;
  • IPC-каналы, используемые сущностями для взаимодействия между собой.

Эти данные представляют собой словарь с ключем entities, содержащий список словарей сущностей. Ключи словаря сущности приведены в таблице ниже.

Ключи словаря сущности в init-описании

Ключ

Обязательный

Описание

name

Да

Имя сущности

task

Нет

Идентификатор сущности, который по умолчанию совпадает с именем сущности. У каждой сущности должен быть уникальный идентификатор.

Можно запустить несколько сущностей с одним именем, но разными идентификаторами.

path

Нет

Имя исполняемого файла в ROMFS (в образе решения), из которого будет запущена сущность. По умолчанию сущность будет запущена из файла в ROMFS с именем, совпадающим с кратким именем сущности. Например, сущности Client и net.Client по умолчанию будут запущены из файла Client.

Можно запустить несколько сущностей с одним именем из разных исполняемых файлов. При этом идентификаторы этих сущностей должны быть разными.

connections

Нет

Ключ словаря, содержащего список словарей IPC-каналов сущности. Этот список задает статически создаваемые IPC-каналы, клиентскими дескрипторами которых будет владеть сущность. По умолчанию список пуст. (Помимо статически создаваемых IPC-каналов, сущности могут использовать динамически создаваемые IPC-каналы.)

Ключи словаря IPC-каналов сущности приведены в таблице ниже.

Ключи словаря IPC-каналов сущности в init-описании

Ключ

Обязательный

Описание

id

Да

Идентификатор IPC-канала, который может быть задан как конкретным значением, так и ссылкой вида

{var: <имя константы>, include: <путь к заголовочному файлу>}.

У каждого IPC-канала должен быть уникальный идентификатор.

(Идентификатор IPC-канала используется сущностями, чтобы получить IPC-дескриптор.)

target

Да

Идентификатор сущности, которая будет владеть серверным дескриптором IPC-канала.

Примеры init-описаний

В приведенных примерах файл с init-описанием называется init.yaml, хотя может иметь любое имя.

init.yaml

# init-описание решения, содержащего сущность-клиент и сущность-сервер

entities:

# Сущность Client будет отправлять запросы сущности Server.

- name: Client

connections:

# Идентификатор сущности-сервера, которой сущность Client будет

# отправлять запросы

- target: Server

# Идентификатор IPC-канала для обмена IPC-сообщениями

# между сущностями

id: server_connection

# Сущность Server будет выступать в роли сервера

# (будет отвечать на запросы сущности Client).

- name: Server

init.yaml

# init-описание, где идентификатор IPC-канала задан ссылкой

entities:

- name: Client

connections:

- target: Server

# Идентификатор IPC-канала содержится в константе SERVER_CONN

# в файле src/example.h

id: {var: SERVER_CONN, include: src/example.h}

- name: Server

init.yaml

# init-описание, где заданы имена исполняемых файлов, из

# которых будут запущены сущности Client, ClientServer и

# MainServer

entities:

- name: Client

path: cl

connections:

- target: ClientServer

id: server_connection_cs

- name: ClientServer

path: csr

connections:

- target: MainServer

id: server_connection_ms

- name: MainServer

path: msr

init.yaml

# init-описание, при котором сущности MainServer и BkServer

# будут запущены из одного исполняемого файла

entities:

- name: Client

connections:

- id: server_connection_ms

target: MainServer

- id: server_connection_bs

target: BkServer

- name: MainServer

path: srv

- name: BkServer

path: srv

init.yaml

# init-описание, при котором будут запущены две сущности

# Server с разными идентификаторами из одного исполняемого

# файла

entities:

- name: Client

connections:

- id: server_connection_us

# Идентификатор сущности-сервера

target: UserServer

- id: server_connection_ps

# Идентификатор сущности-сервера

target: PrivilegedServer

- task: UserServer

name: Server

- task: PrivilegedServer

name: Server

В начало

IPC и транспорт

В этом разделе справки

Реализация IPC

Транспорт

В начало

Основы IPC в KasperskyOS

В KasperskyOS единственным способом межпроцессного взаимодействия (IPC) является обмен сообщениями.

Виды сообщений и роли сущностей

Обмен сообщениями в KasperskyOS построен по клиент-серверной модели.

Во взаимодействии двух сущностей одна из них является клиентом (клиентская сущность), а вторая — сервером (серверная сущность). Клиент инициирует взаимодействие, отправляя сообщение-запрос (далее также "запрос"). Сервер получает запрос и отвечает на него, отправляя клиенту сообщение-ответ (далее также "ответ").

IPC-каналы

Чтобы две сущности могли обмениваться сообщениями, между ними должен быть установлен IPC-канал (далее также "канал" или "соединение"). Каждая сущность может иметь несколько соединений, выступая как клиент для одних сущностей и как сервер — для других.

Системные вызовы

KasperskyOS предоставляет три системных вызова для обмена IPC-сообщениями: Call, Recv и Reply. Соответствующие функции объявлены в файле syscalls.h:

  • Call() – используется клиентом для отправки запроса и получения ответа. Принимает IPC-дескриптор, буфер с запросом и буфер под ответ.
  • Recv() – используется сервером для получения запроса. Принимает IPC-дескриптор и буфер под запрос.
  • Reply() – используется сервером для отправки ответа. Принимает IPC-дескриптор и буфер с ответом.

Функции Call(), Recv() и Reply() возвращают rcOk или код ошибки.

Вызовы Call() и Recv() являются блокирующими, то есть обмен сообщениями происходит по принципу рандеву:

  • Серверный поток, выполнивший вызов Recv(), остается заблокированным до момента получения запроса.
  • Клиентский поток, выполнивший вызов Call(), остается заблокированным до момента получения ответа от сервера.

Системные вызовы редко используются в коде сущности. Вместо них рекомендуется использовать более удобные NK-сгенерированные методы, которые, в свою очередь, выполняют системные вызовы.

В начало

IPC-каналы

IPC-каналы соединяют сущности друг с другом и используются для обмена IPC-сообщениями.

Канал представлен парой IPC-дескрипторов: клиентским и серверным, которые связаны друг с другом.

Две сущности соединены IPC-каналом

Канал является направленным. Сущность, которой принадлежит клиентский дескриптор канала (сущность-клиент), может отправлять запросы и получать ответы по этому каналу. Сущность, которой принадлежит серверный дескриптор канала (сущность-сервер), может получать запросы и отправлять ответы по этому каналу.

Сущность может иметь несколько соединений (каналов) с другими сущностями, выступая как клиент в одних соединениях, и как сервер – в других.

Сущность A является клиентом для сущности C и сервером – для сущности B. Обозначения: cl_1, cl_2 – клиентские IPC-дескрипторы; srv_1, srv_2 – серверные IPC-дескрипторы.

Создание и использование каналов

IPC-каналы создаются статически и динамически.

Статически созданные IPC-каналы создаются сущностью Einit при старте решения. Код сущности Einit генерируется на основе init-описания, в котором указываются все каналы (т.е. соединения), которые нужно создать.

Помимо статически создаваемых IPC-каналов, сущности могут использовать динамически создаваемые IPC-каналы.

Чтобы отправить и принять IPC-сообщение по конкретному каналу, нужно знать соответствующие значения IPC-дескрипторов (клиентского и серверного). Для получения значений IPC-дескрипторов используется локатор сервисов.

В этом разделе

Динамическое создание IPC-каналов

В начало
Динамическое создание IPC-каналов

Возможность динамического создания IPC-каналов позволяет изменять топологию взаимодействия сущностей "на лету". Это требуется, если неизвестно, какая именно сущность-сервер станет поставщиком ресурса, требуемого сущности-клиенту. Например, может быть неизвестно, на какой именно накопитель нужно будет записывать данные.

Динамически созданный IPC-канал, в отличие от статически созданного, имеет следующие особенности:

  • создается между запущенными сущностями;
  • создается совместно сущностью-клиентом и сущностью-сервером;
  • предусматривает удаление;
  • предусматривает использование сервера имен (сущности kl.core.NameServer), который обеспечивает передачу сведений о доступных реализациях интерфейсов от сущностей-серверов к сущностям-клиентам.

При динамическом создании IPC-канала используются функции:

  • интерфейса сервера имен (Name Server);
  • менеджера соединений (Connection Manager).

Интерфейс сервера имен представлен для пользователя следующими файлами:

  • coresrv/ns/ns_api.h – заголовочный файл библиотеки libkos;
  • coresrv/ns/NameServer.idl – описание IPC-интерфейса сервера имен на языке IDL.

Менеджер соединений представлен для пользователя следующими файлами:

  • coresrv/cm/cm_api.h – заголовочный файл библиотеки libkos;
  • services/cm/CM.idl – описание IPC-интерфейса менеджера соединений на языке IDL.

Динамическое создание IPC-канала осуществляется по следующему сценарию:

  1. Запускаются сущность-клиент, сущность-сервер и сервер имен.
  2. Сущность-сервер подключается к серверу имен функцией NsCreate() и публикует имя сущности-сервера, имя интерфейса и имя реализации интерфейса функцией NsPublishService().
  3. Сущность-клиент подключается к серверу имен функцией NsCreate() и выполняет поиск имени сущности-сервера и имени реализации интерфейса по имени интерфейса функцией NsEnumServices().
  4. Сущность-клиент запрашивает доступ к реализации интерфейса функцией KnCmConnect(), передавая ей в качестве аргументов найденные имя сущности-сервера и имя реализации интерфейса.
  5. Сущность-сервер вызывает функцию KnCmListen() для проверки наличия запроса доступа к предоставляемой реализации интерфейса от сущности-клиента.
  6. Сущность-сервер принимает запрос доступа к предоставляемой реализации интерфейса функцией KnCmAccept(), передавая ей в качестве аргументов имя сущности-клиента и имя реализации интерфейса, которые получены функцией KnCmListen().

Для использования сервера имен политика безопасности решения должна разрешать взаимодействие сущности kl.core.NameServer с сущностями, между которыми могут динамически создаваться IPC-каналы.

Сущность-сервер может снимать с публикации на сервере имен ранее опубликованные реализации интерфейсов функцией NsUnPublishService().

Сущность-сервер может отклонять запросы доступа к реализациям интерфейсов функцией KnCmDrop().

ns_api.h (фрагмент)

/**

* Функция осуществляет попытку подключиться к серверу имен name

* в течение msecs миллисекунд. Если параметр name имеет значение

* RTL_NULL, функция пытается подключиться к серверу имен ns

* (серверу имен по умолчанию). Выходной параметр ns содержит дескриптор

* соединения с сервером имен.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode NsCreate(const char *name, rtl_uint32_t msecs, NsHandle *ns);

/**

* Функция публикует реализацию интерфейса на сервере имен.

* Параметр ns задает дескриптор соединения с сервером имен.

* Параметр server задает имя сущности-сервера (например, ata).

* Параметр type задает имя публикуемого интерфейса (например, IBlkDev).

* Параметр service задает имя реализации публикуемого интерфейса

* (например, blkdev.blkdev).

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode NsPublishService(NsHandle ns, const char *type, const char *server,

const char *service);

/**

* Функция снимает с публикации реализацию интерфейса на сервере имен.

* Параметр ns задает дескриптор соединения с сервером имен.

* Параметр server задает имя сущности-сервера. Параметр type задает имя

* опубликованного интерфейса. Параметр service задает имя реализации

* опубликованного интерфейса.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode NsUnPublishService( NsHandle ns, const char *type, const char *server,

const char *service);

/**

* Функция перечисляет реализации интерфейса, опубликованные на сервере

* имен. Параметр ns задает дескриптор соединения с сервером имен.

* Параметр type задает имя требуемого интерфейса. Параметр index

* задает индекс для перечисления реализаций интерфейса.

* Выходной параметр server содержит имя сущности-сервера, предоставляющей

* реализацию интерфейса. Выходной параметр service содержит имя реализации

* интерфейса.

* Например, перечисление реализаций интерфейса IBlkDev осуществляется

* следующим образом.

* rc = NsEnumServices(ns, "IBlkDev", 0, outServerName, outServiceName);

* rc = NsEnumServices(ns, "IBlkDev", 1, outServerName, outServiceName);

* ...

* rc = NsEnumServices(ns, "IBlkDev", N, outServerName, outServiceName);

* Вызовы функции с инкрементированием индекса продолжается до тех пор,

* пока функция не вернет rcResourceNotFound.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode NsEnumServices(NsHandle ns, const char *type, unsigned index,

char *server, char *service);

cm_api.h (фрагмент)

/**

* Функция запрашивает доступ к реализации интерфейса с именем service,

* предоставляемой сущностью-сервером server. Параметр msecs задает время

* ожидания принятия запроса сущностью-сервером в миллисекундах. Выходной

* параметр endpoint содержит клиентский IPC-дескриптор. Выходной параметр

* rsid содержит идентификатор реализации интерфейса.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode KnCmConnect(const char *server, const char *service,

rtl_uint32_t msecs, Handle *endpoint,

rtl_uint32_t *rsid);

/**

* Функция проверяет наличие запроса доступа к реализации интерфейса

* с именем filter. Если параметр filter имеет значение RTL_NULL,

* проверяется наличие запроса доступа к любой реализации

* интерфейса. Параметр msecs задает время ожидания запроса

* в миллисекундах. Выходной параметр client содержит имя

* сущности-клиента. Выходной параметр service содержит имя реализации

* интерфейса.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode KnCmListen(const char *filter, rtl_uint32_t msecs, char *client,

char *service);

/**

* Функция отклоняет запрос доступа к реализации интерфейса

* с именем service от сущности-клиента client.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode KnCmDrop(const char *client, const char *service);

/**

* Функция принимает запрос доступа к реализации интерфейса

* с именем service от сущности-клиента client. Параметр rsid задает

* идентификатор реализации интерфейса. Параметр listener задает

* слушающий дескриптор. Если параметр listener имеет значение

* INVALID_HANDLE, создается новый слушающий дескриптор.

* Выходной параметр endpoint содержит серверный IPC-дескриптор.

* В случае успеха функция возвращает rcOk, иначе возвращает код ошибки.

*/

Retcode KnCmAccept(const char *client, const char *service, rtl_uint32_t rsid,

Handle listener, Handle *endpoint);

Параметр filter функции KnCmListen() является зарезервированным и не влияет на ее поведение. Поведение функции соответствует значению RTL_NULL параметра filter.

Слушающий дескриптор – серверный IPC-дескриптор с расширенными правами. Он создается при вызове функции KnCmAccept() с аргументом INVALID_HANDLE в параметре listener. Если при вызове функции KnCmAccept() передать ей слушающий дескриптор, то созданный серверный IPC-дескриптор обеспечит возможность получать запросы по всем IPC-каналам, ассоциированным с этим слушающим дескриптором. (Первый IPC-канал, ассоциированный со слушающим дескриптором, создается при вызове функции KnCmAccept() с аргументом INVALID_HANDLE в параметре listener. Второй и последующие IPC-каналы, ассоциированные со слушающим дескриптором, создаются при втором и последующих вызовах функции KnCmAccept() с передачей ей слушающего дескриптора, полученного при первом вызове.)

Слушающий дескриптор используется также при статическом создании IPC-каналов. Он создается при вызове функции KnHandleConnectEx() с аргументом INVALID_HANDLE в параметре srListener. Если при вызове функции KnHandleConnectEx() передать ей слушающий дескриптор, то созданный серверный IPC-дескриптор обеспечит возможность получать запросы по всем IPC-каналам, ассоциированным с этим слушающим дескриптором. Ассоциация слушающего дескриптора с несколькими IPC-каналами происходит, когда для одной сущности-сервера создается несколько IPC-каналов с одинаковыми именами.

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

В начало

Общая схема обмена сообщениями

Рассмотрим две сущности ("клиент" и "сервер"), между которыми установлен IPC-канал. Пусть cl — клиентский IPC-дескриптор этого канала, sr — серверный IPC-дескриптор этого канала.

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

Код сущности-клиента:

client.c

// Получение клиентского IPC-дескриптора cl с помощью локатора сервисов

// Отправка запроса

Call(cl, &RequestBuffer, &ResponseBuffer);

Код сущности-сервера:

server.c

// Получение серверного IPC-дескриптора sr с помощью локатора сервисов

// Получение запроса

Recv(sr, &RequestBuffer);

// Обработка запроса

// Отправка ответа

Reply(sr, &ResponseBuffer);

Обмен сообщениями происходит следующим образом:

  1. Один из потоков клиента выполняет системный вызов Call(), передав в аргументах дескриптор cl (клиентский дескриптор используемого канала), указатель на буфер с сообщением-запросом и указатель на буфер для ответа.
  2. Сообщение-запрос передается подсистеме Kaspersky Security System для проверки. Если Kaspersky Security System возвращает решение "разрешено", переходим к пункту 3. В противном случае вызов Call() завершается с кодом ошибки rcSecurityDisallow, переходим к пункту 9.
  3. Если сервер ожидает запрос от этого клиента (уже выполнил вызов Recv(), передав первым аргументом sr), переходим к пункту 4. В противном случае поток клиента остается заблокированным до тех пор, пока один из потоков сервера не выполнит системный вызов Recv() с первым аргументом sr.
  4. Сообщение-запрос копируется в адресное пространство сервера. Поток сервера разблокируется, а вызов Recv() завершается с кодом rcOk.
  5. Сервер обрабатывает полученное сообщение. Поток клиента остается заблокированным.
  6. Сервер выполняет системный вызов Reply(), передав в аргументах дескриптор sr и указатель на буфер с сообщением-ответом.
  7. Сообщение-ответ передается подсистеме Kaspersky Security System для проверки. Если Kaspersky Security System возвращает решение "разрешено", переходим к пункту 8. В противном случае вызовы Call() и Reply() завершаются с кодом ошибки rcSecurityDisallow (см. пункт 3).
  8. Сообщение-ответ копируется в адресное пространство клиента. Поток сервера разблокируется, вызов Reply() завершается с кодом rcOk. Поток клиента разблокируется, вызов Call() завершается с кодом rcOk.
  9. Обмен завершен.

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

В начало

Локатор сервисов (Service Locator)

Локатор сервисов — небольшая библиотека, которая позволяет:

  • узнать значение IPC-дескриптора по имени соединения;
  • узнать значение идентификатора интерфейса (RIID) по имени реализации интерфейса.

Значения IPC-дескриптора и RIID необходимы для обращения к конкретному интерфейсу конкретной серверной сущности. Эти значения используются при инициализации транспорта.

Чтобы использовать локатор сервисов, нужно в коде сущности подключить файл sl_api.h:

#include <coresrv/sl/sl_api.h>

Основные функции локатора сервисов

Функции, используемые на клиентской стороне:

  • ServiceLocatorConnect() – принимает имя соединения и возвращает клиентский IPC-дескриптор, соответствующий этому соединению (каналу).
  • ServiceLocatorGetRiid() – принимает IPC-дескриптор и имя реализации интерфейса в формате <имя экземпляра компонента>.<имя реализации интерфейса>. Возвращает соответствующий идентификатор RIID (порядковый номер реализации интерфейса).

Имя соединения указывается в файле init.yaml, имя экземпляра компонента – в EDL-файле, а имя реализации интерфейса – в CDL-файле.

Функции, используемые на серверной стороне:

  • ServiceLocatorRegister() – принимает имя соединения и возвращает серверный IPC-дескриптор, соответствующий этому соединению (каналу). Если канал входит в группу, то функция вернет слушающий дескриптор этой группы.

Пример использования локатора сервисов

Рассмотрим следующее решение, содержащее две сущности — Client и Server.

Клиентская сущность не реализует ни одного интерфейса.

Client.edl

entity Client

Серверная сущность содержит экземпляр компонента Ping. Имя экземпляра: ping_comp.

Server.edl

entity Server

components {

ping_comp: Ping

}

Пусть компонент Ping содержит именованную реализацию интерфейса Ping, объявленного в файле Ping.idl. Имя реализации: ping_impl.

Ping.cdl

component Ping

interfaces {

ping_impl: Ping

}

(Для краткости файл Ping.idl не приведен.)

В init-описании укажем, что сущности Client и Server должны быть соединены каналом с именем server_connection:

init.yaml

entities:

- name: Client

connections:

- target: Server

id: server_connection

- name: Server

Использование локатора сервисов на стороне клиента:

client.c

/* Подключаем локатор сервисов */

#include <coresrv/sl/sl_api.h>

/* Получаем клиентский IPC-дескриптор канала "server_connection" */

Handle handle = ServiceLocatorConnect("server_connection");

/* Получаем идентификатор (RIID) реализации ping_impl, содержащейся в экземпляре ping_comp */

nk_iid_t riid = ServiceLocatorGetRiid(handle, "ping_comp.ping_impl");

Использование локатора сервисов на стороне сервера:

server.c

/* Подключаем локатор сервисов */

#include <coresrv/sl/sl_api.h>

nk_iid_t iid;

/* Получаем серверный IPC-дескриптор канала "server_connection" */

Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);

В начало

Группы каналов

Каналы, в которых сущность выступает сервером, могут быть объединены в одну или несколько групп.

Каждой группе каналов соответствует свой слушающий дескриптор (listener handle).

Два IPC-канала имеют одинаковое имя "second_connection_to_A" и объединены в группу. Еще один канал с именем "first_connection_to_A" не входит в эту группу. Обозначения: cl_1, cl_2, cl_3 — клиентские IPC-дескрипторы; srv_1 — серверный IPC-дескриптор; ls_1 — слушающий IPC-дескриптор.

Слушающий дескриптор позволяет серверу принимать запросы сразу по всем каналам из группы. При этом нет необходимости создавать отдельный поток на каждый канал — достаточно в одном потоке сервера выполнить системный вызов Recv(), указав слушающий дескриптор.

Создание группы каналов с помощью init-описания

Чтобы каналы образовали группу, они должны соединяться с одной серверной сущностью и иметь одинаковые имена. Например, чтобы получить систему каналов, изображенную выше, можно использовать следующее init-описание:

init.yaml

entities:

# Сущность "A" выступает как сервер, поэтому ее список соединений пуст.

- name: A

# Сущность "B" будет соединена сущностью "A" двумя разными каналами.

- name: B

connections:

- target: A

id: first_connection_to_A

- target: A

id: second_connection_to_A

# Сущность "С" будет соединена с сущностью "A" каналом с именем

# "second_connection_to_A". Два канала с одинаковыми именами будут объединены

# в группу: на стороне сущности "A" они будут иметь один и тот же

# IPC-дескриптор (слушающий дескриптор ls_1).

- name: C

connections:

- target: A

id: second_connection_to_A

Обмен сообщениями в случае группы каналов

Рассмотрим, как происходит обмен сообщениями для группы каналов, описанной выше.

Клиентские сущности B и C получают значение клиентского дескриптора по имени соединения first_connection_to_A и отправляют запрос:

entity_B.c, entity_C.c

// Получение клиентского IPC-дескриптора cl, соответствующего соединению " second_connection_to_A".

Handle cl = ServiceLocatorConnect("second_connection_to_A");

// Отправка запроса

Call(cl, &RequestBuffer, &ResponseBuffer);

Оба используемых канала имеют одно имя — second_connection_to_A. Однако по этому имени сущности B и C получат разные значения дескрипторов: сущность B получит значение cl_2, а сущность C — значение cl_3.

Серверная сущность A получает значение слушающего дескриптора ls_1. Далее сущность A ожидает запросы по двум каналам сразу (от B и от C), используя дескриптор ls_1. После получения и обработки запроса сущность A отравляет ответ, используя дескриптор ls_1. Ответ будет отправлен тому клиенту, который инициировал запрос:

entity_A.c

nk_iid_t iid;

// Получение слушающего дескриптора ls_1, соответствующего соединению

"second_connection_to_A"

Handle ls_1 = ServiceLocatorRegister("second_connection_to_A", NULL, 0, &iid);

// Ожидание запроса от сущности B или C

Recv(ls_1, &RequestBuffer);

// Обработка поступившего запроса

// Отправка ответа тому клиенту, от которого пришел запрос

Reply(ls_1, &ResponseBuffer);

В начало

Структура IPC-сообщения

В KasperskyOS все взаимодействия между сущностями статически типизированы. Допустимые структуры IPC-сообщения определяются IDL-описанием интерфейсов сущности-получателя сообщения (сервера).

Корректное IPC-сообщение (как запрос, так и ответ) содержит фиксированную часть и арену.

Фиксированная часть сообщения

Фиксированная часть сообщения содержит аргументы фиксированного размера, а также RIID и MID.

Аргументы фиксированного размера – это аргументы любых IDL-типов, кроме типа sequence.

RIID и MID идентифицируют вызываемый интерфейс и метод:

  • RIID (Runtime Implementation ID) является порядковым номером вызываемой реализации интерфейса сущности (начиная с нуля).
  • MID (Method ID) является порядковым номером метода в содержащем его интерфейсе (начиная с нуля).

Тип фиксированной части сообщения генерируется компилятором NK на основе IDL-описания интерфейса. Для каждого метода интерфейса генерируется отдельная структура. Также генерируются типы union для хранения любого запроса к серверу, компоненту или интерфейсу.

Например, для метода Ping интерфейса Ping (компонент Ping сущности Server в примере echo) компилятор NK создаст тип фиксированной части запроса Ping_Ping_req и фиксированной части ответа Ping_Ping_res. Также будут сгенерированы следующие типы union:

  • Ping_req и Ping_res – фиксированные части запроса и ответа для любого метода интерфейса Ping;
  • Ping_component_req и Ping_component_res – фиксированные части запроса и ответа для любого метода любого интерфейса, реализация которого включена в компонент Ping;

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

  • Server_entity_req и Server_entity_res – фиксированные части запроса и ответа для любого метода любого интерфейса, реализация которого включена в любой компонент, экземпляр которого входит в сущность Server.

Арена

Арена представляет собой буфер для хранения аргументов переменного размера (IDL‑тип sequence).

Проверка сообщения в Kaspersky Security System

Проверку структуры отправляемого сообщения на корректность выполняет подсистема Kaspersky Security Module. Проверяются как запросы, так и ответы. Если сообщение имеет некорректную структуру, оно будет отклонено без вызова связанных с ним методов моделей безопасности.

Формирование структуры сообщения

В составе KasperskyOS Community Edition поставляются следующие инструменты, позволяющие упростить для разработчика создание и упаковку IPC-сообщения:

Формирование простейшего IPC-сообщения показано в примере echo.

В начало

Транспорт NkKosTransport

Транспорт NkKosTransport является удобной надстройкой над системными вызовами Call, Recv и Reply. Он позволяет работать отдельно с фиксированной частью и ареной сообщений.

Структура NkKosTransport и методы работы с ней объявлены в файле transport-kos.h.

Чтобы инициализировать транспорт, достаточно вызвать функцию NkKosTransport_Init(), указав IPC-дескриптор канала, по которому должны передаваться сообщения (handle):

#include <coresrv/nk/transport-kos.h>

NkKosTransport transport;

NkKosTransport_Init(&transport, handle, NK_NULL, 0);

Канал имеет два IPC-дескриптора: клиентский и серверный. Поэтому на стороне клиента при инициализации транспорта нужно указать клиентский дескриптор, а на стороне сервера – серверный дескриптор используемого канала.

Для вызова транспорта используются функции nk_transport_call(), nk_transport_recv() и nk_transport_reply(), объявленные в transport.h (подключен в файле transport-kos.h).

Функция nk_transport_call() предназначена для отправки запроса и получения ответа:

/* Фиксированная часть (req) и арена запроса (req_arena) должны быть инициализированы

* фактическими входными аргументами вызываемого метода. Фиксированная часть (req)

* должна также содержать значения RIID и MID.

* Функция nk_transport_call() формирует запрос и выполняет системный вызов Call.

* Полученный от сервера ответ помещается в res (фиксированная часть ответа) и

* res_arena (арена ответа). */

nk_transport_call(&transport.base, (struct nk_message *)&req, &req_arena, (struct nk_message *)&res, &res_arena);

При использовании на стороне клиента сгенерированного интерфейсного метода (например, метод IPing_Ping из примера echo) в запросе автоматически проставляются соответствующие значения RIID и MID, после чего вызывается функция nk_transport_call().

Функция nk_transport_recv() предназначена для получения запроса:

/* Функция nk_transport_recv () выполняет системный вызов Recv.

* Полученный от клиента запрос помещается в req (фиксированная часть ответа) и

* req_arena (арена ответа). */

nk_transport_recv(&transport.base, (struct nk_message *)&req, &req_arena);

Функция nk_transport_reply() предназначена для отправки ответа:

/* Фиксированная часть (res) и арена запроса (res_arena) должны быть инициализированы

* фактическими выходными аргументами вызываемого метода сервера.

* Функция nk_transport_reply() формирует ответ и выполняет системный вызов Reply. */

nk_transport_reply(&transport.base, (struct nk_message *)&res, &res_arena);

В начало

Сгенерированные методы и типы

При сборке решения компилятор NK на основе EDL-, CDL- и IDL-описаний генерирует набор специальных методов и типов, упрощающих формирование, отправку, прием и обработку IPC-сообщений.

Рассмотрим статическое описание сущности Server из примера echo. Это описание состоит из трех файлов: Server.edl, Ping.cdl и Ping.idl:

Server.edl

/* Описание сущности Server. */

entity Server

/* pingComp - именованный экземпляр компонента Ping. */

components {

pingComp: Ping

}

Ping.cdl

/* Описание компонента Ping. */

component Ping

/* pingImpl - реализация интерфейса Ping. */

interfaces {

pingImpl: Ping

}

Ping.idl

/* Описание интерфейса Ping. */

package Ping

interface {

Ping(in UInt32 value, out UInt32 result);

}

На основе этих файлов будут сгенерированы файлы Server.edl.h, Ping.cdl.h, и Ping.idl.h содержащие следующие методы и типы:

Методы и типы, общие для клиента и сервера

  • Абстрактные интерфейсы, содержащие указатели на реализации входящих в них методов.

    В нашем примере будет сгенерирован один абстрактный интерфейс – Ping:

    struct Ping_ops {

    nk_err_t (*Ping)(struct Ping *,

    const struct Ping_req *,

    const struct nk_arena *,

    struct Ping_res *,

    struct nk_arena *); };

    struct Ping {

    const struct Ping_ops *ops;

    };

  • Набор интерфейсных методов.

    При вызове интерфейсного метода в запросе автоматически проставляются соответствующие значения RIID и MID, после чего вызывается функция nk_transport_call().

    В нашем примере будет сгенерирован единственный интерфейсный метод Ping_Ping:

    nk_err_t Ping_Ping(struct Ping *,

    const struct Ping_Ping_req *,

    const struct nk_arena *,

    struct Ping_Ping_res *,

    struct nk_arena *);

Методы и типы, используемые только на клиенте

  • Типы прокси-объектов.

    Прокси-объект используется как аргумент интерфейсного метода. В нашем примере будет сгенерирован единственный тип прокси-объекта Ping_proxy:

    struct Ping_proxy {

    struct Ping base;

    struct nk_transport *transport;

    nk_iid_t iid;

    };

  • Функции для инициализации прокси-объектов.

    В нашем примере будет сгенерирована единственная инициализирующая функция Ping_proxy_init:

    void Ping_proxy_init(struct Ping_proxy *, struct nk_transport *, nk_iid_t);

  • Типы, определяющие структуру фиксированной части сообщения для каждого конкретного метода.

    В нашем примере будет сгенерировано два таких типа: Ping_Ping_req (для запроса) и Ping_Ping_res (для ответа).

    struct Ping_Ping_req {

    struct nk_message base_;

    nk_uint32_t value;

    };

    struct Ping_Ping_res {

    struct nk_message base_;

    nk_uint32_t result;

    };

Методы и типы, используемые только на сервере

  • Тип, содержащий реализации всех интерфейсов компонента, а также инициализирующая функция. (Для каждого компонента сервера.)

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

    В нашем примере будет сгенерирована структура Ping_component и функция Ping_component_init:

    struct Ping_component {

    struct Ping *pingImpl;

    };

    void Ping_component_init(struct Ping_component *, struct Ping *);

  • Тип, содержащий реализации всех интерфейсов предоставляемых сущностью-сервером непосредственно; все экземпляры компонентов, входящие в сущность-сервер; а также инициализирующая функция.

    В нашем примере будет сгенерирована структура Server_entity и функция Server_entity_init:

    struct Server_entity {

    struct Ping_component *pingComp;

    };

    void Server_entity_init(struct Server_entity *, struct Ping_component *);

  • Типы, определяющие структуру фиксированной части сообщения для любого метода конкретного интерфейса.

    В нашем примере будет сгенерировано два таких типа: Ping_req (для запроса) и Ping_res (для ответа).

    union Ping_req {

    struct nk_message base_;

    struct Ping_Ping_req Ping;

    };

    union Ping_res {

    struct nk_message base_;

    struct Ping_Ping_res Ping;

    };

  • Типы, определяющие структуру фиксированной части сообщения для любого метода любого интерфейса, реализация которого включена в конкретный компонент.

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

    В нашем примере будет сгенерировано два таких типа: Ping_component_req (для запроса) и Ping_component_res (для ответа).

    union Ping_component_req {

    struct nk_message base_;

    union Ping_req pingImpl;

    };

    union Ping_component_res {

    struct nk_message base_;

    union Ping_res pingImpl;

    };

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

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

    В нашем примере будет сгенерировано два таких типа: Server_entity_req (для запроса) и Server_entity_res (для ответа).

    union Server_entity_req {

    struct nk_message base_;

    union Ping_req pingComp_pingImpl;

    };

    union Server_entity_res {

    struct nk_message base_;

    union Ping_res pingComp_pingImpl;

    };

  • Dispatch-методы (диспетчеры) для отдельного интерфейса, компонента или сущности.

    Диспетчеры анализируют полученный запрос (значения RIID и MID), вызывают реализацию соответствующего метода, после чего сохраняют ответ в буфер. В нашем примере будут сгенерированы диспетчеры Ping_dispatch, Ping_component_dispatch и Server_entity_dispatch.

    Диспетчер сущности обрабатывает запрос и вызывает методы, реализуемые этой сущностью. Если запрос содержит некорректный RIID (например, относящийся к другой реализации интерфейса, которой нет у этой сущности) или некорректный MID, диспетчер возвращает NK_EOK или NK_ENOENT.

    nk_err_t Server_entity_dispatch(struct Server_entity *,

    const union Server_entity_req *,

    const struct nk_arena *,

    union Server_entity_res *,

    struct nk_arena *);

    В специальных случаях можно использовать диспетчеры интерфейса и компонента. Они принимают дополнительный аргумент – ID реализации интерфейса (nk_iid_t). Запрос будет обработан только если переданный аргумент и RIID из запроса совпадают, а MID корректен. В противном случае диспетчеры возвращают NK_EOK или NK_ENOENT.

    nk_err_t Ping_dispatch(struct Ping *,

    nk_iid_t,

    const union Ping_req *,

    const struct nk_arena *,

    union Ping_res *,

    struct nk_arena *);

    nk_err_t Ping_component_dispatch(struct Ping_component *,

    nk_iid_t,

    const union Ping_component_req *,

    const struct nk_arena *,

    union Ping_component_res *,

    struct nk_arena *);

В начало

Инструменты для сборки решения

Этот раздел содержит описание скриптов, утилит, компиляторов и шаблонов сборки, поставляемых в KasperskyOS Community Edition.

В этом разделе

Скрипты и компиляторы

Подготовка загрузочного образа решения

Развертывание загрузочного образа решения на целевых устройствах

В начало

Скрипты и компиляторы

Этот раздел содержит описание скриптов, утилит и компиляторов, поставляемых в рамках KasperskyOS Community Edition.

В этом разделе

Утилиты и скрипты сборки

Кросс-компиляторы

В начало

Утилиты и скрипты сборки

В состав KasperskyOS Community Edition входят следующие утилиты и скрипты сборки:

  • nk-gen-c

    Компилятор NK (nk-gen-c) генерирует набор транспортных методов и типов на основе EDL-, CDL- и IDL-описаний программ, компонентов и интерфейсов. Транспортные методы и типы нужны для формирования, отправки, приема и обработки IPC-сообщений.

  • nk-psl-gen-c

    Компилятор nk-psl-gen-c генерирует исходный код модуля безопасности Kaspersky Security System на основе файла политики безопасности решения (security.psl) и EDL-описаний программ, входящих в решение.

  • einit

    Утилита einit позволяет автоматизировать создание кода инициализирующего программы Einit. Это приложение первым запускается при загрузке KasperskyOS и запускает остальные программы, а также создает каналы (соединения) между ними.

  • makekss

    Скрипт makekss создает модуль безопасности Kaspersky Security System для ядра KasperskyOS.

  • makeimg

    Скрипт makeimg создает финальный загружаемый образ решения на базе KasperskyOS со всеми запускаемыми программами и модулем Kaspersky Security System.

В этом разделе

nk-gen-c

nk-psl-gen-c

einit

makekss

makeimg

В начало
nk-gen-c

Компилятор NK (nk-gen-c) генерирует набор транспортных методов и типов на основе EDL-, CDL- и IDL-описаний классов процессов, компонентов и интерфейсов. Транспортные методы и типы нужны для формирования, отправки, приема и обработки IPC-сообщений.

Транспортные методы и типы генерируются с полностью квалифицированными именами. В именах в качестве префиксов используется полное имя класса процесса/компонента/интерфейса. (объявленное в соответствующем EDL-, CDL- или IDL-файле) с заменой точек на подчеркивания (_).

Компилятор NK принимает EDL-, CDL- или IDL-файл и создает следующие файлы:

  • H-файл, содержащий объявление и реализацию транспортных методов и типов.
  • D-файл, в котором перечислены зависимости созданного C-файла. Этот файл может быть использован для автоматизации сборки с помощью утилиты make.

Синтаксис использования компилятора NK:

nk-gen-c [-I PATH][-o PATH][--types][--interface][--client][--server][--extended-errors][--enforce-alignment-check][--help][--version] FILE

Параметры:

  • FILE

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

  • -I PATH

    Путь к директории, содержащей вспомогательные файлы, необходимые для генерации транспортных методов и типов. По умолчанию эти файлы располагаются в директории /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

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

    Чтобы указать более одной директории, можно использовать несколько параметров -I.

  • -o PATH

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

  • -h, --help

    Отображает текст справки.

  • --version

    Отображает версию nk-gen-c

  • --enforce-alignment-check

    Включает обязательную проверку выравнивания обращений к памяти, даже если такая проверка отключена для целевой платформы. Если проверка включена, то компилятор NK добавляет дополнительные проверки выравнивания в код валидаторов IPC-сообщений.

    По умолчанию параметры проверки выравнивания обращения к памяти задаются для каждой платформы в файле system.platform.

  • --extended-errors

    Включает расширенную обработку ошибок в коде клиентских и серверных методов.

Выборочная генерация

Чтобы уменьшить количество генерируемого компилятором NK кода, можно использовать флаги выборочной генерации. Например, для программ, реализующих интерфейсы, удобно использовать флаг --server, а для программ, являющихся клиентами интерфейсов, удобно использовать флаг --client.

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

Флаги выборочной генерации для описаний интерфейсов (IDL-файлов):

  • --types

    Компилятор создаст только файлы, содержащие все константы и типы, включая переопределенные (typedef), из входного IDL-файла, а также типы из импортируемых IDL-файлов, которые используются в типах входного файла.

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

  • --interface

    Компилятор создаст файлы, создаваемые с флагом --types, а также структуры сообщений запросов и ответов для всех методов этого интерфейса.

  • --client

    Компилятор создаст файлы, создаваемые с флагом --interface, а также клиентские прокси-объекты и функции их инициализации для всех методов этого интерфейса.

  • --server

    Компилятор создаст файлы, создаваемые с флагом --interface, а также типы и методы диспетчера этого интерфейса.

Флаги выборочной генерации для описаний компонентов (CDL-файлов) и классов процессов (EDL-файлов):

  • --types

    Компилятор создаст файлы, создаваемые с флагом --types для всех интерфейсов, используемых в этом компоненте/классе процессов.

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

  • --interface

    Компилятор создаст файлы, создаваемые с флагом --types для этого компонента/класса процессов, а также файлы, создаваемые с флагом --interface для всех интерфейсов, используемых в этом компоненте/классе процессов.

  • --client

    Компилятор создаст файлы, создаваемые с флагом --interface, а также клиентские прокси-объекты и функции их инициализации для всех интерфейсов, используемых в этом компоненте/классе процессов.

  • --server

    Компилятор создаст файлы, создаваемые с флагом --interface, а также типы и методы диспетчера этого компонента/класса процессов и типы и методы диспетчеров для всех интерфейсов, используемых в этом компоненте/классе процессов.

В начало
nk-psl-gen-c

Компилятор nk-psl-gen-c генерирует исходный код модуля безопасности Kaspersky Security System на основе файла политики безопасности решения (security.psl) и EDL-описаний классов процессов, входящих в решение. Этот код используется скриптом makekss.

Компилятор nk-psl-gen-c также позволяет генерировать и запускать код тестовых сценариев для политики безопасности решения, написанных на языке PAL.

Синтаксис использования компилятора nk-psl-gen-c:

nk-psl-gen-c [-I PATH][-o PATH][--audit PATH][--tests ARG][--help][--version] FILE

Параметры:

  • FILE

    Путь к PSL-описанию политики безопасности решения (security.psl)

  • -I,--include-dir PATH

    Путь к директории, содержащей вспомогательные файлы, необходимые для генерации транспортных методов и типов. По умолчанию эти файлы располагаются в директории /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

    Компилятору nk-psl-gen-c потребуется доступ ко всем EDL-описаниям классов процессов, перечисленных в конфигурации безопасности, а также к CDL- или IDL-описаниям их компонентов и интерфейсов. Для того, чтобы компилятор nk-psl-gen-c мог найти эти описания, нужно передать пути к расположению этих описаний в параметре -I.

    Чтобы указать более одной директории, можно использовать несколько параметров -I.

  • -o,--output PATH

    Путь к создаваемому файлу, содержащему код модуля безопасности.

  • -t, --tests ARG

    Флаг контроля генерации кода и запуска тестовых сценариев для политики безопасности решения. Может принимать следующие значения:

    • skip – код тестовых сценариев не генерируется. Это значение используется по умолчанию, если флаг --tests не указан.
    • generate – код тестовых сценариев генерируется, но не компилируется и не запускается.
    • run – код тестовых сценариев генерируется, компилируется с помощью компилятора gcc и запускается.
  • -a, --audit PATH

    Путь к создаваемому файлу, содержащему код декодера аудита.

  • -h, --help

    Отображает текст справки.

  • --version

    Отображает версию nk-psl-gen-c.

В начало
einit

Утилита einit позволяет автоматизировать создание кода инициализирующей программы Einit. Это приложение первым запускается при загрузке KasperskyOS и запускает остальные программы, а также создает каналы (соединения) между ними.

Утилита einit принимает файл init-описания (по умолчанию init.yaml) и создает .c файл, в котором содержится код инициализирующей программы Einit. Программу Einit затем необходимо собрать с помощью компилятора C, поставляемого в KasperskyOS Community Edition.

Синтаксис использования утилиты einit:

einit -I PATH -o PATH [--help] FILE

Параметры:

  • FILE

    Путь к файлу описания классов процессов и соединений init.yaml.

  • -I PATH

    Путь к директории, содержащей вспомогательные файлы, необходимые для генерации инициализирующей программы. По умолчанию эти файлы располагаются в директории /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/include.

  • -o, --out-file PATH

    Путь к создаваемому .c файлу с кодом инициализирующей программы.

  • -h, --help

    Отображает текст справки.

В начало
makekss

Скрипт makekss создает модуль безопасности Kaspersky Security System.

Скрипт вызывает компилятор nk-psl-gen-c для генерации исходного кода модуля безопасности и затем компилирует полученный код, вызывая компилятор C, поставляемый в KasperskyOS Community Edition.

Скрипт принимает файл с описанием политики безопасности решения (по умолчанию security.psl) и создает файл модуля безопасности ksm.module.

Синтаксис использования скрипта makekss:

makekss --target=ARCH --module=PATH --with-nk="PATH" --with-nktype="TYPE" --with-nkflags="FLAGS" [--output="PATH"][--help][--with-cc="PATH"][--with-cflags="FLAGS"] FILE

Параметры:

  • FILE

    Путь к файлу конфигурации безопасности (.psl).

  • --target=ARCH

    Архитектура, для которой производится сборка.

  • --module=-lPATH

    Путь к библиотеке ksm_kss. Этот ключ передается компилятору C для компоновки с этой библиотекой.

  • --with-nk=PATH

    Путь к компилятору nk-psl-gen-c, который будет использоваться для генерации исходного кода модуля безопасности. По умолчанию компилятор расположен в /opt/KasperskyOS-Community-Edition-<version>/toolchain/bin/nk-psl-gen-c.

  • --with-nktype="TYPE"

    Указывает на тип компилятора NK, который будет использоваться. Для использования компилятора nk-psl-gen-c, необходимо указать тип psl.

  • --with-nkflags="FLAGS"

    Параметры, с которыми вызывается компилятор nk-psl-gen-c.

    Компилятору nk-psl-gen-c потребуется доступ ко всем EDL-описаниям классов процессов, перечисленных в конфигурации безопасности, а также к CDL- или IDL-описаниям их компонентов и интерфейсов. Для того, чтобы компилятор nk-psl-gen-c мог найти эти описания, нужно передать пути к расположению этих описаний в параметре --with-nkflags, используя параметр -I компилятора nk-psl-gen-c.

  • --output=PATH

    Путь к создаваемому файлу модуля безопасности.

  • --with-cc=PATH

    Путь к компилятору C, который будет использоваться для сборки модуля безопасности. По умолчанию используется компилятора, поставляемый в KasperskyOS Community Edition.

  • --with-cflags=FLAGS

    Параметры, с которыми вызывается компилятор C.

  • -h, --help

    Отображает текст справки.

В начало
makeimg

Скрипт makeimg создает финальный загружаемый образ решения на базе KasperskyOS со всеми запускаемыми программами и модулем Kaspersky Security System.

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

  • образ решения;
  • образ решения без таблиц символов (.stripped);
  • образ решения с отладочными таблицами символов (.dbg.syms).

Синтаксис использования скрипта makeimg:

makeimg --target=ARCH --sys-root=PATH --with-toolchain=PATH --ldscript=PATH --img-src=PATH --img-dst=PATH --with-init=PATH [--with-extra-asflags=FLAGS][--with-extra-ldflags=FLAGS][--help] FILES

Параметры:

  • FILES

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

    Модуль безопасности (ksm.module) нужно указывать явно, иначе он не будет включен в образ решения. Программу Einit указывать не нужно, так как она будет включена в образ решения автоматически.

  • --target=ARCH

    Архитектура, для которой производится сборка.

  • --sys-root=PATH

    Путь к корневой директории sysroot. По умолчанию эта директория расположена в /opt/KasperskyOS-Community-Edition-<version>/sysroot-arm-kos/.

  • --with-toolchain=PATH

    Путь к набору вспомогательных утилит, необходимых для сборки решения. По умолчанию эти утилиты расположены в /opt/KasperskyOS-Community-Edition-<version>/toolchain/.

  • --ldscript=PATH

    Путь к скрипту компоновщика, необходимому для сборки решения. По умолчанию этот скрипт расположен в /opt/KasperskyOS-Community-Edition-<version>/libexec/arm-kos/.

  • --img-src=PATH

    Путь к заранее скомпилированному ядру KasperskyOS, не содержащему romfs. По умолчанию ядро расположено в /opt/KasperskyOS-Community-Edition-<version>/libexec/arm-kos/.

  • --img-dst=PATH

    Путь к создаваемому файлу образа.

  • --with-init=PATH

    Путь к исполняемому файлу инициализирующей программы Einit.

  • --with-extra-asflags=FLAGS

    Дополнительные флаги для ассемблера AS.

  • --with-extra-ldflags=FLAGS

    Дополнительные флаги для компоновщика LD.

  • -h, --help

    Отображает текст справки.

В начало

Кросс-компиляторы

Свойства кросс-компиляторов KasperskyOS

Кросс-компиляторы, входящие в состав KasperskyOS Community Edition, поддерживают процессоры с архитектурой arm.

В toolchain в составе KasperskyOS Community Edition входят следующие инструменты для кросс-компиляции:

  • GCC:
    • arm-kos-gcc
    • arm-kos-g++
  • Binutils:
    • Ассемблер AS: arm-kos-as
    • Компоновщик LD: arm-kos-ld

В GCC, кроме стандартных макросов, определен дополнительный макрос __KOS__=1. Использование этого макроса упрощает портирование программного кода на KasperskyOS, а также разработку платформонезависимых приложений.

Чтобы просмотреть список стандартных макросов GCC, выполните следующую команду:

echo '' | arm-kos-gcc -dM -E -

Особенности работы компоновщика

При выполнении сборки исполняемого файла программы компоновщик по умолчанию связывает следующие библиотеки в указанном порядке:

  1. libc – стандартная библиотека языка C.
  2. libm – библиотека, реализующая математические функции стандартной библиотеки языка C.
  3. libvfs_stubs – библиотека, содержащая заглушки функций ввода/вывода (например, open, socket, read, write).
  4. libkos – библиотека, состоящая из двух частей. Первая часть предоставляет C-интерфейс для доступа к функциям ядра KasperskyOS. Она доступна через заголовочные файлы, находящиеся в директории coresrv, например: #include <coresrv/vmm/vmm_api.h>. Вторая часть библиотеки libkos является оберткой над первой частью и содержит дополнительные функции синхронизации: mutex, semaphore, event. Взаимодействие других библиотек (включая libc) с ядром происходит через библиотеку libkos.
  5. libenv – клиентская библиотека подсистемы настройки окружения программ (переменных окружения, аргументов функции main и пользовательских конфигураций).
  6. libsrvtransport-u – внутренняя библиотека с реализацией транспорта межпроцессного взаимодействия сервисов ядра KasperskyOS.
В начало

Подготовка загрузочного образа решения

Для автоматизации процесса подготовки загрузочного образа решения нужно настроить систему сборки. За основу можно взять системы сборки, реализованные в примерах.

Этот раздел описывает различные способы настройки системы сборки для подготовки загрузочного образа решения.

В этом разделе

Использование шаблона Makefile из состава KasperskyOS Community Edition

Использование CMake из состава KasperskyOS Community Edition

Использование собственной системы сборки

В начало

Использование шаблона Makefile из состава KasperskyOS Community Edition

Для упрощения процесса подготовки загрузочного образа решения с использованием системы сборки make вы можете использовать шаблон build.mk из состава KasperskyOS Community Edition. Файл шаблона располагается по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/common/build.mk

Чтобы подготовить систему сборки make с использованием шаблона build.mk, в скрипте сборки Makefile:

  1. Задайте значение переменной targets. В значении переменной перечислите через пробел все программы, входящие в решение. Указывать программы Einit, kl.core.Core необязательно, так как они обрабатываются отдельно.
  2. Для каждой программы, указанной в переменной targets, задайте значения следующим переменным:
    • <имя-программы>-objects – список объектных файлов программы. Необходимо перечислить все объектные файлы.

      Имена объектных файлов получаются из имен исходных файлов сущности по следующим правилам:

      • *.c → *.o
      • *.idl → *.idl.o
      • *.cdl → *.cdl.o
      • *.edl → *.edl.o
    • <имя-программы>-ldflags – список флагов, передаваемых компоновщику. Если сущность использует виртуальную файловую систему, необходимо передать флаги, указанные в переменной LIBVFS_REMOTE.
    • <имя-программы>-base – адрес загрузки программы в шестнадцатеричной системе. Если переменная не указана, адрес присваивается автоматически. Используйте эту переменную для отладки программы.

    Этот же адрес передается отладчику с помощью следующей команды, которая может быть добавлена в .gdbinit-файл:

    add-symbol-file <имя-программы> <адрес-загрузки-программы>

  3. Если раздел ROMFS должен содержать дополнительные файлы, задайте значение переменной ROMFS-FILES. В значении переменной перечислите файлы через пробел. Если вы используете виртуальную файловую систему, необходимо передать файл, указанный в переменной VFS_ENTITY.
  4. Добавьте инструкцию импорта шаблона build.mk с помощью следующей команды:

    include /opt/KasperskyOS-Community-Edition-<version>/common/build.mk

В файле шаблона build.mk реализованы следующие цели сборки:

  • sim (цель по умолчанию) – запуск загрузочного образа решения с помощью эмулятора QEMU.

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

  • kos-image – сборка загрузочного образа решения для запуска на целевой аппаратной платформе.
  • gdbsim – запуск загрузочного образа решения с возможностью отладки. После старта эмуляции QEMU ожидает старта отладчика. Для этого в другой командной строке необходимо вызвать цель gdb.

    Убедитесь, что порт протокола TCP/IP 1234 открыт, например с помощью команды netstat -anput.

    Порт 1234 прослушивается программой gdbserver, которая используется для удаленной отладки приложений и входит в состав эмулятора QEMU.

    При использовании отладчика gdb необходимо использовать аппаратные точки останова (hbreak). Эмулятор QEMU, который используется в примерах, запускается с ключом -enable-kvm, из-за чего использование обычных точек останова (break) невозможно.

  • gdb – запуск отладчика. После запуска отладчика для программ, которые требуется отлаживать, выполните следующие команды:

    add-symbol-file <имя-программы> <адрес-загрузки-программы>

    target remote localhost:1234

Пример

В этом примере используется система сборки make. Помимо действий, выполняемых в шаблоне build.mk, в скрипте сборки необходимо указать программу Hello и список объектных файлов этой программы. Адрес загрузки указывается для целей отладки решения.

Makefile

# Список программ для сборки.

targets = hello

# Список объектных файлов программы Hello

hello-objects = hello.o hello.edl.o

# Адрес загрузки сущности Hello (в hex)

hello-base = 800000

# Включить шаблон с общими правилами сборки.

include ../common/build.mk

Чтобы запустить систему сборки make, выполните команду make hello.

Чтобы запустить пример hello, находясь в директории /opt/KasperskyOS-Community-Edition-<version>/examples/hello, выполните команду make.

В начало

Использование CMake из состава KasperskyOS Community Edition

Система автоматизации сборки CMake поддерживает кросс-компиляцию приложений. Для выполнения кросс-компиляции с помощью CMake требуется указать путь к файлу расширения системы сборки (toolchain.cmake).

Чтобы выполнить кросс-компиляцию приложения для KasperskyOS, задайте значение переменной: DCMAKE_TOOLCHAIN_FILE=/opt/KasperskyOS-Community-Edition-<version>/toolchain/share/toolchain.cmake

В состав KasperskyOS Community Edition входит библиотека platform, содержащая набор готовых скриптов для системы CMake.

Чтобы подготовить приложение к отладке:

  1. В файле CMakeLists.txt задайте значение параметра LINK_FLAGS для приложения, которое вы хотите отлаживать, следующим образом:

    set_target_properties (<имя-приложения> PROPERTIES LINK_FLAGS "-Ttext <адрес-секции-text>")

    Скрипт автоматически создает .gdbinit-файлы. В .gdbinit-файлах находится набор команд для отладчика GDB. Этот набор команд определяет, по какому адресу отладчик GDB загружает сущности для отладки.

  2. Выполните сборку приложения.
В начало

Использование собственной системы сборки

Вы можете использовать другие системы сборки или реализовать собственную систему сборки для подготовки загрузочного образа решения.

Для того чтобы подготовить загрузочный образ решения, система сборки должна включать следующие действия:

  1. Генерацию кода транспортных методов и типов, используемых для формирования, отправки, приема и обработки IPC-сообщений между сущностями, входящими в решение.

    Для этого воспользуйтесь компилятором NK. В аргументах команды передайте путь к файлам EDL-, CDL- и IDL-описаний сущностей, компонентов и интерфейсов.

  2. Сборку всех сущностей, входящих в решение.

    Для этого воспользуйтесь кросс-компиляторами, входящими в состав KasperskyOS Community Edition.

  3. Сборку инициализирующей сущности Einit.

    Для генерации кода сущности Einit воспользуйтесь утилитой einit. В аргументах команды передайте путь к файлу файл init-описания (по умолчанию init.yaml).

    Сущность Einit затем необходимо собрать с помощью компилятора C, поставляемого в рамках KasperskyOS Community Edition.

  4. Сборку модуля ядра с подсистемой Kaspersky Security System.

    Для этого воспользуйтесь скриптом makekss. В аргументах команды передайте путь к файлу конфигурации безопасности (по умолчанию security.psl).

  5. Создание образа решения.

    Для этого воспользуйтесь скриптом makeimg. В аргументах команды передайте исполняемые ELF-файлы сущностей, модуль ядра с Kaspersky Security System, образ ядра KasperskyOS и любые дополнительные файлы.

В начало

Развертывание загрузочного образа решения на целевых устройствах

Чтобы развернуть загрузочный образ решения на целевом устройстве:

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

    fdisk -l

  3. При необходимости создайте на носителе новый раздел, в котором будет развернут загрузочный образ решения, например с помощью утилиты fdisk.
  4. Если в разделе отсутствует файловая система, создайте ее, например с помощью утилиты mkfs.

    Вы можете использовать любую файловую систему, которую поддерживает загрузчик GRUB 2.

  5. Смонтируйте носитель информации.

    mkdir /mnt/kos_device && mount /dev/sdXY /mnt/kos_device

    Здесь /mnt/kos_device – точка монтирования; /dev/sdXY – имя блочного устройства; X – буква, соответствующая подключенному носителю; Y – номер раздела.

  6. Установите на носитель загрузчик операционной системы GRUB 2.

    Чтобы установить GRUB 2, выполните следующую команду:

    grub-install --force --removable \
    --boot-directory=/mnt/kos_device/boot /dev/sdX

    Здесь /mnt/kos_device – точка монтирования /dev/sdX – имя блочного устройства; X – буква, соответствующая подключенному носителю.

  7. Скопируйте загрузочный образ решения в корневую директорию смонтированного носителя.
  8. В файле /mnt/kos_device/boot/grub/grub.cfg добавьте секцию menuentry, указывающую на загрузочный образ решения.

    menuentry "KasperskyOS" {

    multiboot /my_kasperskyos.img

    boot

    }

  9. Размонтируйте носитель.

    umount /mnt/kos_device

    Здесь /mnt/kos_device – точка монтирования.

После выполнения этих действий вы можете запускать KasperskyOS с этого носителя.

В начало

Паттерны безопасности при разработке под KasperskyOS

Каждое решение на базе KasperskyOS имеет определенные сценарии использования и предназначено для противодействия конкретным угрозам безопасности. Тем не менее, существуют типовые сценарии и угрозы, которые встречаются во многих решениях. Этот раздел описывает типовые риски и угрозы, а также содержит описание архитектурных паттернов, применение которых позволит повысить безопасность решения.

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

Система паттернов безопасности – это набор паттернов безопасности вместе с инструкциями по их реализации, сочетанию и практическому использованию в проектировании безопасных программных систем.

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

Этот раздел содержит описание набора паттернов безопасности, примеры реализации которых содержатся в составе KasperskyOS Community Edition.

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

В этом разделе

Паттерн Distrustful Decomposition

Паттерн Defer to Kernel

Паттерн Policy Decision Point

Паттерн Privilege Separation

Паттерн Information Obscurity

В начало

Паттерн Distrustful Decomposition

Описание

При использовании монолитного приложения появляется необходимость дать все необходимые для его работы привилегии одному процессу. Эту проблему решает паттерн Distrustful Decomposition.

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

Использование паттерна Distrustful Decomposition уменьшает:

  • поверхность атаки для каждого из процессов;
  • функциональность и данные, которые станут доступны злоумышленнику, если один из процессов будет скомпрометирован.

Альтернативные названия

Privilege Reduction.

Контекст

Различные функции приложения требуют разного уровня привилегий.

Проблема

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

Решение

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

Структура

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

distrustful_decomposition_structure

Работа

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

Рекомендации по реализации

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

Особенности реализации в KasperskyOS

В универсальных ОС (например Linux, Windows) этот паттерн не использует ничего, кроме стандартной модели процессов/привилегий, уже существующей в этих ОС. Каждая программа выполняется в собственном пространстве процессов с потенциально разными привилегиями пользователя в каждом процессе, однако атака на ядро ОС снижает ценность применения этого паттерна.

Специфика применения этого паттерна при разработке под KasperskyOS состоит в том, что контроль над процессами и IPC возложен на микроядро, атака на которое сложна. Для контроля IPC используется модуль безопасности Kaspersky Security Module.

За счет использования механизмов KasperskyOS достигается высокий уровень надежности программной системы при том же или меньшем объеме усилий разработчика в сравнении с использованием этого же паттерна в программах под универсальные ОС.

Кроме этого, KasperskyOS предоставляет возможность гибкой настройки политик безопасности. При этом процесс задания и изменения политик безопасности потенциально независим от процесса разработки самих приложений.

Связанные паттерны

Использование паттерна Distrustful Decomposition предполагает использование паттернов Defer to Kernel и Policy Decision Point.

Примеры реализации

Примеры реализации паттерна Distrustful Decomposition:

Источники

Паттерн Distrustful Decomposition подробно рассмотрен в следующих работах:

В этом разделе

Пример Secure Logger

Пример Separate Storage

В начало

Пример Secure Logger

Пример Secure Logger демонстрирует использование паттерна Distrustful Decomposition для решения задачи разделения функциональности чтения и записи в журнал событий.

Архитектура примера

Цель безопасности в примере Secure Logger заключается в том, чтобы предотвратить возможность искажения или удаления информации в журнале событий. В примере для достижения этой цели безопасности используются возможности, предоставляемые KasperskyOS.

При рассмотрении системы журналирования можно выделить следующие функциональные шаги:

  • генерация информации для записи в журнал;
  • сохранение информации в журнал;
  • чтение записей из журнала;
  • предоставление записей в удобном для потребителя виде.

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

Для этого пример Secure Logger содержит четыре сущности: Application, Logger, Reader и LogViewer.

  • Сущность Application инициирует создание записей в журнале событий, поддерживаемом сущностью Logger.
  • Сущность Logger создает записи в журнале и записывает их на диск.
  • Сущность Reader читает записи с диска для передачи сущности LogViewer.
  • Сущность LogViewer передает записи пользователю.

IPC-интерфейс, который предоставляет сущность Logger, предназначен только для записи в хранилище. IPC-интерфейс сущности Reader предназначен только для чтения из хранилища. Архитектура примера выглядит следующим образом:

secure_logger_uml

  • Сущность Application использует интерфейс сущности Logger для сохранения записей.
  • Сущность LogViewer использует интерфейс сущности Reader для чтения записей и предоставления их пользователю.

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

Политика безопасности в примере Secure Logger имеет следующие особенности:

  • Сущность Application имеет возможность обращаться к сущности Logger для создания новой записи в журнале событий.
  • Сущность LogViewer имеет возможность обращаться к сущности Reader для чтения записей из журнала событий.
  • Сущность Application не имеет возможности обращаться к сущности Reader для чтения записей из журнала событий.
  • Сущность LogViewer не имеет возможности обращаться к сущности Logger для создания новой записи в журнале событий.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/secure_logger

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример Separate Storage

Пример Separate Storage демонстрирует использование паттерна Distrustful Decomposition для решения задачи раздельного хранения данных для доверенных и недоверенных приложений.

Архитектура примера

Пример Separate Storage содержит две пользовательские сущности: UserManager и CertificateManager.

Эти сущности работают с данными, которые размещаются в соответствующих файлах:

  • Сущность UserManager работает с данными из файла userlist.txt;
  • Сущность CertificateManager работает с данными из файла certificate.cer.

Каждая из этих сущностей использует собственный экземпляр сущности VFS для доступа к отдельной файловой системе. При этом каждая сущность VFS включает в себя драйвер блочного устройства, связанный с отдельным логическим разделом диска. Сущность UserManager не имеет доступа к файловой системе сущности CertificateManager и наоборот.

secure_logger_uml

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

Политика безопасности в примере Separate Storage имеет следующие особенности:

  • Сущность UserManager имеет доступ к файловой системе только через сущность VfsUser.
  • Сущность CertificateManager имеет доступ к файловой системе через только через сущность VfsCertificate.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/separate_storage

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd hdd.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

Подготовка SD-карты для запуска на Raspberry Pi 4 B

Для запуска примера Separate Storage на Raspberry Pi 4 B необходимы следующие дополнительные действия:

  • SD-карта, помимо загрузочного раздела с образом решения, должна также содержать 2 дополнительных раздела с файловой системой ext2 или ext3.
  • первый дополнительный раздел должен содержать файл userlist.txt из директории ./resources/files/
  • второй дополнительный раздел должен содержать файл certificate.cer из директории ./resources/files/

Для запуска примера Separate Storage на Raspberry Pi 4 B можно использовать SD-карту, подготовленную для запуска примера embed_ext2_with_separate_vfs на Raspberry Pi 4 B, скопировав файлы userlist.txt и certificate.cer на соответствующие разделы.

В начало

Паттерн Defer to Kernel

Описание

Паттерн Defer to Kernel предполагает использование преимущества контроля разрешений на уровне ядра ОС.

Целью этого паттерна является четкое отделение функциональности, требующей повышенных привилегий, от функциональности, не требующей повышенных привилегий, с помощью механизмов, доступных на уровне ядра ОС. Использование механизмов ядра позволяет не реализовывать новых средств для арбитража решений безопасности на уровне пользователя.

Альтернативные названия

Policy Enforcement Point (PEP), Protected System, Enclave.

Контекст

Паттерн Defer to Kernel применим, если система имеет следующие характеристики:

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

Проблема

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

Решение

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

Структура

defer_to_kernel_structure

Работа

  • Функциональность и управление данными с разными привилегиями разделены между процессами.
  • Изоляцию процессов обеспечивает ядро ОС.
  • Процесс-1 хочет запросить привилегированную функциональность или данные у Процесса-2, используя IPC.
  • Ядро контролирует IPC и разрешает или не разрешает коммуникацию исходя из политик безопасности и доступной ему информации о контексте работы и состоянии Процесса-1.

Рекомендации по реализации

Для того чтобы конкретная реализация паттерна работала безопасно и надежно, необходимо следующее:

  • Изоляция

    Необходимо обеспечить полную и гарантированную изоляцию процессов.

  • Невозможность обойти ядро

    Абсолютно все IPC-взаимодействия должны контролироваться ядром.

  • Самозащита ядра

    Необходимо обеспечить доверенность ядра, его собственную защиту от компрометации.

  • Доказуемость

    Требуется определенный уровень гарантий безопасности и надежности в отношении ядра.

  • Возможность внешнего вычисления разрешений о доступе

    Необходимо, чтобы разрешения о доступе вычислялись на уровне ОС, а не были реализованы в прикладном коде.

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

Особенности реализации в KasperskyOS

Ядро KasperskyOS гарантирует изоляцию сущностей и представляет собой Policy Enforcement Point (PEP).

Связанные паттерны

Паттерн Defer to Kernel является частным случаем паттернов Distrustful Decomposition и Policy Decision Point. Паттерн Policy Decision Point определяет абстрактный процесс, перехватывающий все запросы к ресурсам и проверяющий их на соответствие заданной политике безопасности. Специфика паттерна Defer to Kernel в том, что эту проверку выполняет ядро ОС – это более надежное и портируемое решение, сокращающее время разработки и тестирования.

Следствия

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

Кроме этого, появляется возможность доказать безопасность решения в целом, доказав правильность работы ядра. Сложность доказуемости правильной работы кода нелинейно растет с увеличением его размера. Паттерн Defer to Kernel минимизирует объем доверенного кода – при условии, что ядро ОС невелико.

Примеры реализации

Пример реализации паттерна Defer to Kernel: Пример Defer to Kernel.

Источники

Паттерн Defer to Kernel подробно рассмотрен в следующих работах:

  • Chad Dougherty, Kirk Sayre, Robert C. Seacord, David Svoboda, Kazuya Togashi (JPCERT/CC), "Secure Design Patterns" (March-October 2009). Software Engineering Institute. https://resources.sei.cmu.edu/asset_files/TechnicalReport/2009_005_001_15110.pdf
  • Dangler, Jeremiah Y., "Categorization of Security Design Patterns" (2013). Electronic Theses and Dissertations. Paper 1119. https://dc.etsu.edu/etd/1119
  • Schumacher, Markus, Fernandez-Buglioni, Eduardo, Hybertson, Duane, Buschmann, Frank, and Sommerlad, Peter. "Security Patterns: Integrating Security and Systems Engineering" (2006).

В этом разделе

Пример Defer to Kernel

В начало

Пример Defer to Kernel

Пример Defer to Kernel демонстрирует использование паттернов Defer to Kernel и Policy Decision Point.

Пример Defer to Kernel содержит три пользовательские сущности: PictureManager, ValidPictureClient и NonValidPictureClient.

В этом примере сущности ValidPictureClient и NonValidPictureClient обращаются к сущности PictureManager для получения информации.

Только сущности ValidPictureClient разрешено взаимодействие с сущностью PictureManager.

Ядро KasperskyOS гарантирует изоляцию сущностей.

Контроль взаимодействия сущностей в KasperskyOS вынесен в модуль безопасности Kaspersky Security Module. Эта подсистема анализирует каждый отправляемый запрос и ответ и на основе заданной политики безопасности выносит решение: разрешить или запретить его доставку.

Политика безопасности в примере Defer to Kernel имеет следующие особенности:

  • Сущности ValidPictureClient явно разрешено взаимодействие с сущностью PictureManager.
  • Сущности NonValidPictureClient взаимодействие с сущностью PictureManager не разрешено явно. Таким образом, это взаимодействие запрещено (принцип Default Deny).

Динамическое создание IPC-каналов

Пример также демонстрирует возможность динамического создания IPC-каналов между сущностями. Динамическое создание IPC-каналов осуществляется с помощью сервера имен – специального сервиса ядра, представленного сущностью NameServer. Возможность динамического создания IPC-каналов позволяет изменять топологию взаимодействия сущностей "на лету".

Любая сущность, которой разрешено взаимодействие с NameServer по IPC, может зарегистрировать в сервере имен свои интерфейсы. Другая сущность может запросить у сервера имен зарегистрированные интерфейсы, после чего осуществить подключение к нужному интерфейсу.

При этом все взаимодействия по IPC (даже созданные динамически) контролируются с помощью модуля безопасности.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/defer_to_kernel

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Паттерн Policy Decision Point

Описание

Паттерн Policy Decision Point предполагает инкапсуляцию вычисления решений на основе методов моделей безопасности в отдельный компонент системы, который обеспечивает выполнение этих методов безопасности в полном объеме и в правильной последовательности.

Альтернативные названия

Check Point, Access Decision Function.

Контекст

Система имеет функции с разным уровнем привилегий, а политика безопасности нетривиальна (содержит много привязок методов моделей безопасности к событиям безопасности).

Проблема

Если проверки соблюдения политики безопасности разнесены по разным компонентам системы, возникают следующие проблемы:

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

Решение

Все проверки соблюдения политики безопасности проводятся в отдельном компоненте Policy Decision Point (PDP). Этот компонент отвечает за обеспечение правильного порядка проверок и за их полноту. Происходит отделение проверки политики от кода, реализующего бизнес-логику.

Структура

pdp_structure

Работа

  • Policy Enforcement Point (PEP) получает запрос на доступ к функциональности или данным.

    PEP может представлять собой, например, ядро ОС. Подробнее см. Паттерн Defer to Kernel.

  • PEP собирает атрибуты запроса, необходимые для принятия решений по управлению доступом.
  • PEP запрашивает решение по управлению доступом у Policy Decision Point (PDP).
  • PDP вычисляет решение о предоставлении доступа на основе политики безопасности и информации, полученной в запросе от PEP.
  • PEP отклоняет или разрешает взаимодействие на основе решения PDP.

Рекомендации по реализации

При реализации необходимо учитывать проблему "Время проверки vs. Время использования". Например, если политика безопасности зависит от быстро меняющегося статуса какого-либо объекта системы, вычисленное решение так же быстро теряет актуальность. В системе, использующей паттерн Policy Decision Point, необходимо позаботиться о минимизации интервала между принятием решения о доступе и моментом выполнения запроса на основе этого решения.

Особенности реализации в KasperskyOS

Ядро KasperskyOS гарантирует изоляцию сущностей и представляет собой Policy Enforcement Point (PEP).

Контроль взаимодействия сущностей в KasperskyOS вынесен в модуль безопасности Kaspersky Security Module. Этот модуль анализирует каждый отправляемый запрос и ответ и на основе заданной политики безопасности выносит решение: разрешить или запретить его доставку. Таким образом, Kaspersky Security Module выполняет роль Policy Decision Point (PDP).

Следствия

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

Связанные паттерны

Использование паттерна Policy Decision Point предполагает использование паттернов Distrustful decomposition и Defer to Kernel.

Примеры реализации

Пример реализации паттерна Policy Decision Point: Пример Defer to Kernel.

Источники

Паттерн Policy Decision Point подробно рассмотрен в следующих работах:

В начало

Паттерн Privilege Separation

Описание

Паттерн Privilege Separation предполагает использование непривилегированных изолированных модулей системы для взаимодействия с клиентами (другими модулями или пользователями), которые не имеют привилегий. Целью паттерна Privilege Separation является уменьшение количества кода, выполняемого с особыми привилегиями, не влияющее на функциональность программы и не ограничивающее ее.

Паттерн Privilege Separation является частным случаем паттерна Distrustful Decomposition.

Пример

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

Контекст

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

Проблема

Когда клиент, имеющий неизвестные привилегии, взаимодействует с привилегированным компонентом системы, возникают риски компрометации данных и функциональности, доступных этому компоненту.

Решение

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

Работа

Работа паттерна делится на две фазы:

  • Pre-Authentication. Клиент еще не аутентифицирован. Он отправляет запрос к привилегированному мастер-процессу. Мастер-процесс создает дочерний процесс, лишенный привилегий (в том числе, доступа к файловой системе), который выполняет аутентификацию клиента.
  • Post-Authentication. Клиент аутентифицирован и авторизован. Привилегированный мастер-процесс создает новый дочерний процесс, обладающий привилегиями, соответствующими правам клиента. Этот процесс отвечает за все дальнейшее взаимодействие с клиентом.

Рекомендации по реализации в KasperskyOS

На этапе Pre-Authentication мастер-процесс может хранить состояние каждого непривилегированного процесса в виде конечного автомата и изменять состояние автомата при аутентификации.

Запросы дочерних процессов к мастер-процессу выполняются с использованием стандартных механизмов IPC. При этом контроль взаимодействий осуществляется с помощью модуля безопасности Kaspersky Security Module.

Следствия

Если атакующий получает контроль над непривилегированным процессом, он не получит доступа ни к каким привилегированным функциям или данным. Если он получает контроль над авторизованным процессом, он получит только привилегии этого процесса.

Кроме того, организованный таким образом код проще проверять и тестировать – особого внимания требует лишь функциональность, работающая с повышенными привилегиями.

Примеры реализации

Пример реализации паттерна Privilege Separation: Пример Device Access.

Источники

Паттерн Privilege Separation подробно рассмотрен в следующих работах:

В этом разделе

Пример Device Access

В начало

Пример Device Access

Пример Device Access демонстрирует использование паттерна Privilege Separation.

Архитектура примера

Пример содержит три сущности: Device, LoginManager и Storage.

В этом примере сущность Device обращается к сущности Storage для получения информации и к сущности LoginManager для авторизации.

Сущность Device получает доступ к сущности Storage только после успешной авторизации.

secure_logger_uml

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

Политика безопасности в примере Device Access имеет следующие особенности:

  • Сущность Device имеет возможность обращаться к сущности LoginManager для авторизации.
  • Вызовами метода GetInfo() сущности Storage управляют методы модели безопасности Flow:
    • Конечный автомат, описанный в конфигурации объекта session, имеет два состояния: unauthenticated и authenticated.
    • Исходное состояние – unauthenticated.
    • Разрешены переходы из unauthenticated в authenticated и обратно.
    • Объект session создается при запуске сущности Device.
    • При успешном вызове сущностью Device метода Login() сущности LoginManager состояние объекта session изменяется на authenticated.
    • При успешном вызове сущностью Device метода Logout() сущности LoginManager состояние объекта session изменяется на unauthenticated.
    • При вызове сущностью Device метода GetInfo() сущности Storage проверяется текущее состояние объекта session. Вызов разрешается, только если текущее состояние объекта – authenticated.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/device_access

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Паттерн Information Obscurity

Описание

Цель паттерна Information Obscurity – шифрование конфиденциальных данных в небезопасных средах с целью защиты данных от кражи.

Контекст

Этот паттерн следует использовать, когда данные часто передаются между частями системы и/или между системой и другими (внешними) системами.

Проблема

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

Решение

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

Примеры реализации

Пример реализации паттерна Information Obscurity: Пример Secure Login.

Источники

Паттерн Information Obscurity подробно рассмотрен в следующих работах:

  • Dangler, Jeremiah Y., "Categorization of Security Design Patterns" (2013). Electronic Theses and Dissertations. Paper 1119. https://dc.etsu.edu/etd/1119
  • Schumacher, Markus, Fernandez-Buglioni, Eduardo, Hybertson, Duane, Buschmann, Frank, and Sommerlad, Peter. "Security Patterns: Integrating Security and Systems Engineering" (2006).

В этом разделе

Пример Secure Login

В начало

Пример Secure Login

Пример Secure Login демонстрирует использование паттерна Information Obscurity. Пример демонстрирует возможность передачи критической для системы информации через недоверенную среду.

Архитектура примера

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

С точки зрения архитектуры в таких системах можно выделить следующие субъекты:

  • Источник данных: браузер пользователя.
  • Точка коммуникации с устройством: веб-сервер.
  • Подсистема обработки информации от пользователя: подсистема аутентификации.

При этом для использования криптографической защиты необходимо выполнить следующие шаги:

  1. Обеспечить взаимодействие источника данных и устройства по протоколу HTTPS. Это позволит избежать "прослушивания" HTTP-трафика и атак типа MITM (man in the middle).
  2. Выработать между источником данных и подсистемой обработки информации общий секрет.
  3. Использовать этот секрет для шифрования информации на стороне источника данных и расшифровки на стороне подсистемы обработки информации. Это позволит избежать компрометации данных внутри устройства (в точке коммуникации).

Пример Secure Login включает следующие компоненты:

  • Веб-сервер Civetweb (недоверенный компонент, сущность WebServer).
  • Подсистему аутентификацию пользователей (доверенный компонент, сущность AuthService).
  • TLS-терминатор (доверенный компонент, сущность TlsEntity). Этот компонент поддерживает транспортный механизм TLS (transport layer security). TLS-терминатор совместно с веб-сервером поддерживают протокол HTTPS на стороне устройства (веб-сервер взаимодействует с браузером через TLS-терминатор).

Процесс аутентификации пользователя происходит по следующей схеме:

  1. Пользователь открывает в браузере страницу по адресу https://localhost:1106 (при запуске примера на QEMU) или по адресу https://<IP-адрес Raspberry Pi>:1106 (при запуске примера на Raspberry Pi 4 B). HTTP-трафик между браузером и TLS-терминатором будет передаваться в зашифрованном виде, а веб-сервер будет работать с открытым HTTP-трафиком. (В примере используется самоподписанный сертификат, поэтому большинство современных браузеров сообщит о незащищенности соединения. Нужно согласиться использовать незащищенное соединение, которое тем не менее будет зашифрованным).
  2. Веб-сервер Civetweb, запущенный в сущности WebServer, отображает страницу index.html, содержащую приглашение к аутентификации.
  3. Пользователь нажимает на кнопку Log in.
  4. Сущность WebServer обращается к сущности AuthService по IPC для получения страницы, содержащей форму ввода имени пользователя и пароля.
  5. Сущность AuthService выполняет следующие действия:
    • генерирует закрытый ключ, открытые параметры, а также вычисляет открытый ключ по алгоритму Диффи-Хеллмана;
    • создает страницу auth.html с формой ввода имени пользователя и пароля (код страницы содержит открытые параметры и открытый ключ);
    • передает полученную страницу сущности WebServer по IPC.
  6. Веб-сервер Civetweb, запущенный в сущности WebServer, отображает страницу auth.html с формой ввода имени пользователя и пароля.
  7. Пользователь заполняет форму и нажимает на кнопку Submit (корректные данные для аутентификации содержатся в файле secure_login/auth_service/src/authservice.cpp).
  8. Код страницы auth.html, который исполняется на стороне браузера, осуществляет следующие действия:
    • генерирует закрытый ключ, вычисляет открытый ключ и общий секретный ключ по алгоритму Диффи-Хеллмана;
    • выполняет шифрование пароля операцией XOR с использование общего секретного ключа;
    • передает веб-северу имя пользователя, зашифрованный пароль и открытый ключ.
  9. Сущность WebServer обращается к сущности AuthService по IPC для получения страницы, содержащей результат аутентификации, передавая имя пользователя, зашифрованный пароль и открытый ключ.
  10. Сущность AuthService выполняет следующие действия:
    • вычисляет общий секретный ключ по алгоритму Диффи-Хеллмана;
    • расшифровывает пароль с использованием общего секретного ключа;
    • возвращает страницу result_err.html или страницу result_ok.html в зависимости от результата аутентификации.
  11. Веб-сервер Civetweb, запущенный в сущности WebServer, отображает страницу result_err.html или страницу result_ok.html.

Таким образом, конфиденциальные данные передаются через сеть и веб-сервер только в зашифрованном виде. Кроме того, весь HTTP-трафик передается через сеть в зашифрованном виде. Для передачи данных между компонентами используются взаимодействия по IPC, которые контролируются модулем Kaspersky Security Module.

Unit-тестирование с использованием фреймворка GoogleTest

Помимо паттерна Information Obscurity пример Secure Login демонстрирует использование фреймворка GoogleTest для выполнения unit-тестирования программ, разработанных под KasperskyOS (KasperskyOS Community Edition содержит в своем составе этот фреймворк).

Исходный код тестов находится по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/secure_login/tests

Эти unit-тесты предназначены для верификации некоторых cpp-модулей подсистемы аутентификации и веб-сервера.

Чтобы запустить тестирование, выполните следующие действия:

  1. Перейдите в директорию с примером Secure Login.
  2. Удалите директорию build с результатами предыдущей сборки, выполнив команду:

    sudo rm -rf build/

  3. Откройте файл скрипта cross-build.sh в текстовом редакторе.
  4. Добавьте в скрипт флаг сборки -D RUN_TESTS="y" \ (например, после флага сборки -D CMAKE_BUILD_TYPE:STRING=Release \).
  5. Сохраните файл скрипта, а затем выполните команду:

    $ sudo ./cross-build.sh

Тесты выполняются в сущности TestEntity. Сущности AuthService и WebServer не запускаются, поэтому при выполнении тестирования пример нельзя использовать для демонстрации паттерна Information Obscurity.

После завершения тестирования выводятся результаты выполнения тестов.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/secure_login

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -net nic,macaddr=52:54:00:12:34:56 -net user,hostfwd=tcp::1106-:1106 -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Приложения

Этот раздел содержит информацию, которая дополняет основной текст документа.

В этом разделе

Дополнительные примеры

В начало

Пример net_with_separate_vfs

Пример представляет собой простейший случай взаимодействия по сети с использованием сокетов Беркли.

Пример состоит из сущностей Client и Server, связанных TCP-сокетом с использованием loopback-интерфейса. В коде сущностей используются стандартные POSIX-функции.

Чтобы соединить сущности сокетом через loopback, они должны использовать один экземпляр сетевого стека, то есть взаимодействовать с "общей" сущностью VFS (в этом примере сущность называется NetVfs).

Для корректного соединения сущностей Client и Server с сущностью NetVfs необходимо также включить в решение сущность Env.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/net_with_separate_vfs

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример net2_with_separate_vfs

Пример демонстрирует особенности решения, в котором сущность использует стандартные функции POSIX для взаимодействия с внешним сервером.

Пример net2_with_separate_vfs является видоизмененным примером net_with_separate_vfs. В отличие от примера net_with_separate_vfs, в этом примере сущность взаимодействует по сети не с другой сущностью, а с внешним сервером.

Пример состоит из сущности Client, запущенной в KasperskyOS под QEMU, и программы Server, запущенной в хостовой операционной системе Linux. Сущность Client и процесс Server связаны TCP-сокетом. В коде сущности Client используются стандартные функции POSIX.

Чтобы соединить сущность Client и процесс Server сокетом, сущность Client должна взаимодействовать с сущностью NetVfs. Сущность NetVfs при сборке компонуется с сетевым драйвером, который обеспечит взаимодействие с процессом Server, запущенным в Linux.

Для корректного соединения сущности Client с сущностью NetVfs необходимо также включить в решение сущность Env.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/net2_with_separate_vfs

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример embedded_vfs

Пример показывает, как встроить виртуальную файловую систему (далее VFS), поставляемую в составе KasperskyOS Community Edition, в разрабатываемую сущность.

В этом примере сущность Client полностью инкапсулирует реализацию VFS из KasperskyOS Community Edition. Это позволяет избавиться от использования IPC для всех стандартных функций ввода-вывода (stdio.h, socket.h и так далее), например, для отладки или повышения производительности.

Сущность Client тестирует следующие операции:

  • создание директории;
  • создание и удаление файла;
  • чтение из файла и запись в файл.

Поставляемые ресурсы

В пример входит образ жесткого диска с файловой системой FAT32 – hdd.img.

Этот пример не содержит реализации драйверов блочных устройств, с которыми работает Client. Эти драйверы (сущности ATA и SDCard) поставляются в составе KasperskyOS Community Edition и добавляются в файле сборки ./CMakeLists.txt.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/embedded_vfs

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd hdd.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример embed_ext2_with_separate_vfs

Пример показывает, как встроить новую файловую систему в виртуальную файловую систему (VFS), поставляемую в составе KasperskyOS Community Edition.

В этом примере сущность Client тестирует работу файловых систем (ext2, ext3, ext4) на блочных устройствах. Для этого Client обращается по IPC к драйверу файловой системы (сущности FileVfs), а FileVfs в свою очередь обращается по IPC к блочному устройству.

Файловые системы ext2 и ext3 работают с настройками по умолчанию. Файловая система ext4 работает, если отключить extent (mkfs.ext4 -O ^64bit,^extent /dev/foo).

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/embed_ext2_with_separate_vfs

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd hdd.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

Подготовка SD-карты для запуска на Raspberry Pi 4 B

Для запуска примера embed_ext2_with_separate_vfs на Raspberry Pi 4 B необходимо, чтобы SD-карта, помимо загрузочного раздела с образом решения, также содержала 3 дополнительных раздела с файловыми системами ext2, ext3 и ext4 соответственно.

В начало

Пример multi_vfs_ntpd

Этот пример показывает как использовать ntp-сервис в KasperskyOS. Сущность kl.Ntpd поставляется в составе KasperskyOS Community Edition и представляет собой реализацию ntp-клиента, который в фоновом режиме получает параметры времени от внешних ntp-серверов и передает их ядру KasperskyOS.

Пример также демонстрирует использование разных виртуальных файловых систем (далее VFS) в одном решении. В примере для доступа к функциям работы с файловой системой и функциям работы с сетью используются разные VFS:

  • Для работы с сетью используется сущность VfsNet.
  • Для работы с файловой системой используются сущности VfsRamfs и VfsSdCardFs.

Сущность Client использует стандартные функции библиотеки libc для получения информации о времени, которые транслируются в обращения к сущностям VFS по IPC.

Сущность Env используется для передачи переменных окружения и аргументов функции main другим сущностям.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Поставляемые ресурсы

В пример входят следующие файлы конфигурации:

  • ./resources/include/config.h.in содержит описание бэкенда файловой системы, которая будет использоваться в решении: sdcard или ramfs.

    Для каждого бэкенда в решении также используется отдельная сущность VFS: VfsSdCardFs или VfsRamfs соответственно.

  • Директории ./resources/ramfs/etc и /resources/sdcard/etc содержат файлы конфигурации для сущностей VFS и Ntpd.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/multi_vfs_ntpd

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример multi_vfs_dns_client

Этот пример показывает как использовать внешний dns-сервис в KasperskyOS.

Пример также демонстрирует использование разных виртуальных файловых систем (далее VFS) в одном решении. В примере для доступа к функциям работы с файловой системой и функциям работы с сетью используются разные VFS:

  • Для работы с сетью используется сущность VfsNet.
  • Для работы с файловой системой используются сущности VfsRamfs и VfsSdCardFs.

Сущность Client использует стандартные функции библиотеки libc для обращения ко внешнему dns-сервису, которые транслируются в обращения к сущности VfsNet по IPC.

Сущность Env используется для передачи переменных окружения и аргументов функции main другим сущностям.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Поставляемые ресурсы

В пример входят следующие файлы конфигурации:

  • ./resources/include/config.h.in содержит описание бэкенда файловой системы, которая будет использоваться в решении: sdcard или ramfs.

    Для каждого бэкенда в решении также используется отдельная сущность VFS: VfsSdCardFs или VfsRamfs соответственно.

  • Директории ./resources/ramfs/etc и /resources/sdcard/etc содержат файлы конфигурации для сущностей VFS.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/multi_vfs_dns_client

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример multi_vfs_dhcpcd

Пример использования сущности kl.Dhcpcd.

Сущность Dhcpcd представляет собой реализацию DHCP-клиента, который в фоновом режиме получает параметры сетевых интерфейсов от внешнего DHCP-сервера и передает их сущности виртуальной файловой системы (далее VFS).

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

  • Для работы с сетью используется сущность VfsNet.
  • Для работы с файловой системой используются сущности VfsRamfs и VfsSdCardFs.

Сущность Client использует стандартные функции библиотеки libc для получения информации о сетевых интерфейсах (ioctl), которые транслируются в обращения к сущности VFS по IPC.

Сущность Env используется для передачи переменных окружения и аргументов функции main другим сущностям.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Поставляемые ресурсы

В пример входят следующие файлы конфигурации:

  • ./resources/include/config.h.in содержит описание бэкенда файловой системы, которая будет использоваться в решении: sdcard или ramfs.

    Для каждого бэкенда в решении также используется отдельная сущность VFS: VfsSdCardFs или VfsRamfs соответственно.

  • Директории ./resources/ramfs/etc и /resources/sdcard/etc содержат файлы конфигурации для сущностей VFS и Dhcpcd.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/multi_vfs_dhcpcd

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример mqtt_publisher

Пример использования протокола MQTT в KasperskyOS.

В этом примере MQTT-подписчик должен быть запущен в хостовой операционной системе, а MQTT-издатель в KasperskyOS. Сущность Publisher представляет собой реализацию MQTT-издателя, который публикует текущее время с интервалом 5 секунд.

В результате успешного запуска и работы примера MQTT-подписчик, запущенный в хостовой операционной системе, выведет сообщение "received PUBLISH" с топиком "datetime".

Пример также демонстрирует использование разных виртуальных файловых систем (далее VFS) в одном решении. В примере для доступа к функциям работы с файловой системой и функциям работы с сетью используются разные VFS:

  • Для работы с сетью используется сущность VfsNet.
  • Для работы с файловой системой используются сущности VfsRamfs и VfsSdCardFs.

Сущность Env используется для передачи переменных окружения и аргументов функции main другим сущностям.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Запуск Mosquitto

Для запуска этого примера MQTT брокер Mosquitto должен быть установлен и запущен в хостовой системе. Для установки и запуска Mosquitto выполните следующие команды:

$ sudo apt install mosquitto mosquitto-clients

$ sudo /etc/init.d/mosquitto start

Для запуска MQTT-подписчика в хостовой системе выполните следующую команду:

$ mosquitto_sub -d -t "datetime"

Поставляемые ресурсы

В пример входят следующие файлы конфигурации:

  • ./resources/include/config.h.in содержит описание бэкенда файловой системы, которая будет использоваться в решении: sdcard или ramfs.

    Для каждого бэкенда в решении также используется отдельная сущность VFS: VfsSdCardFs или VfsRamfs соответственно.

  • Директории ./resources/ramfs/etc и /resources/sdcard/etc содержат файлы конфигурации для сущностей VFS и Ntpd.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/mqtt_publisher

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример mqtt_subscriber

Пример использования протокола MQTT в KasperskyOS.

В этом примере MQTT-издатель должен быть запущен в хостовой операционной системе, а MQTT-подписчик в KasperskyOS. Сущность Subscriber представляет собой реализацию MQTT-подписчика.

В результате успешного запуска и работы примера MQTT-подписчик, запущенный в KasperskyOS, выведет сообщение "Got message with topic: my/awesome/topic, payload: hello".

Пример также демонстрирует использование разных виртуальных файловых систем (далее VFS) в одном решении. В примере для доступа к функциям работы с файловой системой и функциям работы с сетью используются разные VFS:

  • Для работы с сетью используется сущность VfsNet.
  • Для работы с файловой системой используются сущности VfsRamfs и VfsSdCardFs.

Сущность Env используется для передачи переменных окружения и аргументов функции main другим сущностям.

Для сборки и запуска примера используется система CMake из состава KasperskyOS Community Edition.

Запуск Mosquitto

Для запуска этого примера MQTT брокер Mosquitto должен быть установлен и запущен в хостовой системе. Для установки и запуска Mosquitto выполните следующие команды:

$ sudo apt install mosquitto mosquitto-clients

$ sudo /etc/init.d/mosquitto start

Для запуска MQTT-издателя в хостовой системе выполните следующую команду:

$ mosquitto_pub -t "my/awesome/topic" -m "hello"

Поставляемые ресурсы

В пример входят следующие файлы конфигурации:

  • ./resources/include/config.h.in содержит описание бэкенда файловой системы, которая будет использоваться в решении: sdcard или ramfs.

    Для каждого бэкенда в решении также используется отдельная сущность VFS: VfsSdCardFs или VfsRamfs соответственно.

  • Директории ./resources/ramfs/etc и /resources/sdcard/etc содержат файлы конфигурации для сущностей VFS и Ntpd.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/mqtt_subscriber

Сборка и запуск примера

Чтобы запустить пример на QEMU, перейдите в директорию с примером, соберите пример и выполните следующие команды:

$ cd build/einit

# Перед выполнением следующей команды убедитесь, что путь к

# директории с исполняемым файлом qemu-system-arm сохранен в

# переменной окружения PATH. В случае отсутствия

# добавьте его в переменную PATH.

$ qemu-system-arm -m 2048 -machine vexpress-a15 -nographic -monitor none -sd sdcard0.img -kernel kos-qemu-image

Также см. "Сборка и запуск примеров".

В начало

Пример gpio_input

Пример использования драйвера GPIO.

Этот пример позволяет проверить функциональность ввода GPIO пинов. Используется порт "gpio0". Все пины, кроме указанных в массиве exceptionPinArr, по умолчанию ориентированы на ввод, напряжение на пинах согласуется с состоянием регистров подтягивающих резисторов. Состояния всех пинов, начиная с GPIO0 (с учетом указанных в массиве exceptionPinArr), будут последовательно считаны, сообщения о состояниях пинов будут выведены в консоль. Задержка между считываниями смежных пинов определяется макроопределением DELAY_S (время указывается в секундах).

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

При сборке и запуске этого примера на QEMU возникает ошибка. Это ожидаемое поведение, поскольку драйвера GPIO для QEMU нет.

При сборке и запуске этого примера на Raspberry Pi 4 B ошибка не возникает.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/gpio_input

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример gpio_output

Пример использования драйвера GPIO.

Этот пример позволяет проверить функциональность вывода GPIO пинов. Используется порт "gpio0". Начальное состояние всех пинов GPIO должно соответствовать логическому нулю (напряжение на пине отсутствует). Все пины, кроме указанных в массиве exceptionPinArr, будут настроены на вывод. Каждый пин, начиная с GPIO0 (с учетом указанных в массиве exceptionPinArr), будет последовательно переведен в состояние логической единицы (появление на пине напряжения), а затем в состояние логического нуля. Задержка между изменениями состояния пинов определяется макроопределением DELAY_S (время указывается в секундах). Включение/выключение пинов производится от GPIO0 до GPIO27 и обратно до GPIO0.

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

При сборке и запуске этого примера на QEMU возникает ошибка. Это ожидаемое поведение, поскольку драйвера GPIO для QEMU нет.

При сборке и запуске этого примера на Raspberry Pi 4 B ошибка не возникает.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/gpio_output

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример gpio_interrupt

Пример использования драйвера GPIO.

Этот пример позволяет проверить функциональность прерываний для GPIO пинов. Используется порт "gpio0". В битовой маске pinsBitmap структуры контекста прерываний CallBackContext пины из массива exceptionPinArr помечаются отработавшими, чтобы в дальнейшем пример мог корректно завершиться. Все пины, кроме указанных в массиве exceptionPinArr, переводятся в состояние PINS_MODE. Для всех пинов, кроме указанных в массиве exceptionPinArr, будет зарегистрирована функция обработки прерывания.

В бесконечном цикле происходит проверка условия равенства битовой маски pinsBitmap из структуры контекста прерываний CallBackContext битовой маске окончания работы примера DONE_BITMASK (соответствует условию, когда прерывание произошло на каждом GPIO пине). Также в цикле снимается функция-обработчик для последнего пина, на котором произошла обработка прерывания. При возникновении в первый раз прерывания на пине вызывается функция-обработчик, которая помечает соответствующий пин в битовой маске pinsBitmap в структуре контекста прерываний CallBackContext . Функция-обработчик для этого пина в дальнейшем снимается.

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

Прерывания для событий GPIO_EVENT_LOW_LEVEL и GPIO_EVENT_HIGH_LEVEL не поддерживаются.

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

При сборке и запуске этого примера на QEMU возникает ошибка. Это ожидаемое поведение, поскольку драйвера GPIO для QEMU нет.

При сборке и запуске этого примера на Raspberry Pi 4 B ошибка не возникает.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/gpio_interrupt

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Пример gpio_echo

Пример использования драйвера GPIO.

Этот пример позволяет проверить функциональность ввода/вывода GPIO пинов, а также работу GPIO прерываний. Используется порт "gpio0". Пин вывода (GPIO_PIN_OUT) следует соединить с пином ввода (GPIO_PIN_IN). Устанавливается конфигурация для пина вывода (номер пина определяется в макросе GPIO_PIN_OUT), а также для пина ввода (GPIO_PIN_IN). Конфигурация пина ввода указана в макросе IN_MODE. Регистрируется обработчик прерываний для пина ввода. Несколько раз изменяется состояние пина вывода. В случае корректной работы примера, при изменении состояния пина вывода должен вызываться обработчик прерываний, который выводит состояние пина ввода, при этом состояния пина вывода и пина ввода должны совпадать.

При сборке и запуске этого примера на QEMU возникает ошибка. Это ожидаемое поведение, поскольку драйвера GPIO для QEMU нет.

При сборке и запуске этого примера на Raspberry Pi 4 B ошибка не возникает.

Файлы примера

Код примера и скрипты для сборки находятся по следующему пути:

/opt/KasperskyOS-Community-Edition-<version>/examples/gpio_echo

Сборка и запуск примера

См. "Сборка и запуск примеров".

В начало

Лицензирование программы

Условия использования программы изложены в лицензионном договоре или подобном документе, на основании которого используется программа.

В начало

Предоставление данных

KasperskyOS Community Edition не запрашивает, не хранит и не обрабатывает никакую персональную информацию, а также никакую другую информацию, не относящуюся к персональным данным.

В начало

Информация о стороннем коде

Информация о стороннем коде содержится в файле legal_notices.txt, расположенном в папке установки приложения.

В начало

Уведомления о товарных знаках

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

Arm и Mbed – зарегистрированные товарные знаки или товарные знаки Arm Limited (или дочерних компаний) в США и/или других странах.

CentOS – товарный знак компании Red Hat, Inc.

Debian – зарегистрированный товарный знак Software in the Public Interest, Inc.

Eclipse Mosquitto – товарный знак Eclipse Foundation, Inc.

GoogleTest – товарный знак Google, Inc.

Intel и Core – товарные знаки Intel Corporation, зарегистрированные в Соединенных Штатах Америки и в других странах.

Linux – товарный знак Linus Torvalds, зарегистрированный в США и в других странах.

Raspberry Pi – товарный знак Raspberry Pi Foundation.

Ubuntu – зарегистрированный товарный знак Canonical Ltd.

Visual Studio, Windows – товарные знаки Microsoft Corporation, зарегистрированные в Соединенных Штатах Америки и в других странах.

В начало