diff mbox series

[5/5] selinux: add prefix/suffix matching support to filename type transitions

Message ID 20230531112927.1957093-6-juraj@jurajmarcin.com (mailing list archive)
State Changes Requested
Delegated to: Paul Moore
Headers show
Series selinux: add prefix/suffix matching to filename type transitions | expand

Commit Message

Juraj Marcin May 31, 2023, 11:29 a.m. UTC
Currently, filename type transitions support only exact name matching.
However, in practice, the names contain variable parts. This leads to
many duplicated rules in the policy that differ only in the part of the
name, or it is even impossible to cover all possible combinations.

This patch extends the filename type transitions structures to include
new types of filename transitions - prefix and suffix filename
transitions. It also implements the reading and writing of those rules
in the kernel binary policy format together with increasing its version.
Last, it updates the function responsible for determining the new
context to also include the prefix and suffix filename transitions in
the process. It does that by first checking for the exact match, then
the longest matching prefix and then the longest matching suffix, in
that order. That way the exact match rules have precedence before new
rules, to ensure compatibility with older policies.

Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
---
 security/selinux/include/security.h |  3 +-
 security/selinux/ss/avtab.c         | 53 +++++++++++++++++++--
 security/selinux/ss/avtab.h         |  2 +
 security/selinux/ss/avtab_test.c    | 16 +++++++
 security/selinux/ss/policydb.c      |  5 ++
 security/selinux/ss/services.c      | 71 ++++++++++++++++++++++++-----
 6 files changed, 133 insertions(+), 17 deletions(-)

Comments

Stephen Smalley July 17, 2023, 6:33 p.m. UTC | #1
On Wed, May 31, 2023 at 7:32 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
>
> Currently, filename type transitions support only exact name matching.
> However, in practice, the names contain variable parts. This leads to
> many duplicated rules in the policy that differ only in the part of the
> name, or it is even impossible to cover all possible combinations.
>
> This patch extends the filename type transitions structures to include
> new types of filename transitions - prefix and suffix filename
> transitions. It also implements the reading and writing of those rules
> in the kernel binary policy format together with increasing its version.
> Last, it updates the function responsible for determining the new
> context to also include the prefix and suffix filename transitions in
> the process. It does that by first checking for the exact match, then
> the longest matching prefix and then the longest matching suffix, in
> that order. That way the exact match rules have precedence before new
> rules, to ensure compatibility with older policies.
>
> Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> ---
>  security/selinux/include/security.h |  3 +-
>  security/selinux/ss/avtab.c         | 53 +++++++++++++++++++--
>  security/selinux/ss/avtab.h         |  2 +
>  security/selinux/ss/avtab_test.c    | 16 +++++++
>  security/selinux/ss/policydb.c      |  5 ++
>  security/selinux/ss/services.c      | 71 ++++++++++++++++++++++++-----
>  6 files changed, 133 insertions(+), 17 deletions(-)
>

> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 131647e7ec68..2faf92bf12da 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -1661,6 +1661,60 @@ static int compute_sid_handle_invalid_context(
>         return -EACCES;
>  }
>
> +static int security_compute_type_trans_otype(struct avtab_trans *trans,
> +                                            const char *name, u32 *res_type)
> +{
> +       u32 *otype;
> +       size_t len;
> +       char *namedup = NULL;
> +       size_t i;
> +
> +       /*
> +        * use default otype if not empty and then try to find more specific
> +        * rule using name
> +        */
> +       if (trans->otype)
> +               *res_type = trans->otype;
> +       if (!name)
> +               return 0;
> +
> +       /* try to find full name */
> +       otype = symtab_search(&trans->name_trans, name);
> +       if (otype) {
> +               *res_type = *otype;
> +               return 0;
> +       }
> +
> +       /* copy name for shortening */
> +       len = strlen(name);
> +       namedup = kmemdup(name, len + 1, GFP_KERNEL);

This is called in various contexts that cannot sleep; hence, the
allocation must be GFP_ATOMIC here. With the proper config options
(CONFIG_DEBUG_ATOMIC_SLEEP=y) you would have seen repeated warnings
about this in the kernel messages, ala:

[  219.944942] BUG: sleeping function called from invalid context at
include/linux/sched/mm.h:306
[  219.944951] in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid:
1, name: systemd
[  219.944956] preempt_count: 0, expected: 0
[  219.944959] RCU nest depth: 1, expected: 0
[  219.944963] INFO: lockdep is turned off.
[  219.944967] CPU: 0 PID: 1 Comm: systemd Tainted: G    B   W
 6.5.0-rc1+ #32
[  219.944979] Call Trace:
[  219.944982]  <TASK>
[  219.944986]  dump_stack_lvl+0x75/0x90
[  219.944997]  __might_resched+0x1e1/0x310
[  219.945008]  ? security_compute_sid.part.0+0x9d8/0xe50
[  219.945017]  __kmem_cache_alloc_node+0x343/0x380
[  219.945026]  ? security_compute_sid.part.0+0x9d8/0xe50
[  219.945033]  ? rcu_is_watching+0x23/0x50
[  219.945046]  ? security_compute_sid.part.0+0x9d8/0xe50
[  219.945054]  __kmalloc_node_track_caller+0x52/0x160
[  219.945068]  kmemdup+0x22/0x50
[  219.945078]  security_compute_sid.part.0+0x9d8/0xe50
[  219.945093]  ? __pfx_security_compute_sid.part.0+0x10/0x10
[  219.945101]  ? rcu_is_watching+0x23/0x50
[  219.945110]  ? lock_release+0xa0/0x380
[  219.945116]  ? avc_has_perm_noaudit+0xb4/0x250
[  219.945127]  ? __pfx_lock_release+0x10/0x10
[  219.945134]  ? rcu_is_watching+0x23/0x50
[  219.945143]  ? lock_acquire+0xb5/0x390
[  219.945148]  ? __filename_parentat+0x282/0x350
[  219.945160]  ? avc_has_perm_noaudit+0xcc/0x250
[  219.945178]  security_transition_sid+0x63/0xa0
[  219.945191]  may_create+0x16a/0x1c0
[  219.945201]  ? __pfx_may_create+0x10/0x10
[  219.945207]  ? selinux_inode_permission+0x1c6/0x290
[  219.945216]  ? __pfx_selinux_inode_permission+0x10/0x10
[  219.945226]  ? kernfs_iop_permission+0x84/0xa0
[  219.945238]  security_inode_mkdir+0x61/0x80
[  219.945251]  vfs_mkdir+0x226/0x380
[  219.945262]  do_mkdirat+0x1a8/0x1d0
[  219.945272]  ? __pfx_do_mkdirat+0x10/0x10
[  219.945280]  ? getname_flags.part.0+0xc6/0x250
[  219.945290]  __x64_sys_mkdir+0x78/0xa0
[  219.945300]  do_syscall_64+0x3c/0x90
[  219.945308]  entry_SYSCALL_64_after_hwframe+0x6e/0xd8
[  219.945317] RIP: 0033:0x7ff080322d6b
[  219.945337] Code: 8b 05 a1 30 0d 00 bb ff ff ff ff 64 c7 00 16 00
00 00 e9 62 ff ff ff e8 e3 1b 02 00 0f 1f 00 f3 0f 1e fa b8 53 00 00
00 0f 05 <48> 3d 00 f0 ff ff 77 05 c3 0f 1f 40 00 48 8b 15 69 30 0d 00
f7 d8
[  219.945343] RSP: 002b:00007ffe3fbd6cd8 EFLAGS: 00000246 ORIG_RAX:
0000000000000053
[  219.945350] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007ff080322d6b
[  219.945355] RDX: 0000000000000000 RSI: 00000000000001ed RDI: 000055a4731c2a10
[  219.945360] RBP: 00007ffe3fbd6d10 R08: 00000000ffffff9c R09: 00007ffe3fbd6b60
[  219.945364] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ff0806af143
[  219.945368] R13: 000055a47311ce20 R14: 0000000000000001 R15: 00000000000003a0
[  219.945382]  </TASK>

> +       if (!namedup)
> +               return -ENOMEM;
> +
> +       /* try to find possible prefixes of name starting from the longest */
> +       for (i = len; i > 0; i--) {
> +               namedup[i] = '\0';
> +               otype = symtab_search(&trans->prefix_trans, namedup);
> +               if (otype) {
> +                       kfree(namedup);
> +                       *res_type = *otype;
> +                       return 0;
> +               }
> +       }
> +       kfree(namedup);
> +
> +       /*try to find possible suffixes of name starting from the longest */
> +       for (i = 0; i < len; i++) {
> +               otype = symtab_search(&trans->suffix_trans, &name[i]);
> +               if (otype) {
> +                       *res_type = *otype;
> +                       return 0;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
>  static int security_compute_sid(u32 ssid,
>                                 u32 tsid,
>                                 u16 orig_tclass,
> @@ -1802,18 +1856,11 @@ static int security_compute_sid(u32 ssid,
>         if (avdatum) {
>                 /* Use the type from the type transition/member/change rule. */
>                 if (avkey.specified & AVTAB_TRANSITION) {
> -                       /*
> -                        * use default otype if not empty and then to try to
> -                        * find more specific rule using objname
> -                        */
> -                       if (avdatum->u.trans->otype)
> -                               newcontext.type = avdatum->u.trans->otype;
> -                       if (objname) {
> -                               otype = symtab_search(&avdatum->u.trans->name_trans,
> -                                                     objname);
> -                               if (otype)
> -                                       newcontext.type = *otype;
> -                       }
> +                       rc = security_compute_type_trans_otype(avdatum->u.trans,
> +                                                              objname,
> +                                                              &newcontext.type);
> +                       if (rc)
> +                               goto out_unlock;
>                 } else {
>                         newcontext.type = avdatum->u.data;
>                 }
> --
> 2.40.0
>
Stephen Smalley July 17, 2023, 6:51 p.m. UTC | #2
On Mon, Jul 17, 2023 at 2:33 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Wed, May 31, 2023 at 7:32 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
> >
> > Currently, filename type transitions support only exact name matching.
> > However, in practice, the names contain variable parts. This leads to
> > many duplicated rules in the policy that differ only in the part of the
> > name, or it is even impossible to cover all possible combinations.
> >
> > This patch extends the filename type transitions structures to include
> > new types of filename transitions - prefix and suffix filename
> > transitions. It also implements the reading and writing of those rules
> > in the kernel binary policy format together with increasing its version.
> > Last, it updates the function responsible for determining the new
> > context to also include the prefix and suffix filename transitions in
> > the process. It does that by first checking for the exact match, then
> > the longest matching prefix and then the longest matching suffix, in
> > that order. That way the exact match rules have precedence before new
> > rules, to ensure compatibility with older policies.
> >
> > Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> > Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> > ---
> >  security/selinux/include/security.h |  3 +-
> >  security/selinux/ss/avtab.c         | 53 +++++++++++++++++++--
> >  security/selinux/ss/avtab.h         |  2 +
> >  security/selinux/ss/avtab_test.c    | 16 +++++++
> >  security/selinux/ss/policydb.c      |  5 ++
> >  security/selinux/ss/services.c      | 71 ++++++++++++++++++++++++-----
> >  6 files changed, 133 insertions(+), 17 deletions(-)
> >
>
> > diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> > index 131647e7ec68..2faf92bf12da 100644
> > --- a/security/selinux/ss/services.c
> > +++ b/security/selinux/ss/services.c
> > @@ -1661,6 +1661,60 @@ static int compute_sid_handle_invalid_context(
> >         return -EACCES;
> >  }
> >
> > +static int security_compute_type_trans_otype(struct avtab_trans *trans,
> > +                                            const char *name, u32 *res_type)
> > +{
> > +       u32 *otype;
> > +       size_t len;
> > +       char *namedup = NULL;
> > +       size_t i;
> > +
> > +       /*
> > +        * use default otype if not empty and then try to find more specific
> > +        * rule using name
> > +        */
> > +       if (trans->otype)
> > +               *res_type = trans->otype;
> > +       if (!name)
> > +               return 0;
> > +
> > +       /* try to find full name */
> > +       otype = symtab_search(&trans->name_trans, name);
> > +       if (otype) {
> > +               *res_type = *otype;
> > +               return 0;
> > +       }
> > +
> > +       /* copy name for shortening */
> > +       len = strlen(name);
> > +       namedup = kmemdup(name, len + 1, GFP_KERNEL);
>
> This is called in various contexts that cannot sleep; hence, the
> allocation must be GFP_ATOMIC here. With the proper config options
> (CONFIG_DEBUG_ATOMIC_SLEEP=y) you would have seen repeated warnings
> about this in the kernel messages, ala:
>
> [  219.944942] BUG: sleeping function called from invalid context at
> include/linux/sched/mm.h:306
> [  219.944951] in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid:
> 1, name: systemd
> [  219.944956] preempt_count: 0, expected: 0
> [  219.944959] RCU nest depth: 1, expected: 0
> [  219.944963] INFO: lockdep is turned off.
> [  219.944967] CPU: 0 PID: 1 Comm: systemd Tainted: G    B   W
>  6.5.0-rc1+ #32
> [  219.944979] Call Trace:
> [  219.944982]  <TASK>
> [  219.944986]  dump_stack_lvl+0x75/0x90
> [  219.944997]  __might_resched+0x1e1/0x310
> [  219.945008]  ? security_compute_sid.part.0+0x9d8/0xe50
> [  219.945017]  __kmem_cache_alloc_node+0x343/0x380
> [  219.945026]  ? security_compute_sid.part.0+0x9d8/0xe50
> [  219.945033]  ? rcu_is_watching+0x23/0x50
> [  219.945046]  ? security_compute_sid.part.0+0x9d8/0xe50
> [  219.945054]  __kmalloc_node_track_caller+0x52/0x160
> [  219.945068]  kmemdup+0x22/0x50
> [  219.945078]  security_compute_sid.part.0+0x9d8/0xe50
> [  219.945093]  ? __pfx_security_compute_sid.part.0+0x10/0x10
> [  219.945101]  ? rcu_is_watching+0x23/0x50
> [  219.945110]  ? lock_release+0xa0/0x380
> [  219.945116]  ? avc_has_perm_noaudit+0xb4/0x250
> [  219.945127]  ? __pfx_lock_release+0x10/0x10
> [  219.945134]  ? rcu_is_watching+0x23/0x50
> [  219.945143]  ? lock_acquire+0xb5/0x390
> [  219.945148]  ? __filename_parentat+0x282/0x350
> [  219.945160]  ? avc_has_perm_noaudit+0xcc/0x250
> [  219.945178]  security_transition_sid+0x63/0xa0
> [  219.945191]  may_create+0x16a/0x1c0
> [  219.945201]  ? __pfx_may_create+0x10/0x10
> [  219.945207]  ? selinux_inode_permission+0x1c6/0x290
> [  219.945216]  ? __pfx_selinux_inode_permission+0x10/0x10
> [  219.945226]  ? kernfs_iop_permission+0x84/0xa0
> [  219.945238]  security_inode_mkdir+0x61/0x80
> [  219.945251]  vfs_mkdir+0x226/0x380
> [  219.945262]  do_mkdirat+0x1a8/0x1d0
> [  219.945272]  ? __pfx_do_mkdirat+0x10/0x10
> [  219.945280]  ? getname_flags.part.0+0xc6/0x250
> [  219.945290]  __x64_sys_mkdir+0x78/0xa0
> [  219.945300]  do_syscall_64+0x3c/0x90
> [  219.945308]  entry_SYSCALL_64_after_hwframe+0x6e/0xd8
> [  219.945317] RIP: 0033:0x7ff080322d6b
> [  219.945337] Code: 8b 05 a1 30 0d 00 bb ff ff ff ff 64 c7 00 16 00
> 00 00 e9 62 ff ff ff e8 e3 1b 02 00 0f 1f 00 f3 0f 1e fa b8 53 00 00
> 00 0f 05 <48> 3d 00 f0 ff ff 77 05 c3 0f 1f 40 00 48 8b 15 69 30 0d 00
> f7 d8
> [  219.945343] RSP: 002b:00007ffe3fbd6cd8 EFLAGS: 00000246 ORIG_RAX:
> 0000000000000053
> [  219.945350] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007ff080322d6b
> [  219.945355] RDX: 0000000000000000 RSI: 00000000000001ed RDI: 000055a4731c2a10
> [  219.945360] RBP: 00007ffe3fbd6d10 R08: 00000000ffffff9c R09: 00007ffe3fbd6b60
> [  219.945364] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ff0806af143
> [  219.945368] R13: 000055a47311ce20 R14: 0000000000000001 R15: 00000000000003a0
> [  219.945382]  </TASK>

Also recommend enabling CONFIG_DEBUG_KMEMLEAK and
CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN - seeing kernel: kmemleak: 2634 new
suspected memory leaks (see /sys/kernel/debug/kmemleak) in kernel
messages and a lot of these warnings in /sys/kernel/debug/kmemleak:

unreferenced object 0xffff8881b05d6ea8 (size 8):
  comm "systemd", pid 1, jiffies 4294785029 (age 2050.052s)
  hex dump (first 8 bytes):
    6d 64 33 00 cc cc cc cc                          md3.....
  backtrace:
    [<ffffffff9f54d692>] __kmalloc_node_track_caller+0x52/0x160
    [<ffffffff9f5388d2>] kmemdup+0x22/0x50
    [<ffffffff9fac9702>] filenametr_tab_insert+0x332/0x650
    [<ffffffff9fac5634>] hashtab_map+0x84/0xc0
    [<ffffffff9fac8adb>] avtab_filename_trans_write+0x2fb/0x3a0
    [<ffffffff9fad3838>] policydb_write+0x508/0x1220
    [<ffffffff9fae0a10>] security_read_state_kernel+0xe0/0x1c0
    [<ffffffff9fae7343>] selinux_ima_measure_state_locked+0x643/0x7b0
    [<ffffffff9fadad35>] selinux_policy_commit+0x315/0x470
    [<ffffffff9fac125d>] sel_write_load+0xd6d/0xfa0
    [<ffffffff9f69560e>] vfs_write+0x18e/0x720
    [<ffffffff9f6960e7>] ksys_write+0xb7/0x140
    [<ffffffffa07dc29c>] do_syscall_64+0x3c/0x90
    [<ffffffffa0a000aa>] entry_SYSCALL_64_after_hwframe+0x6e/0xd8
diff mbox series

Patch

diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index c483680fe22e..0cf0767d061b 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -47,10 +47,11 @@ 
 #define POLICYDB_VERSION_GLBLUB		32
 #define POLICYDB_VERSION_COMP_FTRANS	33 /* compressed filename transitions */
 #define POLICYDB_VERSION_AVTAB_FTRANS	34 /* filename transitions moved to avtab */
+#define POLICYDB_VERSION_PREFIX_SUFFIX	35 /* prefix/suffix support for filename transitions */
 
 /* Range of policy versions we understand*/
 #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
-#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_AVTAB_FTRANS
+#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_PREFIX_SUFFIX
 
 /* Mask for just the mount related flags */
 #define SE_MNTMASK	0x0f
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index db7123670ef8..f58707930189 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -301,6 +301,12 @@  static void avtab_trans_destroy(struct avtab_trans *trans)
 {
 	hashtab_map(&trans->name_trans.table, avtab_trans_destroy_helper, NULL);
 	hashtab_destroy(&trans->name_trans.table);
+	hashtab_map(&trans->prefix_trans.table, avtab_trans_destroy_helper,
+		    NULL);
+	hashtab_destroy(&trans->prefix_trans.table);
+	hashtab_map(&trans->suffix_trans.table, avtab_trans_destroy_helper,
+		    NULL);
+	hashtab_destroy(&trans->suffix_trans.table);
 }
 
 void avtab_destroy(struct avtab *h)
@@ -517,6 +523,14 @@  static int avtab_trans_read(void *fp, struct policydb *pol,
 	if (rc)
 		goto bad;
 
+	if (pol->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+		rc = avtab_trans_read_name_trans(pol, &trans->prefix_trans, fp);
+		if (rc)
+			goto bad;
+		rc = avtab_trans_read_name_trans(pol, &trans->suffix_trans, fp);
+		if (rc)
+			goto bad;
+	}
 	return 0;
 
 bad:
@@ -706,9 +720,14 @@  int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol,
 		 * also each transition entry must meet at least one condition
 		 * to be considered non-empty:
 		 *  - set (non-zero) otype
-		 *  - non-empty filename transitions table
+		 *  - non-empty full name transitions table
+		 *  - non-empty prefix name transitions table
+		 *  - non-empty suffix name transitions table
 		 */
-		if (!otype && !datum.u.trans->name_trans.table.nel) {
+		if (!otype &&
+		    !datum.u.trans->name_trans.table.nel &&
+		    !datum.u.trans->prefix_trans.table.nel &&
+		    !datum.u.trans->suffix_trans.table.nel) {
 			pr_err("SELinux: avtab: empty transition\n");
 			avtab_trans_destroy(&trans);
 			return -EINVAL;
@@ -809,18 +828,44 @@  static int avtab_trans_write(struct policydb *p, struct avtab_trans *cur,
 	__le32 buf32[2];
 
 	if (p->policyvers >= POLICYDB_VERSION_AVTAB_FTRANS) {
-		/* write otype and number of filename transitions */
+		/* write otype and number of name transitions */
 		buf32[0] = cpu_to_le32(cur->otype);
 		buf32[1] = cpu_to_le32(cur->name_trans.table.nel);
 		rc = put_entry(buf32, sizeof(u32), 2, fp);
 		if (rc)
 			return rc;
 
-		/* write filename transitions */
+		/* write name transitions */
 		rc = hashtab_map(&cur->name_trans.table,
 				 avtab_trans_write_helper, fp);
 		if (rc)
 			return rc;
+
+		if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+			/* write number of prefix transitions */
+			buf32[0] = cpu_to_le32(cur->prefix_trans.table.nel);
+			rc = put_entry(buf32, sizeof(u32), 1, fp);
+			if (rc)
+				return rc;
+
+			/* write prefix transitions */
+			rc = hashtab_map(&cur->prefix_trans.table,
+					 avtab_trans_write_helper, fp);
+			if (rc)
+				return rc;
+
+			/* write number of suffix transitions */
+			buf32[0] = cpu_to_le32(cur->suffix_trans.table.nel);
+			rc = put_entry(buf32, sizeof(u32), 1, fp);
+			if (rc)
+				return rc;
+
+			/* write suffix transitions */
+			rc = hashtab_map(&cur->suffix_trans.table,
+					 avtab_trans_write_helper, fp);
+			if (rc)
+				return rc;
+		}
 	} else if (cur->otype) {
 		buf32[0] = cpu_to_le32(cur->otype);
 		rc = put_entry(buf32, sizeof(u32), 1, fp);
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
index 162ef1be85e7..929e322715d1 100644
--- a/security/selinux/ss/avtab.h
+++ b/security/selinux/ss/avtab.h
@@ -51,6 +51,8 @@  struct avtab_key {
 struct avtab_trans {
 	u32 otype;		/* default resulting type of the new object */
 	struct symtab name_trans;	/* filename transitions */
+	struct symtab prefix_trans;	/* prefix filename transitions */
+	struct symtab suffix_trans;	/* prefix filename transitions */
 };
 
 /*
diff --git a/security/selinux/ss/avtab_test.c b/security/selinux/ss/avtab_test.c
index daa8e4cfaeb2..7e63204629fd 100644
--- a/security/selinux/ss/avtab_test.c
+++ b/security/selinux/ss/avtab_test.c
@@ -124,6 +124,8 @@  static void filename_trans_read__simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file1");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
@@ -134,6 +136,8 @@  static void filename_trans_read__simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file2");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
@@ -213,6 +217,8 @@  static void filename_trans_read__comp_simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file1");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
@@ -223,6 +229,8 @@  static void filename_trans_read__comp_simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file2");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
@@ -493,12 +501,16 @@  static void read__pre_avtab_ftrans(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 45, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	key = (struct avtab_key){46, 47, 48, AVTAB_TRANSITION};
 	node = avtab_search(&p.te_avtab, &key);
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 49, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 0, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	avtab_destroy(&p.te_avtab);
 }
@@ -555,6 +567,8 @@  static void read__simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 41, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file1");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
@@ -565,6 +579,8 @@  static void read__simple(struct kunit *test)
 	KUNIT_ASSERT_NOT_NULL(test, node);
 	KUNIT_EXPECT_EQ(test, 40, node->u.trans->otype);
 	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->prefix_trans.table.nel);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->suffix_trans.table.nel);
 
 	otype = symtab_search(&node->u.trans->name_trans, "file2");
 	KUNIT_ASSERT_NOT_NULL(test, otype);
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 96b8d5bd8e4e..db759ec3e1ce 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -162,6 +162,11 @@  static const struct policydb_compat_info policydb_compat[] = {
 		.sym_num	= SYM_NUM,
 		.ocon_num	= OCON_NUM,
 	},
+	{
+		.version	= POLICYDB_VERSION_PREFIX_SUFFIX,
+		.sym_num	= SYM_NUM,
+		.ocon_num	= OCON_NUM,
+	},
 };
 
 static const struct policydb_compat_info *policydb_lookup_compat(int version)
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 131647e7ec68..2faf92bf12da 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -1661,6 +1661,60 @@  static int compute_sid_handle_invalid_context(
 	return -EACCES;
 }
 
+static int security_compute_type_trans_otype(struct avtab_trans *trans,
+					     const char *name, u32 *res_type)
+{
+	u32 *otype;
+	size_t len;
+	char *namedup = NULL;
+	size_t i;
+
+	/*
+	 * use default otype if not empty and then try to find more specific
+	 * rule using name
+	 */
+	if (trans->otype)
+		*res_type = trans->otype;
+	if (!name)
+		return 0;
+
+	/* try to find full name */
+	otype = symtab_search(&trans->name_trans, name);
+	if (otype) {
+		*res_type = *otype;
+		return 0;
+	}
+
+	/* copy name for shortening */
+	len = strlen(name);
+	namedup = kmemdup(name, len + 1, GFP_KERNEL);
+	if (!namedup)
+		return -ENOMEM;
+
+	/* try to find possible prefixes of name starting from the longest */
+	for (i = len; i > 0; i--) {
+		namedup[i] = '\0';
+		otype = symtab_search(&trans->prefix_trans, namedup);
+		if (otype) {
+			kfree(namedup);
+			*res_type = *otype;
+			return 0;
+		}
+	}
+	kfree(namedup);
+
+	/*try to find possible suffixes of name starting from the longest */
+	for (i = 0; i < len; i++) {
+		otype = symtab_search(&trans->suffix_trans, &name[i]);
+		if (otype) {
+			*res_type = *otype;
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
 static int security_compute_sid(u32 ssid,
 				u32 tsid,
 				u16 orig_tclass,
@@ -1802,18 +1856,11 @@  static int security_compute_sid(u32 ssid,
 	if (avdatum) {
 		/* Use the type from the type transition/member/change rule. */
 		if (avkey.specified & AVTAB_TRANSITION) {
-			/*
-			 * use default otype if not empty and then to try to
-			 * find more specific rule using objname
-			 */
-			if (avdatum->u.trans->otype)
-				newcontext.type = avdatum->u.trans->otype;
-			if (objname) {
-				otype = symtab_search(&avdatum->u.trans->name_trans,
-						      objname);
-				if (otype)
-					newcontext.type = *otype;
-			}
+			rc = security_compute_type_trans_otype(avdatum->u.trans,
+							       objname,
+							       &newcontext.type);
+			if (rc)
+				goto out_unlock;
 		} else {
 			newcontext.type = avdatum->u.data;
 		}