KasperskyOS Community Edition 1.0

Part 1. Simple application (POSIX)

KasperskyOS Community Edition includes a set of libraries (libc, libm and libpthread) ensuring that the developed applications are partially compatible with the POSIX family of standards.

This part of the Guide illustrates the following:

  • Printing a string to the screen by using fprintf()
  • Using VFS component for working with a network and file systems
  • Creating the Einit initializing entity
  • POSIX support limitations

To simplify the descriptions, the example in this part of the Guide is built without the ksm.module. For this reason, when example is started, a notification WARNING! Booting an insecure kernel! is displayed. The third part of the Guide examines a solution security policy, use of security policies, and building ksm.module.

In this Help section

hello example

VFS: working with a network and files

POSIX support limitations

Page top
[Topic ch1_posix]

hello example

In the software development world, learning a new technology traditionally starts with using that technology to greet the world. We will keep that tradition with KasperskyOS, so we begin with an example that displays Hello world! on the screen.

KasperskyOS lets you develop solutions in the C and C++ languages.

The hello.c code looks familiar and simple to a developer that uses C, and is fully compatible with POSIX:

hello.c

#include <stdio.h>

#include <stdlib.h>

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

{

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

return EXIT_SUCCESS;

}

To run the Hello file in KasperskyOS, multiple additional actions are required.

Development of applications for KasperskyOS has the following specifics:

First of all, each entity (the term used to refer to applications and their associated processes in KasperskyOS) must be statically described. A description is contained in files with the EDL, CDL and IDL extensions, which are used for building a solution. The minimum possible description of an entity is an EDL file that indicates the name of the entity. All entities developed in the first part of the Guide have a minimum static description (only an EDL file with the entity name).

Secondly, all entities to be started must be contained in the KasperskyOS image being loaded. For this reason, each example in this Guide presents not just an individual entity but a ready-to-use KasperskyOS-based solution that includes an image of the kernel that initializes the entity and auxiliary entities, such as drivers.

EDL description of the Hello entity

A static description of the Hello entity consists of a single file named Hello.edl that must indicate the entity name:

Hello.edl

/* The entity name follows the reserved word "entity". */

entity Hello

The entity name must begin with an uppercase letter. The name of an EDL file must match the name of the entity that it describes.

The second part of the Guide shows examples of more complex EDL descriptions, and also presents CDL and IDL descriptions.

Creating the Einit initializing entity

When KasperskyOS is loaded, the kernel starts an entity named Einit. The Einit entity starts all other entities included in the solution, which means that it serves as the initializing entity.

The KasperskyOS Community Edition toolkit includes the einit tool, which lets you generate the code of the initializing entity (einit.c) based on the init description. In the example provided below, the file containing the init description is named init.yaml, but it can have any name.

For more details, refer to Entity startup.

If you want the Hello entity to start after the operating system is loaded, all you need to do is specify its name in the init.yaml file and build an Einit entity from it.

init.yaml

entities:

# Start the "Hello" entity.

- name: Hello

Build scheme for the hello example

The general build scheme for the hello example looks as follows:

Building and running the hello example

See the Building and running examples section.

Page top
[Topic hello_example][Topic vfs_chapter]

VFS: overview

The VFS component contains the implementations of file systems and the network stack. POSIX calls for working with file systems and the network are sent to the VFS component, which then calls the block device driver or network driver, respectively.

vfs_overview

Multiple copies of the VFS component can be added to a solution to separate the data streams of different entities. Each VFS copy is built separately and can contain the entire VFS functionality or a specific part of it, for example:

  • One or more file systems
  • Network stack
  • Network stack and network driver

The VFS component can be used either directly (through static linking) or via IPC (as a separate entity). Use of VFS functionality via IPC enables the solution developer to do the following:

  • Use a solution security policy to control method calls that work with the network and file systems.
  • Connect multiple client entities to one VFS entity.
  • Connect one client entity to two VFS entities to separately work with the network and file systems.
Page top
[Topic vfs_overview]

Building a VFS entity

KasperskyOS Community Edition does not provide a ready-to-use image for an entity containing the VFS component. The solution developer can independently build one or more entities that each include the specific VFS functionality necessary for the solution.

Please also refer to the multi_vfs_dhcpcd, multi_vfs_dns_client and multi_vfs_ntpd examples provided in KasperskyOS Community Edition.

Building a network VFS

