Содержание
Формальные спецификации компонентов решения на базе KasperskyOS
При разработке решения создаются формальные спецификации его компонентов, которые формируют "картину мира" для модуля безопасности Kaspersky Security Module. Формальная спецификация компонента решения на базе KasperskyOS (далее формальная спецификация компонента решения) представляет собой систему IDL-, CDL-, EDL-описаний (IDL- и CDL-описания опциональны) этого компонента. Эти описания используются для автоматической генерации транспортного кода компонентов решения, а также исходного кода модуля безопасности и инициализирующей программы. Также формальные спецификации компонентов решения используются как исходные данные для описания политики безопасности решения.
У ядра KasperskyOS так же, как и у компонентов решения, есть формальная спецификация (подробнее см. "Методы служб ядра KasperskyOS").
Каждый компонент решения соответствует EDL-описанию. С точки зрения формальной спецификации компонент решения – это контейнер компонентов, предоставляющих службы. Одновременно может использоваться несколько экземпляров одного компонента решения, то есть из одного исполняемого файла может быть запущено несколько процессов. Процессы, которые соответствуют одному и тому же EDL-описанию, являются процессами одного класса. EDL-описание задает имя класса процессов и компоненты, для которых процесс заданного класса является контейнером.
Каждый компонент из состава компонента решения соответствует CDL-описанию. Это описание задает имя компонента, предоставляемые службы, интерфейс безопасности, а также вложенные компоненты. Компоненты могут одновременно предоставлять службы, поддерживать интерфейс безопасности и являться контейнерами для других компонентов. Каждый компонент может предоставлять несколько служб с одним или несколькими интерфейсами.
Каждый интерфейс определяется в IDL-описании. Это описание задает имя интерфейса, сигнатуры интерфейсных методов и типы данных для параметров интерфейсных методов. Данные, которые состоят из сигнатур интерфейсных методов и определений типов данных для параметров интерфейсных методов, называются пакетом.
Процессы, которые не содержат компонентов, могут быть только клиентами. Процессы, содержащие компоненты, могут быть серверами и/или клиентами. Если компоненты из состава процесса предоставляют службы, то процесс может быть сервером, но и одновременно может быть клиентом. Если компоненты из состава процесса не предоставляют службы (только поддерживают интерфейс безопасности), то процесс может быть только клиентом.
Формальная спецификация компонента решения не определяет, как будет реализован этот компонент. То есть наличие компонентов в формальной спецификации компонента решения не означает, что эти компоненты будут присутствовать в архитектуре этого компонента решения.
Имена классов процессов, компонентов, пакетов и интерфейсов
Классы процессов, компоненты, пакеты и интерфейсы идентифицируются в IDL-, CDL-, EDL-описаниях по именам. Имена классов процессов, компонентов и пакетов образуют в рамках решения на базе KasperskyOS три множества имен, в которых любое имя является уникальным в рамках своего множества. Множество имен пакетов включает в себя множество имен интерфейсов.
Имя класса процессов, компонента, пакета или интерфейса является ссылкой на IDL-, CDL- или EDL-файл, в котором это имя задано. Эта ссылка представляет собой путь к IDL-, CDL- или EDL-файлу (без расширения и точки перед ним) относительно директории, которая включена в набор директорий, где генераторы исходного кода выполняют поиск IDL-, CDL-, EDL-файлов. (Этот набор директорий задается параметрами -I
<путь к файлам
>.) В качестве разделителя в описании пути используется точка.
Например, имя класса процессов kl.core.NameServer
является ссылкой на EDL-файл NameServer.edl
, который находится в KasperskyOS SDK по пути:
sysroot-*-kos/include/kl/core
При этом генераторы исходного кода должны быть настроены на поиск IDL-, CDL-, EDL-файлов в директории:
sysroot-*-kos/include
Имя IDL-, CDL- или EDL-файла начинается с заглавной буквы и не может содержать символов подчеркивания _
.
EDL-описание
EDL-описания помещаются в отдельные файлы *.edl
, которые содержат следующие данные:
- Имя класса процессов. Используется декларация:
entity <имя класса процессов>
- [Опционально] Список экземпляров компонентов. Используется декларация:
components {
<имя экземпляра компонента : имя компонента>
...
}
Каждый экземпляр компонента указывается отдельной строкой. Имя экземпляра компонента не может содержать символов подчеркивания
_
. Список может содержать несколько экземпляров одного компонента. Каждый экземпляр компонента в списке имеет уникальное имя.
Язык EDL чувствителен к регистру символов.
В EDL-описании могут использоваться однострочные и многострочные комментарии.
В EDL-описании могут задаваться поддерживаемые службы и интерфейс безопасности точно так же, как они задаются в CDL-описании. Такая практика не рекомендуется, так как в общем случае EDL- и CDL-описания создаются разными участниками процесса разработки решения на базе KasperskyOS. CDL-описания создаются разработчиками системного и прикладного ПО. EDL-описания создаются разработчиками, которые выполняют интеграцию системного и прикладного ПО в единое решение.
Примеры EDL-файлов
Hello.edl
// Класс процессов, которые не содержат компонентов.
entity Hello
Signald.edl
/* Класс процессов, которые содержат
* один экземпляр одного компонента. */
entity kl.Signald
components {
signals : kl.Signals
}
LIGHTCRAFT.edl
/* Класс процессов, которые содержат
* два экземпляра разных компонентов. */
entity kl.drivers.LIGHTCRAFT
components {
KUSB : kl.drivers.KUSB
KIDF : kl.drivers.KIDF
}
CDL-описание
CDL-описания помещаются в отдельные файлы *.cdl
, которые содержат следующие данные:
- Имя компонента. Используется декларация:
component <имя компонента>
- [Опционально] Интерфейс безопасности. Используется декларация:
security <имя интерфейса>
- [Опционально] Список служб. Используется декларация:
endpoints {
<имя службы : имя интерфейса>
...
}
Каждая служба указывается отдельной строкой. Имя службы не может содержать символов подчеркивания
_
. Список может содержать несколько служб с одинаковым интерфейсом. Каждая служба в списке имеет уникальное имя. - [Опционально] Список экземпляров вложенных компонентов. Используется декларация:
components {
<имя экземпляра компонента : имя компонента>
...
}
Каждый экземпляр компонента указывается отдельной строкой. Имя экземпляра компонента не может содержать символов подчеркивания
_
. Список может содержать несколько экземпляров одного компонента. Каждый экземпляр компонента в списке имеет уникальное имя.
Язык CDL чувствителен к регистру символов.
В CDL-описании могут использоваться однострочные и многострочные комментарии.
В CDL-описании используется как минимум одна опциональная декларация. Если в CDL-описании не использовать ни одной опциональной декларации, то этому описанию будет соответствовать "пустой" компонент, который не предоставляет служб, не содержит вложенных компонентов и не поддерживает интерфейс безопасности.
Примеры CDL-файлов
KscProductEventsProvider.cdl
// Компонент предоставляет одну службу.
component kl.KscProductEventsProvider
endpoints {
eventProvider : kl.IKscProductEventsProvider
}
KscConnectorComponent.cdl
// Компонент предоставляет несколько служб.
component kl.KscConnectorComponent
endpoints {
KscConnCommandSender : kl.IKscConnCommandSender
KscConnController : kl.IKscConnController
KscConnSettingsHolder : kl.IKscConnSettingsHolder
KscDataProvider : kl.IKscDataProvider
ProductDataHolder : kl.IProductDataHolder
KscDataNotifier : kl.IKscDataNotifier
KscConnectorStateNotifier : kl.IKscConnectorStateNotifier
}
FsVerifier.cdl
/* Компонент не предоставляет службы, поддерживает
* интерфейс безопасности и содержит один экземпляр
* другого компонента. */
component FsVerifier
security Approve
components {
verifyComp : Verify
}
IDL-описание
IDL-описания помещаются в отдельные файлы *.idl
, которые содержат следующие данные:
- Имя пакета. Используется декларация:
package <имя пакета>
- [Опционально] Пакеты, из которых импортируются типы данных для параметров интерфейсных методов. Используется декларация:
import <имя пакета>
- [Опционально] Определения типов данных для параметров интерфейсных методов.
- [Опционально] Сигнатуры интерфейсных методов. Используется декларация:
interface {
<имя интерфейсного метода([параметры])>;
...
}
Каждая сигнатура метода указывается отдельной строкой. Имя метода не может содержать символов подчеркивания
_
. Каждый метод в списке имеет уникальное имя. Параметры методов разделяются на входные (in
), выходные (out
) и параметры для передачи сведений об ошибках (error
).Входные и выходные параметры передаются в IPC-запросах и IPC-ответах соответственно. Error-параметры передаются в IPC-ответах, если сервер не может корректно обработать соответствующие IPC-запросы.
Сервер может информировать клиента об ошибках обработки IPC-запросов как через error-параметры, так и через выходные параметры интерфейсных методов. Если при возникновении ошибки сервер устанавливает флаг ошибки в IPC-ответе, то этот IPC-ответ содержит error-параметры и не содержит выходных параметров. В противном случае этот IPC-ответ содержит выходные параметры так же, как и при корректной обработке запросов. (Для установки флага ошибки в IPC-ответах используется макрос
nk_err_reset()
, определенный в заголовочном файлеnk/types.h
из состава KasperskyOS SDK.)Отправка IPC-ответа с установленным флагом ошибки и отправка IPC-ответа со снятым флагом ошибки являются разными видами событий для модуля безопасности Kaspersky Security Module. При описании политики безопасности решения это позволяет удобно разделять обработку событий, которые связаны с корректным и некорректным выполнением IPC-запросов. Если сервер не устанавливает флаг ошибки в IPC-ответах, то для обработки событий, связанных с некорректным выполнением IPC-запросов, модулю безопасности требуется проверять значения выходных параметров, которые сигнализируют об ошибках. (Клиент может проверить состояние флага ошибки в IPC-ответе, даже если соответствующий интерфейсный метод не содержит error-параметров. Для этого клиент использует макрос
nk_msg_check_err()
, определенный в заголовочном файлеnk/types.h
из состава KasperskyOS SDK.)Сигнатуры интерфейсных методов не могут импортироваться из других IDL-файлов.
Язык IDL чувствителен к регистру символов.
В IDL-описании могут использоваться однострочные и многострочные комментарии.
В IDL-описании используется как минимум одна опциональная декларация. Если в IDL-описании не использовать ни одной опциональной декларации, то этому описанию будет соответствовать "пустой" пакет, который не задает ни интерфейсных методов, ни типов данных (в том числе из других IDL-описаний).
Некоторые IDL-файлы из состава KasperskyOS SDK не описывают интерфейсные методы, а только содержат определения типов данных. Такие IDL-файлы используются только как экспортеры типов данных.
Если пакет содержит описание интерфейсных методов, то имя интерфейса соответствует имени пакета.
Примеры IDL-файлов
Env.idl
package kl.Env
// Определения типов данных для параметров интерфейсного метода
typedef string<128> Name;
typedef string<256> Arg;
typedef sequence<Arg,256> Args;
// Интерфейс включает один метод.
interface {
Read(in Name name, out Args args, out Args envs);
}
Kpm.idl
package kl.Kpm
// Импорт типов данных для параметров интерфейсных методов
import kl.core.Types
// Определение типа данных для параметров интерфейсных методов
typedef string<64> String;
/* Интерфейс включает несколько методов.
* Часть методов не имеет параметров. */
interface {
Shutdown();
Reboot();
PowerButtonPressedWait();
TerminationSignalWait(in UInt32 entityId, in String entityName);
EntityTerminated(in UInt32 entityId);
Terminate(in UInt32 callingEntityId);
}
MessageBusSubs.idl
package kl.MessageBusSubs
// Импорт типов данных для параметров интерфейсного метода
import kl.MessageBusTypes
/* Интерфейс включает метод, который имеет
* входной и выходные параметры, а также
* error-параметр.*/
interface {
Wait(in ClientId id,
out Message topic,
out BundleId dataId,
error ResultCode result);
}
WaylandTypes.idl
// Пакет содержит только определения типов данных.
package kl.WaylandTypes
typedef UInt32 ClientId;
typedef bytes<8192> Buffer;
typedef string<4096> ConnectionId;
typedef SInt32 SsizeT;
typedef UInt32 SizeT;
typedef SInt32 ShmFd;
typedef SInt32 ShmId;
typedef bytes<16384000> ShmBuffer;
Типы данных в языке IDL
Примитивные типы
В языке IDL поддерживаются следующие примитивные типы:
SInt8
,SInt16
,SInt32
,SInt64
(знаковое целое число);UInt8
,UInt16
,UInt32
,UInt64
(беззнаковое целое число);Handle
(значение, двоичное представление которого состоит из нескольких полей, включая поле дескриптора и поле маски прав дескриптора);bytes<
<размер в байтах
>>
(байтовый буфер);string<
<размер в байтах
>>
(строковый буфер).
Байтовый буфер представляет собой область памяти с размером, не превышающим заданного числа байт. Строковый буфер представляет собой байтовый буфер, последний байт которого является терминирующим нулем. Максимальный размер строкового буфера на единицу больше заданного из-за наличия дополнительного байта с терминирующим нулем. Для передачи байтового или строкового буфера через IPC будет задействовано столько памяти, сколько фактически занимает этот буфер.
Для числовых типов можно объявлять именованные константы с помощью ключевого слова const
:
const UInt32 DeviceNameMax = 64;
const UInt32 HandleTypeUserLast = 0x0001FFFF;
Константы используются, чтобы избежать проблемы "магических чисел". К примеру, если в IDL-описании определены константы для кодов возврата интерфейсного метода, то при описании политики можно интерпретировать эти коды без дополнительных сведений.
Помимо примитивных типов в языке IDL поддерживаются составные типы: объединения, структуры, массивы и последовательности. В определении составных типов константы примитивных типов могут применяться как параметры (например, чтобы задать размер массива).
Конструкции bytes<
<размер в байтах
>>
и string<
<размер в байтах
>>
используются в определениях составных типов, сигнатурах интерфейсных методов и при создании псевдонимов типов, так как сами по себе они определяют анонимные типы (типы без имени).
Объединения
Объединение позволяет хранить данные разных типов в одной области памяти. В IPC-сообщении объединение снабжается дополнительным полем tag
, позволяющим определить, какой именно член объединения используется.
Для определения объединения используется следующая конструкция:
union <имя типа> {
<тип члена> <имя члена>;
...
}
Пример определения объединения:
union ExitInfo {
UInt32 code;
ExceptionInfo exc;
}
Структуры
Для определения структуры используется следующая конструкция:
struct <имя типа> {
<тип поля> <имя поля>;
...
}
Пример определения структуры:
struct SessionEvqParams {
UInt32 count;
UInt32 align;
UInt32 size;
}
Массивы
Для определения массива используется следующая конструкция:
array<<тип элементов, число элементов>>
Эта конструкция используется в определениях других составных типов, сигнатурах интерфейсных методов и при создании псевдонимов типов, так как сама по себе она определяет анонимный тип.
Последовательности
Последовательность представляет собой массив переменного размера. При определении последовательности указывается максимальное число ее элементов, однако фактически можно передать (через IPC) меньшее их число. При этом для передачи будет задействовано столько памяти, сколько занимают передаваемые элементы.
Для определения последовательности используется следующая конструкция:
sequence<<тип элементов, число элементов>>
Эта конструкция используется в определениях других составных типов, сигнатурах интерфейсных методов и при создании псевдонимов типов, так как сама по себе она определяет анонимный тип.
Типы на основе составных типов
На основе составных типов могут быть определены другие составные типы. При этом определение массива или последовательности может быть вложено в определение другого типа:
struct BazInfo {
array<UInt8, 100> a;
sequence<sequence<UInt32, 100>, 200> b;
string<100> c;
bytes<4096> d;
UInt64 e;
}
Определение объединения или структуры не может быть вложено в определение другого типа. Однако в определение типа может быть включено заранее описанное определение объединения или структуры. Это выполняется посредством указания в определении типа имен включаемых типов:
union foo {
UInt32 value1;
UInt8 value2;
}
struct bar {
UInt32 a;
UInt8 b;
}
struct BazInfo {
foo x;
bar y;
}
Создание псевдонимов типов
Псевдонимы типов используются для повышения удобства работы с типами. Псевдонимы типов могут применяться, например, для того, чтобы задать типам с абстрактными именами мнемонические имена. Также назначение псевдонимов для анонимных типов позволяет получить именованные типы.
Для создания псевдонима типа используется следующая конструкция:
typedef <имя типа/определение анонимного типа> <псевдоним типа>
Пример создания мнемонических псевдонимов:
typedef UInt64 ApplicationId;
typedef Handle PortHandle;
Пример создания псевдонима определению массива:
typedef array<UInt8, 4> IP4;
Пример создания псевдонима определению последовательности:
const UInt32 MaxDevices = 8;
struct Device {
string<32> DeviceName;
UInt8 DeviceID;
}
typedef sequence<Device, MaxDevices> Devices;
Пример создания псевдонима определению объединения:
union foo {
UInt32 value1;
UInt8 value2;
}
typedef foo bar;
Определение анонимных типов в сигнатурах интерфейсных методов
Анонимные типы могут быть определены в сигнатурах интерфейсных методов.
Пример определения последовательности в сигнатуре интерфейсного метода:
Poll(in Generation generation,
in UInt32 timeout,
out Generation currentGeneration,
out sequence<Report, DeviceMax> report,
out UInt32 count,
out UInt32 rc);