Contents
Formal specifications of KasperskyOS-based solution components
Solution development includes the creation of formal specifications for its components that form a global picture for the Kaspersky Security Module. A formal specification of a KasperskyOS-based solution component (hereinafter referred to as the formal specification of the solution component) is comprised of a system of IDL, CDL and EDL descriptions (IDL and CDL descriptions are optional) for this component. These descriptions are used to automatically generate transport code of solution components, and source code of the security module and the initializing program. The formal specifications of solution components are also used as source data for the solution security policy description.
Just like solution components, the KasperskyOS kernel also has a formal specification (for details, see "Methods of KasperskyOS core endpoints").
Each solution component corresponds to an EDL description. In terms of a formal specification, a solution component is a container for components that provide endpoints. Multiple instances of one solution component may be used at the same time, which means that multiple processes can be started from the same executable file. Processes that correspond to the same EDL description are processes of the same class. An EDL description defines the name of a process class and the components for which a process of the defined class serves as a container.
Each component that is part of a solution component corresponds to a CDL description. This description defines the component name, provided endpoints, security interface, and embedded components. Components can simultaneously provide endpoints, support a security interface, and serve as containers for other components. Each component can provide multiple endpoints with one or more interfaces.
Each interface is defined in an IDL description. This description defines the interface name, signatures of interface methods, and data types for the parameters of interface methods. The data comprising signatures of interface methods and definitions of data types for parameters of interface methods is referred to as a package.
Processes that do not contain components may only act as clients. Processes that contain components can act as servers and/or clients. If components from a process provide endpoints, the process can act as a server and a client at the same time. If components from a process do not provide endpoints (and only support a security interface), the process can act only as a client.
The formal specification of a solution component does not define how this component will be implemented. In other words, the presence of components in a formal specification of a solution component does not mean that these components will be present in the architecture of this solution component.
Names of process classes, components, packages and interfaces
Process classes, components, packages and interfaces are identified by their names in IDL, CDL and EDL descriptions. The names of process classes, components and packages form three sets of names within a KasperskyOS-based solution, in which any name is unique within its own set. A set of package names includes a set of interface names.
The name of a process class, component, package or interface is a link to the IDL, CDL or EDL file in which this name is defined. This link is a path to the IDL, CDL or EDL file (without the extension and dot before it) relative to the directory that is included in the set of directories where the source code generators search for IDL, CDL and EDL files. (This set of directories is defined by parameters -I
<path to files
>.) A dot is used as a separator in a path description.
For example, the kl.core.NameServer
process class name is a link to the EDL file named NameServer.edl
, which is located in the KasperskyOS SDK at the following path:
sysroot-*-kos/include/kl/core
However, source code generators must be configured to search for IDL, CDL and EDL files in the following directory:
sysroot-*-kos/include
The name of an IDL, CDL or EDL file begins with an uppercase letter and must not contain any underscores _
.
EDL description
EDL descriptions are put into separate *.edl
files, which contain the following data:
- Process class name. The following declaration is used:
entity <process class name>
- [Optional] List of instances of components. The following declaration is used:
components {
<component instance name : component name>
...
}
Each component instance is indicated in a separate line. The component instance name must not contain any underscores
_
. The list can contain multiple instances of one component. Each component instance in the list has a unique name.
The EDL language is case sensitive.
Single-line comments and multi-line comments can be used in an EDL description.
Supported endpoints and the security interface can be defined in an EDL description the same way they are defined in a CDL description. However, this practice is not recommended because EDL descriptions and CDL descriptions are usually created by different participants of the development process for KasperskyOS-based solutions. CDL descriptions are created by developers of system programs and application software. EDL descriptions are created by developers that integrate system programs and application software into a unified solution.
Examples of EDL files
Hello.edl
// Class of processes that do not contain components.
entity Hello
Signald.edl
/* Class of processes that contain
* one instance of one component. */
entity kl.Signald
components {
signals : kl.Signals
}
LIGHTCRAFT.edl
/* Class of processes that contain
* two instances of different components. */
entity kl.drivers.LIGHTCRAFT
components {
KUSB : kl.drivers.KUSB
KIDF : kl.drivers.KIDF
}
CDL description
CDL descriptions are put into separate *.cdl
files, which contain the following data:
- The name of the component. The following declaration is used:
component <component name>
- [Optional] Security interface. The following declaration is used:
security <interface name>
- [Optional] List of endpoints. The following declaration is used:
endpoints {
<endpoint name : interface name>
...
}
Each endpoint is indicated in a separate line. The endpoint name must not contain any underscores
_
. The list can contain multiple endpoints with the same interface. Each endpoint in the list has a unique name. - [Optional] List of instances of embedded components. The following declaration is used:
components {
<component instance name : component name>
...
}
Each component instance is indicated in a separate line. The component instance name must not contain any underscores
_
. The list can contain multiple instances of one component. Each component instance in the list has a unique name.
The CDL language is case sensitive.
Single-line comments and multi-line comments can be used in a CDL description.
At least one optional declaration is used in a CDL description. If a CDL description does not use at least one optional declaration, this description will correspond to an "empty" component that does not provide endpoints, does not contain embedded components, and does not support a security interface.
Examples of CDL files
KscProductEventsProvider.cdl
// Component provides one endpoint.
component kl.KscProductEventsProvider
endpoints {
eventProvider : kl.IKscProductEventsProvider
}
KscConnectorComponent.cdl
// Component provides multiple endpoints.
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 does not provide endpoints, supports
* a security interface, and contains one instance
* of another component. */
component FsVerifier
security Approve
components {
verifyComp : Verify
}
IDL description
IDL descriptions are put into separate *.idl
files, which contain the following data:
- Package name. The following declaration is used:
package <package name>
- [Optional] Packages from which the data types for interface method parameters are imported. The following declaration is used:
import <package name>
- [Optional] Definitions of data types for parameters of interface methods.
- [Optional] Signatures of interface methods. The following declaration is used:
interface {
<interface method name([parameters])>;
...
}
Each method signature is indicated in a separate line. The method name must not contain any underscores
_
. Each method in the list has a unique name. The parameters of methods are divided into input parameters (in
), output parameters (out
), and parameters for transmitting error information (error
).Input parameters and output parameters are transmitted in IPC requests and IPC responses, respectively. Error parameters are transmitted in IPC responses if the server cannot correctly handle the corresponding IPC requests.
The server can inform a client about IPC request processing errors via error parameters as well as through output parameters of interface methods. If the server sets the error flag in an IPC response when an error occurs, this IPC response will contain the error parameters without any output parameters. Otherwise this IPC response will contain output parameters just like when requests are correctly processed. (The error flag is set in IPC responses by using the
nk_err_reset()
macro defined in thenk/types.h
header file from the KasperskyOS SDK.)An IPC response sent with the error flag set and an IPC response with the error flag not set are considered to be different types of events for the Kaspersky Security Module. When describing a solution security policy, this difference lets you conveniently distinguish between the processing of events associated with the correct execution of IPC requests and the processing of events associated with incorrect execution of IPC requests. If the server does not set the error flag in IPC responses, the security module must check the values of output parameters indicating errors to properly process events related to the incorrect execution of IPC requests. (A client can check the state of the error flag in an IPC response even if the corresponding interface method does not contain error parameters. To do so, the client uses the
nk_msg_check_err()
macro defined in thenk/types.h
header file from the KasperskyOS SDK.)Signatures of interface methods cannot be imported from other IDL files.
The IDL language is case sensitive.
Single-line comments and multi-line comments can be used in an IDL description.
At least one optional declaration is used in a IDL description. If an IDL description does not use at least one optional declaration, this description will correspond to an "empty" package that does not assign any interface methods or data types (including from other IDL descriptions).
Some IDL files from the KasperskyOS SDK do not describe interface methods, but instead only contain definitions of data types. These IDL files are used only as exporters of data types.
If a package contains a description of interface methods, the interface name matches the package name.
Examples of IDL files
Env.idl
package kl.Env
// Definitions of data types for interface method parameters
typedef string<128> Name;
typedef string<256> Arg;
typedef sequence<Arg,256> Args;
// Interface includes one method.
interface {
Read(in Name name, out Args args, out Args envs);
}
Kpm.idl
package kl.Kpm
// Import data types for parameters of interface methods
import kl.core.Types
// Definition of data type for parameters of interface methods
typedef string<64> String;
/* Interface includes multiple methods.
* Some methods do not have any parameters. */
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 data types for interface method parameters
import kl.MessageBusTypes
/* Interface includes a method that has
* input and output parameters, and
* an error parameter.*/
interface {
Wait(in ClientId id,
out Message topic,
out BundleId dataId,
error ResultCode result);
}
WaylandTypes.idl
// Package contains only definitions of data types.
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 data types
Primitive types
IDL supports the following primitive types:
SInt8
,SInt16
,SInt32
,SInt64
(signed integer)UInt8
,UInt16
,UInt32
,UInt64
(unsigned integer)Handle
(value whose binary representation consists of multiple fields, including a handle field and a handle permissions mask field)bytes<
<size in bytes
>>
(byte buffer)string<
<size in bytes
>>
(string buffer)
A byte buffer is a memory area with a size that does not exceed the defined number of bytes. A string buffer is a byte buffer whose last byte is a terminating zero. The maximum size of a string buffer is a unit larger than the defined size due to the additional byte with the terminating zero. To transfer a byte buffer or string buffer via IPC, the amount of memory that is actually occupied by this buffer will be used.
For numeric types, you can declare named constants by using the reserved word const
:
const UInt32 DeviceNameMax = 64;
const UInt32 HandleTypeUserLast = 0x0001FFFF;
Constants are used to avoid problems associated with "magic numbers". For example, if an IDL description defines constants for return codes of an interface method, you can interpret these codes without additional information when describing a policy.
In addition to primitive types, the IDL language supports composite types, such as unions, structures, arrays and sequences. In a definition of composite types, constants of primitive types may be applied as parameters (for example, to assign an array size).
The bytes<
<size in bytes
>>
and string<
<size in bytes
>>
constructs are used in definitions of composite types, signatures of interface methods, and when creating type aliases because they define anonymous types (types without a name).
Unions
A union lets you store different types of data in one memory area. In an IPC message, a union is provided with an additional tag
field that lets you define which specific member of the union is used.
The following construct is used to define a union:
union <type name> {
<member type> <member name>;
...
}
Example of a union definition:
union ExitInfo {
UInt32 code;
ExceptionInfo exc;
}
Structures
The following construct is used to define a structure:
struct <type name> {
<field type> <field name>;
...
}
Example of a structure definition:
struct SessionEvqParams {
UInt32 count;
UInt32 align;
UInt32 size;
}
Arrays
The following construct is used to define an array:
array<<type of elements, number of elements>>
This construct is used in definitions of other composite types, signatures of interface methods, and when creating type aliases because it defines an anonymous type.
Sequences
A sequence is a variable-sized array. When defining a sequence, its maximum number of elements is specified. However, you can actually transmit less than this number (via IPC). In this case, only an amount of memory necessary for the transmitted elements will be used.
The following construct is used to define a sequence:
sequence<<type of elements, number of elements>>
This construct is used in definitions of other composite types, signatures of interface methods, and when creating type aliases because it defines an anonymous type.
Types based on composite types
Composite types can be used to define other composite types. The definition of an array or sequence can also be included in the definition of another type:
struct BazInfo {
array<UInt8, 100> a;
sequence<sequence<UInt32, 100>, 200> b;
string<100> c;
bytes<4096> d;
UInt64 e;
}
The definition of a union or structure cannot be included in the definition of another type. However, a previously described definition of a union or structure can be included in a type definition. This is done by indicating the names of the included types in the type definition:
union foo {
UInt32 value1;
UInt8 value2;
}
struct bar {
UInt32 a;
UInt8 b;
}
struct BazInfo {
foo x;
bar y;
}
Creating aliases of types
Type aliases make it more convenient to work with types. For example, type aliases can be used to assign mnemonic names to types that have abstract names. Assigned aliases for anonymous types also let you receive named types.
The following construct is used to create a type alias:
typedef <type name/anonymous type definition> <type alias>
Example of creating mnemonic aliases:
typedef UInt64 ApplicationId;
typedef Handle PortHandle;
Example of creating an alias for an array definition:
typedef array<UInt8, 4> IP4;
Example of creating an alias for a sequence definition:
const UInt32 MaxDevices = 8;
struct Device {
string<32> DeviceName;
UInt8 DeviceID;
}
typedef sequence<Device, MaxDevices> Devices;
Example of creating an alias for a union definition:
union foo {
UInt32 value1;
UInt8 value2;
}
typedef foo bar;
Defining anonymous types in signatures of interface methods
Anonymous types can be defined in signatures of interface methods.
Example of defining a sequence in an interface method signature:
Poll(in Generation generation,
in UInt32 timeout,
out Generation currentGeneration,
out sequence<Report, DeviceMax> report,
out UInt32 count,
out UInt32 rc);