To build a "network" VFS entity containing a network driver, the file containing the main() function must be linked to the vfs_server, vfs_net and dnet_implementation libraries:

CMakeLists.txt (fragment)

target_link_libraries (Net1Vfs ${vfs_SERVER_LIB}

${vfs_NET_LIB}

${dnet_IMPLEMENTATION_LIB})

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

vfs_net1

Using a "network" VFS entity linked to a network driver

To use a network driver via IPC (as a separate entity), the dnet_client library must be used instead of the dnet_implementation library:

CMakeLists.txt (fragment)

target_link_libraries (Net2Vfs ${vfs_SERVER_LIB}

${vfs_NET_LIB}

${dnet_CLIENT_LIB})

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

vfs_net2

Using a "network" VFS entity and a network driver as a separate entity

File operations are used by some functions, including printing to stdout. For these functions to work correctly, the vfs_implementation library must be added instead of vfs_net during the build.

Building a file VFS

To build a "file" VFS entity, the file containing the main() function must be linked to the vfs_server and vfs_fs libraries and to the libraries for implementing file systems:

CMakeLists.txt (fragment)

target_link_libraries (VfsFs

${vfs_SERVER_LIB}

${LWEXT4_LIB}

${vfs_FS_LIB})

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

In this example, the VFS entity is prepared to connect to the ramdisk driver entity.

A block device driver cannot be linked to VFS and is always used via IPC:

vfs_fs

Using a "file" VFS entity and a driver as a separate entity

If necessary, you can build a VFS entity containing the network stack and the file systems. To do so, use the vfs_server, vfs_implementation, and dnet_implementation libraries (or dnet_client), and the file system implementation libraries.

Page top
[Topic vfs_entity_build]

Env entity

The Env service entity allows running entities to pass arguments and environment variables. When started, each entity automatically sends a request to the Env entity and receives the necessary data.

By including the Env entity in the solution, you can mount file systems when VFS is started, connect one client entity to two VFS entities, and perform many other tasks.

To use the Env entity in your solution, the following is required:

1. Develop the code of the Env entity by using macros from env/env.h.

2. Build an image of the entity by linking it to the env_server library.

3. In the init description, indicate that the Env entity must be started and connected to the selected entities (Env acts a server in this case). The channel name is defined by the ENV_SERVICE_NAME macro declared in the env/env.h file.

4. Include the Env entity image in the solution image.

Env entity code

The code of the Env entity utilizes the following macros and functions declared in the env/env.h file:

  • ENV_REGISTER_ARGS(name,argarr) – arguments from the argarr array will be passed to the entity named name.
  • ENV_REGISTER_VARS(name,envarr) – environment variables from the envarr array will be passed to the "name" entity.
  • ENV_REGISTER_PROGRAM_ENVIRONMENT(name,argarr,envarr) – arguments and environment variables will be passed to the entity named name.
  • envServerRun() – initialize the server part of the entity so that it can respond to requests.

Example:

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 example for use of the Env entity

In the next example, the Client entity will be connected to the Env entity whose image is located in the env folder:

init.yaml

entities:

- name: env.Env

- name: Client

connections:

- target: env.Env

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

Page top
[Topic vfs_env_entity]

Connecting a client entity to one or two VFS entities

Calls of network- and file POSIX functions can be forwarded to two separate VFS components by connecting a client entity to two different VFS entities. If such data stream separation is not required (for example, if the client only works with the network), connecting the client entity to a single VFS entity is enough.

Connecting to one VFS entity

The name of the IPC channel between the client entity and the VFS entity must be defined by the _VFS_CONNECTION_ID macro declared in the vfs/defs.h file. In this case, "network" calls and "file" calls will be sent to this VFS entity.

Example:

init.yaml

- name: ClientEntity

connections:

- target: VfsEntity

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

- name: VfsEntity

Connecting to two VFS entities

Let's examine a client entity that is connected to two different VFS entities. We will name them "network" VFS and "file" VFS.

The _VFS_NETWORK_BACKEND environment variable is used so that "network" calls from the client entity are sent only to the "network" VFS:

  • For the "network" VFS entity: _VFS_NETWORK_BACKEND=server:<name of the IPC channel to the "network" VFS>
  • For the client entity: _VFS_NETWORK_BACKEND=client: <name of the IPC channel to the "network" VFS>

