selinux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Stephen Smalley <sds@tycho.nsa.gov>
To: Ondrej Mosnacek <omosnace@redhat.com>
Cc: selinux@vger.kernel.org, Paul Moore <paul@paul-moore.com>,
	James Carter <jwcart2@tycho.nsa.gov>,
	Steve Lawrence <slawrence@tresys.com>,
	Petr Lautrbach <plautrba@redhat.com>
Subject: Re: [PATCH] selinux: support attributes in type transitions
Date: Mon, 13 May 2019 09:18:57 -0400	[thread overview]
Message-ID: <67f9a097-0552-6b76-4eea-6f9b52534a3d@tycho.nsa.gov> (raw)
In-Reply-To: <CAFqZXNsUBtek6YY2qnCpqfTSJxpyyDfdf_zh_+f4LN2K0-nF_Q@mail.gmail.com>

On 5/13/19 7:16 AM, Ondrej Mosnacek wrote:
> On Mon, May 6, 2019 at 11:02 PM Stephen Smalley <sds@tycho.nsa.gov> wrote:
>> On 5/6/19 9:12 AM, Ondrej Mosnacek wrote:
>>> The amount of filename transition rules used in Fedora has grown a lot
>>> due to many domains needing rules for correct /dev file labeling. This
>>> has in turn caused the binary policy to increase in size a lot, too.
>>>
>>> To mitigate this, start properly handling type attributes in type
>>> transitions so that userspace can avoid always expanding them and
>>> generate smaller policies. (Note: adding attribute support only for
>>> named transition rules would be enough, but this patch adds it to all
>>> type transition rules to keep better consistency.)
>>>
>>> As an illustration, this change will reduce Fedora 31 binary policy from
>>> ~8.4 MiB to only ~2.8 MiB (~250K type transition rules to ~28K). These
>>> numbers only take into account named file transition rules; the
>>> reduction from basic type transition rules is likely going to be much
>>> less radical.
>>>
>>> The fact whether the kernel supports this feature is signaled to
>>> userspace by an increment in policy version. With older policies the
>>> transition computation is handled as before to avoid performance
>>> regression.
>>
>> NB I haven't looked at the code itself yet.  However, my concerns with
>> this change conceptually are:
>>
>> 1) The AVC does not cache security_transition_sid() results and
>> therefore doing the attribute expansion at runtime versus policy
>> build-time is likely to have a more significant runtime performance
>> impact (for operations that involve security_transition_sid, e.g. file
>> creation, socket creation, msgsnd(2)) than the corresponding impact from
>> when we introduced attribute support for allow rules in the kernel
>> (commit 782ebb992ec20b5afdd5786ee8c2f1b58b631f24), which was at least
>> mitigated by caching.  This could perhaps be addressed by augmenting the
>> AVC to cache security_transition_sid() results as well, as has already
>> been done in the userspace AVC in libselinux (where the equivalent to
>> security_transition_sid is security_compute_create(3) and the AVC
>> provides an avc_compute_create(3) interface).  You note that you avoid a
>> performance regression for older policies by falling back to the old
>> logic, but that won't help with newer policies of course.
> 
> Yes, I'm probably underestimating the performance impact of the
> change... I'm trying right now to collect some statistics to help
> quantify the difference and try some optimization ideas.
> 
>>
>> 2) Even with caching of results, we have seen performance problems with
>> security_compute_av() having unacceptable latency when called on AVC
>> misses due to types with many attributes, requiring a number of changes
>> to the policy toolchain to allow better control over attribute expansion
>> and retention, which would presumably also be needed here.  This change
>> would further require retaining attributes used in type transitions in
>> addition to those used in allow rules, which could impact the
>> performance of security_compute_av() in addition to
>> security_transition_sid().
>>
>> 3) IIRC, the pattern we saw when we made the equivalent change for allow
>> rules was likewise an immediate drastic reduction in kernel memory usage
>> (as reported in the commit message) followed by a long period of
>> explosive allow rule growth in policy, ultimately bringing policy size
>> and kernel memory usage back up to its prior levels (or worse).
>> Subsequently there was significant work to shrink the policy through a
>> major rewrite of allow rules to use attributes whenever possible. This
>> in turn required the introduction of more attributes per type, which
>> then harmed security_compute_av() performance as above. We want to avoid
>> a similar cycle here.
> 
> Again, this is something that I will need to analyze via statistics
> and think it through...
> 
>>
>> 4) This change does not fix the limitation in the ability to support
>> more flexible name-based transitions (e.g. simple wildcarding, file
>> globs, or regex matching), which is another factor in the large number
>> of name-based transition rules in Fedora policy and would benefit a
>> wider set of use cases IMHO.
> 
> Yes, this feature remains relevant and no doubt would be very useful,
> however I'm not sure how much work that would require... Implementing
> even simplified regexes in the kernel sounds tricky... (or is there
> already some library that we could use? I didn't check). But maybe in
> the end it will be easier than going in the direction of this patch :)

