Creating abilities

Updated: April 19, 2023

Not only can servers check that their clients have the appropriate abilities, but they can create custom abilities.

This allows system services to define their own arbitrary abilities, and then securely and efficiently verify that a client possesses a required set of abilities. The kernel doesn't need any special knowledge of particular system services, and a client can be granted particular capabilities before the associated server has initialized them.

Allocating capabilities

The following functions allocate capabilities:

int procmgr_ability_lookup(const char * name);
int procmgr_ability_create(const char * name, unsigned flags);

A client can call procmgr_ability_lookup() to obtain a numeric ability identifier, which can then be used in a call to procmgr_ability() or to verify the abilities of a client (described below).

The parameter is a string that uniquely identifies the ability, and should consist of a service identifier followed by a capability identifier (e.g., fs-qnx6/some_devctl). Calling procmgr_ability_lookup() twice with the same string is guaranteed to return the same number. If the ability can't be found in the current list of abilities, the requested ability is added to the list. If the ability can't be added to the list, a negative errno value is returned, indicating the nature of the failure.

The server calls procmgr_ability_create(), which functions identically to procmgr_ability_lookup() but allows the server to use the flags parameter to additionally specify the privilege domains (PROCMGR_ADN_ROOT, PROCMGR_ADN_NONROOT) that will have the ability by default (i.e., if the ability is not specifically granted or restricted using procmgr_ability()). These default privilege domains can be set only once. If you make any further calls to procmgr_ability_create() for the same ability, the flags parameter is ignored (i.e., the call is equivalent to calling procmgr_ability_lookup()).

To create an ability, the server must possess the PROCMGR_AID_ABLE_CREATE ability.

Note: There's no requirement for a call to procmgr_ability_create() to precede calls to procmgr_ability_lookup(). This avoids forcing any specific ordering of process initialization, and means that processes don't need to hold on to root privileges until they can synchronize with the servers and get the ability identifiers that they need.

Verifying capabilities

The server can use the following functions to verify that a client has the required capabilities:

ConnectClientInfoAble()
This function is similar to ConnectClientInfoExt(), but accepts a list of capabilities and sets the _NTO_CI_UNABLE bit in the returned struct _client_info if the sending process doesn't possess all of the required capabilities:
struct _client_able {
    unsigned ability;
    unsigned flags;
    uint64_t range_lo;
    uint64_t range_hi;
};

int ConnectClientInfoAble( int scoid,
                           struct _client_info **info_pp,
                           const int flags,
                           struct _client_able abilities[],
                           const int nable);
  

Each of the required abilities must be from the static set of kernel abilities (PROCMGR_AID_*) or must have been previously created using procmgr_ability_create().

iofunc_client_info_able()
This function is similar to iofunc_client_info_ext(), but—like ConnectClientInfoAble()—takes an array of abilities to check:
int iofunc_client_info_able( resmgr_context_t * const ctp,
                             const int ioflag,
                             struct _client_info **info_pp,
                             const int flags,
                             struct _client_able abilities[],
                             const int nable);
  

The iofunc_check_access() function inspects the returned struct _client_info and rejects the request if the capability check failed.

Note: When you're finished with the struct _client_info structure, call iofunc_client_info_ext_free() to free it.

For example, here's how you could use iofunc_client_info_able() in your server process to check for an ability:

struct  _client_able ability;
struct _client_info * infop;
int ability_count;
int error;
int result;

/*  Determine whether or not the caller has this ability */

ability.ability = my_ability;
ability.flags = 0;
ability.range_hi = INT_MAX;
ability.range_lo = 0;
ability_count = 1;

error = iofunc_client_info_able(ctp, 0, &infop, 0, &ability, ability_count);
if((error != EOK) || (infop == NULL)) {
   /* An error occurred. */
   ...
}

/* The client has the requested ability if _NTO_CI_UNABLE isn't set
   in the info flags. */

result = !(infop->flags & _NTO_CI_UNABLE);

/* Free the _client_info structure. */

(void)iofunc_client_info_ext_free(&infop);