SECMODEL(9)

πŸ“¦ version 11.0, in nixpkgs unstable

🌸 SECMODEL(9) 🌸 β€” security model development guidelines

okay bestie, so this is from the Kernel Developer's Manual and it's literally the glow-up guide for writing your own security model in NetBSD. yes it's a 9-section manpage. yes it's iconic. let's go πŸ’…


✨ SYNOPSIS ✨

#include <secmodel/secmodel.h>

int
secmodel_register(secmodel_t *sm, const char *id, const char *name,
    prop_dictionary_t behavior, secmodel_eval_t sm_eval,
    secmodel_setinfo_t sm_setinfo);

int
secmodel_deregister(secmodel_t sm);

int
secmodel_eval(const char *id, const char *what, void *arg, void *ret);

static int
secmodel_<model>_eval(const char *what, void *arg, void *ret);

πŸ¦‹ DESCRIPTION πŸ¦‹

so NetBSD gives you a complete abstraction of the underlying security model used inside the operating system, through a whole set of kauth(9) scopes and actions. it's giving ✨separation of concerns✨ β€” it lets you keep the traditional security model (the one based on a single super-user plus those above-super-user restrictions known as securelevel) while decoupling it easily from the system. no cap.

and here's the fun part: you can totally modify the security model β€” either just a little bit, or swap in an entirely different model β€” by attaching/detaching kauth(9) listeners. yup, that's the secmodel pluggable framework doing its main character thing πŸ”₯

a secmodel is typically implemented as a module(9), and it can be either built-in statically or loaded dynamically at run-time. they base their decisions on whatever information is available β€” directly from the kernel, from a userspace daemon, or even from a centralized network authorization server. slay, flexible queen πŸ‘‘


πŸŽ€ DATA TYPES πŸŽ€

the secmodel framework offers the following data types:

  • secmodel_t β€” an opaque type that describes a secmodel. it's mysterious. it's giving iykyk πŸ™ˆ

πŸ’– FUNCTIONS πŸ’–

  • secmodel_register(sm, id, name, behavior, sm_eval, sm_setinfo) β€” registers a security model to the secmodel framework and stores its description inside sm. 🫢
    • sm β€” the secmodel description.
    • id β€” the unique identifier of the secmodel.
    • name β€” the descriptive human-readable name of the secmodel.
    • behavior β€” (optional) a prop_dictionary(3) that declares the behavior of this security model, like β€œcopy credentials on fork.”
    • sm_eval β€” (optional) the secmodel_<model>_eval() callback used by a secmodel to register an evaluation routine that can be queried later by another security model.
    • sm_setinfo β€” (optional) the secmodel_<model>_setinfo() callback used by a secmodel to register a routine that permits other security models to alter the secmodel internals. currently not implemented (don't come for it, it's coming later bestie πŸ’€).
  • secmodel_deregister(sm) β€” deregisters the secmodel described by sm. bye girl πŸ‘‹
  • secmodel_eval(id, what, arg, ret) β€” calls the evaluation callback implemented by a security model. the return value can be either:
    • zero (0), when the call succeeded. πŸŽ‰
    • positive, when the error comes directly from the secmodel framework.
    • negative, when the error comes from the evaluation callback implemented in the targeted security model. the value is then implementation-defined (so like... ask the model, not me πŸ™ˆ).

the arguments, one by one:

  • id β€” the unique identifier of the targeted secmodel.
  • what β€” the query that will be passed down to the targeted secmodel.
  • arg β€” the arguments passed to the evaluation routine of the targeted secmodel.
  • ret β€” the answer of the evaluation routine.

🌈 RETURN VALUES 🌈

if successful, functions return 0. otherwise, these error values come through:

  • [EEXIST] β€” the secmodel is already registered. bestie it's literally already in the group chat.
  • [EFAULT] β€” an invalid address or reference was passed as parameter. wrong vibe, wrong address.
  • [EINVAL] β€” an invalid value was passed as parameter.
  • [ENOENT] β€” the targeted secmodel does not exist, or it does not implement an evaluation callback. ghosted. πŸ’€

πŸ‘‘ WRITING A SECURITY MODEL πŸ‘‘

