SECMODEL(9)

NAME
secmodel - security model development guidelines

SYNOPSIS
#include <secmodel/secmodel.h>

DESCRIPTION
NetBSD provides a complete abstraction of the underlying security model used with the operating system to a set of kauth(9) scopes and actions.

It is possible to modify the security model -- either slightly or using an entirely different model -- by attaching/detaching kauth(9) listeners. This document describes this process.

  Background
In NetBSD 4.0, Kernel Authorization -- kauth(9) -- was introduced as the subsystem responsible for authorization and credential management. Before its introduction, there were several ways for providing resource access control:
-   Checking if the user in question is the superuser via suser().
-   Comparing the user-id against hard-coded values, often zero,
-   Checking the system securelevel.

The problem with the above is that the interface ("can X do Y?") was tightly coupled with the implementation ("is X Z?"). kauth(9) allowed us to separate them, dispatching requests with highly detailed context using a consistent and clear KPI.

The result is a pluggable framework for attaching "listeners" that can modify the behavior of the system, security-wise. It allows us to maintain the existing security model (based on a single superuser and above-superuser restrictions known as securelevel) but easily decouple it from the system, given we want to use a different one.

The different security model can be implemented in the kernel or loaded as an LKM, base its decisions on available information, dispatch the decision to a userspace daemon, or even to a centralized network authorization server.

  The kauth(9) KPI
Before writing a new security model, one should be familiar with the kauth(9) KPI, its limitations, requirements, and so on.

First, some terminology. According to kauth(9), the system is logically divided to scopes, where each scope denotes a different area of interest in the system -- something like a namespace. For example, NetBSD has the process, network, and machdep scopes, representing process-related, network-related, and machdep-related actions.

Each scope has a collection of actions -- or requests -- forming the high level indication of the request type. Each request is automatically associated with credentials and between zero to four arguments providing the request context.

For example, in the process scope there are requests such as "can signal", "can change rlimits", and "can change corename".

Each scope in the system is associated with listeners, which are actually callback routines, that get called when an authorization request on the relevant scope takes place.