The analogous _VFS_FILESYSTEM_BACKEND environment variable is used to send "file" calls:

  • For the "file" VFS entity: _VFS_FILESYSTEM_BACKEND=server:<name of the IPC channel to the "file" VFS>
  • For the client entity: _VFS_FILESYSTEM_BACKEND=client: <name of the IPC channel to the "file" VFS>

As a result, the functions for working with the network and files will be sent to two different VFS entities.

In the next example, the Client entity is connected to two VFS entities – the "network" VfsFirst entity and the "file" VfsSecond entity:

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 entity code:

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;

}

Please also refer to the multi_vfs_dhcpcd, multi_vfs_dns_client and multi_vfs_ntpd examples provided in KasperskyOS Community Edition.

Page top
[Topic vfs_separate]

Mounting file systems when VFS starts

By default, the VFS component provides access to the following:

  • RAMFS file system. RAMFS is mounted to the root directory by default.
  • ROMFS object storage. The storage contains non-executable files (including configuration files) that are added to the solution image during the build. The ROMFS file system is not mounted by default. However, the storage can be accessed indirectly through the -f argument, for example.

If you need to mount other file systems, this can be done either by using the mount() call after the VFS starts or immediately when the VFS starts by passing the following arguments and environment variables to it:

  • -l <entry in fstab format>

    The -l argument lets you mount the file system.

  • -f <path to fstab file>

    The -f argument lets you pass the file containing entries in fstab format for mounting file systems. The ROMFS storage will be searched for the file. If the UMNAP_ROMFS variable is defined, the file system mounted using the ROOTFS variable will be searched for the file.

  • UNMAP_ROMFS

    If the UNMAP_ROMFS variable is defined, the ROMFS storage will be deleted. This helps conserve memory and change behavior when using the -f argument.

  • ROOTFS = <entry in fstab format>

    The ROOTFS variable lets you mount a file system to the root directory. In combination with the UNMAP_ROMFS variable and the -f argument, it lets you search for the fstab file in the mounted file system instead of in the ROMFS storage.

Example:

env.c

#include <env/env.h>

#include <stdlib.h>

int main(int argc, char** argv)

{

/* The devfs and romfs file systems will be mounted for the Vfs1 entity. */

const char* Vfs1Args[] = {

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

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

};

ENV_REGISTER_ARGS("Vfs1", Vfs1Args);

/* The file systems defined through the /etc/dhcpcd.conf file located in the ROMFS storage will be mounted for the Vfs2 entity. */

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

ENV_REGISTER_ARGS("Vfs2", Vfs2Args);

/* The ext2 file system containing the /etc/fstab file used for mounting additional file systems will be mounted to the root directory for the Vfs3 entity. The ROMFS storage will be deleted. */

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;

}

Please also refer to the net_with_separate_vfs, net2_with_separate_vfs, multi_vfs_dhcpcd, multi_vfs_dns_client and multi_vfs_ntpd examples provided in KasperskyOS Community Edition.

Page top
[Topic vfs_mount_filesystems]

POSIX support limitations

KasperskyOS uses a limited POSIX interface oriented toward the POSIX.1-2008 standard (without XSI support). These limitations are primarily due to security precautions.

Limitations affect the following:

  • Interaction between processes
  • Interaction between threads via signals
  • Standard input/output
  • Asynchronous input/output
  • Use of robust mutexes
  • Terminal operations
  • Shell usage
  • Management of file handles

Limitations include:

  • Unimplemented interfaces
  • Interfaces that are implemented with deviations from the POSIX.1-2008 standard
  • Stub interfaces that do not perform any operations except assign the ENOSYS value to the errno variable and return the value -1

In KasperskyOS, signals cannot interrupt the Call(), Recv(), and Reply() system calls that support the operation of libraries that implement the POSIX interface.

The KasperskyOS kernel does not transmit signals.

Limitations on interaction between processes

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

fork()

Create a new (child) process.

Stub

unistd.h

pthread_

atfork()

Register the handlers that are called before and after the child process is created.

Not implemented

pthread.h

wait()

Wait for the child process to stop or complete.

Stub

sys/wait.h

waitid()

Wait for the state of the child process to change.

Not implemented

sys/wait.h

waitpid()

Wait for the child process to stop or complete.

Stub

sys/wait.h

execl()

Run the executable file.

Stub

unistd.h

execle()

Run the executable file.

Stub

unistd.h

execlp()

Run the executable file.

Stub

unistd.h

execv()

Run the executable file.

Stub

unistd.h

