Re: [RFC PATCH v4 1/2] selinux: encapsulate policy state, refactor policy load

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Aug 5, 2020 at 11:52 AM Stephen Smalley
<stephen.smalley.work@xxxxxxxxx> wrote:
>
> Encapsulate the policy state in its own structure (struct
> selinux_policy) that is separately allocated but referenced from the
> selinux_ss structure.  The policy state includes the SID table
> (particularly the context structures), the policy database, and the
> mapping between the kernel classes/permissions and the policy values.
> Refactor the security server portion of the policy load logic to
> cleanly separate loading of the new structures from committing the new
> policy.  Unify the initial policy load and reload code paths as much
> as possible, avoiding duplicated code.  Make sure we are taking the
> policy read-lock prior to any dereferencing of the policy.  Move the
> copying of the policy capability booleans into the state structure
> outside of the policy write-lock because they are separate from the
> policy and are read outside of any policy lock; possibly they should
> be using at least READ_ONCE/WRITE_ONCE or smp_load_acquire/store_release.
> Restore the load mutex that was previously removed by
> commit 89abd0acf033 ("SELinux: drop load_mutex in security_load_policy")
> to make explicit the exclusion even though it is currently redundant
> with the fsi->mutex held by selinuxfs; this makes clear that we do
> not need to take the policy read-lock across sidtab_convert() and will
> be useful in the future for lockdep checking.
>
> These changes simplify the policy loading logic, reduce the size of
> the critical section while holding the policy write-lock, and should
> facilitate future changes to e.g. refactor the entire policy reload
> logic including the selinuxfs code to make the updating of the policy
> and the selinuxfs directory tree atomic and/or to convert the policy
> read-write lock to RCU.
>
> Signed-off-by: Stephen Smalley <stephen.smalley.work@xxxxxxxxx>
> ---
> v4 does not take the policy read-lock across sidtab_convert() and
> therefore does not require changing allocations by it to be atomic
> or dropping the cond_resched() call.  To make obvious that taking
> the policy read-lock is not necessary in security_load_policy(), restore
> the load mutex to security_load_policy() that was removed back in
> commit 89abd0acf033 ("SELinux: drop load_mutex in security_load_policy").
> However, since we have refactored security_load_policy() in this change
> to split out selinux_policy_commit(), we need to take the mutex in
> security_load_policy() and release it in selinux_policy_commit().

I'm not in love with the idea of splitting the lock/unlock across
different functions, more below in the relevant code section.

>  security/selinux/ss/services.c | 403 +++++++++++++++++----------------
>  security/selinux/ss/services.h |  11 +-
>  2 files changed, 220 insertions(+), 194 deletions(-)

...


> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 9e76a80db6e1..6dea93fac9e2 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -2115,11 +2126,73 @@ static void security_load_policycaps(struct selinux_state *state)
>                         pr_info("SELinux:  unknown policy capability %u\n",
>                                 i);
>         }
> +
> +       read_unlock(&state->ss->policy_rwlock);
>  }
>
>  static int security_preserve_bools(struct selinux_state *state,
>                                    struct policydb *newpolicydb);
>
> +static void selinux_policy_free(struct selinux_policy *policy)
> +{
> +       if (!policy)
> +               return;
> +
> +       policydb_destroy(&policy->policydb);
> +       sidtab_destroy(&policy->sidtab);
> +       kfree(policy->map.mapping);
> +       kfree(policy);
> +}
> +
> +static void selinux_policy_commit(struct selinux_state *state,
> +                               struct selinux_policy *newpolicy)
> +{
> +       struct selinux_policy *oldpolicy;
> +       u32 seqno;
> +
> +       lockdep_assert_held(&state->ss->load_mutex);
> +
> +       /* If switching between different policy types, log MLS status */
> +       oldpolicy = state->ss->policy;
> +       if (oldpolicy) {
> +               if (oldpolicy->policydb.mls_enabled && !newpolicy->policydb.mls_enabled)
> +                       pr_info("SELinux: Disabling MLS support...\n");
> +               else if (!oldpolicy->policydb.mls_enabled && newpolicy->policydb.mls_enabled)
> +                       pr_info("SELinux: Enabling MLS support...\n");
> +       }
> +
> +       /* Install the new policy. */
> +       write_lock_irq(&state->ss->policy_rwlock);
> +       state->ss->policy = newpolicy;
> +       seqno = ++state->ss->latest_granting;
> +       write_unlock_irq(&state->ss->policy_rwlock);
> +
> +       /* Load the policycaps from the new policy */
> +       security_load_policycaps(state);
> +
> +       if (!selinux_initialized(state)) {
> +               /*
> +                * After first policy load, the security server is
> +                * marked as initialized and ready to handle requests and
> +                * any objects created prior to policy load are then labeled.
> +                */
> +               selinux_mark_initialized(state);
> +               mutex_unlock(&state->ss->load_mutex);
> +               selinux_complete_init();
> +       } else
> +               mutex_unlock(&state->ss->load_mutex);
> +
> +       /* Free the old policy */
> +       selinux_policy_free(oldpolicy);
> +
> +       /* Flush external caches and notify userspace of policy load */
> +       avc_ss_reset(state->avc, seqno);
> +       selnl_notify_policyload(seqno);
> +       selinux_status_update_policyload(state, seqno);
> +       selinux_netlbl_cache_invalidate();
> +       selinux_xfrm_notify_policyload();
> +}

I can somewhat understand if you don't want to have all the old policy
cleanup, reset, and notify code in security_load_policy(), but I
really dislike that the mutex lock/unlock is split across the two
functions.

What if selinux_policy_commit() returned oldpolicy on success and we
created a new function, selinux_policy_retire() (name?), that would be
called from security_load_policy and could handle the cleanup, reset,
and notify code.  The mutex unlock could happen between the calls to
selinux_policy_commit() and selinux_policy_retire()?

I'm open to other ideas as well.

-- 
paul moore
www.paul-moore.com



[Index of Archives]     [Selinux Refpolicy]     [Linux SGX]     [Fedora Users]     [Fedora Desktop]     [Yosemite Photos]     [Yosemite Camping]     [Yosemite Campsites]     [KDE Users]     [Gnome Users]

  Powered by Linux