This revision is part of a series. Click on the `Stack` tab below to see the context.
This series has also been squeezed into D47633 to provide an overall view.
Commit message:
TL;DR:
Now monitor setcred() calls, and reject or grant them according to the
new rules specification.
Drop monitoring setuid() and setgroups(). As previously explained in
the commit introducing the setcred() system call, MAC/do must know the
entire new credentials while the old ones are still available to be able
to approve or reject the requested changes. To this end, the chosen
approach was to introduce a new system call, setcred(), instead of
modifying existing ones to be able to participate in a "prepare than
commit"-like protocol.
******
The MAC framework typically calls several hooks of its registered
policies as part of the privilege checking/granting process. Each
system call calls some dedicated hook early, to which it usually passes
the same arguments it received, whose goal is to forcefulibly deny access to
to the functionality when needed (i.e., a single deny by any policy
globally denies the access). Then, the system call usually calls
priv_check() or priv_check_cred() an unspecified number of times, each
of which may trigger calls to two generic MAC hooks. The first such
call is to mac_priv_check(), and always happens. Its role is to deny
access early and forcefullyibly, as for system calls' dedicated early hooks.as can be done also in system calls'
The seconddedicated early hooks (with different reach, mac_priv_grant(), is called only if the priv_check*() andhowever). The second,
mac_priv_grant(), is called only if the priv_check*() and
prison_priv_check() generic code doesn't handle the request by itself,
i.e., doesn't explicitly grant access (to the super user, or to all
users for a few specific privileges). It allows any single policy to
grant the requested access (regardless of whether the other policies do
so or not).
MAC/do currently only has an effect on processes spawned from the '/usr/bin/mdo'
'/usr/bin/mdo' executable. It implements all setcred() hooks, called via
via mac_cred_setcred_enter(), mac_cred_check_setcred() and
mac_cred_setcred_exit(). In the first one, implemented in
mac_do_setcred_enter(), it checks if MAC/do has to apply to the current
process, allocates (or re-uses) per-thread data to be later used by the
other hooks (those of setcred() and the mac_priv_grant() one, called by
priv_check*()) and fills them with the current context (the rules to
apply). This is both because memory allocations cannot be performed
while holding the process lock and to ensure that all hooks called by
a single setcred() see the same rules to apply (not doing this would be
a security hazard as rules are concurrently changed by the
administrator, as explained in more details below). In the second one
(implemented by mac_do_check_setcred()), it checks for forbidden orit stores in MAC/do's
mandatory supplementary groups according to applicable rulesper-thread data the new credentials. Indeed, and ifthe next MAC/do's hook
present denies access (which takes precedence over any other MAC policyimplementation to be called, mac_do_priv_grant() (implementing the
that would want to grant the request). If it doesn't deny access, itmac_priv_grant() hook) must have knowledge of the new credentials that
stores in MAC/do's per-thread data the new credentials. Indeedsetcred() wants to install in order to validate them (or not), which the
next MAC/do's hook implementation to be called, mac_do_priv_grant()MAC framework can't provide as the priv_check*() API only passes the
(implementing the mac_priv_grant() hook) must have knowledge of the newcurrent credentials and a specific privilege number to the
credentials that setcred() wants to install in order to validate themmac_priv_check() and mac_priv_grant() hooks. By contrast, the very
(or not), which the MAC framework can't provide as the priv_check*() APIpoint of MAC/do is to grant the privilege of changing credentials not
only passes the current credentials and a specific privilege number tobased on the current ones but also on the seeked-for ones.
The MAC framework's constraints that mac_priv_grant() hooks are called
the mac_priv_check()without context and mac_priv_grant() hooks. By contrast, the verythat MAC modules must compose (each module may
point of MAC/do is to grant or deny the privilege of changingimplement any of the available hooks, and in particular those of
setcredentials not only based on the current ones but also on the()) impose some aspects of MAC/do's design. Because MAC/do's
seeked-for ones.
This split of checks between two MAC hooks is a consequence of therules are tied to jails, accessing the current rules requires holding
necessity to preserve MAC policies composition (i.e., using several ofthe corresponding jail's lock. As other policies might try to grab the
them at once in a compatible, predictable waysame jail's lock in the same hooks, as foreseen whenit is not possible to keep the
establishing the MAC framework itself). Both impose furtherrules' jail's lock between mac_do_setcred_enter() and
restrictions on MAC/do's design. Because MAC/do'smac_do_priv_grant() to ensure that the rules are tied tostill alive. We have
jailsthus augmented 'struct rules' with a reference count, accessing the current rules requires holding the correspondingand its lifecyle
jail's lock. As other policies might try to grab the sameis now decoupled from being referenced or not by a jail's lock,. As a thread
it is not possible to keep the rules' jail's lock betweenenters mac_cred_setcred_enter(), it grabs a hold on the current rules
mac_do_setcred_eand keeps a pointer() and mac_do_priv_grant() to ensure consistency of to them in the per-thread data. In its
mac_do_setcred_exit(), MAC/do just "frees" the checks.per-thread data, But the latter is necessary as rules can concurrentlyin
change: If newly installparticular by dropping the referenced rules start to deny some specific changes,(we wrote "frees" within
and some thread is pasguillemets, as in fact the mac_cred_check_setcred() hook but before theper-thread structure is reused, and only
freed when a thread exits or the module is unloaded).
Additionally, ensuring that all hooks have a consistent view of the
rules to apply might become crucial if we augment MAC/do with forceful
access denial policies in the future (i.e., policies that forcibly
disable access regardless of other MAC policies wanting to grant that
access). Indeed, without the above-mentioned design, if newly installed
rules start to forcibly deny some specific transitions, and some thread
is past the mac_cred_check_setcred() hook but before the
mac_priv_grant() one, the latter may grant some privileges that should
have been rejected first by the former (depending on the content of
user-supplied rules). To this end, we have augmented 'struct rules'
A previous version of this change used to implement access denial
with a reference count,mandated by the '!' and its lifecyle is now decoupled from being'-' GID flags in mac_do_check_setcred() with the
referenced or not by a jail. As a thread enters setcred_enter(), itgoal to have this rejection prevail over potential other MAC modules
grabs a hold onauthorizing the current rules and keeps a pointer to them in thetransition. However, this approach had two drawbacks.
per-thread dataFirst, ensuring that all hooks have a consistent view of rulesit was incompatible both conceptually and in the current
to apply. In its mac_do_setcred_exit(), MAC/do just "frees" theimplementation with multiple rules being treated as an inclusive
per-thread datadisjunction, in particular by droppwhere any single rule granting the referenced rules (weaccess is enough for MAC/do
wrote "frees" within guillemets, as in fact the per-thread structure isto grant access. Explicit denial requested by one matching rule could
reusedprevent another rule from granting access. The implementation could
have been fixed, but the conflation of rules being considered as
disjoint for explicit granting but conjunct for forced denial would have
remained. Second, MAC/do applies only to processes spawned from
a particular executable, and imposing system-wide restrictions on only
these processes is conceptually strange and probably not very useful.
In the end, we moved the implementation of explicit access denial into
mac_do_priv_grant(), and only freed when a thread exits or the module is unloaded)along with the interpretation of other target
clauses.
The separate definition of 'struct mac_do_data_header' may seem odd, as
it is only used in 'struct mac_do_setcred_data'. It is a remnant of an
earlier version that was not using setcred(), but rather implemented
hooks for setuid() and setgroups(). We however kept it, as it clearly
separates the machinery to pass data from dedicated system call hooks to
priv_grant() from the actual data that MAC/do needs to monitor a call to
setcred() specifically. It may be useful in the future if we evolve
MAC/do to also grant privileges through other system calls (each seen as
a complete credentials transition on its own).
The target supplementary groups are checked with merge-like algorithms
leveraging the fact that all supplementary groups in credentials
('struct ucred') and in each rule ('struct rule') are sorted, avoiding
to start a binary search for each considered GID which is asymptotically
more costly. All access granting/denial is thus at most linear and in
at most the sum of the number of requested groups, currently held ones
and those contained in the rule, per applicable rule. This should be
enough in all practical cases. There is however still room for more
optimizations, without or with changes in rules' data structures, if the
need ever arises.