Table of Contents

Name

sysjail -- enforce processes in systrace policies

Synopsis

#include <sys/param.h>
#include <stdint.h>
#include <sysjail.h>

int
sj_init();

void
sj_teardown();

int
sj_pol_new(int devnum);

int
sj_dev_new(const char *path);

int
sj_pol_set(int polnum, const struct sj_pol_t *pol);

int
sj_execv(int polnum, const char *file, char *const argv[]);

int
sj_poll(const struct sj_aux_t *aux, int size);

void
sj_iowrite(int devnum, void *buf, size_t len, pid_t pid, register_t reg);

void
sj_ioread(int devnum, void *buf, size_t len, pid_t pid, register_t reg);

void
sj_argwrite(int devnum, uintptr_t arg, size_t len, int index, pid_t pid, uint16_t seqn);

int
sj_piddb_has(pid_t pid);

int
sj_piddb_copy(pid_t **pid, size_t *len);

void
(*sj_vdbg)(const char *file, int line, const char *fmt, ...);

void
(*sj_vwrn)(const char *file, int line, const char *fmt, ...);

void
(*sj_vwrnx)(const char *file, int line, const char *fmt, ...);

void
sj_warnx(const char *fmt, ...);

void
sj_dbg(const char *fmt, ...);

void
sj_warn(const char *fmt, ...);

Description

The sysjail functions control processes under systrace policies. Callers are responsible for managing actual system call interceptions; the functions handle processes, policies, emulations, and devices. The systrace.h and systrace.c files are designed to be drop-able into any source tree on a supporting system. This manual provides a high-level overview of the functions. For structure contents and detailed listenings, consult the header file documentation.

sj_init()
Initialises the systrace function library. This must be called before any other functions, and must be symmetric to a call to sj_teardown(). Returns 0 on failure, 1 on success.

sj_teardown()
Tears down all structures and frees all resources claimed by the function library. After this has been called, no subsequent sysjail functions should be called. The sj_init() function may be re-called to reinitialise the system.

sj_dev_new()
Initialises a physical systrace(4) device and registers it with the system. Returns an identifier for the device for subsequent use, or -1 if an error occured.

sj_pol_new()
Creates a policy and binds it to a device. The default device permits all system calls.

sj_pol_set()
Fills a policy with callbacks. This may be called at any time to set or modify policy routes. Returns 0 on failure, 1 on success.

sj_execv()
Forks and executes a process in the context of a policy. The process's children will inherit this policy. See execv(3) for details on calling conventions. Returns 0 on failure, 1 on success.

sj_poll()
Polls on processes' intercepted system calls or auxiliary read events. Returns only on interception of a SIGTERM, when all governed processes have exited, or a call-back requests a shutdown. Returns 0 on error (internal error, call-back shutdown with error code) and 1 on success (call-back shutdown with success code).

sj_piddb_has() Check if a process is imprisoned in the jail. Returns -1 on failure, 0 if is, 1 if it is not.

sj_piddb_copy() Copies the entire process database into an allocated structure. Memory must be freed by the caller. Returns 0 on failure (no memory need be freed) and 1 on success.

sj_vdbg
If set, accepts debugging messages from within the system. Defaults to being unset.

sj_vwrn
If set, accepts warning messages (those that have errno set) from within the system. Defaults to being unset.

sj_vwrnx
If set, accepts warning messages from within the system. Defaults to being unset.

sj_dbg()
Formats output and calls to sj_vdbg, if set.

sj_warn()
Formats output and calls to sj_vwrn, if set.

sj_warnx()
Formats output and calls to sj_vwrnx, if set.

sj_ioread()
Read data from a traced process's address space. Returns -1 on internal error, 0 on success, or a positive errno value if the kernel copy operation fails.

sj_iowrite()
Write data to a traced process's address space. Returns -1 on internal error, 0 on success, or a positive errno value if the kernel copy operation fails.

sj_argwrite()
Modify a single system call argument. Returns -1 on internal error, 0 on success, or a positive errno" value if the kernel operation fails.

Usage