Even basic prefix match would allow elimination of many redundant 
entries.  WRT glob or regex matching, there are some examples in 
lib/glob.c (glob), kernel/trace/* (simple regex), and 
security/apparmor/* (dfa).  That said, I don't think this will avoid the 
need for some form of this patch as well.

> 
>>
>> 5) It is unclear (to me) that Fedora truly needs to rely so heavily on
>> name-based type transitions for /dev node labeling, or to define the
>> transitions for so many domains, which ostensibly is the motivation for
>> this change.  Fedora didn't appear to need these rules prior to the use
>> of devtmpfs, and even with the use of devtmpfs, arguably these rules are
>> only needed for kernel_t and maybe unconfined_t, not all unconfined
>> domains. Name-based transitions were not designed to support general
>> purpose /dev node labeling; they were supposed to only be to handle edge
>> cases where userspace could not be modified to correctly label files and
>> per-file granularity was required.  Neither upstream refpolicy nor
>> Android employ large numbers of name-based transitions to my knowledge.
>> We don't want to encourage misuse of these rules or further growth in them.
> 
> I don't have a deep understanding of this, but I'm not sure if we can
> reliably enforce correct devtmpfs labeling in userspace (at least in
> Fedora). There shouldn't be too many services that need/may create
> files in devtmpfs, so they should probably be fixed, but now we have a
> chicken-and-egg problem: we can't remove the rules before all these
> cases are found and fixed, but as long as the rules are there, there
> is no push for the issues to be fixed...
> 
> I'll see if I can implement this without adding too much complexity
> and without killing performance. If not, then I'll probably just give
> up on this front...
> 
>>
>> That said, I'm willing to consider such a change, but I'd like to see
>> that the above issues are adequately explored before accepting it.
>>
>>>
>>> Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
>>> ---
>>>
>>> Very mildly tested by applying a proof-of-concept patch against
>>> libsepol [1], which disables type attribute expansion on src/target of
>>> named file transition rules, and running the following on Fedora Rawhide
>>> (from an unconfined root shell):
>>>
>>>       mknod /dev/ram0 b 252 16
>>>       ls -lZ /dev/ram0
>>>       rm -f /dev/ram0
>>>
>>> Without this patch, the corresponding transition rule (which happens to
>>> have an attribute specified as the source type) didn't apply and the
>>> file got label device_t (as expected). With this patch (+ the policy
>>> version fallback condition disabled), the rule applied as it should and
>>> the file got labeled correctly as fixed_disk_device_t.
>>>
>>> We don't have a proper corresponding userspace patch yet, but I'd like
>>> to have this patch out and get some feedback before moving on further.
>>>
>>> See RHBZ 1660142 [2] for original discussion leading to this patch.
>>>
>>> [1] https://github.com/WOnder93/selinux/commit/26283729657ec33b6743e929e1b5b40a54294043
>>> [2] https://bugzilla.redhat.com/show_bug.cgi?id=1660142
>>>
>>>    security/selinux/include/security.h |  33 +++---
>>>    security/selinux/ss/services.c      | 177 ++++++++++++++++++++--------
>>>    2 files changed, 147 insertions(+), 63 deletions(-)
>>>
>>> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
>>> index 111121281c47..aa6b5a689cb7 100644
>>> --- a/security/selinux/include/security.h
>>> +++ b/security/selinux/include/security.h
>>> @@ -22,28 +22,29 @@
>>>    #define SECCLASS_NULL                       0x0000 /* no class */
>>>
>>>    /* Identify specific policy version changes */
>>> -#define POLICYDB_VERSION_BASE                15
>>> -#define POLICYDB_VERSION_BOOL                16
>>> -#define POLICYDB_VERSION_IPV6                17
>>> -#define POLICYDB_VERSION_NLCLASS     18
>>> -#define POLICYDB_VERSION_VALIDATETRANS       19
>>> -#define POLICYDB_VERSION_MLS         19
>>> -#define POLICYDB_VERSION_AVTAB               20
>>> -#define POLICYDB_VERSION_RANGETRANS  21
>>> -#define POLICYDB_VERSION_POLCAP              22
>>> -#define POLICYDB_VERSION_PERMISSIVE  23
>>> -#define POLICYDB_VERSION_BOUNDARY    24
>>> -#define POLICYDB_VERSION_FILENAME_TRANS      25
>>> -#define POLICYDB_VERSION_ROLETRANS   26
>>> +#define POLICYDB_VERSION_BASE                        15
>>> +#define POLICYDB_VERSION_BOOL                        16
>>> +#define POLICYDB_VERSION_IPV6                        17
>>> +#define POLICYDB_VERSION_NLCLASS             18
>>> +#define POLICYDB_VERSION_VALIDATETRANS               19
>>> +#define POLICYDB_VERSION_MLS                 19
>>> +#define POLICYDB_VERSION_AVTAB                       20
>>> +#define POLICYDB_VERSION_RANGETRANS          21
>>> +#define POLICYDB_VERSION_POLCAP                      22
>>> +#define POLICYDB_VERSION_PERMISSIVE          23
>>> +#define POLICYDB_VERSION_BOUNDARY            24
>>> +#define POLICYDB_VERSION_FILENAME_TRANS              25
>>> +#define POLICYDB_VERSION_ROLETRANS           26
>>>    #define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS        27
>>> -#define POLICYDB_VERSION_DEFAULT_TYPE        28
>>> +#define POLICYDB_VERSION_DEFAULT_TYPE                28
>>>    #define POLICYDB_VERSION_CONSTRAINT_NAMES   29
>>> -#define POLICYDB_VERSION_XPERMS_IOCTL        30
>>> +#define POLICYDB_VERSION_XPERMS_IOCTL                30
>>>    #define POLICYDB_VERSION_INFINIBAND         31
>>> +#define POLICYDB_VERSION_TYPE_TRANS_ATTR     32
>>>
>>>    /* Range of policy versions we understand*/
>>>    #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
>>> -#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_INFINIBAND
>>> +#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_TYPE_TRANS_ATTR
>>>
>>>    /* Mask for just the mount related flags */
>>>    #define SE_MNTMASK  0x0f
>>> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
>>> index cc043bc8fd4c..1e8293201184 100644
>>> --- a/security/selinux/ss/services.c
>>> +++ b/security/selinux/ss/services.c
>>> @@ -1610,30 +1610,137 @@ out:
>>>        return -EACCES;
>>>    }
>>>
>>> -static void filename_compute_type(struct policydb *policydb,
>>> -                               struct context *newcontext,
>>> -                               u32 stype, u32 ttype, u16 tclass,
>>> -                               const char *objname)
>>> +static void compute_type_noattr(struct policydb *policydb,
>>> +                             struct context *newcontext,
>>> +                             u32 stype, u32 ttype,
>>> +                             u16 tclass, u32 specified,
>>> +                             const char *objname)
>>>    {
>>> -     struct filename_trans ft;
>>> -     struct filename_trans_datum *otype;
>>> +     struct avtab_key avkey;
>>> +     struct avtab_datum *avdatum = NULL;
>>> +     struct avtab_node *node;
>>>
>>>        /*
>>> -      * Most filename trans rules are going to live in specific directories
>>> -      * like /dev or /var/run.  This bitmap will quickly skip rule searches
>>> -      * if the ttype does not contain any rules.
>>> +      * if we have a objname this is a file trans check so check those
>>> +      * rules
>>>         */
>>> -     if (!ebitmap_get_bit(&policydb->filename_trans_ttypes, ttype))
>>> +     if (objname) {
>>> +             struct filename_trans ft;
>>> +             struct filename_trans_datum *otype;
>>> +
>>> +             /*
>>> +              * Most filename trans rules are going to live in specific
>>> +              * directories like /dev or /var/run.  This bitmap will quickly
>>> +              * skip rule searches if the ttype does not contain any rules.
>>> +              */
>>> +             if (ebitmap_get_bit(&policydb->filename_trans_ttypes, ttype)) {
>>> +                     ft.stype = stype;
>>> +                     ft.ttype = ttype;
>>> +                     ft.tclass = tclass;
>>> +                     ft.name = objname;
>>> +
>>> +                     otype = hashtab_search(policydb->filename_trans, &ft);
>>> +                     if (otype) {
>>> +                             newcontext->type = otype->otype;
>>> +                             return;
>>> +                     }
>>> +             }
>>> +     }
>>> +
>>> +     /* Look for a type transition/member/change rule. */
>>> +     avkey.source_type = stype;
>>> +     avkey.target_type = ttype;
>>> +     avkey.target_class = tclass;
>>> +     avkey.specified = specified;
>>> +     avdatum = avtab_search(&policydb->te_avtab, &avkey);
>>> +     if (avdatum) {
>>> +             newcontext->type = avdatum->u.data;
>>>                return;
>>> +     }
>>> +
>>> +     /* If no permanent rule, also check for enabled conditional rules */
>>> +     node = avtab_search_node(&policydb->te_cond_avtab, &avkey);
>>> +     for (; node; node = avtab_search_node_next(node, specified)) {
>>> +             if (node->key.specified & AVTAB_ENABLED) {
>>> +                     newcontext->type = node->datum.u.data;
>>> +                     return;
>>> +             }
>>> +     }
>>> +}
>>>
>>> -     ft.stype = stype;
>>> -     ft.ttype = ttype;
>>> -     ft.tclass = tclass;
>>> -     ft.name = objname;
>>> +static void compute_type(struct policydb *policydb, struct context *newcontext,
>>> +                      u32 stype, u32 ttype, u16 tclass, u32 specified,
>>> +                      const char *objname)
>>> +{
>>> +     struct avtab_key avkey;
>>> +     struct avtab_datum *avdatum = NULL;
>>> +     struct avtab_node *node;
>>> +     struct ebitmap *sattr, *tattr;
>>> +     struct ebitmap_node *snode, *tnode;
>>> +     unsigned int i, j;
>>> +
>>> +     sattr = &policydb->type_attr_map_array[stype - 1];
>>> +     tattr = &policydb->type_attr_map_array[ttype - 1];
>>> +
>>> +     if (objname) {
>>> +             struct filename_trans ft;
>>> +             struct filename_trans_datum *otype;
>>> +
>>> +             ebitmap_for_each_positive_bit(tattr, tnode, j) {
>>> +                     /*
>>> +                      * Most filename trans rules are going to live in
>>> +                      * specific directories like /dev or /var/run.  This
>>> +                      * bitmap will quickly skip rule searches if the ttype
>>> +                      * does not contain any rules.
>>> +                      */
>>> +                     if (!ebitmap_get_bit(&policydb->filename_trans_ttypes,
>>> +                                          j + 1))
>>> +                             continue;
>>>
>>> -     otype = hashtab_search(policydb->filename_trans, &ft);
>>> -     if (otype)
>>> -             newcontext->type = otype->otype;
>>> +                     ebitmap_for_each_positive_bit(sattr, snode, i) {
>>> +                             ft.stype = i + 1;
>>> +                             ft.ttype = j + 1;
>>> +                             ft.tclass = tclass;
>>> +                             ft.name = objname;
>>> +
>>> +                             otype = hashtab_search(policydb->filename_trans,
>>> +                                                    &ft);
>>> +                             if (otype) {
>>> +                                     newcontext->type = otype->otype;
>>> +                                     return;
>>> +                             }
>>> +                     }
>>> +             }
>>> +     }
>>> +
>>> +     /* Look for a type transition/member/change rule. */
>>> +     avkey.target_class = tclass;
>>> +     avkey.specified = specified;
>>> +     ebitmap_for_each_positive_bit(sattr, snode, i) {
>>> +             ebitmap_for_each_positive_bit(tattr, tnode, j) {
>>> +                     avkey.source_type = i + 1;
>>> +                     avkey.target_type = j + 1;
>>> +                     avdatum = avtab_search(&policydb->te_avtab, &avkey);
>>> +                     if (avdatum) {
>>> +                             newcontext->type = avdatum->u.data;
>>> +                             return;
>>> +                     }
>>> +
>>> +                     /*
>>> +                      * If no permanent rule, also check for enabled
>>> +                      * conditional rules
>>> +                      */
>>> +                     node = avtab_search_node(&policydb->te_cond_avtab,
>>> +                                              &avkey);
>>> +                     for (; node;
>>> +                          node = avtab_search_node_next(node, specified)) {
>>> +                             if (node->key.specified & AVTAB_ENABLED) {
>>> +                                     newcontext->type = node->datum.u.data;
>>> +                                     return;
>>> +                             }
>>> +                     }
>>> +             }
>>> +     }
>>>    }
>>>
>>>    static int security_compute_sid(struct selinux_state *state,
>>> @@ -1650,9 +1757,6 @@ static int security_compute_sid(struct selinux_state *state,
>>>        struct class_datum *cladatum = NULL;
>>>        struct context *scontext = NULL, *tcontext = NULL, newcontext;
>>>        struct role_trans *roletr = NULL;
>>> -     struct avtab_key avkey;
>>> -     struct avtab_datum *avdatum;
>>> -     struct avtab_node *node;
>>>        u16 tclass;
>>>        int rc = 0;
>>>        bool sock;
>>> @@ -1748,33 +1852,12 @@ static int security_compute_sid(struct selinux_state *state,
>>>                }
>>>        }
>>>
>>> -     /* Look for a type transition/member/change rule. */
>>> -     avkey.source_type = scontext->type;
>>> -     avkey.target_type = tcontext->type;
>>> -     avkey.target_class = tclass;
>>> -     avkey.specified = specified;
>>> -     avdatum = avtab_search(&policydb->te_avtab, &avkey);
>>> -
>>> -     /* If no permanent rule, also check for enabled conditional rules */
>>> -     if (!avdatum) {
>>> -             node = avtab_search_node(&policydb->te_cond_avtab, &avkey);
>>> -             for (; node; node = avtab_search_node_next(node, specified)) {
>>> -                     if (node->key.specified & AVTAB_ENABLED) {
>>> -                             avdatum = &node->datum;
>>> -                             break;
>>> -                     }
>>> -             }
>>> -     }
>>> -
>>> -     if (avdatum) {
>>> -             /* Use the type from the type transition/member/change rule. */
>>> -             newcontext.type = avdatum->u.data;
>>> -     }
>>> -
>>> -     /* if we have a objname this is a file trans check so check those rules */
>>> -     if (objname)
>>> -             filename_compute_type(policydb, &newcontext, scontext->type,
>>> -                                   tcontext->type, tclass, objname);
>>> +     if (policydb->policyvers < POLICYDB_VERSION_TYPE_TRANS_ATTR)
>>> +             compute_type_noattr(policydb, &newcontext, scontext->type,
>>> +                                 tcontext->type, tclass, specified, objname);
>>> +     else
>>> +             compute_type(policydb, &newcontext, scontext->type,
>>> +                          tcontext->type, tclass, specified, objname);
>>>
>>>        /* Check for class-specific changes. */
>>>        if (specified & AVTAB_TRANSITION) {
>>>
> 
> --
> Ondrej Mosnacek <omosnace at redhat dot com>
> Software Engineer, Security Technologies
> Red Hat, Inc.
> 


      reply	other threads:[~2019-05-13 13:26 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-06 13:12 [PATCH] selinux: support attributes in type transitions Ondrej Mosnacek
2019-05-06 20:59 ` Stephen Smalley
2019-05-13 11:16   ` Ondrej Mosnacek
2019-05-13 13:18     ` Stephen Smalley [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=67f9a097-0552-6b76-4eea-6f9b52534a3d@tycho.nsa.gov \
    --to=sds@tycho.nsa.gov \
    --cc=jwcart2@tycho.nsa.gov \
    --cc=omosnace@redhat.com \
    --cc=paul@paul-moore.com \
    --cc=plautrba@redhat.com \
    --cc=selinux@vger.kernel.org \
    --cc=slawrence@tresys.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).