before you go writing a security model, bestie, you should be familiar with the kauth(9) KPI β€” its limitations, requirements, and so on. see kauth(9) for details. do your homework, it's giving responsible queen πŸ“š

a security model is based on the kernel module(9) framework, and can be built-in statically inside the kernel or loaded dynamically at run-time. code-wise, it's composed of these components:

  1. module(9) routines, especially a MODULE() declaration and a secmodel_<model>_modcmd() function used to start (through MODULE_CMD_INIT) and stop (through MODULE_CMD_FINI) the secmodel. πŸ“¦
  2. entry routines, named secmodel_<model>_init() and secmodel_<model>_start(), used to initialize and start the security model β€” plus another function called secmodel_<model>_stop(), to stop the security model in case the module is to be unloaded.
  3. a sysctl(9) setup routine for the model. this should create an entry for the model in the sysctl(7) 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. don't skip the name variable, it's not optional, it's the moment ✨

  1. a sysctl(9) teardown routine used to destroy the sysctl(7) tree associated with the model. clean up after yourself, we love a tidy icon 🧹
  2. if the model uses any private data inside credentials, listening on the credentials scope, KAUTH_SCOPE_CRED, is required. no exceptions, bestie.
  3. optionally, internal data-structures used by the model. these must all be prefixed with "secmodel_<model>_" β€” every single one, branding matters πŸ’…
  4. a set of listeners, attached to various scopes, used to enforce the policy the model intends to implement. this is where the actual rules live 🎯
  5. finally, a security model should register itself after being initialized using secmodel_register(), and deregister itself before being stopped using secmodel_deregister(). intro then outro, periodt.

🧚 EXAMPLES 🧚

below is sample code for a kauth(9) network scope listener for the jenna security model. it's used to allow users with a user-id below 1000 to 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. 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 denied. like, your fave said yes but the group chat said no 😭
  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. oops? πŸ™ˆ

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. decide your era first, bestie πŸ’œ

πŸ’… PROGRAMMING CONSIDERATIONS πŸ’…

okay bestie, before you go off and write your own security model, there are a few things you really need to remember:

  • Pay attention to whether your secmodel implementation actually does the policy you want. Some rights can hand out way more privilege on the system than others β€” like allowing calls to chroot(2) or mounting a file-system. That's main-character-level power, so be careful. πŸ‘‘
  • All unhandled requests are denied by default. No vibes-based allow, no accidental yes. Denied. Periodt.
  • Authorization requests can not be issued while the kernel is holding any locks. This is a requirement from kernel code so that security models can be designed where the request gets dispatched to userspace or a different host. Basically: no lock-holding while asking permission, bestie. πŸš«πŸ”’
  • Private listener data β€” like internal data structures β€” is entirely the developer's responsibility. Locking, synchronization, and garbage collection are all things that kauth(9) does not take care of for you. You are the main character of your own memory management. 🫢

🧚 STACKING ON 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. iconic independence, honestly. πŸ’…

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

  1. Register 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 register 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 is 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_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);
}
  1. 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);
}

πŸ‘‘ AVAILABLE SECURITY MODELS πŸ‘‘

The following is a list of security models available in the default NetBSD distribution. Little lineup of icons, honestly:


πŸ” CODE REFERENCES πŸ”

The core of the secmodel implementation is in sys/secmodel/secmodel.c.

The header file <secmodel/secmodel.h> describes the public interface.

To make it easier on developers to write new security models from scratch, NetBSD maintains an example secmodel under share/examples/secmodel/. Free starter pack, basically. 🎁✨


🌈 SEE ALSO 🌈


πŸ“œ HISTORY πŸ“œ

Kernel Authorization was introduced in NetBSD 4.0 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 super-user 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) allows separating them, dispatching requests with highly detailed context using a consistent and clear KPI. separation slay. πŸ’…βœ¨

The secmodel framework was extended in NetBSD 6.0 to implement secmodel registration and evaluation procedure calls.


πŸ’– AUTHORS πŸ’–

Elad Efrat <elad@NetBSD.org>


source/version: Debian 🌸 date: December 4, 2011 🌸 page: SECMODEL(9) β€” and that's the whole iconic manual page, bestie. no cap. πŸ’…πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from