execve()

Run the executable file.

Stub

unistd.h

execvp()

Run the executable file.

Stub

unistd.h

fexecve()

Run the executable file.

Stub

unistd.h

setpgid()

Move the process to another group or create a group.

Stub

unistd.h

setsid()

Create a session.

Not implemented

unistd.h

getpgrp()

Get the group ID for the calling process.

Not implemented

unistd.h

getpgid()

Get the group ID.

Stub

unistd.h

getppid()

Get the ID of the parent process.

Not implemented

unistd.h

getsid()

Get the session ID.

Stub

unistd.h

times()

Get the time values for the process and its descendants.

Stub

sys/times.h

kill()

Send a signal to the process or group of processes.

Only the SIGTERM signal can be sent. The pid parameter is ignored.

signal.h

pause()

Wait for a signal.

Not implemented

unistd.h

sigpending()

Check for received blocked signals.

Not implemented

signal.h

sigprocmask()

Get and change the set of blocked signals.

Stub

signal.h

sigsuspend()

Wait for a signal.

Stub

signal.h

sigwait()

Wait for a signal from the defined set of signals.

Stub

signal.h

sigqueue()

Send a signal to the process.

Not implemented

signal.h

sigtimedwait()

Wait for a signal from the defined set of signals.

Not implemented

signal.h

sigwaitinfo()

Wait for a signal from the defined set of signals.

Not implemented

signal.h

sem_init()

Create an unnamed semaphore.

You cannot create an unnamed semaphore for synchronization between processes. If a non-zero value is passed to the function through the pshared parameter, it will only return the value -1 and will assign the ENOTSUP value to the errno variable.

semaphore.h

sem_open()

Create/open a named semaphore.

You cannot open a named semaphore that was created by another process. Named semaphores (like unnamed semaphores) are local, which means that they are accessible only to the process that created them.

semaphore.h

pthread_

mutexattr_

setpshared()

Define the mutex attribute that allows the mutex to be used by multiple processes.

You cannot define the mutex attribute that allows the mutex to be used by multiple processes. If the PTHREAD_PROCESS_SHARED value is passed to the function through the pshared parameter, it will only return the ENOSYS value.

pthread.h

pthread_

barrierattr_

setpshared()

Define the barrier attribute that allows the barrier to be used by multiple processes.

You cannot define the barrier attribute that allows the barrier to be used by multiple processes. If the PTHREAD_PROCESS_SHARED value is passed to the function through the pshared parameter, it will only return the ENOSYS value.

pthread.h

pthread_

condattr_

setpshared()

Define the conditional variable attribute that allows the conditional variable to be used by multiple processes.

You cannot define the conditional variable attribute that allows the conditional variable to be used by multiple processes. If the PTHREAD_PROCESS_SHARED value is passed to the function through the pshared parameter, it will only return the ENOSYS value.

pthread.h

pthread_

rwlockattr_

setpshared()

Define the read/write lock object attribute that allows the read/write lock object attribute to be used by multiple processes.

You cannot define the read/write lock object attribute that allows the read/write lock object attribute to be used by multiple processes. If the PTHREAD_PROCESS_SHARED value is passed to the function through the pshared parameter, it will only return the ENOSYS value.

pthread.h

pthread_

spin_init()

Create a spin lock.

You cannot create a spin lock for synchronization between processes. If the PTHREAD_PROCESS_SHARED value is passed to the function through the pshared parameter, this value will be ignored.

pthread.h

shm_open()

Create or open a shared memory object.

Not implemented

sys/mman.h

mmap()

Map to memory.

You cannot perform memory mapping for interaction between processes. If the MAP_SHARED value is passed to the function through the flags parameter, this value will be ignored. In addition, you cannot pass combinations of the PROT_WRITE|PROT_EXEC and PROT_READ|PROT_WRITE|PROT_EXEC flags through the prot parameter. In this case, the function will only return the MAP_FAILED value and will assign the ENOMEM value to the errno variable.

sys/mman.h

mprotect()

Define the memory access permissions.

This function works as a stub by default. To use this function, define special settings for the KasperskyOS kernel.

sys/mman.h

pipe()

Create an unnamed channel.

You cannot use an unnamed channel for data transfer between processes. Unnamed channels are local, which means that they are accessible only to the process that created them.

unistd.h

mkfifo()

Create a special FIFO file (named channel).

Stub

sys/stat.h

mkfifoat()