The sysjail function library may be used by dropping the sysjail.h and sysjail.c files into a source tree and interfacing with the functions. The source file does not require any specific compiler flags to run (assuming that sysjail.h is in the same directory):

cc -c -o sysjail.o sysjail.c

Before using the function library, it must be initialised with sj_init().

One or more (usually one) devices must then be initialised with sj_dev_new(), which registers physical devices with the system.

A policy is the main concept in tracing processes. A policy, in the sense of sysjail, defines the set of intercepted system calls and their corresponding function callbacks. Policies are created by sj_pol_new() and filled in with sj_pol_set(). Policies may be changed at any time during system run-time with sj_pol_set(). This function dictates the routes taken by interceptions under given emulation modes.

The general strategy for a policy is to register a system call for all potential emulations. For example, to intercept fcntl(2) , one may set appropriate call-backs for 92 (native on OpenBSD) and 55 (Linux emulation). In many events, this call-back may be the same; however, some calls have different semantics between unices.

Once one or more policies have been generated and (optionally) filled-in, they may be assigned to processes with sj_execv(). Policies are inherited by the children of the process executed by this function.

Finally, one begins the event loop with sj_poll(). This function does not return until a SIGTERM is detected, all controlled processes exit, or a call-back function returns an exit request. Before exiting the process, sj_teardown() should be called to reclaim all resources properly.

Logging

During the sysjail library run-time, logging occurs in two ways. The first is using the standard err(3) and family. The second is using the internal methods as listed above. After forking a child, all logging uses the standard err(3) functions. Internally, sysjail only uses the provided log utilities. Developers are encouraged to use these log functions in writing intercept functions.

Implementation

This is not intended to be a thorough examination of internals. Obviously, devices correspond to systrace(4) devices. It's unlikely that more than one device will be specified for a system. Policies are bound to devices. A policy is represented by a logical identifier, not to be confused with the policy number generated by systrace(4) . Internally, sysjail manages multiple physical policies per logical policy in order to account for changing emulation, as systrace(4) dictates that only one emulation mode may exist per policy. Changing a policy with sj_pol_set() results in this change propogating to all current and future physical policies referenced by the logical identifier.

In general, emulation is handled by registering all system call to a peremulation policy. The system calls for execve(2) and execv(2) are then registered for a dummy callback (whether or not a callback has been specified), guaranteeing that we will see when emulation changes.

Processes are started by sj_execv() with a policy and retain the policy during run-time (although the policy's soft values, those specified by sj_pol_set(), may be changed in run-time). The sj_poll() function loops over read events from systrace(4) (and auxiliary descriptors) until all processes are exhausted, a signal is received, an error occurs, or a call-back requests exit.

Signals

Between calls to sj_init() and sj_teardown(), the signals SIGTERM and SIGUSR1 have their handlers re-assigned and are masked. It's the caller's responsibility to re-assign these after sj_teardown(), if desired. Existing signal masks are not touched.

Examples

The following example has cb_native() and cb_linux() (not listed) handle call-backs for fcntl(2) . The command-line arguments are used as file and parameters for execution. Note that error handling is omitted for brevity. This example must be executed as root.

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

int
pol, dev, rc; struct sj_pol_t policy;

sj_init();

dev = sj_dev_new(SJ_DEVICE);
pol = sj_pol_new(dev);

memset(&policy, 0, sizeof(struct sj_pol_t)); policy.route[SJ_EMUL_NONE][92].ask = cb_native; policy.route[SJ_EMUL_LINUX][55].ask = cb_linux;

sj_pol_set(pol, &policy);

++argv;
sj_execv(pol, *argv, argv);

rc = sj_poll(NULL, 0);
sj_teardown();

return(rc ? 0 : 1);
}

See Also

systrace(4) , chroot(2) , execv(2) , systrace(1) , sysjail(1) , jail(1) , sjls(1) , jls(1)

History

This is a complete re-write of the sysjail-1.0.4 core.

Authors

The sysjail function library was written by Kristaps Dzonsons for the bsd.lv project.


Table of Contents