Every listener receives the request and its context, and can make a decision of either "allow", "deny", or "defer" (if it doesn't want to be the one deciding).

It is important to note that a single "deny" is enough to fail a request, and at least a single "allow" is required to allow it. In other words, it is impossible to attach listeners that weaken the security of the system or override decisions made by other listeners.

At last, there are several things you should remember about kauth(9):

-   Authorization requests can not be issued when the kernel is holding any locks. This is a requirement from kernel code, to allow designing security models where the request should be dispatched to userspace or a different host.

-   Private listener data -- such as internal data-structures -- is entirely under the responsibility of the developer. Locking, synchronization, and garbage collection are all things that kauth(9) does not take care of for you!

  Writing a new security model
A security model is composed of (code-wise) the following components:

1.  Entry routines, named secmodel_model_init() and secmodel_model_start(), used to initialize and start the security model.

    If the security model is to be started automatically by the kernel and is compiled in it, a function called secmodel_start() can be added to call the model's start routine.

    If the security model is to be built and used as an LKM, another function called secmodel_model_stop(), to stop the security model in case the module is to be unloaded.

2.  A sysctl(9) setup routine for the model. This should create an entry for the model in the sysctl(9) namespace, under the "security.models.model" hierarchy.

    All "knobs" for the model should be located under the new node, as well as a mandatory "name" variable, indicating a descriptive human-readable name for the model.

    If the module is to be used as an LKM, explicit calls to the setup routine and sysctl_teardown() are to be used to create and destroy the sysctl(9) tree.

3.  If the model uses any private data inside credentials, listening on the credentials scope, KAUTH_SCOPE_CRED, is required.

4.  Optionally, internal data-structures used by the model. These must all be prefixed with "secmodel_model_".

5.  A set of listeners, attached to various scopes, used to enforce the policy the model intends to implement.

6.  Finally, a security model should register itself when loaded using secmodel_register() and deregister it when unloaded (if used as an LKM) using secmodel_deregister().

Below is sample code for a kauth(9) network scope listener for the jenna security model. It is used to allow users with a user-id below 1000 bind to reserved ports (for example, 22/TCP):

    int
    secmodel_jenna_network_cb(kauth_cred_t cred, kauth_action_t action,
        void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
    {
    	int result;
    	/* Default defer. */
    	result = KAUTH_RESULT_DEFER;
    	switch (action) {
    	case KAUTH_NETWORK_BIND:
    		/*
    		 * We only care about bind(2) requests to privileged
    		 * ports.
    		 */
    		if ((u_long)arg0 == KAUTH_REQ_NETWORK_BIND_PRIVPORT) {
    			/*
    			 * If the user-id is below 1000, which may
    			 * indicate a "reserved" user-id, allow the
    			 * request.
    			 */
    			if (kauth_cred_geteuid(cred)  1000)
    				result = KAUTH_RESULT_ALLOW;
    		}
    		break;
    	}
    	return (result);
    }

There are two main issues, however, with that listener, that you should be aware of when approaching to write your own security model:

1.  As mentioned, kauth(9) uses restrictive decisions: if you attach this listener on-top of an existing security model, even if it would allow the request, it could still be failed.

2.  If you attach this listener as the only listener for the network scope, there are many other requests that will be deferred and, eventually, denied -- which may not be desired.

That's why before implementing listeners, it should be clear whether they implement an entirely new from scratch security model, or add on-top of an existing one.

  Adding on-top of an existing security model
One of the shortcomings of kauth(9) is that it does not provide any stacking mechanism, similar to Linux Security Modules (LSM). This, however, is considered a feature in reducing dependency on other people's code.

To properly "stack" minor adjustments on-top of an existing security model, one could use one of two approaches:

-   Registering an internal scope for the security model to be used as a fall-back when requests are deferred.

    This requires the security model developer to add an internal scope for every scope the model partly covers, and registering the fall-back listeners to it. In the model's listener(s) for the scope, when a defer decision is made, the request is passed to be authorized on the internal scope, effectively using the fall-back security model.

    Here's example code that implements the above:

        #include secmodel/bsd44/bsd44.h
        /*
         * Internal fall-back scope for the network scope.
         */
        #define	JENNA_ISCOPE_NETWORK "jenna.iscope.network"
        static kauth_scope_t secmodel_jenna_iscope_network;
        /*
         * Jenna's entry point. Register internal scope for the network scope
         * which we partly cover for fall-back authorization.
         */
        void
        secmodel_jenna_start(void)
        {
        	secmodel_jenna_iscope_network = kauth_register_scope(
        	    JENNA_ISCOPE_NETWORK, NULL, NULL);
        	kauth_listen_scope(JENNA_ISCOPE_NETWORK,
        	    secmodel_bsd44_suser_network_cb, NULL);
        	kauth_listen_scope(JENNA_ISCOPE_NETWORK,
        	    secmodel_bsd44_securelevel_network_cb, NULL);
        }
        /*
         * Jenna sits on top of another model, effectively filtering requests.
         * If it has nothing to say, it discards the request. This is a good
         * example for fine-tuning a security model for a special need.
         */
        int
        secmodel_jenna_network_cb(kauth_cred_t cred, kauth_action_t action,
            void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
        {
        	int result;
        	/* Default defer. */
        	result = KAUTH_RESULT_DEFER;
        	switch (action) {
        	case KAUTH_NETWORK_BIND:
        		/*
        		 * We only care about bind(2) requests to privileged
        		 * ports.
        		 */
        		if ((u_long)arg0 == KAUTH_REQ_NETWORK_BIND_PRIVPORT) {
        			if (kauth_cred_geteuid(cred)  1000)
        				result = KAUTH_RESULT_ALLOW;
        		}
        		break;
        	}
        	/*
        	 * If we have don't have a decision, fall-back to the bsd44
        	 * security model.
        	 */
        	if (result == KAUTH_RESULT_DEFER)
        		result = kauth_authorize_action(
        		    secmodel_jenna_iscope_network, cred, action,
        		    arg0, arg1, arg2, arg3);
        	return (result);
        }

-   If the above is not desired, or cannot be used for any reason, there is always the ability to manually call the fall-back routine:

        int
        secmodel_jenna_network_cb(kauth_cred_t cred, kauth_action_t action,
            void *cookie, void *arg0, void *arg1, void *arg2, void *arg3)
        {
        	int result;
        	/* Default defer. */
        	result = KAUTH_RESULT_DEFER;
        	switch (action) {
        	case KAUTH_NETWORK_BIND:
        		/*
        		 * We only care about bind(2) requests to privileged
        		 * ports.
        		 */
        		if ((u_long)arg0 == KAUTH_REQ_NETWORK_BIND_PRIVPORT) {
        			if (kauth_cred_geteuid(cred)  1000)
        				result = KAUTH_RESULT_ALLOW;
        		}
        		break;
        	}
        	/*
        	 * If we have don't have a decision, fall-back to the bsd44
        	 * security model's suser behavior.
        	 */
        	if (result == KAUTH_RESULT_DEFER)
        		result = secmodel_bsd44_suser_network_cb(cred, action,
        		    cookie, arg0, arg1, arg2, arg3);
        	return (result);
        }

  Writing a new security model from scratch
When writing a security model from scratch, aside from the obvious issues of carefully following the desired policy to be implemented and paying attention to all of the issues outlined above, one must also remember that any unhandled requests will be denied by default.

To make it easier on developers to write new security models from scratch, NetBSD maintains skeleton listeners that contain every possible request and arguments.

  Available security models
The following is a list of security models available in the default NetBSD distribution. To choose, one should edit /usr/src/sys/conf/std.

secmodel_bsd44
    Traditional NetBSD security model, derived from 4.4BSD.

secmodel_overlay
    Sample overlay security model, sitting on-top of secmodel_bsd44(9).

FILES
/usr/share/examples/secmodel

SEE ALSO
kauth(9), secmodel_bsd44(9), secmodel_overlay(9)

AUTHORS
Elad Efrat <elad@NetBSD.org>