Create a special FIFO file (named channel).

Not implemented

sys/stat.h

Limitations on interaction between threads via signals

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

pthread_kill()

Send a signal to an execution thread.

You cannot send a signal to an execution thread. If a signal number is passed to the function through the sig parameter, it only returns the ENOSYS value.

signal.h

pthread_sigmask()

Get and change the set of blocked signals.

Stub

signal.h

siglongjmp()

Restore the state of the control thread and the signals mask.

Not implemented

setjmp.h

sigsetjmp()

Save the state of the control thread and the signals mask.

Not implemented

setjmp.h

Standard input/output limitations

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

dprintf()

Formatted print to file.

Not implemented

stdio.h

fmemopen()

Use memory as a data stream.

Not implemented

stdio.h

open_memstream()

Use dynamically allocated memory as a data stream.

Not implemented

stdio.h

vdprintf()

Formatted print to file.

Not implemented

stdio.h

Asynchronous input/output limitations

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

aio_cancel()

Cancel input/output requests that are waiting to be handled.

Not implemented

aio.h

aio_error()

Receive an error from an asynchronous input/output operation.

Not implemented

aio.h

aio_fsync()

Request the execution of input/output operations.

Not implemented

aio.h

aio_read()

Request a file read operation.

Not implemented

aio.h

aio_return()

Get the status of an asynchronous input/output operation.

Not implemented

aio.h

aio_suspend()

Wait for the completion of asynchronous input/output operations.

Not implemented

aio.h

aio_write()

Request a file write operation.

Not implemented

aio.h

lio_listio()

Request execution of a set of input/output operations.

Not implemented

aio.h

Limitations on the use of robust mutexes

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

pthread_mutex_consistent()

Return a robust mutex to a consistent state.

Not implemented

pthread.h

pthread_mutexattr_getrobust()

Get a robust mutex attribute.

Not implemented

pthread.h

pthread_mutexattr_setrobust()

Define a robust mutex attribute.

Not implemented

pthread.h

Terminal operation limitations

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

ctermid()

Get the path to the file of the control terminal.

This function only returns or passes an empty string through the s parameter.

stdio.h

tcsetattr()

Define the terminal settings.

The input speed, output speed, and other settings specific to hardware terminals are ignored.

termios.h

tcdrain()

Wait for output completion.

This function only returns the value -1.

termios.h

tcflow()

Suspend or resume receipt or transmission of data.

Suspending output and resuming suspended output are not supported.

termios.h

tcflush()

Clear the input queue or output queue, or both of these queues.

This function only returns the value -1.

termios.h

tcsendbreak()

Break the connection with the terminal for a set time.

This function only returns the value -1.

termios.h

ttyname()

Get the path to the terminal file.

This function only returns a null pointer.

unistd.h

ttyname_r()

Get the path to the terminal file.

This function only returns an error value.

unistd.h

tcgetpgrp()

Get the ID of a group of processes using the terminal.

This function only returns the value -1.

unistd.h

tcsetpgrp()

Define the ID for a group of processes using the terminal.

This function only returns the value -1.

unistd.h

tcgetsid()

Get the ID of a group of processes for the leader of the session connected to the terminal.

This function only returns the value -1.

termios.h

Shell operation limitations

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

popen()

Create a child process for command execution and a channel for this process.

This function only assigns the ENOSYS value to the errno variable and returns the value NULL.

stdio.h

pclose()

Close the channel with the child process created by the popen() function, and wait for this child process to terminate.

This function cannot be used because its input parameter is the data stream handle returned by the popen() function, which cannot return anything except the value NULL.

stdio.h

system()

Create a child process for command execution.

Stub

stdlib.h

wordexp()

Perform a shell-like expansion of the string.

Not implemented

wordexp.h

wordfree()

Free up the memory allocated for the results of calling the wordexp() interface.

Not implemented

wordexp.h

Limitations on management of file handles

Interface

Purpose

Implementation

Header file based on the POSIX.1-2008 standard

dup()

Make a copy of the handle of an opened file.

Handles of regular files, standard I/O streams, sockets and channels are supported. There is no guarantee that the lowest available handle will be received.

fcntl.h

dup2()

Make a copy of the handle of an opened file.

Handles of regular files, standard I/O streams, sockets and channels are supported. The handle of an opened file needs to be passed through the fildes2 parameter.

fcntl.h

Page top

[Topic posix_uns_ifaces]