diff mbox series

[v3] checkpolicy,libsepol: add prefix/suffix matching to filename type transitions

Message ID 20231108103345.4014148-1-juraj@jurajmarcin.com (mailing list archive)
State Changes Requested
Headers show
Series [v3] checkpolicy,libsepol: add prefix/suffix matching to filename type transitions | expand

Commit Message

Juraj Marcin Nov. 8, 2023, 10:33 a.m. UTC
Currently, filename transitions are stored separately from other type
enforcement rules and only support 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 implements the equivalent changes made by this kernel
patch [1].

This patch updates the policydb structure to contain prefix and suffix
filename transition tables along normal filename transitions table and
updates the code that accesses those tables. Furthermore, it adds
match_type attribute to module and CIL structures that store filename
transitions and updates functions that parse conf and CIL policy files.

This patch does not significantly change the binary policy size when
prefix/suffix rules are not used. In addition, with prefix/suffix rules,
the number of filename transitions can be reduced, and therefore also
binary policy size can be reduced.

Syntax of the new prefix/suffix filename transition rule:

    type_transition source_type target_type : class default_type object_name match_type;

    (typetransition source_type_id target_type_id class_id object_name match_type default_type_id)

where match_type is either keyword "prefix" or "suffix"

Examples:

    type_transition ta tb:CLASS01 tc "file01" prefix;
    type_transition td te:CLASS01 tf "file02" suffix;

    (typetransition ta tb CLASS01 "file01" prefix td)
    (typetransition td te CLASS01 "file02" suffix tf)

In the kernel, the rules have the following order of priority, if no
matching rule is found, the code moves on to the next category:
- exact filename transitions,
- prefix filename transitions in the order of the longest prefix match,
- suffix filename transitions in the order of the longest suffix match.
This ensures the compatibility with older policies.

[1]: https://lore.kernel.org/selinux/20231108101427.3514509-1-juraj@jurajmarcin.com/

Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
---
v3:
- reworked the solution from scratch, this time only adding the
  prefix/suffix matching feature without moving filename transition
  rules to the avtab
---
 checkpolicy/policy_define.c                |  7 +-
 checkpolicy/policy_define.h                |  2 +-
 checkpolicy/policy_parse.y                 | 13 +++-
 checkpolicy/policy_scan.l                  |  4 ++
 checkpolicy/test/dismod.c                  | 14 +++-
 checkpolicy/test/dispol.c                  | 26 ++++++-
 libsepol/cil/src/cil.c                     |  6 ++
 libsepol/cil/src/cil_binary.c              |  8 +--
 libsepol/cil/src/cil_build_ast.c           | 26 +++++--
 libsepol/cil/src/cil_copy_ast.c            |  1 +
 libsepol/cil/src/cil_internal.h            |  4 ++
 libsepol/cil/src/cil_policy.c              | 17 ++++-
 libsepol/cil/src/cil_resolve_ast.c         | 12 ++++
 libsepol/cil/src/cil_write_ast.c           |  2 +
 libsepol/include/sepol/policydb/policydb.h | 20 ++++--
 libsepol/src/expand.c                      | 17 ++++-
 libsepol/src/kernel_to_cil.c               | 36 +++++++++-
 libsepol/src/kernel_to_conf.c              | 36 +++++++++-
 libsepol/src/link.c                        |  1 +
 libsepol/src/module_to_cil.c               | 21 ++++--
 libsepol/src/policydb.c                    | 82 +++++++++++++++++-----
 libsepol/src/policydb_validate.c           | 36 ++++++++--
 libsepol/src/write.c                       | 44 +++++++++---
 23 files changed, 367 insertions(+), 68 deletions(-)

Comments

James Carter Nov. 8, 2023, 3:52 p.m. UTC | #1
On Wed, Nov 8, 2023 at 5:35 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
>
> Currently, filename transitions are stored separately from other type
> enforcement rules and only support 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 implements the equivalent changes made by this kernel
> patch [1].
>
> This patch updates the policydb structure to contain prefix and suffix
> filename transition tables along normal filename transitions table and
> updates the code that accesses those tables. Furthermore, it adds
> match_type attribute to module and CIL structures that store filename
> transitions and updates functions that parse conf and CIL policy files.
>
> This patch does not significantly change the binary policy size when
> prefix/suffix rules are not used. In addition, with prefix/suffix rules,
> the number of filename transitions can be reduced, and therefore also
> binary policy size can be reduced.
>
> Syntax of the new prefix/suffix filename transition rule:
>
>     type_transition source_type target_type : class default_type object_name match_type;
>
>     (typetransition source_type_id target_type_id class_id object_name match_type default_type_id)
>
> where match_type is either keyword "prefix" or "suffix"
>
> Examples:
>
>     type_transition ta tb:CLASS01 tc "file01" prefix;
>     type_transition td te:CLASS01 tf "file02" suffix;
>
>     (typetransition ta tb CLASS01 "file01" prefix td)
>     (typetransition td te CLASS01 "file02" suffix tf)
>
> In the kernel, the rules have the following order of priority, if no
> matching rule is found, the code moves on to the next category:
> - exact filename transitions,
> - prefix filename transitions in the order of the longest prefix match,
> - suffix filename transitions in the order of the longest suffix match.
> This ensures the compatibility with older policies.
>
> [1]: https://lore.kernel.org/selinux/20231108101427.3514509-1-juraj@jurajmarcin.com/
>
> Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> ---
> v3:
> - reworked the solution from scratch, this time only adding the
>   prefix/suffix matching feature without moving filename transition
>   rules to the avtab
> ---
>  checkpolicy/policy_define.c                |  7 +-
>  checkpolicy/policy_define.h                |  2 +-
>  checkpolicy/policy_parse.y                 | 13 +++-
>  checkpolicy/policy_scan.l                  |  4 ++
>  checkpolicy/test/dismod.c                  | 14 +++-
>  checkpolicy/test/dispol.c                  | 26 ++++++-
>  libsepol/cil/src/cil.c                     |  6 ++
>  libsepol/cil/src/cil_binary.c              |  8 +--
>  libsepol/cil/src/cil_build_ast.c           | 26 +++++--
>  libsepol/cil/src/cil_copy_ast.c            |  1 +
>  libsepol/cil/src/cil_internal.h            |  4 ++
>  libsepol/cil/src/cil_policy.c              | 17 ++++-
>  libsepol/cil/src/cil_resolve_ast.c         | 12 ++++
>  libsepol/cil/src/cil_write_ast.c           |  2 +
>  libsepol/include/sepol/policydb/policydb.h | 20 ++++--
>  libsepol/src/expand.c                      | 17 ++++-
>  libsepol/src/kernel_to_cil.c               | 36 +++++++++-
>  libsepol/src/kernel_to_conf.c              | 36 +++++++++-
>  libsepol/src/link.c                        |  1 +
>  libsepol/src/module_to_cil.c               | 21 ++++--
>  libsepol/src/policydb.c                    | 82 +++++++++++++++++-----
>  libsepol/src/policydb_validate.c           | 36 ++++++++--
>  libsepol/src/write.c                       | 44 +++++++++---
>  23 files changed, 367 insertions(+), 68 deletions(-)
>
> diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> index 260e609d..fb8325ee 100644
> --- a/checkpolicy/policy_define.c
> +++ b/checkpolicy/policy_define.c
> @@ -3159,7 +3159,7 @@ avrule_t *define_cond_filename_trans(void)
>         return COND_ERR;
>  }
>
> -int define_filename_trans(void)
> +int define_filename_trans(uint32_t match_type)
>  {
>         char *id, *name = NULL;
>         type_set_t stypes, ttypes;
> @@ -3261,7 +3261,7 @@ int define_filename_trans(void)
>                         ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
>                                 rc = policydb_filetrans_insert(
>                                         policydbp, s+1, t+1, c+1, name,
> -                                       NULL, otype, NULL
> +                                       NULL, otype, match_type, NULL
>                                 );
>                                 if (rc != SEPOL_OK) {
>                                         if (rc == SEPOL_EEXIST) {
> @@ -3279,7 +3279,7 @@ int define_filename_trans(void)
>                         if (self) {
>                                 rc = policydb_filetrans_insert(
>                                         policydbp, s+1, s+1, c+1, name,
> -                                       NULL, otype, NULL
> +                                       NULL, otype, match_type, NULL
>                                 );
>                                 if (rc != SEPOL_OK) {
>                                         if (rc == SEPOL_EEXIST) {
> @@ -3317,6 +3317,7 @@ int define_filename_trans(void)
>                 ftr->tclass = c + 1;
>                 ftr->otype = otype;
>                 ftr->flags = self ? RULE_SELF : 0;
> +               ftr->match_type = match_type;
>         }
>
>         free(name);
> diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
> index 075b048d..05869346 100644
> --- a/checkpolicy/policy_define.h
> +++ b/checkpolicy/policy_define.h
> @@ -57,7 +57,7 @@ int define_role_trans(int class_specified);
>  int define_role_types(void);
>  int define_role_attr(void);
>  int define_roleattribute(void);
> -int define_filename_trans(void);
> +int define_filename_trans(uint32_t match_type);
>  int define_sens(void);
>  int define_te_avtab(int which);
>  int define_te_avtab_extended_perms(int which);
> diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
> index 356626e2..ee4be4ea 100644
> --- a/checkpolicy/policy_parse.y
> +++ b/checkpolicy/policy_parse.y
> @@ -153,6 +153,7 @@ typedef int (* require_func_t)(int pass);
>  %token FILESYSTEM
>  %token DEFAULT_USER DEFAULT_ROLE DEFAULT_TYPE DEFAULT_RANGE
>  %token LOW_HIGH LOW HIGH GLBLUB
> +%token PREFIX SUFFIX
>
>  %left OR
>  %left XOR
> @@ -410,6 +411,12 @@ cond_rule_def           : cond_transition_def
>                         { $$ = NULL; }
>                          ;
>  cond_transition_def    : TYPE_TRANSITION names names ':' names identifier filename ';'
> +                        { $$ = define_cond_filename_trans() ;
> +                          if ($$ == COND_ERR) return -1;}
> +                       | TYPE_TRANSITION names names ':' names identifier filename PREFIX ';'
> +                        { $$ = define_cond_filename_trans() ;
> +                          if ($$ == COND_ERR) return -1;}
> +                       | TYPE_TRANSITION names names ':' names identifier filename SUFFIX ';'
>                          { $$ = define_cond_filename_trans() ;
>                            if ($$ == COND_ERR) return -1;}
>                         | TYPE_TRANSITION names names ':' names identifier ';'
> @@ -449,7 +456,11 @@ cond_dontaudit_def : DONTAUDIT names names ':' names names ';'
>                         ;
>                         ;
>  transition_def         : TYPE_TRANSITION  names names ':' names identifier filename ';'
> -                       {if (define_filename_trans()) return -1; }
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_EXACT)) return -1; }
> +                       | TYPE_TRANSITION  names names ':' names identifier filename PREFIX ';'
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_PREFIX)) return -1; }
> +                       | TYPE_TRANSITION  names names ':' names identifier filename SUFFIX ';'
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_SUFFIX)) return -1; }
>                         | TYPE_TRANSITION names names ':' names identifier ';'
>                          {if (define_compute_type(AVRULE_TRANSITION)) return -1;}
>                          | TYPE_MEMBER names names ':' names identifier ';'
> diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
> index c998ff8b..0780ef15 100644
> --- a/checkpolicy/policy_scan.l
> +++ b/checkpolicy/policy_scan.l
> @@ -270,6 +270,10 @@ low |
>  LOW                            { return(LOW); }
>  glblub |
>  GLBLUB                         { return(GLBLUB); }
> +PREFIX |
> +prefix                         { return(PREFIX); }
> +SUFFIX |
> +suffix                         { return(SUFFIX); }
>  "/"[^ \n\r\t\f]*               { return(PATH); }
>  \""/"[^\"\n]*\"                { return(QPATH); }
>  \"[^"/"\"\n]+\"        { return(FILENAME); }
> diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> index fa7117f5..5995e135 100644
> --- a/checkpolicy/test/dismod.c
> +++ b/checkpolicy/test/dismod.c
> @@ -564,13 +564,25 @@ static void display_role_allow(role_allow_rule_t * ra, policydb_t * p, FILE * fp
>
>  static void display_filename_trans(filename_trans_rule_t * tr, policydb_t * p, FILE * fp)
>  {
> +       const char *match_str = "";
>         fprintf(fp, "filename transition");
>         for (; tr; tr = tr->next) {
>                 display_type_set(&tr->stypes, 0, p, fp);
>                 display_type_set(&tr->ttypes, 0, p, fp);
>                 display_id(p, fp, SYM_CLASSES, tr->tclass - 1, ":");
>                 display_id(p, fp, SYM_TYPES, tr->otype - 1, "");
> -               fprintf(fp, " %s\n", tr->name);
> +               switch (tr->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +               fprintf(fp, " %s%s\n", tr->name, match_str);
>         }
>  }
>
> diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
> index b567ce77..4426b844 100644
> --- a/checkpolicy/test/dispol.c
> +++ b/checkpolicy/test/dispol.c
> @@ -450,6 +450,7 @@ static void display_role_trans(policydb_t *p, FILE *fp)
>
>  struct filenametr_display_args {
>         policydb_t *p;
> +       uint32_t match_type;
>         FILE *fp;
>  };
>
> @@ -464,6 +465,19 @@ static int filenametr_display(hashtab_key_t key,
>         FILE *fp = args->fp;
>         ebitmap_node_t *node;
>         uint32_t bit;
> +       const char *match_str = "";
> +
> +       switch (args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         do {
>                 ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
> @@ -471,7 +485,7 @@ static int filenametr_display(hashtab_key_t key,
>                         display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
>                         display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
>                         display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
> -                       fprintf(fp, " %s\n", ft->name);
> +                       fprintf(fp, " %s%s\n", ft->name, match_str);
>                 }
>                 ftdatum = ftdatum->next;
>         } while (ftdatum);
> @@ -487,7 +501,15 @@ static void display_filename_trans(policydb_t *p, FILE *fp)
>         fprintf(fp, "filename_trans rules:\n");
>         args.p = p;
>         args.fp = fp;
> -       hashtab_map(p->filename_trans, filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                   filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                   filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                   filenametr_display, &args);
>  }
>
>  static int menu(void)
> diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> index 2021187d..37769fc4 100644
> --- a/libsepol/cil/src/cil.c
> +++ b/libsepol/cil/src/cil.c
> @@ -97,6 +97,8 @@ char *CIL_KEY_TUNABLEIF;
>  char *CIL_KEY_ALLOW;
>  char *CIL_KEY_DONTAUDIT;
>  char *CIL_KEY_TYPETRANSITION;
> +char *CIL_KEY_PREFIX;
> +char *CIL_KEY_SUFFIX;
>  char *CIL_KEY_TYPECHANGE;
>  char *CIL_KEY_CALL;
>  char *CIL_KEY_TUNABLE;
> @@ -269,6 +271,8 @@ static void cil_init_keys(void)
>         CIL_KEY_ALLOW = cil_strpool_add("allow");
>         CIL_KEY_DONTAUDIT = cil_strpool_add("dontaudit");
>         CIL_KEY_TYPETRANSITION = cil_strpool_add("typetransition");
> +       CIL_KEY_PREFIX = cil_strpool_add("prefix");
> +       CIL_KEY_SUFFIX = cil_strpool_add("suffix");
>         CIL_KEY_TYPECHANGE = cil_strpool_add("typechange");
>         CIL_KEY_CALL = cil_strpool_add("call");
>         CIL_KEY_TUNABLE = cil_strpool_add("tunable");
> @@ -2456,6 +2460,8 @@ void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
>         (*nametypetrans)->obj = NULL;
>         (*nametypetrans)->name_str = NULL;
>         (*nametypetrans)->name = NULL;
> +       (*nametypetrans)->match_type_str = NULL;
> +       (*nametypetrans)->match_type = FILENAME_TRANS_MATCH_EXACT;
>         (*nametypetrans)->result_str = NULL;
>         (*nametypetrans)->result = NULL;
>  }
> diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> index a8e3616a..75a9e064 100644
> --- a/libsepol/cil/src/cil_binary.c
> +++ b/libsepol/cil/src/cil_binary.c
> @@ -1168,7 +1168,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
>                                                 type_datum_t *sepol_src,
>                                                 type_datum_t *sepol_tgt,
>                                                 struct cil_list *class_list,
> -                                               char *name,
> +                                               char *name, uint32_t match_type,
>                                                 type_datum_t *sepol_result)
>  {
>         int rc;
> @@ -1183,7 +1183,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
>                 rc = policydb_filetrans_insert(
>                         pdb, sepol_src->s.value, sepol_tgt->s.value,
>                         sepol_obj->s.value, name, NULL,
> -                       sepol_result->s.value, &otype
> +                       sepol_result->s.value, match_type, &otype
>                 );
>                 if (rc != SEPOL_OK) {
>                         if (rc == SEPOL_EEXIST) {
> @@ -1252,7 +1252,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
>
>                         rc = __cil_typetransition_to_avtab_helper(
>                                 pdb, sepol_src, sepol_src, class_list,
> -                               name, sepol_result
> +                               name, typetrans->match_type, sepol_result
>                         );
>                         if (rc != SEPOL_OK) goto exit;
>                 }
> @@ -1270,7 +1270,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
>
>                                 rc = __cil_typetransition_to_avtab_helper(
>                                         pdb, sepol_src, sepol_tgt, class_list,
> -                                       name, sepol_result
> +                                       name, typetrans->match_type, sepol_result
>                                 );
>                                 if (rc != SEPOL_OK) goto exit;
>                         }
> diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> index 8976c254..94dadef8 100644
> --- a/libsepol/cil/src/cil_build_ast.c
> +++ b/libsepol/cil/src/cil_build_ast.c
> @@ -3392,10 +3392,11 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>                 CIL_SYN_STRING,
>                 CIL_SYN_STRING,
>                 CIL_SYN_STRING | CIL_SYN_END,
> -               CIL_SYN_END
> +               CIL_SYN_STRING | CIL_SYN_END,
> +               CIL_SYN_END,
>         };
>         size_t syntax_len = sizeof(syntax)/sizeof(*syntax);
> -       char *s1, *s2, *s3, *s4, *s5;
> +       char *s1, *s2, *s3, *s4, *s5, *s6;
>
>         if (db == NULL || parse_current == NULL || ast_node == NULL ) {
>                 goto exit;
> @@ -3411,12 +3412,22 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>         s3 = parse_current->next->next->next->data;
>         s4 = parse_current->next->next->next->next->data;
>         s5 = NULL;
> +       s6 = NULL;
>
>         if (parse_current->next->next->next->next->next) {
>                 if (s4 == CIL_KEY_STAR) {
> -                       s4 = parse_current->next->next->next->next->next->data;
> +                       if (parse_current->next->next->next->next->next->next) {
> +                               s4 = parse_current->next->next->next->next->next->next->data;
> +                       } else {
> +                               s4 = parse_current->next->next->next->next->next->data;
> +                       }
>                 } else {
> -                       s5 = parse_current->next->next->next->next->next->data;
> +                       if (parse_current->next->next->next->next->next->next) {
> +                               s5 = parse_current->next->next->next->next->next->data;
> +                               s6 = parse_current->next->next->next->next->next->next->data;
> +                       } else {
> +                               s5 = parse_current->next->next->next->next->next->data;
> +                       }
>                 }
>         }
>
> @@ -3428,8 +3439,13 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>                 nametypetrans->src_str = s1;
>                 nametypetrans->tgt_str = s2;
>                 nametypetrans->obj_str = s3;
> -               nametypetrans->result_str = s5;
>                 nametypetrans->name_str = s4;
> +               if (s6) {
> +                       nametypetrans->match_type_str = s5;
> +                       nametypetrans->result_str = s6;
> +               } else {
> +                       nametypetrans->result_str = s5;
> +               }
>
>                 ast_node->data = nametypetrans;
>                 ast_node->flavor = CIL_NAMETYPETRANSITION;
> diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
> index bc972f03..300390ec 100644
> --- a/libsepol/cil/src/cil_copy_ast.c
> +++ b/libsepol/cil/src/cil_copy_ast.c
> @@ -726,6 +726,7 @@ int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
>         new->tgt_str = orig->tgt_str;
>         new->obj_str = orig->obj_str;
>         new->name_str = orig->name_str;
> +       new->match_type_str = orig->match_type_str;
>         new->result_str = orig->result_str;
>
>
> diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> index 9e492cb9..7f0be652 100644
> --- a/libsepol/cil/src/cil_internal.h
> +++ b/libsepol/cil/src/cil_internal.h
> @@ -114,6 +114,8 @@ extern char *CIL_KEY_TUNABLEIF;
>  extern char *CIL_KEY_ALLOW;
>  extern char *CIL_KEY_DONTAUDIT;
>  extern char *CIL_KEY_TYPETRANSITION;
> +extern char *CIL_KEY_PREFIX;
> +extern char *CIL_KEY_SUFFIX;
>  extern char *CIL_KEY_TYPECHANGE;
>  extern char *CIL_KEY_CALL;
>  extern char *CIL_KEY_TUNABLE;
> @@ -580,6 +582,8 @@ struct cil_nametypetransition {
>         struct cil_class *obj;
>         char *name_str;
>         struct cil_name *name;
> +       char *match_type_str;
> +       uint32_t match_type;
>         char *result_str;
>         void *result; /* type or alias */
>
> diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> index feb97868..9776dfd3 100644
> --- a/libsepol/cil/src/cil_policy.c
> +++ b/libsepol/cil/src/cil_policy.c
> @@ -1260,6 +1260,7 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
>         struct cil_name *name;
>         struct cil_list *class_list;
>         struct cil_list_item *i1;
> +       const char *match_type_str = "";
>
>         src = trans->src;
>         tgt = trans->tgt;
> @@ -1268,7 +1269,21 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
>
>         class_list = cil_expand_class(trans->obj);
>         cil_list_for_each(i1, class_list) {
> -               fprintf(out, "type_transition %s %s : %s %s \"%s\";\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn);
> +               switch (trans->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_type_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_type_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_type_str = " suffix";
> +                       break;
> +               default:
> +                       match_type_str = "???";
> +                       break;
> +               }
> +               fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn, match_type_str);
>         }
>         cil_list_destroy(&class_list, CIL_FALSE);
>  }
> diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
> index 33b9d321..3b00f065 100644
> --- a/libsepol/cil/src/cil_resolve_ast.c
> +++ b/libsepol/cil/src/cil_resolve_ast.c
> @@ -717,6 +717,18 @@ int cil_resolve_nametypetransition(struct cil_tree_node *current, void *extra_ar
>                 nametypetrans->name = (struct cil_name *)name_datum;
>         }
>
> +       if (nametypetrans->match_type_str == NULL) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_EXACT;
> +       } else if (nametypetrans->match_type_str == CIL_KEY_PREFIX) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       } else if (nametypetrans->match_type_str == CIL_KEY_SUFFIX) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       } else {
> +               cil_tree_log(current, CIL_ERR, "Invalid name match type \"%s\"", nametypetrans->match_type_str);
> +               rc = SEPOL_ERR;
> +               goto exit;
> +       }
> +
>         rc = cil_resolve_name(current, nametypetrans->result_str, CIL_SYM_TYPES, extra_args, &result_datum);
>         if (rc != SEPOL_OK) {
>                 goto exit;
> diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> index 4da7a77c..99c82292 100644
> --- a/libsepol/cil/src/cil_write_ast.c
> +++ b/libsepol/cil/src/cil_write_ast.c
> @@ -1178,6 +1178,8 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
>                 fprintf(out, "%s ", datum_or_str(DATUM(rule->tgt), rule->tgt_str));
>                 fprintf(out, "%s ", datum_or_str(DATUM(rule->obj), rule->obj_str));
>                 fprintf(out, "\"%s\" ", datum_or_str(DATUM(rule->name), rule->name_str));
> +               if (rule->match_type != FILENAME_TRANS_MATCH_EXACT)
> +                       fprintf(out, "%s ", rule->match_type_str);
>                 fprintf(out, "%s", datum_or_str(DATUM(rule->result), rule->result_str));
>                 fprintf(out, ")\n");
>                 break;
> diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> index 6682069e..7727ca52 100644
> --- a/libsepol/include/sepol/policydb/policydb.h
> +++ b/libsepol/include/sepol/policydb/policydb.h
> @@ -321,6 +321,7 @@ typedef struct filename_trans_rule {
>         uint32_t tclass;
>         char *name;
>         uint32_t otype; /* new type */
> +       uint32_t match_type;
>         struct filename_trans_rule *next;
>  } filename_trans_rule_t;
>
> @@ -423,6 +424,14 @@ typedef struct genfs {
>  /* OCON_NUM needs to be the largest index in any platform's ocontext array */
>  #define OCON_NUM   9
>
> +/* filename transitions table array indices */
> +enum {
> +       FILENAME_TRANS_MATCH_EXACT,
> +       FILENAME_TRANS_MATCH_PREFIX,
> +       FILENAME_TRANS_MATCH_SUFFIX,
> +       FILENAME_TRANS_MATCH_NUM,
> +};
> +
>  /* section: module information */
>
>  /* scope_index_t holds all of the symbols that are in scope in a
> @@ -593,7 +602,7 @@ typedef struct policydb {
>         hashtab_t range_tr;
>
>         /* file transitions with the last path component */
> -       hashtab_t filename_trans;
> +       hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
>         uint32_t filename_trans_count;
>
>         ebitmap_t *type_attr_map;
> @@ -657,7 +666,8 @@ extern int policydb_sort_ocontexts(policydb_t *p);
>  extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
>                                      uint32_t ttype, uint32_t tclass,
>                                      const char *name, char **name_alloc,
> -                                    uint32_t otype, uint32_t *present_otype);
> +                                    uint32_t otype, uint32_t match_type,
> +                                    uint32_t *present_otype);
>
>  /* Deprecated */
>  extern int policydb_context_isvalid(const policydb_t * p,
> @@ -758,10 +768,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
>  #define POLICYDB_VERSION_INFINIBAND            31 /* Linux-specific */
>  #define POLICYDB_VERSION_GLBLUB                32
>  #define POLICYDB_VERSION_COMP_FTRANS   33 /* compressed filename transitions */
> +#define POLICYDB_VERSION_PREFIX_SUFFIX 34 /* prefix and suffix filename transitions */
>
>  /* Range of policy versions we understand*/
>  #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
> -#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_COMP_FTRANS
> +#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_PREFIX_SUFFIX
>
>  /* Module versions and specific changes*/
>  #define MOD_POLICYDB_VERSION_BASE              4
> @@ -784,9 +795,10 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
>  #define MOD_POLICYDB_VERSION_INFINIBAND                19
>  #define MOD_POLICYDB_VERSION_GLBLUB            20
>  #define MOD_POLICYDB_VERSION_SELF_TYPETRANS    21
> +#define MOD_POLICYDB_VERSION_PREFIX_SUFFIX     22
>
>  #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
> -#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_SELF_TYPETRANS
> +#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_PREFIX_SUFFIX
>
>  #define POLICYDB_CONFIG_MLS    1
>
> diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> index ee5f9185..4df9521a 100644
> --- a/libsepol/src/expand.c
> +++ b/libsepol/src/expand.c
> @@ -1419,18 +1419,31 @@ static int expand_filename_trans_helper(expand_state_t *state,
>         rc = policydb_filetrans_insert(
>                 state->out, s + 1, t + 1,
>                 rule->tclass, rule->name,
> -               NULL, mapped_otype, &present_otype
> +               NULL, mapped_otype, rule->match_type, &present_otype
>         );
>         if (rc == SEPOL_EEXIST) {
>                 /* duplicate rule, ignore */
>                 if (present_otype == mapped_otype)
>                         return 0;
>
> -               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\":  %s vs %s",
> +               const char *match_str = "";
> +               switch (rule->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\"%s:  %s vs %s",
>                     state->out->p_type_val_to_name[s],
>                     state->out->p_type_val_to_name[t],
>                     state->out->p_class_val_to_name[rule->tclass - 1],
>                     rule->name,
> +                   match_str,
>                     state->out->p_type_val_to_name[present_otype - 1],
>                     state->out->p_type_val_to_name[mapped_otype - 1]);
>                 return -1;
> diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> index 8fcc385d..81efeaa2 100644
> --- a/libsepol/src/kernel_to_cil.c
> +++ b/libsepol/src/kernel_to_cil.c
> @@ -1869,6 +1869,7 @@ exit:
>
>  struct map_filename_trans_args {
>         struct policydb *pdb;
> +       uint32_t match_type;
>         struct strs *strs;
>  };
>
> @@ -1883,6 +1884,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>         struct ebitmap_node *node;
>         uint32_t bit;
>         int rc;
> +       const char *match_str = "";
> +
> +       switch (map_args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
>         class = pdb->p_class_val_to_name[ft->tclass - 1];
> @@ -1893,8 +1907,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
>                         src = pdb->p_type_val_to_name[bit];
>                         rc = strs_create_and_add(strs,
> -                                                "(typetransition %s %s %s \"%s\" %s)",
> -                                                5, src, tgt, class, filename, new);
> +                                                "(typetransition %s %s %s \"%s\"%s %s)",
> +                                                6, src, tgt, class, filename, match_str, new);
>                         if (rc)
>                                 return rc;
>                 }
> @@ -1919,7 +1933,23 @@ static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
>         args.pdb = pdb;
>         args.strs = strs;
>
> -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                        map_filename_trans_to_str, &args);
>         if (rc != 0) {
>                 goto exit;
>         }
> diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
> index b0ae16d9..99bef76e 100644
> --- a/libsepol/src/kernel_to_conf.c
> +++ b/libsepol/src/kernel_to_conf.c
> @@ -1845,6 +1845,7 @@ exit:
>
>  struct map_filename_trans_args {
>         struct policydb *pdb;
> +       uint32_t match_type;
>         struct strs *strs;
>  };
>
> @@ -1859,6 +1860,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>         struct ebitmap_node *node;
>         uint32_t bit;
>         int rc;
> +       const char *match_str = "";
> +
> +       switch (map_args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
>         class = pdb->p_class_val_to_name[ft->tclass - 1];
> @@ -1869,8 +1883,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
>                         src = pdb->p_type_val_to_name[bit];
>                         rc = strs_create_and_add(strs,
> -                                                "type_transition %s %s:%s %s \"%s\";",
> -                                                5, src, tgt, class, new, filename);
> +                                                "type_transition %s %s:%s %s \"%s\"%s;",
> +                                                6, src, tgt, class, new, filename, match_str);
>                         if (rc)
>                                 return rc;
>                 }
> @@ -1895,7 +1909,23 @@ static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
>         args.pdb = pdb;
>         args.strs = strs;
>
> -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                        map_filename_trans_to_str, &args);
>         if (rc != 0) {
>                 goto exit;
>         }
> diff --git a/libsepol/src/link.c b/libsepol/src/link.c
> index 3b7742bc..f432087f 100644
> --- a/libsepol/src/link.c
> +++ b/libsepol/src/link.c
> @@ -1440,6 +1440,7 @@ static int copy_filename_trans_list(filename_trans_rule_t * list,
>                 new_rule->name = strdup(cur->name);
>                 if (!new_rule->name)
>                         goto err;
> +               new_rule->match_type = cur->match_type;
>
>                 if (type_set_or_convert(&cur->stypes, &new_rule->stypes, module) ||
>                     type_set_or_convert(&cur->ttypes, &new_rule->ttypes, module))
> diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> index d2868019..98e1c7ba 100644
> --- a/libsepol/src/module_to_cil.c
> +++ b/libsepol/src/module_to_cil.c
> @@ -1609,6 +1609,7 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
>         unsigned int ttype;
>         struct type_set *ts;
>         struct filename_trans_rule *rule;
> +       const char *match_str = "";
>
>         for (rule = rules; rule != NULL; rule = rule->next) {
>                 ts = &rule->stypes;
> @@ -1623,19 +1624,31 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
>                         goto exit;
>                 }
>
> +               switch (rule->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +
>                 for (stype = 0; stype < num_stypes; stype++) {
>                         for (ttype = 0; ttype < num_ttypes; ttype++) {
> -                               cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
> +                               cil_println(indent, "(typetransition %s %s %s \"%s\"%s %s)",
>                                             stypes[stype], ttypes[ttype],
>                                             pdb->p_class_val_to_name[rule->tclass - 1],
> -                                           rule->name,
> +                                           rule->name, match_str,
>                                             pdb->p_type_val_to_name[rule->otype - 1]);
>                         }
>                         if (rule->flags & RULE_SELF) {
> -                               cil_println(indent, "(typetransition %s self %s \"%s\" %s)",
> +                               cil_println(indent, "(typetransition %s self %s \"%s\"%s %s)",
>                                             stypes[stype],
>                                             pdb->p_class_val_to_name[rule->tclass - 1],
> -                                           rule->name,
> +                                           rule->name, match_str,
>                                             pdb->p_type_val_to_name[rule->otype - 1]);
>                         }
>                 }
> diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> index f9537caa..29d7971a 100644
> --- a/libsepol/src/policydb.c
> +++ b/libsepol/src/policydb.c
> @@ -208,6 +208,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = OCON_IBENDPORT + 1,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_KERN,
> +        .version = POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = OCON_IBENDPORT + 1,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>         {
>          .type = POLICY_BASE,
>          .version = MOD_POLICYDB_VERSION_BASE,
> @@ -334,6 +341,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = OCON_IBENDPORT + 1,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_BASE,
> +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = OCON_IBENDPORT + 1,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>         {
>          .type = POLICY_MOD,
>          .version = MOD_POLICYDB_VERSION_BASE,
> @@ -460,6 +474,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = 0,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_MOD,
> +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = 0,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>  };
>
>  #if 0
> @@ -909,10 +930,14 @@ int policydb_init(policydb_t * p)
>         if (rc)
>                 goto err;
>
> -       p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
> -       if (!p->filename_trans) {
> -               rc = -ENOMEM;
> -               goto err;
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               p->filename_trans[i] = hashtab_create(filenametr_hash,
> +                                                     filenametr_cmp,
> +                                                     (1 << 10));
> +               if (!p->filename_trans[i]) {
> +                       rc = -ENOMEM;
> +                       goto err;
> +               }
>         }
>
>         p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
> @@ -926,7 +951,9 @@ int policydb_init(policydb_t * p)
>
>         return 0;
>  err:
> -       hashtab_destroy(p->filename_trans);
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               hashtab_destroy(p->filename_trans[i]);
> +       }
>         hashtab_destroy(p->range_tr);
>         for (i = 0; i < SYM_NUM; i++) {
>                 hashtab_destroy(p->symtab[i].table);
> @@ -1564,8 +1591,10 @@ void policydb_destroy(policydb_t * p)
>         if (lra)
>                 free(lra);
>
> -       hashtab_map(p->filename_trans, filenametr_destroy, NULL);
> -       hashtab_destroy(p->filename_trans);
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               hashtab_map(p->filename_trans[i], filenametr_destroy, NULL);
> +               hashtab_destroy(p->filename_trans[i]);
> +       }
>
>         hashtab_map(p->range_tr, range_tr_destroy, NULL);
>         hashtab_destroy(p->range_tr);
> @@ -2599,7 +2628,7 @@ static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
>  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>                               uint32_t tclass, const char *name,
>                               char **name_alloc, uint32_t otype,
> -                             uint32_t *present_otype)
> +                             uint32_t match_type, uint32_t *present_otype)
>  {
>         filename_trans_key_t *ft, key;
>         filename_trans_datum_t *datum, *last;
> @@ -2609,7 +2638,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>         key.name = (char *)name;
>
>         last = NULL;
> -       datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
> +       datum = hashtab_search(p->filename_trans[match_type],
> +                              (hashtab_key_t)&key);
>         while (datum) {
>                 if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
>                         if (present_otype)
> @@ -2657,7 +2687,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>                         ft->tclass = tclass;
>                         ft->name = name_dup;
>
> -                       if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> +                       if (hashtab_insert(p->filename_trans[match_type],
> +                                          (hashtab_key_t)ft,
>                                            (hashtab_datum_t)datum)) {
>                                 free(name_dup);
>                                 free(datum);
> @@ -2704,8 +2735,9 @@ static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
>         tclass = le32_to_cpu(buf[2]);
>         otype  = le32_to_cpu(buf[3]);
>
> +       // This version does not contain other than exact filename transitions
>         rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
> -                                      otype, NULL);
> +                                      otype, FILENAME_TRANS_MATCH_EXACT, NULL);
>         if (rc) {
>                 if (rc != SEPOL_EEXIST)
>                         goto err;
> @@ -2753,7 +2785,8 @@ out:
>         return rc;
>  }
>
> -static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> +static int filename_trans_read_one(policydb_t *p, uint32_t match_type,
> +                                  struct policy_file *fp)
>  {
>         filename_trans_key_t *ft = NULL;
>         filename_trans_datum_t **dst, *datum, *first = NULL;
> @@ -2823,7 +2856,7 @@ static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
>         ft->tclass = tclass;
>         ft->name = name;
>
> -       rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> +       rc = hashtab_insert(p->filename_trans[match_type], (hashtab_key_t)ft,
>                             (hashtab_datum_t)first);
>         if (rc)
>                 goto err;
> @@ -2842,7 +2875,8 @@ err:
>         return -1;
>  }
>
> -static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> +static int filename_trans_read(policydb_t *p, struct policy_file *fp,
> +                              uint32_t match_type)
>  {
>         unsigned int i;
>         uint32_t buf[1], nel;
> @@ -2855,13 +2889,17 @@ static int filename_trans_read(policydb_t *p, struct policy_file *fp)
>
>         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
>                 for (i = 0; i < nel; i++) {
> +                       /*
> +                        * this version does not have other than exact match
> +                        * transitions
> +                        */
>                         rc = filename_trans_read_one_compat(p, fp);
>                         if (rc < 0)
>                                 return -1;
>                 }
>         } else {
>                 for (i = 0; i < nel; i++) {
> -                       rc = filename_trans_read_one(p, fp);
> +                       rc = filename_trans_read_one(p, match_type, fp);
>                         if (rc < 0)
>                                 return -1;
>                 }
> @@ -3837,7 +3875,7 @@ static int role_allow_rule_read(role_allow_rule_t ** r, struct policy_file *fp)
>  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                                     struct policy_file *fp)
>  {
> -       uint32_t buf[3], nel, i, len;
> +       uint32_t buf[4], nel, i, len;
>         unsigned int entries;
>         filename_trans_rule_t *ftr, *lftr;
>         int rc;
> @@ -3883,7 +3921,9 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                 if (type_set_read(&ftr->ttypes, fp))
>                         return -1;
>
> -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> +                       entries = 4;
> +               else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
>                         entries = 3;
>                 else
>                         entries = 2;
> @@ -3895,6 +3935,8 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                 ftr->otype = le32_to_cpu(buf[1]);
>                 if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
>                         ftr->flags = le32_to_cpu(buf[2]);
> +               if (p->policyvers >=  MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> +                       ftr->match_type = le32_to_cpu(buf[3]);
>         }
>
>         return 0;
> @@ -4470,7 +4512,11 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
>                 if (role_allow_read(&p->role_allow, fp))
>                         goto bad;
>                 if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
> -                   filename_trans_read(p, fp))
> +                   filename_trans_read(p, fp, FILENAME_TRANS_MATCH_EXACT))
> +                       goto bad;
> +               if (r_policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX &&
> +                   (filename_trans_read(p, fp, FILENAME_TRANS_MATCH_PREFIX) ||
> +                    filename_trans_read(p, fp, FILENAME_TRANS_MATCH_SUFFIX)))
>                         goto bad;
>         } else {
>                 /* first read the AV rule blocks, then the scope tables */
> diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c

Unfortunately, the patch to validate policy capabilities that I merged
yesterday causes problems in this part of your patch.

> index 892a0ffd..3f2ba5ea 100644
> --- a/libsepol/src/policydb_validate.c
> +++ b/libsepol/src/policydb_validate.c
> @@ -1,4 +1,3 @@
> -
>  #include <sepol/policydb/conditional.h>
>  #include <sepol/policydb/ebitmap.h>
>  #include <sepol/policydb/policydb.h>

polcaps.h header was added before the policydb.h header

> @@ -1115,14 +1114,27 @@ bad:
>         return -1;
>  }
>
> -static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
> +static int validate_filename_trans_hashtabs(sepol_handle_t *handle,
> +                                           const policydb_t *p,
> +                                           validate_t flavors[])

The polcaps patch actually gives you what you want here already.

>  {
> -       if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
> -               ERR(handle, "Invalid filename trans");
> -               return -1;

This has changed.

> +       if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                       validate_filename_trans, flavors))
> +               goto bad;
> +
> +       if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                               validate_filename_trans, flavors))
> +                       goto bad;
> +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                               validate_filename_trans, flavors))
> +                       goto bad;
>         }
>
>         return 0;
> +bad:
> +       ERR(handle, "Invalid filename trans");
> +       return -1;
>  }
>
>  static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
> @@ -1334,6 +1346,15 @@ static int validate_filename_trans_rules(sepol_handle_t *handle, const filename_
>                 if (validate_simpletype(filename_trans->otype, p, flavors))
>                         goto bad;
>
> +               switch (filename_trans->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       break;
> +               default:
> +                       goto bad;
> +               }
> +
>                 /* currently only the RULE_SELF flag can be set */
>                 if ((filename_trans->flags & ~RULE_SELF) != 0)
>                         goto bad;
> @@ -1554,9 +1575,10 @@ int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
>                         goto bad;
>                 if (validate_role_allows(handle, p->role_allow, flavors))
>                         goto bad;
> -               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
> -                       if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
> +               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> +                       if (validate_filename_trans_hashtabs(handle, p, flavors))
>                                 goto bad;
> +               }

Your change is very similar to what the polcap patch already did.

The needed changes should be fairly straightforward.
Thanks,
Jim


>         } else {
>                 if (validate_avrule_blocks(handle, p->global, p, flavors))
>                         goto bad;
> diff --git a/libsepol/src/write.c b/libsepol/src/write.c
> index 283d11c8..c9398a98 100644
> --- a/libsepol/src/write.c
> +++ b/libsepol/src/write.c
> @@ -653,7 +653,8 @@ static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
>         return 0;
>  }
>
> -static int filename_trans_write(struct policydb *p, void *fp)
> +static int filename_trans_write(struct policydb *p, uint32_t match_type,
> +                               void *fp)
>  {
>         size_t items;
>         uint32_t buf[1];
> @@ -663,20 +664,25 @@ static int filename_trans_write(struct policydb *p, void *fp)
>                 return 0;
>
>         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> +               /*
> +                * This version does not have other than exact match
> +                * transitions, there is no need to count other ones.
> +                */
>                 buf[0] = cpu_to_le32(p->filename_trans_count);
>                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
>                 if (items != 1)
>                         return POLICYDB_ERROR;
>
> -               rc = hashtab_map(p->filename_trans, filename_write_one_compat,
> -                                fp);
> +               rc = hashtab_map(p->filename_trans[match_type],
> +                                filename_write_one_compat, fp);
>         } else {
> -               buf[0] = cpu_to_le32(p->filename_trans->nel);
> +               buf[0] = cpu_to_le32(p->filename_trans[match_type]->nel);
>                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
>                 if (items != 1)
>                         return POLICYDB_ERROR;
>
> -               rc = hashtab_map(p->filename_trans, filename_write_one, fp);
> +               rc = hashtab_map(p->filename_trans[match_type],
> +                                filename_write_one, fp);
>         }
>         return rc;
>  }
> @@ -1944,7 +1950,7 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
>  {
>         int nel = 0;
>         size_t items, entries;
> -       uint32_t buf[3], len;
> +       uint32_t buf[4], len;
>         filename_trans_rule_t *ftr;
>
>         for (ftr = t; ftr; ftr = ftr->next)
> @@ -1974,8 +1980,11 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
>                 buf[0] = cpu_to_le32(ftr->tclass);
>                 buf[1] = cpu_to_le32(ftr->otype);
>                 buf[2] = cpu_to_le32(ftr->flags);
> +               buf[3] = cpu_to_le32(ftr->match_type);
>
> -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
> +                       entries = 4;
> +               } else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
>                         entries = 3;
>                 } else if (!(ftr->flags & RULE_SELF)) {
>                         entries = 2;
> @@ -2370,12 +2379,29 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
>                 if (role_allow_write(p->role_allow, fp))
>                         return POLICYDB_ERROR;
>                 if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> -                       if (filename_trans_write(p, fp))
> +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_EXACT,
> +                                                fp))
>                                 return POLICYDB_ERROR;
>                 } else {
> -                       if (p->filename_trans)
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_EXACT])
>                                 WARN(fp->handle, "Discarding filename type transition rules");
>                 }
> +               if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_PREFIX,
> +                                                fp) ||
> +                           filename_trans_write(p, FILENAME_TRANS_MATCH_SUFFIX,
> +                                                fp))
> +                               return POLICYDB_ERROR;
> +               } else {
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_PREFIX] &&
> +                           p->filename_trans[FILENAME_TRANS_MATCH_PREFIX]->nel)
> +                               WARN(fp->handle,
> +                                    "Discarding prefix filename type transition rules");
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX] &&
> +                           p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX]->nel)
> +                               WARN(fp->handle,
> +                                    "Discarding suffix filename type transition rules");
> +               }
>         } else {
>                 if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
>                         return POLICYDB_ERROR;
> --
> 2.41.0
>
Christian Göttsche Nov. 9, 2023, 2:05 p.m. UTC | #2
On Wed, 8 Nov 2023 at 16:52, James Carter <jwcart2@gmail.com> wrote:
>
> On Wed, Nov 8, 2023 at 5:35 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
> >
> > Currently, filename transitions are stored separately from other type
> > enforcement rules and only support 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 implements the equivalent changes made by this kernel
> > patch [1].
> >
> > This patch updates the policydb structure to contain prefix and suffix
> > filename transition tables along normal filename transitions table and
> > updates the code that accesses those tables. Furthermore, it adds
> > match_type attribute to module and CIL structures that store filename
> > transitions and updates functions that parse conf and CIL policy files.
> >
> > This patch does not significantly change the binary policy size when
> > prefix/suffix rules are not used. In addition, with prefix/suffix rules,
> > the number of filename transitions can be reduced, and therefore also
> > binary policy size can be reduced.
> >
> > Syntax of the new prefix/suffix filename transition rule:
> >
> >     type_transition source_type target_type : class default_type object_name match_type;
> >
> >     (typetransition source_type_id target_type_id class_id object_name match_type default_type_id)
> >
> > where match_type is either keyword "prefix" or "suffix"
> >
> > Examples:
> >
> >     type_transition ta tb:CLASS01 tc "file01" prefix;
> >     type_transition td te:CLASS01 tf "file02" suffix;
> >
> >     (typetransition ta tb CLASS01 "file01" prefix td)
> >     (typetransition td te CLASS01 "file02" suffix tf)
> >
> > In the kernel, the rules have the following order of priority, if no
> > matching rule is found, the code moves on to the next category:
> > - exact filename transitions,
> > - prefix filename transitions in the order of the longest prefix match,
> > - suffix filename transitions in the order of the longest suffix match.
> > This ensures the compatibility with older policies.
> >
> > [1]: https://lore.kernel.org/selinux/20231108101427.3514509-1-juraj@jurajmarcin.com/
> >
> > Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> > Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> > ---
> > v3:
> > - reworked the solution from scratch, this time only adding the
> >   prefix/suffix matching feature without moving filename transition
> >   rules to the avtab
> > ---
> >  checkpolicy/policy_define.c                |  7 +-
> >  checkpolicy/policy_define.h                |  2 +-
> >  checkpolicy/policy_parse.y                 | 13 +++-
> >  checkpolicy/policy_scan.l                  |  4 ++
> >  checkpolicy/test/dismod.c                  | 14 +++-
> >  checkpolicy/test/dispol.c                  | 26 ++++++-
> >  libsepol/cil/src/cil.c                     |  6 ++
> >  libsepol/cil/src/cil_binary.c              |  8 +--
> >  libsepol/cil/src/cil_build_ast.c           | 26 +++++--
> >  libsepol/cil/src/cil_copy_ast.c            |  1 +
> >  libsepol/cil/src/cil_internal.h            |  4 ++
> >  libsepol/cil/src/cil_policy.c              | 17 ++++-
> >  libsepol/cil/src/cil_resolve_ast.c         | 12 ++++
> >  libsepol/cil/src/cil_write_ast.c           |  2 +
> >  libsepol/include/sepol/policydb/policydb.h | 20 ++++--
> >  libsepol/src/expand.c                      | 17 ++++-
> >  libsepol/src/kernel_to_cil.c               | 36 +++++++++-
> >  libsepol/src/kernel_to_conf.c              | 36 +++++++++-
> >  libsepol/src/link.c                        |  1 +
> >  libsepol/src/module_to_cil.c               | 21 ++++--
> >  libsepol/src/policydb.c                    | 82 +++++++++++++++++-----
> >  libsepol/src/policydb_validate.c           | 36 ++++++++--
> >  libsepol/src/write.c                       | 44 +++++++++---

Maybe add some syntax tests to secilc/test/ and
checkpolicy/tests/policy_allonce(_mls).conf.

> >  23 files changed, 367 insertions(+), 68 deletions(-)
> >
> > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > index 260e609d..fb8325ee 100644
> > --- a/checkpolicy/policy_define.c
> > +++ b/checkpolicy/policy_define.c
> > @@ -3159,7 +3159,7 @@ avrule_t *define_cond_filename_trans(void)
> >         return COND_ERR;
> >  }
> >
> > -int define_filename_trans(void)
> > +int define_filename_trans(uint32_t match_type)
> >  {
> >         char *id, *name = NULL;
> >         type_set_t stypes, ttypes;
> > @@ -3261,7 +3261,7 @@ int define_filename_trans(void)
> >                         ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
> >                                 rc = policydb_filetrans_insert(
> >                                         policydbp, s+1, t+1, c+1, name,
> > -                                       NULL, otype, NULL
> > +                                       NULL, otype, match_type, NULL
> >                                 );
> >                                 if (rc != SEPOL_OK) {
> >                                         if (rc == SEPOL_EEXIST) {
> > @@ -3279,7 +3279,7 @@ int define_filename_trans(void)
> >                         if (self) {
> >                                 rc = policydb_filetrans_insert(
> >                                         policydbp, s+1, s+1, c+1, name,
> > -                                       NULL, otype, NULL
> > +                                       NULL, otype, match_type, NULL
> >                                 );
> >                                 if (rc != SEPOL_OK) {
> >                                         if (rc == SEPOL_EEXIST) {
> > @@ -3317,6 +3317,7 @@ int define_filename_trans(void)
> >                 ftr->tclass = c + 1;
> >                 ftr->otype = otype;
> >                 ftr->flags = self ? RULE_SELF : 0;
> > +               ftr->match_type = match_type;
> >         }
> >
> >         free(name);
> > diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
> > index 075b048d..05869346 100644
> > --- a/checkpolicy/policy_define.h
> > +++ b/checkpolicy/policy_define.h
> > @@ -57,7 +57,7 @@ int define_role_trans(int class_specified);
> >  int define_role_types(void);
> >  int define_role_attr(void);
> >  int define_roleattribute(void);
> > -int define_filename_trans(void);
> > +int define_filename_trans(uint32_t match_type);
> >  int define_sens(void);
> >  int define_te_avtab(int which);
> >  int define_te_avtab_extended_perms(int which);
> > diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
> > index 356626e2..ee4be4ea 100644
> > --- a/checkpolicy/policy_parse.y
> > +++ b/checkpolicy/policy_parse.y
> > @@ -153,6 +153,7 @@ typedef int (* require_func_t)(int pass);
> >  %token FILESYSTEM
> >  %token DEFAULT_USER DEFAULT_ROLE DEFAULT_TYPE DEFAULT_RANGE
> >  %token LOW_HIGH LOW HIGH GLBLUB
> > +%token PREFIX SUFFIX
> >
> >  %left OR
> >  %left XOR
> > @@ -410,6 +411,12 @@ cond_rule_def           : cond_transition_def
> >                         { $$ = NULL; }
> >                          ;
> >  cond_transition_def    : TYPE_TRANSITION names names ':' names identifier filename ';'
> > +                        { $$ = define_cond_filename_trans() ;
> > +                          if ($$ == COND_ERR) return -1;}
> > +                       | TYPE_TRANSITION names names ':' names identifier filename PREFIX ';'
> > +                        { $$ = define_cond_filename_trans() ;
> > +                          if ($$ == COND_ERR) return -1;}
> > +                       | TYPE_TRANSITION names names ':' names identifier filename SUFFIX ';'
> >                          { $$ = define_cond_filename_trans() ;
> >                            if ($$ == COND_ERR) return -1;}
> >                         | TYPE_TRANSITION names names ':' names identifier ';'
> > @@ -449,7 +456,11 @@ cond_dontaudit_def : DONTAUDIT names names ':' names names ';'
> >                         ;
> >                         ;
> >  transition_def         : TYPE_TRANSITION  names names ':' names identifier filename ';'
> > -                       {if (define_filename_trans()) return -1; }
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_EXACT)) return -1; }
> > +                       | TYPE_TRANSITION  names names ':' names identifier filename PREFIX ';'
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_PREFIX)) return -1; }
> > +                       | TYPE_TRANSITION  names names ':' names identifier filename SUFFIX ';'
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_SUFFIX)) return -1; }
> >                         | TYPE_TRANSITION names names ':' names identifier ';'
> >                          {if (define_compute_type(AVRULE_TRANSITION)) return -1;}
> >                          | TYPE_MEMBER names names ':' names identifier ';'
> > diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
> > index c998ff8b..0780ef15 100644
> > --- a/checkpolicy/policy_scan.l
> > +++ b/checkpolicy/policy_scan.l
> > @@ -270,6 +270,10 @@ low |
> >  LOW                            { return(LOW); }
> >  glblub |
> >  GLBLUB                         { return(GLBLUB); }
> > +PREFIX |
> > +prefix                         { return(PREFIX); }
> > +SUFFIX |
> > +suffix                         { return(SUFFIX); }
> >  "/"[^ \n\r\t\f]*               { return(PATH); }
> >  \""/"[^\"\n]*\"                { return(QPATH); }
> >  \"[^"/"\"\n]+\"        { return(FILENAME); }
> > diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> > index fa7117f5..5995e135 100644
> > --- a/checkpolicy/test/dismod.c
> > +++ b/checkpolicy/test/dismod.c
> > @@ -564,13 +564,25 @@ static void display_role_allow(role_allow_rule_t * ra, policydb_t * p, FILE * fp
> >
> >  static void display_filename_trans(filename_trans_rule_t * tr, policydb_t * p, FILE * fp)
> >  {
> > +       const char *match_str = "";
> >         fprintf(fp, "filename transition");
> >         for (; tr; tr = tr->next) {
> >                 display_type_set(&tr->stypes, 0, p, fp);
> >                 display_type_set(&tr->ttypes, 0, p, fp);
> >                 display_id(p, fp, SYM_CLASSES, tr->tclass - 1, ":");
> >                 display_id(p, fp, SYM_TYPES, tr->otype - 1, "");
> > -               fprintf(fp, " %s\n", tr->name);
> > +               switch (tr->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +               fprintf(fp, " %s%s\n", tr->name, match_str);
> >         }
> >  }
> >
> > diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
> > index b567ce77..4426b844 100644
> > --- a/checkpolicy/test/dispol.c
> > +++ b/checkpolicy/test/dispol.c
> > @@ -450,6 +450,7 @@ static void display_role_trans(policydb_t *p, FILE *fp)
> >
> >  struct filenametr_display_args {
> >         policydb_t *p;
> > +       uint32_t match_type;
> >         FILE *fp;
> >  };
> >
> > @@ -464,6 +465,19 @@ static int filenametr_display(hashtab_key_t key,
> >         FILE *fp = args->fp;
> >         ebitmap_node_t *node;
> >         uint32_t bit;
> > +       const char *match_str = "";
> > +
> > +       switch (args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         do {
> >                 ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
> > @@ -471,7 +485,7 @@ static int filenametr_display(hashtab_key_t key,
> >                         display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
> >                         display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
> >                         display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
> > -                       fprintf(fp, " %s\n", ft->name);
> > +                       fprintf(fp, " %s%s\n", ft->name, match_str);
> >                 }
> >                 ftdatum = ftdatum->next;
> >         } while (ftdatum);
> > @@ -487,7 +501,15 @@ static void display_filename_trans(policydb_t *p, FILE *fp)
> >         fprintf(fp, "filename_trans rules:\n");
> >         args.p = p;
> >         args.fp = fp;
> > -       hashtab_map(p->filename_trans, filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                   filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                   filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                   filenametr_display, &args);
> >  }
> >
> >  static int menu(void)
> > diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> > index 2021187d..37769fc4 100644
> > --- a/libsepol/cil/src/cil.c
> > +++ b/libsepol/cil/src/cil.c
> > @@ -97,6 +97,8 @@ char *CIL_KEY_TUNABLEIF;
> >  char *CIL_KEY_ALLOW;
> >  char *CIL_KEY_DONTAUDIT;
> >  char *CIL_KEY_TYPETRANSITION;
> > +char *CIL_KEY_PREFIX;
> > +char *CIL_KEY_SUFFIX;
> >  char *CIL_KEY_TYPECHANGE;
> >  char *CIL_KEY_CALL;
> >  char *CIL_KEY_TUNABLE;
> > @@ -269,6 +271,8 @@ static void cil_init_keys(void)
> >         CIL_KEY_ALLOW = cil_strpool_add("allow");
> >         CIL_KEY_DONTAUDIT = cil_strpool_add("dontaudit");
> >         CIL_KEY_TYPETRANSITION = cil_strpool_add("typetransition");
> > +       CIL_KEY_PREFIX = cil_strpool_add("prefix");
> > +       CIL_KEY_SUFFIX = cil_strpool_add("suffix");
> >         CIL_KEY_TYPECHANGE = cil_strpool_add("typechange");
> >         CIL_KEY_CALL = cil_strpool_add("call");
> >         CIL_KEY_TUNABLE = cil_strpool_add("tunable");
> > @@ -2456,6 +2460,8 @@ void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
> >         (*nametypetrans)->obj = NULL;
> >         (*nametypetrans)->name_str = NULL;
> >         (*nametypetrans)->name = NULL;
> > +       (*nametypetrans)->match_type_str = NULL;
> > +       (*nametypetrans)->match_type = FILENAME_TRANS_MATCH_EXACT;
> >         (*nametypetrans)->result_str = NULL;
> >         (*nametypetrans)->result = NULL;
> >  }
> > diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> > index a8e3616a..75a9e064 100644
> > --- a/libsepol/cil/src/cil_binary.c
> > +++ b/libsepol/cil/src/cil_binary.c
> > @@ -1168,7 +1168,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
> >                                                 type_datum_t *sepol_src,
> >                                                 type_datum_t *sepol_tgt,
> >                                                 struct cil_list *class_list,
> > -                                               char *name,
> > +                                               char *name, uint32_t match_type,
> >                                                 type_datum_t *sepol_result)
> >  {
> >         int rc;
> > @@ -1183,7 +1183,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
> >                 rc = policydb_filetrans_insert(
> >                         pdb, sepol_src->s.value, sepol_tgt->s.value,
> >                         sepol_obj->s.value, name, NULL,
> > -                       sepol_result->s.value, &otype
> > +                       sepol_result->s.value, match_type, &otype
> >                 );
> >                 if (rc != SEPOL_OK) {
> >                         if (rc == SEPOL_EEXIST) {
> > @@ -1252,7 +1252,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
> >
> >                         rc = __cil_typetransition_to_avtab_helper(
> >                                 pdb, sepol_src, sepol_src, class_list,
> > -                               name, sepol_result
> > +                               name, typetrans->match_type, sepol_result
> >                         );
> >                         if (rc != SEPOL_OK) goto exit;
> >                 }
> > @@ -1270,7 +1270,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
> >
> >                                 rc = __cil_typetransition_to_avtab_helper(
> >                                         pdb, sepol_src, sepol_tgt, class_list,
> > -                                       name, sepol_result
> > +                                       name, typetrans->match_type, sepol_result
> >                                 );
> >                                 if (rc != SEPOL_OK) goto exit;
> >                         }
> > diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> > index 8976c254..94dadef8 100644
> > --- a/libsepol/cil/src/cil_build_ast.c
> > +++ b/libsepol/cil/src/cil_build_ast.c
> > @@ -3392,10 +3392,11 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >                 CIL_SYN_STRING,
> >                 CIL_SYN_STRING,
> >                 CIL_SYN_STRING | CIL_SYN_END,
> > -               CIL_SYN_END
> > +               CIL_SYN_STRING | CIL_SYN_END,
> > +               CIL_SYN_END,
> >         };
> >         size_t syntax_len = sizeof(syntax)/sizeof(*syntax);
> > -       char *s1, *s2, *s3, *s4, *s5;
> > +       char *s1, *s2, *s3, *s4, *s5, *s6;
> >
> >         if (db == NULL || parse_current == NULL || ast_node == NULL ) {
> >                 goto exit;
> > @@ -3411,12 +3412,22 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >         s3 = parse_current->next->next->next->data;
> >         s4 = parse_current->next->next->next->next->data;
> >         s5 = NULL;
> > +       s6 = NULL;
> >
> >         if (parse_current->next->next->next->next->next) {
> >                 if (s4 == CIL_KEY_STAR) {
> > -                       s4 = parse_current->next->next->next->next->next->data;
> > +                       if (parse_current->next->next->next->next->next->next) {
> > +                               s4 = parse_current->next->next->next->next->next->next->data;
> > +                       } else {
> > +                               s4 = parse_current->next->next->next->next->next->data;
> > +                       }
> >                 } else {
> > -                       s5 = parse_current->next->next->next->next->next->data;
> > +                       if (parse_current->next->next->next->next->next->next) {
> > +                               s5 = parse_current->next->next->next->next->next->data;
> > +                               s6 = parse_current->next->next->next->next->next->next->data;
> > +                       } else {
> > +                               s5 = parse_current->next->next->next->next->next->data;
> > +                       }
> >                 }
> >         }
> >
> > @@ -3428,8 +3439,13 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >                 nametypetrans->src_str = s1;
> >                 nametypetrans->tgt_str = s2;
> >                 nametypetrans->obj_str = s3;
> > -               nametypetrans->result_str = s5;
> >                 nametypetrans->name_str = s4;
> > +               if (s6) {
> > +                       nametypetrans->match_type_str = s5;
> > +                       nametypetrans->result_str = s6;
> > +               } else {
> > +                       nametypetrans->result_str = s5;
> > +               }
> >
> >                 ast_node->data = nametypetrans;
> >                 ast_node->flavor = CIL_NAMETYPETRANSITION;
> > diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
> > index bc972f03..300390ec 100644
> > --- a/libsepol/cil/src/cil_copy_ast.c
> > +++ b/libsepol/cil/src/cil_copy_ast.c
> > @@ -726,6 +726,7 @@ int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
> >         new->tgt_str = orig->tgt_str;
> >         new->obj_str = orig->obj_str;
> >         new->name_str = orig->name_str;
> > +       new->match_type_str = orig->match_type_str;
> >         new->result_str = orig->result_str;
> >
> >
> > diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> > index 9e492cb9..7f0be652 100644
> > --- a/libsepol/cil/src/cil_internal.h
> > +++ b/libsepol/cil/src/cil_internal.h
> > @@ -114,6 +114,8 @@ extern char *CIL_KEY_TUNABLEIF;
> >  extern char *CIL_KEY_ALLOW;
> >  extern char *CIL_KEY_DONTAUDIT;
> >  extern char *CIL_KEY_TYPETRANSITION;
> > +extern char *CIL_KEY_PREFIX;
> > +extern char *CIL_KEY_SUFFIX;
> >  extern char *CIL_KEY_TYPECHANGE;
> >  extern char *CIL_KEY_CALL;
> >  extern char *CIL_KEY_TUNABLE;
> > @@ -580,6 +582,8 @@ struct cil_nametypetransition {
> >         struct cil_class *obj;
> >         char *name_str;
> >         struct cil_name *name;
> > +       char *match_type_str;
> > +       uint32_t match_type;
> >         char *result_str;
> >         void *result; /* type or alias */
> >
> > diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> > index feb97868..9776dfd3 100644
> > --- a/libsepol/cil/src/cil_policy.c
> > +++ b/libsepol/cil/src/cil_policy.c
> > @@ -1260,6 +1260,7 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
> >         struct cil_name *name;
> >         struct cil_list *class_list;
> >         struct cil_list_item *i1;
> > +       const char *match_type_str = "";
> >
> >         src = trans->src;
> >         tgt = trans->tgt;
> > @@ -1268,7 +1269,21 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
> >
> >         class_list = cil_expand_class(trans->obj);
> >         cil_list_for_each(i1, class_list) {
> > -               fprintf(out, "type_transition %s %s : %s %s \"%s\";\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn);
> > +               switch (trans->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_type_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_type_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_type_str = " suffix";
> > +                       break;
> > +               default:
> > +                       match_type_str = "???";
> > +                       break;
> > +               }
> > +               fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn, match_type_str);
> >         }
> >         cil_list_destroy(&class_list, CIL_FALSE);
> >  }
> > diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
> > index 33b9d321..3b00f065 100644
> > --- a/libsepol/cil/src/cil_resolve_ast.c
> > +++ b/libsepol/cil/src/cil_resolve_ast.c
> > @@ -717,6 +717,18 @@ int cil_resolve_nametypetransition(struct cil_tree_node *current, void *extra_ar
> >                 nametypetrans->name = (struct cil_name *)name_datum;
> >         }
> >
> > +       if (nametypetrans->match_type_str == NULL) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       } else if (nametypetrans->match_type_str == CIL_KEY_PREFIX) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       } else if (nametypetrans->match_type_str == CIL_KEY_SUFFIX) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       } else {
> > +               cil_tree_log(current, CIL_ERR, "Invalid name match type \"%s\"", nametypetrans->match_type_str);
> > +               rc = SEPOL_ERR;
> > +               goto exit;
> > +       }
> > +
> >         rc = cil_resolve_name(current, nametypetrans->result_str, CIL_SYM_TYPES, extra_args, &result_datum);
> >         if (rc != SEPOL_OK) {
> >                 goto exit;
> > diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> > index 4da7a77c..99c82292 100644
> > --- a/libsepol/cil/src/cil_write_ast.c
> > +++ b/libsepol/cil/src/cil_write_ast.c
> > @@ -1178,6 +1178,8 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
> >                 fprintf(out, "%s ", datum_or_str(DATUM(rule->tgt), rule->tgt_str));
> >                 fprintf(out, "%s ", datum_or_str(DATUM(rule->obj), rule->obj_str));
> >                 fprintf(out, "\"%s\" ", datum_or_str(DATUM(rule->name), rule->name_str));
> > +               if (rule->match_type != FILENAME_TRANS_MATCH_EXACT)
> > +                       fprintf(out, "%s ", rule->match_type_str);
> >                 fprintf(out, "%s", datum_or_str(DATUM(rule->result), rule->result_str));
> >                 fprintf(out, ")\n");
> >                 break;
> > diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> > index 6682069e..7727ca52 100644
> > --- a/libsepol/include/sepol/policydb/policydb.h
> > +++ b/libsepol/include/sepol/policydb/policydb.h
> > @@ -321,6 +321,7 @@ typedef struct filename_trans_rule {
> >         uint32_t tclass;
> >         char *name;
> >         uint32_t otype; /* new type */
> > +       uint32_t match_type;

Maybe add a name for the added enum and mention it here in a comment.

> >         struct filename_trans_rule *next;
> >  } filename_trans_rule_t;
> >
> > @@ -423,6 +424,14 @@ typedef struct genfs {
> >  /* OCON_NUM needs to be the largest index in any platform's ocontext array */
> >  #define OCON_NUM   9
> >
> > +/* filename transitions table array indices */
> > +enum {
> > +       FILENAME_TRANS_MATCH_EXACT,
> > +       FILENAME_TRANS_MATCH_PREFIX,
> > +       FILENAME_TRANS_MATCH_SUFFIX,
> > +       FILENAME_TRANS_MATCH_NUM,
> > +};
> > +
> >  /* section: module information */
> >
> >  /* scope_index_t holds all of the symbols that are in scope in a
> > @@ -593,7 +602,7 @@ typedef struct policydb {
> >         hashtab_t range_tr;
> >
> >         /* file transitions with the last path component */
> > -       hashtab_t filename_trans;
> > +       hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
> >         uint32_t filename_trans_count;
> >
> >         ebitmap_t *type_attr_map;
> > @@ -657,7 +666,8 @@ extern int policydb_sort_ocontexts(policydb_t *p);
> >  extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
> >                                      uint32_t ttype, uint32_t tclass,
> >                                      const char *name, char **name_alloc,
> > -                                    uint32_t otype, uint32_t *present_otype);
> > +                                    uint32_t otype, uint32_t match_type,
> > +                                    uint32_t *present_otype);
> >
> >  /* Deprecated */
> >  extern int policydb_context_isvalid(const policydb_t * p,
> > @@ -758,10 +768,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
> >  #define POLICYDB_VERSION_INFINIBAND            31 /* Linux-specific */
> >  #define POLICYDB_VERSION_GLBLUB                32
> >  #define POLICYDB_VERSION_COMP_FTRANS   33 /* compressed filename transitions */
> > +#define POLICYDB_VERSION_PREFIX_SUFFIX 34 /* prefix and suffix filename transitions */
> >
> >  /* Range of policy versions we understand*/
> >  #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
> > -#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_COMP_FTRANS
> > +#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_PREFIX_SUFFIX
> >
> >  /* Module versions and specific changes*/
> >  #define MOD_POLICYDB_VERSION_BASE              4
> > @@ -784,9 +795,10 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
> >  #define MOD_POLICYDB_VERSION_INFINIBAND                19
> >  #define MOD_POLICYDB_VERSION_GLBLUB            20
> >  #define MOD_POLICYDB_VERSION_SELF_TYPETRANS    21
> > +#define MOD_POLICYDB_VERSION_PREFIX_SUFFIX     22
> >
> >  #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
> > -#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_SELF_TYPETRANS
> > +#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_PREFIX_SUFFIX
> >
> >  #define POLICYDB_CONFIG_MLS    1
> >
> > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > index ee5f9185..4df9521a 100644
> > --- a/libsepol/src/expand.c
> > +++ b/libsepol/src/expand.c
> > @@ -1419,18 +1419,31 @@ static int expand_filename_trans_helper(expand_state_t *state,
> >         rc = policydb_filetrans_insert(
> >                 state->out, s + 1, t + 1,
> >                 rule->tclass, rule->name,
> > -               NULL, mapped_otype, &present_otype
> > +               NULL, mapped_otype, rule->match_type, &present_otype
> >         );
> >         if (rc == SEPOL_EEXIST) {
> >                 /* duplicate rule, ignore */
> >                 if (present_otype == mapped_otype)
> >                         return 0;
> >
> > -               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\":  %s vs %s",
> > +               const char *match_str = "";
> > +               switch (rule->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\"%s:  %s vs %s",
> >                     state->out->p_type_val_to_name[s],
> >                     state->out->p_type_val_to_name[t],
> >                     state->out->p_class_val_to_name[rule->tclass - 1],
> >                     rule->name,
> > +                   match_str,
> >                     state->out->p_type_val_to_name[present_otype - 1],
> >                     state->out->p_type_val_to_name[mapped_otype - 1]);
> >                 return -1;
> > diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> > index 8fcc385d..81efeaa2 100644
> > --- a/libsepol/src/kernel_to_cil.c
> > +++ b/libsepol/src/kernel_to_cil.c
> > @@ -1869,6 +1869,7 @@ exit:
> >
> >  struct map_filename_trans_args {
> >         struct policydb *pdb;
> > +       uint32_t match_type;
> >         struct strs *strs;
> >  };
> >
> > @@ -1883,6 +1884,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >         struct ebitmap_node *node;
> >         uint32_t bit;
> >         int rc;
> > +       const char *match_str = "";
> > +
> > +       switch (map_args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
> >         class = pdb->p_class_val_to_name[ft->tclass - 1];
> > @@ -1893,8 +1907,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
> >                         src = pdb->p_type_val_to_name[bit];
> >                         rc = strs_create_and_add(strs,
> > -                                                "(typetransition %s %s %s \"%s\" %s)",
> > -                                                5, src, tgt, class, filename, new);
> > +                                                "(typetransition %s %s %s \"%s\"%s %s)",
> > +                                                6, src, tgt, class, filename, match_str, new);
> >                         if (rc)
> >                                 return rc;
> >                 }
> > @@ -1919,7 +1933,23 @@ static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
> >         args.pdb = pdb;
> >         args.strs = strs;
> >
> > -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                        map_filename_trans_to_str, &args);
> >         if (rc != 0) {
> >                 goto exit;
> >         }
> > diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
> > index b0ae16d9..99bef76e 100644
> > --- a/libsepol/src/kernel_to_conf.c
> > +++ b/libsepol/src/kernel_to_conf.c
> > @@ -1845,6 +1845,7 @@ exit:
> >
> >  struct map_filename_trans_args {
> >         struct policydb *pdb;
> > +       uint32_t match_type;
> >         struct strs *strs;
> >  };
> >
> > @@ -1859,6 +1860,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >         struct ebitmap_node *node;
> >         uint32_t bit;
> >         int rc;
> > +       const char *match_str = "";
> > +
> > +       switch (map_args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
> >         class = pdb->p_class_val_to_name[ft->tclass - 1];
> > @@ -1869,8 +1883,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
> >                         src = pdb->p_type_val_to_name[bit];
> >                         rc = strs_create_and_add(strs,
> > -                                                "type_transition %s %s:%s %s \"%s\";",
> > -                                                5, src, tgt, class, new, filename);
> > +                                                "type_transition %s %s:%s %s \"%s\"%s;",
> > +                                                6, src, tgt, class, new, filename, match_str);
> >                         if (rc)
> >                                 return rc;
> >                 }
> > @@ -1895,7 +1909,23 @@ static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
> >         args.pdb = pdb;
> >         args.strs = strs;
> >
> > -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                        map_filename_trans_to_str, &args);
> >         if (rc != 0) {
> >                 goto exit;
> >         }
> > diff --git a/libsepol/src/link.c b/libsepol/src/link.c
> > index 3b7742bc..f432087f 100644
> > --- a/libsepol/src/link.c
> > +++ b/libsepol/src/link.c
> > @@ -1440,6 +1440,7 @@ static int copy_filename_trans_list(filename_trans_rule_t * list,
> >                 new_rule->name = strdup(cur->name);
> >                 if (!new_rule->name)
> >                         goto err;
> > +               new_rule->match_type = cur->match_type;
> >
> >                 if (type_set_or_convert(&cur->stypes, &new_rule->stypes, module) ||
> >                     type_set_or_convert(&cur->ttypes, &new_rule->ttypes, module))
> > diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> > index d2868019..98e1c7ba 100644
> > --- a/libsepol/src/module_to_cil.c
> > +++ b/libsepol/src/module_to_cil.c
> > @@ -1609,6 +1609,7 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
> >         unsigned int ttype;
> >         struct type_set *ts;
> >         struct filename_trans_rule *rule;
> > +       const char *match_str = "";
> >
> >         for (rule = rules; rule != NULL; rule = rule->next) {
> >                 ts = &rule->stypes;
> > @@ -1623,19 +1624,31 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
> >                         goto exit;
> >                 }
> >
> > +               switch (rule->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +
> >                 for (stype = 0; stype < num_stypes; stype++) {
> >                         for (ttype = 0; ttype < num_ttypes; ttype++) {
> > -                               cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
> > +                               cil_println(indent, "(typetransition %s %s %s \"%s\"%s %s)",
> >                                             stypes[stype], ttypes[ttype],
> >                                             pdb->p_class_val_to_name[rule->tclass - 1],
> > -                                           rule->name,
> > +                                           rule->name, match_str,
> >                                             pdb->p_type_val_to_name[rule->otype - 1]);
> >                         }
> >                         if (rule->flags & RULE_SELF) {
> > -                               cil_println(indent, "(typetransition %s self %s \"%s\" %s)",
> > +                               cil_println(indent, "(typetransition %s self %s \"%s\"%s %s)",
> >                                             stypes[stype],
> >                                             pdb->p_class_val_to_name[rule->tclass - 1],
> > -                                           rule->name,
> > +                                           rule->name, match_str,
> >                                             pdb->p_type_val_to_name[rule->otype - 1]);
> >                         }
> >                 }
> > diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> > index f9537caa..29d7971a 100644
> > --- a/libsepol/src/policydb.c
> > +++ b/libsepol/src/policydb.c
> > @@ -208,6 +208,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = OCON_IBENDPORT + 1,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_KERN,
> > +        .version = POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = OCON_IBENDPORT + 1,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >         {
> >          .type = POLICY_BASE,
> >          .version = MOD_POLICYDB_VERSION_BASE,
> > @@ -334,6 +341,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = OCON_IBENDPORT + 1,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_BASE,
> > +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = OCON_IBENDPORT + 1,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >         {
> >          .type = POLICY_MOD,
> >          .version = MOD_POLICYDB_VERSION_BASE,
> > @@ -460,6 +474,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = 0,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_MOD,
> > +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = 0,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >  };
> >
> >  #if 0
> > @@ -909,10 +930,14 @@ int policydb_init(policydb_t * p)
> >         if (rc)
> >                 goto err;
> >
> > -       p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
> > -       if (!p->filename_trans) {
> > -               rc = -ENOMEM;
> > -               goto err;
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               p->filename_trans[i] = hashtab_create(filenametr_hash,
> > +                                                     filenametr_cmp,
> > +                                                     (1 << 10));
> > +               if (!p->filename_trans[i]) {
> > +                       rc = -ENOMEM;
> > +                       goto err;
> > +               }
> >         }
> >
> >         p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
> > @@ -926,7 +951,9 @@ int policydb_init(policydb_t * p)
> >
> >         return 0;
> >  err:
> > -       hashtab_destroy(p->filename_trans);
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               hashtab_destroy(p->filename_trans[i]);
> > +       }
> >         hashtab_destroy(p->range_tr);
> >         for (i = 0; i < SYM_NUM; i++) {
> >                 hashtab_destroy(p->symtab[i].table);
> > @@ -1564,8 +1591,10 @@ void policydb_destroy(policydb_t * p)
> >         if (lra)
> >                 free(lra);
> >
> > -       hashtab_map(p->filename_trans, filenametr_destroy, NULL);
> > -       hashtab_destroy(p->filename_trans);
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               hashtab_map(p->filename_trans[i], filenametr_destroy, NULL);
> > +               hashtab_destroy(p->filename_trans[i]);
> > +       }
> >
> >         hashtab_map(p->range_tr, range_tr_destroy, NULL);
> >         hashtab_destroy(p->range_tr);
> > @@ -2599,7 +2628,7 @@ static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
> >  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >                               uint32_t tclass, const char *name,
> >                               char **name_alloc, uint32_t otype,
> > -                             uint32_t *present_otype)
> > +                             uint32_t match_type, uint32_t *present_otype)
> >  {
> >         filename_trans_key_t *ft, key;
> >         filename_trans_datum_t *datum, *last;
> > @@ -2609,7 +2638,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >         key.name = (char *)name;
> >
> >         last = NULL;
> > -       datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
> > +       datum = hashtab_search(p->filename_trans[match_type],
> > +                              (hashtab_key_t)&key);
> >         while (datum) {
> >                 if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
> >                         if (present_otype)
> > @@ -2657,7 +2687,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >                         ft->tclass = tclass;
> >                         ft->name = name_dup;
> >
> > -                       if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> > +                       if (hashtab_insert(p->filename_trans[match_type],
> > +                                          (hashtab_key_t)ft,
> >                                            (hashtab_datum_t)datum)) {
> >                                 free(name_dup);
> >                                 free(datum);
> > @@ -2704,8 +2735,9 @@ static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
> >         tclass = le32_to_cpu(buf[2]);
> >         otype  = le32_to_cpu(buf[3]);
> >
> > +       // This version does not contain other than exact filename transitions
> >         rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
> > -                                      otype, NULL);
> > +                                      otype, FILENAME_TRANS_MATCH_EXACT, NULL);
> >         if (rc) {
> >                 if (rc != SEPOL_EEXIST)
> >                         goto err;
> > @@ -2753,7 +2785,8 @@ out:
> >         return rc;
> >  }
> >
> > -static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> > +static int filename_trans_read_one(policydb_t *p, uint32_t match_type,
> > +                                  struct policy_file *fp)
> >  {
> >         filename_trans_key_t *ft = NULL;
> >         filename_trans_datum_t **dst, *datum, *first = NULL;
> > @@ -2823,7 +2856,7 @@ static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> >         ft->tclass = tclass;
> >         ft->name = name;
> >
> > -       rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> > +       rc = hashtab_insert(p->filename_trans[match_type], (hashtab_key_t)ft,
> >                             (hashtab_datum_t)first);
> >         if (rc)
> >                 goto err;
> > @@ -2842,7 +2875,8 @@ err:
> >         return -1;
> >  }
> >
> > -static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> > +static int filename_trans_read(policydb_t *p, struct policy_file *fp,
> > +                              uint32_t match_type)
> >  {
> >         unsigned int i;
> >         uint32_t buf[1], nel;
> > @@ -2855,13 +2889,17 @@ static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> >
> >         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> >                 for (i = 0; i < nel; i++) {
> > +                       /*
> > +                        * this version does not have other than exact match
> > +                        * transitions
> > +                        */
> >                         rc = filename_trans_read_one_compat(p, fp);
> >                         if (rc < 0)
> >                                 return -1;
> >                 }
> >         } else {
> >                 for (i = 0; i < nel; i++) {
> > -                       rc = filename_trans_read_one(p, fp);
> > +                       rc = filename_trans_read_one(p, match_type, fp);
> >                         if (rc < 0)
> >                                 return -1;
> >                 }
> > @@ -3837,7 +3875,7 @@ static int role_allow_rule_read(role_allow_rule_t ** r, struct policy_file *fp)
> >  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                                     struct policy_file *fp)
> >  {
> > -       uint32_t buf[3], nel, i, len;
> > +       uint32_t buf[4], nel, i, len;
> >         unsigned int entries;
> >         filename_trans_rule_t *ftr, *lftr;
> >         int rc;
> > @@ -3883,7 +3921,9 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                 if (type_set_read(&ftr->ttypes, fp))
> >                         return -1;
> >
> > -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> > +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> > +                       entries = 4;
> > +               else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> >                         entries = 3;
> >                 else
> >                         entries = 2;
> > @@ -3895,6 +3935,8 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                 ftr->otype = le32_to_cpu(buf[1]);
> >                 if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> >                         ftr->flags = le32_to_cpu(buf[2]);
> > +               if (p->policyvers >=  MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> > +                       ftr->match_type = le32_to_cpu(buf[3]);
> >         }
> >
> >         return 0;
> > @@ -4470,7 +4512,11 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
> >                 if (role_allow_read(&p->role_allow, fp))
> >                         goto bad;
> >                 if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
> > -                   filename_trans_read(p, fp))
> > +                   filename_trans_read(p, fp, FILENAME_TRANS_MATCH_EXACT))
> > +                       goto bad;
> > +               if (r_policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX &&
> > +                   (filename_trans_read(p, fp, FILENAME_TRANS_MATCH_PREFIX) ||
> > +                    filename_trans_read(p, fp, FILENAME_TRANS_MATCH_SUFFIX)))
> >                         goto bad;
> >         } else {
> >                 /* first read the AV rule blocks, then the scope tables */
> > diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
>
> Unfortunately, the patch to validate policy capabilities that I merged
> yesterday causes problems in this part of your patch.
>
> > index 892a0ffd..3f2ba5ea 100644
> > --- a/libsepol/src/policydb_validate.c
> > +++ b/libsepol/src/policydb_validate.c
> > @@ -1,4 +1,3 @@
> > -
> >  #include <sepol/policydb/conditional.h>
> >  #include <sepol/policydb/ebitmap.h>
> >  #include <sepol/policydb/policydb.h>
>
> polcaps.h header was added before the policydb.h header
>
> > @@ -1115,14 +1114,27 @@ bad:
> >         return -1;
> >  }
> >
> > -static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
> > +static int validate_filename_trans_hashtabs(sepol_handle_t *handle,
> > +                                           const policydb_t *p,
> > +                                           validate_t flavors[])
>
> The polcaps patch actually gives you what you want here already.
>
> >  {
> > -       if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
> > -               ERR(handle, "Invalid filename trans");
> > -               return -1;
>
> This has changed.
>
> > +       if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                       validate_filename_trans, flavors))
> > +               goto bad;
> > +
> > +       if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                               validate_filename_trans, flavors))
> > +                       goto bad;
> > +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                               validate_filename_trans, flavors))
> > +                       goto bad;
> >         }
> >
> >         return 0;
> > +bad:
> > +       ERR(handle, "Invalid filename trans");
> > +       return -1;
> >  }
> >
> >  static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
> > @@ -1334,6 +1346,15 @@ static int validate_filename_trans_rules(sepol_handle_t *handle, const filename_
> >                 if (validate_simpletype(filename_trans->otype, p, flavors))
> >                         goto bad;
> >
> > +               switch (filename_trans->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       break;
> > +               default:
> > +                       goto bad;
> > +               }
> > +
> >                 /* currently only the RULE_SELF flag can be set */
> >                 if ((filename_trans->flags & ~RULE_SELF) != 0)
> >                         goto bad;
> > @@ -1554,9 +1575,10 @@ int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
> >                         goto bad;
> >                 if (validate_role_allows(handle, p->role_allow, flavors))
> >                         goto bad;
> > -               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
> > -                       if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
> > +               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> > +                       if (validate_filename_trans_hashtabs(handle, p, flavors))
> >                                 goto bad;
> > +               }
>
> Your change is very similar to what the polcap patch already did.
>
> The needed changes should be fairly straightforward.
> Thanks,
> Jim
>
>
> >         } else {
> >                 if (validate_avrule_blocks(handle, p->global, p, flavors))
> >                         goto bad;
> > diff --git a/libsepol/src/write.c b/libsepol/src/write.c
> > index 283d11c8..c9398a98 100644
> > --- a/libsepol/src/write.c
> > +++ b/libsepol/src/write.c
> > @@ -653,7 +653,8 @@ static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
> >         return 0;
> >  }
> >
> > -static int filename_trans_write(struct policydb *p, void *fp)
> > +static int filename_trans_write(struct policydb *p, uint32_t match_type,
> > +                               void *fp)
> >  {
> >         size_t items;
> >         uint32_t buf[1];
> > @@ -663,20 +664,25 @@ static int filename_trans_write(struct policydb *p, void *fp)
> >                 return 0;
> >
> >         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> > +               /*
> > +                * This version does not have other than exact match
> > +                * transitions, there is no need to count other ones.
> > +                */
> >                 buf[0] = cpu_to_le32(p->filename_trans_count);
> >                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
> >                 if (items != 1)
> >                         return POLICYDB_ERROR;
> >
> > -               rc = hashtab_map(p->filename_trans, filename_write_one_compat,
> > -                                fp);
> > +               rc = hashtab_map(p->filename_trans[match_type],
> > +                                filename_write_one_compat, fp);
> >         } else {
> > -               buf[0] = cpu_to_le32(p->filename_trans->nel);
> > +               buf[0] = cpu_to_le32(p->filename_trans[match_type]->nel);
> >                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
> >                 if (items != 1)
> >                         return POLICYDB_ERROR;
> >
> > -               rc = hashtab_map(p->filename_trans, filename_write_one, fp);
> > +               rc = hashtab_map(p->filename_trans[match_type],
> > +                                filename_write_one, fp);
> >         }
> >         return rc;
> >  }
> > @@ -1944,7 +1950,7 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
> >  {
> >         int nel = 0;
> >         size_t items, entries;
> > -       uint32_t buf[3], len;
> > +       uint32_t buf[4], len;
> >         filename_trans_rule_t *ftr;
> >
> >         for (ftr = t; ftr; ftr = ftr->next)
> > @@ -1974,8 +1980,11 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
> >                 buf[0] = cpu_to_le32(ftr->tclass);
> >                 buf[1] = cpu_to_le32(ftr->otype);
> >                 buf[2] = cpu_to_le32(ftr->flags);
> > +               buf[3] = cpu_to_le32(ftr->match_type);
> >
> > -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> > +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +                       entries = 4;
> > +               } else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> >                         entries = 3;
> >                 } else if (!(ftr->flags & RULE_SELF)) {
> >                         entries = 2;
> > @@ -2370,12 +2379,29 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
> >                 if (role_allow_write(p->role_allow, fp))
> >                         return POLICYDB_ERROR;
> >                 if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> > -                       if (filename_trans_write(p, fp))
> > +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_EXACT,
> > +                                                fp))
> >                                 return POLICYDB_ERROR;
> >                 } else {
> > -                       if (p->filename_trans)
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_EXACT])
> >                                 WARN(fp->handle, "Discarding filename type transition rules");
> >                 }
> > +               if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_PREFIX,
> > +                                                fp) ||
> > +                           filename_trans_write(p, FILENAME_TRANS_MATCH_SUFFIX,
> > +                                                fp))
> > +                               return POLICYDB_ERROR;
> > +               } else {
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_PREFIX] &&
> > +                           p->filename_trans[FILENAME_TRANS_MATCH_PREFIX]->nel)
> > +                               WARN(fp->handle,
> > +                                    "Discarding prefix filename type transition rules");
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX] &&
> > +                           p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX]->nel)
> > +                               WARN(fp->handle,
> > +                                    "Discarding suffix filename type transition rules");
> > +               }
> >         } else {
> >                 if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
> >                         return POLICYDB_ERROR;
> > --
> > 2.41.0
> >
James Carter Nov. 9, 2023, 2:51 p.m. UTC | #3
On Wed, Nov 8, 2023 at 5:35 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
>
> Currently, filename transitions are stored separately from other type
> enforcement rules and only support 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 implements the equivalent changes made by this kernel
> patch [1].
>
> This patch updates the policydb structure to contain prefix and suffix
> filename transition tables along normal filename transitions table and
> updates the code that accesses those tables. Furthermore, it adds
> match_type attribute to module and CIL structures that store filename
> transitions and updates functions that parse conf and CIL policy files.
>
> This patch does not significantly change the binary policy size when
> prefix/suffix rules are not used. In addition, with prefix/suffix rules,
> the number of filename transitions can be reduced, and therefore also
> binary policy size can be reduced.
>
> Syntax of the new prefix/suffix filename transition rule:
>
>     type_transition source_type target_type : class default_type object_name match_type;
>
>     (typetransition source_type_id target_type_id class_id object_name match_type default_type_id)
>
> where match_type is either keyword "prefix" or "suffix"
>
> Examples:
>
>     type_transition ta tb:CLASS01 tc "file01" prefix;
>     type_transition td te:CLASS01 tf "file02" suffix;
>
>     (typetransition ta tb CLASS01 "file01" prefix td)
>     (typetransition td te CLASS01 "file02" suffix tf)
>
> In the kernel, the rules have the following order of priority, if no
> matching rule is found, the code moves on to the next category:
> - exact filename transitions,
> - prefix filename transitions in the order of the longest prefix match,
> - suffix filename transitions in the order of the longest suffix match.
> This ensures the compatibility with older policies.
>
> [1]: https://lore.kernel.org/selinux/20231108101427.3514509-1-juraj@jurajmarcin.com/
>
> Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> ---
> v3:
> - reworked the solution from scratch, this time only adding the
>   prefix/suffix matching feature without moving filename transition
>   rules to the avtab
> ---
>  checkpolicy/policy_define.c                |  7 +-
>  checkpolicy/policy_define.h                |  2 +-
>  checkpolicy/policy_parse.y                 | 13 +++-
>  checkpolicy/policy_scan.l                  |  4 ++
>  checkpolicy/test/dismod.c                  | 14 +++-
>  checkpolicy/test/dispol.c                  | 26 ++++++-
>  libsepol/cil/src/cil.c                     |  6 ++
>  libsepol/cil/src/cil_binary.c              |  8 +--
>  libsepol/cil/src/cil_build_ast.c           | 26 +++++--
>  libsepol/cil/src/cil_copy_ast.c            |  1 +
>  libsepol/cil/src/cil_internal.h            |  4 ++
>  libsepol/cil/src/cil_policy.c              | 17 ++++-
>  libsepol/cil/src/cil_resolve_ast.c         | 12 ++++
>  libsepol/cil/src/cil_write_ast.c           |  2 +
>  libsepol/include/sepol/policydb/policydb.h | 20 ++++--
>  libsepol/src/expand.c                      | 17 ++++-
>  libsepol/src/kernel_to_cil.c               | 36 +++++++++-
>  libsepol/src/kernel_to_conf.c              | 36 +++++++++-
>  libsepol/src/link.c                        |  1 +
>  libsepol/src/module_to_cil.c               | 21 ++++--
>  libsepol/src/policydb.c                    | 82 +++++++++++++++++-----
>  libsepol/src/policydb_validate.c           | 36 ++++++++--
>  libsepol/src/write.c                       | 44 +++++++++---
>  23 files changed, 367 insertions(+), 68 deletions(-)
>
> diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> index 260e609d..fb8325ee 100644
> --- a/checkpolicy/policy_define.c
> +++ b/checkpolicy/policy_define.c
> @@ -3159,7 +3159,7 @@ avrule_t *define_cond_filename_trans(void)
>         return COND_ERR;
>  }
>
> -int define_filename_trans(void)
> +int define_filename_trans(uint32_t match_type)
>  {
>         char *id, *name = NULL;
>         type_set_t stypes, ttypes;
> @@ -3261,7 +3261,7 @@ int define_filename_trans(void)
>                         ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
>                                 rc = policydb_filetrans_insert(
>                                         policydbp, s+1, t+1, c+1, name,
> -                                       NULL, otype, NULL
> +                                       NULL, otype, match_type, NULL
>                                 );
>                                 if (rc != SEPOL_OK) {
>                                         if (rc == SEPOL_EEXIST) {
> @@ -3279,7 +3279,7 @@ int define_filename_trans(void)
>                         if (self) {
>                                 rc = policydb_filetrans_insert(
>                                         policydbp, s+1, s+1, c+1, name,
> -                                       NULL, otype, NULL
> +                                       NULL, otype, match_type, NULL
>                                 );
>                                 if (rc != SEPOL_OK) {
>                                         if (rc == SEPOL_EEXIST) {
> @@ -3317,6 +3317,7 @@ int define_filename_trans(void)
>                 ftr->tclass = c + 1;
>                 ftr->otype = otype;
>                 ftr->flags = self ? RULE_SELF : 0;
> +               ftr->match_type = match_type;
>         }
>
>         free(name);
> diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
> index 075b048d..05869346 100644
> --- a/checkpolicy/policy_define.h
> +++ b/checkpolicy/policy_define.h
> @@ -57,7 +57,7 @@ int define_role_trans(int class_specified);
>  int define_role_types(void);
>  int define_role_attr(void);
>  int define_roleattribute(void);
> -int define_filename_trans(void);
> +int define_filename_trans(uint32_t match_type);
>  int define_sens(void);
>  int define_te_avtab(int which);
>  int define_te_avtab_extended_perms(int which);
> diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
> index 356626e2..ee4be4ea 100644
> --- a/checkpolicy/policy_parse.y
> +++ b/checkpolicy/policy_parse.y
> @@ -153,6 +153,7 @@ typedef int (* require_func_t)(int pass);
>  %token FILESYSTEM
>  %token DEFAULT_USER DEFAULT_ROLE DEFAULT_TYPE DEFAULT_RANGE
>  %token LOW_HIGH LOW HIGH GLBLUB
> +%token PREFIX SUFFIX
>
>  %left OR
>  %left XOR
> @@ -410,6 +411,12 @@ cond_rule_def           : cond_transition_def
>                         { $$ = NULL; }
>                          ;
>  cond_transition_def    : TYPE_TRANSITION names names ':' names identifier filename ';'
> +                        { $$ = define_cond_filename_trans() ;
> +                          if ($$ == COND_ERR) return -1;}
> +                       | TYPE_TRANSITION names names ':' names identifier filename PREFIX ';'
> +                        { $$ = define_cond_filename_trans() ;
> +                          if ($$ == COND_ERR) return -1;}
> +                       | TYPE_TRANSITION names names ':' names identifier filename SUFFIX ';'
>                          { $$ = define_cond_filename_trans() ;
>                            if ($$ == COND_ERR) return -1;}
>                         | TYPE_TRANSITION names names ':' names identifier ';'
> @@ -449,7 +456,11 @@ cond_dontaudit_def : DONTAUDIT names names ':' names names ';'
>                         ;
>                         ;
>  transition_def         : TYPE_TRANSITION  names names ':' names identifier filename ';'
> -                       {if (define_filename_trans()) return -1; }
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_EXACT)) return -1; }
> +                       | TYPE_TRANSITION  names names ':' names identifier filename PREFIX ';'
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_PREFIX)) return -1; }
> +                       | TYPE_TRANSITION  names names ':' names identifier filename SUFFIX ';'
> +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_SUFFIX)) return -1; }
>                         | TYPE_TRANSITION names names ':' names identifier ';'
>                          {if (define_compute_type(AVRULE_TRANSITION)) return -1;}
>                          | TYPE_MEMBER names names ':' names identifier ';'
> diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
> index c998ff8b..0780ef15 100644
> --- a/checkpolicy/policy_scan.l
> +++ b/checkpolicy/policy_scan.l
> @@ -270,6 +270,10 @@ low |
>  LOW                            { return(LOW); }
>  glblub |
>  GLBLUB                         { return(GLBLUB); }
> +PREFIX |
> +prefix                         { return(PREFIX); }
> +SUFFIX |
> +suffix                         { return(SUFFIX); }
>  "/"[^ \n\r\t\f]*               { return(PATH); }
>  \""/"[^\"\n]*\"                { return(QPATH); }
>  \"[^"/"\"\n]+\"        { return(FILENAME); }
> diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> index fa7117f5..5995e135 100644
> --- a/checkpolicy/test/dismod.c
> +++ b/checkpolicy/test/dismod.c
> @@ -564,13 +564,25 @@ static void display_role_allow(role_allow_rule_t * ra, policydb_t * p, FILE * fp
>
>  static void display_filename_trans(filename_trans_rule_t * tr, policydb_t * p, FILE * fp)
>  {
> +       const char *match_str = "";
>         fprintf(fp, "filename transition");
>         for (; tr; tr = tr->next) {
>                 display_type_set(&tr->stypes, 0, p, fp);
>                 display_type_set(&tr->ttypes, 0, p, fp);
>                 display_id(p, fp, SYM_CLASSES, tr->tclass - 1, ":");
>                 display_id(p, fp, SYM_TYPES, tr->otype - 1, "");
> -               fprintf(fp, " %s\n", tr->name);
> +               switch (tr->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +               fprintf(fp, " %s%s\n", tr->name, match_str);
>         }
>  }
>
> diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
> index b567ce77..4426b844 100644
> --- a/checkpolicy/test/dispol.c
> +++ b/checkpolicy/test/dispol.c
> @@ -450,6 +450,7 @@ static void display_role_trans(policydb_t *p, FILE *fp)
>
>  struct filenametr_display_args {
>         policydb_t *p;
> +       uint32_t match_type;
>         FILE *fp;
>  };
>
> @@ -464,6 +465,19 @@ static int filenametr_display(hashtab_key_t key,
>         FILE *fp = args->fp;
>         ebitmap_node_t *node;
>         uint32_t bit;
> +       const char *match_str = "";
> +
> +       switch (args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         do {
>                 ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
> @@ -471,7 +485,7 @@ static int filenametr_display(hashtab_key_t key,
>                         display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
>                         display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
>                         display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
> -                       fprintf(fp, " %s\n", ft->name);
> +                       fprintf(fp, " %s%s\n", ft->name, match_str);
>                 }
>                 ftdatum = ftdatum->next;
>         } while (ftdatum);
> @@ -487,7 +501,15 @@ static void display_filename_trans(policydb_t *p, FILE *fp)
>         fprintf(fp, "filename_trans rules:\n");
>         args.p = p;
>         args.fp = fp;
> -       hashtab_map(p->filename_trans, filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                   filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                   filenametr_display, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                   filenametr_display, &args);
>  }
>
>  static int menu(void)
> diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> index 2021187d..37769fc4 100644
> --- a/libsepol/cil/src/cil.c
> +++ b/libsepol/cil/src/cil.c
> @@ -97,6 +97,8 @@ char *CIL_KEY_TUNABLEIF;
>  char *CIL_KEY_ALLOW;
>  char *CIL_KEY_DONTAUDIT;
>  char *CIL_KEY_TYPETRANSITION;
> +char *CIL_KEY_PREFIX;
> +char *CIL_KEY_SUFFIX;
>  char *CIL_KEY_TYPECHANGE;
>  char *CIL_KEY_CALL;
>  char *CIL_KEY_TUNABLE;
> @@ -269,6 +271,8 @@ static void cil_init_keys(void)
>         CIL_KEY_ALLOW = cil_strpool_add("allow");
>         CIL_KEY_DONTAUDIT = cil_strpool_add("dontaudit");
>         CIL_KEY_TYPETRANSITION = cil_strpool_add("typetransition");
> +       CIL_KEY_PREFIX = cil_strpool_add("prefix");
> +       CIL_KEY_SUFFIX = cil_strpool_add("suffix");
>         CIL_KEY_TYPECHANGE = cil_strpool_add("typechange");
>         CIL_KEY_CALL = cil_strpool_add("call");
>         CIL_KEY_TUNABLE = cil_strpool_add("tunable");
> @@ -2456,6 +2460,8 @@ void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
>         (*nametypetrans)->obj = NULL;
>         (*nametypetrans)->name_str = NULL;
>         (*nametypetrans)->name = NULL;
> +       (*nametypetrans)->match_type_str = NULL;
> +       (*nametypetrans)->match_type = FILENAME_TRANS_MATCH_EXACT;
>         (*nametypetrans)->result_str = NULL;
>         (*nametypetrans)->result = NULL;
>  }
> diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> index a8e3616a..75a9e064 100644
> --- a/libsepol/cil/src/cil_binary.c
> +++ b/libsepol/cil/src/cil_binary.c
> @@ -1168,7 +1168,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
>                                                 type_datum_t *sepol_src,
>                                                 type_datum_t *sepol_tgt,
>                                                 struct cil_list *class_list,
> -                                               char *name,
> +                                               char *name, uint32_t match_type,
>                                                 type_datum_t *sepol_result)
>  {
>         int rc;
> @@ -1183,7 +1183,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
>                 rc = policydb_filetrans_insert(
>                         pdb, sepol_src->s.value, sepol_tgt->s.value,
>                         sepol_obj->s.value, name, NULL,
> -                       sepol_result->s.value, &otype
> +                       sepol_result->s.value, match_type, &otype
>                 );
>                 if (rc != SEPOL_OK) {
>                         if (rc == SEPOL_EEXIST) {
> @@ -1252,7 +1252,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
>
>                         rc = __cil_typetransition_to_avtab_helper(
>                                 pdb, sepol_src, sepol_src, class_list,
> -                               name, sepol_result
> +                               name, typetrans->match_type, sepol_result
>                         );
>                         if (rc != SEPOL_OK) goto exit;
>                 }
> @@ -1270,7 +1270,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
>
>                                 rc = __cil_typetransition_to_avtab_helper(
>                                         pdb, sepol_src, sepol_tgt, class_list,
> -                                       name, sepol_result
> +                                       name, typetrans->match_type, sepol_result
>                                 );
>                                 if (rc != SEPOL_OK) goto exit;
>                         }
> diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> index 8976c254..94dadef8 100644
> --- a/libsepol/cil/src/cil_build_ast.c
> +++ b/libsepol/cil/src/cil_build_ast.c
> @@ -3392,10 +3392,11 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>                 CIL_SYN_STRING,
>                 CIL_SYN_STRING,
>                 CIL_SYN_STRING | CIL_SYN_END,
> -               CIL_SYN_END
> +               CIL_SYN_STRING | CIL_SYN_END,
> +               CIL_SYN_END,
>         };
>         size_t syntax_len = sizeof(syntax)/sizeof(*syntax);
> -       char *s1, *s2, *s3, *s4, *s5;
> +       char *s1, *s2, *s3, *s4, *s5, *s6;
>
>         if (db == NULL || parse_current == NULL || ast_node == NULL ) {
>                 goto exit;
> @@ -3411,12 +3412,22 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>         s3 = parse_current->next->next->next->data;
>         s4 = parse_current->next->next->next->next->data;
>         s5 = NULL;
> +       s6 = NULL;
>
>         if (parse_current->next->next->next->next->next) {
>                 if (s4 == CIL_KEY_STAR) {
> -                       s4 = parse_current->next->next->next->next->next->data;
> +                       if (parse_current->next->next->next->next->next->next) {
> +                               s4 = parse_current->next->next->next->next->next->next->data;
> +                       } else {
> +                               s4 = parse_current->next->next->next->next->next->data;
> +                       }
>                 } else {
> -                       s5 = parse_current->next->next->next->next->next->data;
> +                       if (parse_current->next->next->next->next->next->next) {
> +                               s5 = parse_current->next->next->next->next->next->data;
> +                               s6 = parse_current->next->next->next->next->next->next->data;
> +                       } else {
> +                               s5 = parse_current->next->next->next->next->next->data;
> +                       }
>                 }
>         }
>
> @@ -3428,8 +3439,13 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
>                 nametypetrans->src_str = s1;
>                 nametypetrans->tgt_str = s2;
>                 nametypetrans->obj_str = s3;
> -               nametypetrans->result_str = s5;
>                 nametypetrans->name_str = s4;
> +               if (s6) {
> +                       nametypetrans->match_type_str = s5;
> +                       nametypetrans->result_str = s6;
> +               } else {
> +                       nametypetrans->result_str = s5;
> +               }
>
>                 ast_node->data = nametypetrans;
>                 ast_node->flavor = CIL_NAMETYPETRANSITION;
> diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
> index bc972f03..300390ec 100644
> --- a/libsepol/cil/src/cil_copy_ast.c
> +++ b/libsepol/cil/src/cil_copy_ast.c
> @@ -726,6 +726,7 @@ int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
>         new->tgt_str = orig->tgt_str;
>         new->obj_str = orig->obj_str;
>         new->name_str = orig->name_str;
> +       new->match_type_str = orig->match_type_str;
>         new->result_str = orig->result_str;
>
>
> diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> index 9e492cb9..7f0be652 100644
> --- a/libsepol/cil/src/cil_internal.h
> +++ b/libsepol/cil/src/cil_internal.h
> @@ -114,6 +114,8 @@ extern char *CIL_KEY_TUNABLEIF;
>  extern char *CIL_KEY_ALLOW;
>  extern char *CIL_KEY_DONTAUDIT;
>  extern char *CIL_KEY_TYPETRANSITION;
> +extern char *CIL_KEY_PREFIX;
> +extern char *CIL_KEY_SUFFIX;
>  extern char *CIL_KEY_TYPECHANGE;
>  extern char *CIL_KEY_CALL;
>  extern char *CIL_KEY_TUNABLE;
> @@ -580,6 +582,8 @@ struct cil_nametypetransition {
>         struct cil_class *obj;
>         char *name_str;
>         struct cil_name *name;
> +       char *match_type_str;
> +       uint32_t match_type;
>         char *result_str;
>         void *result; /* type or alias */
>
> diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> index feb97868..9776dfd3 100644
> --- a/libsepol/cil/src/cil_policy.c
> +++ b/libsepol/cil/src/cil_policy.c
> @@ -1260,6 +1260,7 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
>         struct cil_name *name;
>         struct cil_list *class_list;
>         struct cil_list_item *i1;
> +       const char *match_type_str = "";
>
>         src = trans->src;
>         tgt = trans->tgt;
> @@ -1268,7 +1269,21 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
>
>         class_list = cil_expand_class(trans->obj);
>         cil_list_for_each(i1, class_list) {
> -               fprintf(out, "type_transition %s %s : %s %s \"%s\";\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn);
> +               switch (trans->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_type_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_type_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_type_str = " suffix";
> +                       break;
> +               default:
> +                       match_type_str = "???";
> +                       break;
> +               }
> +               fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn, match_type_str);
>         }
>         cil_list_destroy(&class_list, CIL_FALSE);
>  }
> diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
> index 33b9d321..3b00f065 100644
> --- a/libsepol/cil/src/cil_resolve_ast.c
> +++ b/libsepol/cil/src/cil_resolve_ast.c
> @@ -717,6 +717,18 @@ int cil_resolve_nametypetransition(struct cil_tree_node *current, void *extra_ar
>                 nametypetrans->name = (struct cil_name *)name_datum;
>         }
>
> +       if (nametypetrans->match_type_str == NULL) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_EXACT;
> +       } else if (nametypetrans->match_type_str == CIL_KEY_PREFIX) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       } else if (nametypetrans->match_type_str == CIL_KEY_SUFFIX) {
> +               nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       } else {
> +               cil_tree_log(current, CIL_ERR, "Invalid name match type \"%s\"", nametypetrans->match_type_str);
> +               rc = SEPOL_ERR;
> +               goto exit;
> +       }
> +
>         rc = cil_resolve_name(current, nametypetrans->result_str, CIL_SYM_TYPES, extra_args, &result_datum);
>         if (rc != SEPOL_OK) {
>                 goto exit;
> diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> index 4da7a77c..99c82292 100644
> --- a/libsepol/cil/src/cil_write_ast.c
> +++ b/libsepol/cil/src/cil_write_ast.c
> @@ -1178,6 +1178,8 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
>                 fprintf(out, "%s ", datum_or_str(DATUM(rule->tgt), rule->tgt_str));
>                 fprintf(out, "%s ", datum_or_str(DATUM(rule->obj), rule->obj_str));
>                 fprintf(out, "\"%s\" ", datum_or_str(DATUM(rule->name), rule->name_str));
> +               if (rule->match_type != FILENAME_TRANS_MATCH_EXACT)
> +                       fprintf(out, "%s ", rule->match_type_str);
>                 fprintf(out, "%s", datum_or_str(DATUM(rule->result), rule->result_str));
>                 fprintf(out, ")\n");
>                 break;
> diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> index 6682069e..7727ca52 100644
> --- a/libsepol/include/sepol/policydb/policydb.h
> +++ b/libsepol/include/sepol/policydb/policydb.h
> @@ -321,6 +321,7 @@ typedef struct filename_trans_rule {
>         uint32_t tclass;
>         char *name;
>         uint32_t otype; /* new type */
> +       uint32_t match_type;
>         struct filename_trans_rule *next;
>  } filename_trans_rule_t;
>
> @@ -423,6 +424,14 @@ typedef struct genfs {
>  /* OCON_NUM needs to be the largest index in any platform's ocontext array */
>  #define OCON_NUM   9
>
> +/* filename transitions table array indices */
> +enum {
> +       FILENAME_TRANS_MATCH_EXACT,
> +       FILENAME_TRANS_MATCH_PREFIX,
> +       FILENAME_TRANS_MATCH_SUFFIX,
> +       FILENAME_TRANS_MATCH_NUM,
> +};
> +
>  /* section: module information */
>
>  /* scope_index_t holds all of the symbols that are in scope in a
> @@ -593,7 +602,7 @@ typedef struct policydb {
>         hashtab_t range_tr;
>
>         /* file transitions with the last path component */
> -       hashtab_t filename_trans;
> +       hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
>         uint32_t filename_trans_count;
>
>         ebitmap_t *type_attr_map;
> @@ -657,7 +666,8 @@ extern int policydb_sort_ocontexts(policydb_t *p);
>  extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
>                                      uint32_t ttype, uint32_t tclass,
>                                      const char *name, char **name_alloc,
> -                                    uint32_t otype, uint32_t *present_otype);
> +                                    uint32_t otype, uint32_t match_type,
> +                                    uint32_t *present_otype);
>
>  /* Deprecated */
>  extern int policydb_context_isvalid(const policydb_t * p,
> @@ -758,10 +768,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
>  #define POLICYDB_VERSION_INFINIBAND            31 /* Linux-specific */
>  #define POLICYDB_VERSION_GLBLUB                32
>  #define POLICYDB_VERSION_COMP_FTRANS   33 /* compressed filename transitions */
> +#define POLICYDB_VERSION_PREFIX_SUFFIX 34 /* prefix and suffix filename transitions */
>
>  /* Range of policy versions we understand*/
>  #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
> -#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_COMP_FTRANS
> +#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_PREFIX_SUFFIX
>
>  /* Module versions and specific changes*/
>  #define MOD_POLICYDB_VERSION_BASE              4
> @@ -784,9 +795,10 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
>  #define MOD_POLICYDB_VERSION_INFINIBAND                19
>  #define MOD_POLICYDB_VERSION_GLBLUB            20
>  #define MOD_POLICYDB_VERSION_SELF_TYPETRANS    21
> +#define MOD_POLICYDB_VERSION_PREFIX_SUFFIX     22
>
>  #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
> -#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_SELF_TYPETRANS
> +#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_PREFIX_SUFFIX
>
>  #define POLICYDB_CONFIG_MLS    1
>
> diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> index ee5f9185..4df9521a 100644
> --- a/libsepol/src/expand.c
> +++ b/libsepol/src/expand.c
> @@ -1419,18 +1419,31 @@ static int expand_filename_trans_helper(expand_state_t *state,
>         rc = policydb_filetrans_insert(
>                 state->out, s + 1, t + 1,
>                 rule->tclass, rule->name,
> -               NULL, mapped_otype, &present_otype
> +               NULL, mapped_otype, rule->match_type, &present_otype
>         );
>         if (rc == SEPOL_EEXIST) {
>                 /* duplicate rule, ignore */
>                 if (present_otype == mapped_otype)
>                         return 0;
>
> -               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\":  %s vs %s",
> +               const char *match_str = "";
> +               switch (rule->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\"%s:  %s vs %s",
>                     state->out->p_type_val_to_name[s],
>                     state->out->p_type_val_to_name[t],
>                     state->out->p_class_val_to_name[rule->tclass - 1],
>                     rule->name,
> +                   match_str,
>                     state->out->p_type_val_to_name[present_otype - 1],
>                     state->out->p_type_val_to_name[mapped_otype - 1]);
>                 return -1;
> diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> index 8fcc385d..81efeaa2 100644
> --- a/libsepol/src/kernel_to_cil.c
> +++ b/libsepol/src/kernel_to_cil.c
> @@ -1869,6 +1869,7 @@ exit:
>
>  struct map_filename_trans_args {
>         struct policydb *pdb;
> +       uint32_t match_type;
>         struct strs *strs;
>  };
>
> @@ -1883,6 +1884,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>         struct ebitmap_node *node;
>         uint32_t bit;
>         int rc;
> +       const char *match_str = "";
> +
> +       switch (map_args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
>         class = pdb->p_class_val_to_name[ft->tclass - 1];
> @@ -1893,8 +1907,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
>                         src = pdb->p_type_val_to_name[bit];
>                         rc = strs_create_and_add(strs,
> -                                                "(typetransition %s %s %s \"%s\" %s)",
> -                                                5, src, tgt, class, filename, new);
> +                                                "(typetransition %s %s %s \"%s\"%s %s)",
> +                                                6, src, tgt, class, filename, match_str, new);
>                         if (rc)
>                                 return rc;
>                 }
> @@ -1919,7 +1933,23 @@ static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
>         args.pdb = pdb;
>         args.strs = strs;
>
> -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                        map_filename_trans_to_str, &args);
>         if (rc != 0) {
>                 goto exit;
>         }
> diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
> index b0ae16d9..99bef76e 100644
> --- a/libsepol/src/kernel_to_conf.c
> +++ b/libsepol/src/kernel_to_conf.c
> @@ -1845,6 +1845,7 @@ exit:
>
>  struct map_filename_trans_args {
>         struct policydb *pdb;
> +       uint32_t match_type;
>         struct strs *strs;
>  };
>
> @@ -1859,6 +1860,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>         struct ebitmap_node *node;
>         uint32_t bit;
>         int rc;
> +       const char *match_str = "";
> +
> +       switch (map_args->match_type) {
> +       case FILENAME_TRANS_MATCH_EXACT:
> +               match_str = "";
> +               break;
> +       case FILENAME_TRANS_MATCH_PREFIX:
> +               match_str = " prefix";
> +               break;
> +       case FILENAME_TRANS_MATCH_SUFFIX:
> +               match_str = " suffix";
> +               break;
> +       }
>
>         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
>         class = pdb->p_class_val_to_name[ft->tclass - 1];
> @@ -1869,8 +1883,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
>                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
>                         src = pdb->p_type_val_to_name[bit];
>                         rc = strs_create_and_add(strs,
> -                                                "type_transition %s %s:%s %s \"%s\";",
> -                                                5, src, tgt, class, new, filename);
> +                                                "type_transition %s %s:%s %s \"%s\"%s;",
> +                                                6, src, tgt, class, new, filename, match_str);
>                         if (rc)
>                                 return rc;
>                 }
> @@ -1895,7 +1909,23 @@ static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
>         args.pdb = pdb;
>         args.strs = strs;
>
> -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                        map_filename_trans_to_str, &args);
> +       if (rc != 0) {
> +               goto exit;
> +       }
> +
> +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                        map_filename_trans_to_str, &args);
>         if (rc != 0) {
>                 goto exit;
>         }
> diff --git a/libsepol/src/link.c b/libsepol/src/link.c
> index 3b7742bc..f432087f 100644
> --- a/libsepol/src/link.c
> +++ b/libsepol/src/link.c
> @@ -1440,6 +1440,7 @@ static int copy_filename_trans_list(filename_trans_rule_t * list,
>                 new_rule->name = strdup(cur->name);
>                 if (!new_rule->name)
>                         goto err;
> +               new_rule->match_type = cur->match_type;
>
>                 if (type_set_or_convert(&cur->stypes, &new_rule->stypes, module) ||
>                     type_set_or_convert(&cur->ttypes, &new_rule->ttypes, module))
> diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> index d2868019..98e1c7ba 100644
> --- a/libsepol/src/module_to_cil.c
> +++ b/libsepol/src/module_to_cil.c
> @@ -1609,6 +1609,7 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
>         unsigned int ttype;
>         struct type_set *ts;
>         struct filename_trans_rule *rule;
> +       const char *match_str = "";
>
>         for (rule = rules; rule != NULL; rule = rule->next) {
>                 ts = &rule->stypes;
> @@ -1623,19 +1624,31 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
>                         goto exit;
>                 }
>
> +               switch (rule->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +                       match_str = "";
> +                       break;
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +                       match_str = " prefix";
> +                       break;
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       match_str = " suffix";
> +                       break;
> +               }
> +
>                 for (stype = 0; stype < num_stypes; stype++) {
>                         for (ttype = 0; ttype < num_ttypes; ttype++) {
> -                               cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
> +                               cil_println(indent, "(typetransition %s %s %s \"%s\"%s %s)",
>                                             stypes[stype], ttypes[ttype],
>                                             pdb->p_class_val_to_name[rule->tclass - 1],
> -                                           rule->name,
> +                                           rule->name, match_str,
>                                             pdb->p_type_val_to_name[rule->otype - 1]);
>                         }
>                         if (rule->flags & RULE_SELF) {
> -                               cil_println(indent, "(typetransition %s self %s \"%s\" %s)",
> +                               cil_println(indent, "(typetransition %s self %s \"%s\"%s %s)",
>                                             stypes[stype],
>                                             pdb->p_class_val_to_name[rule->tclass - 1],
> -                                           rule->name,
> +                                           rule->name, match_str,
>                                             pdb->p_type_val_to_name[rule->otype - 1]);
>                         }
>                 }
> diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> index f9537caa..29d7971a 100644
> --- a/libsepol/src/policydb.c
> +++ b/libsepol/src/policydb.c
> @@ -208,6 +208,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = OCON_IBENDPORT + 1,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_KERN,
> +        .version = POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = OCON_IBENDPORT + 1,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>         {
>          .type = POLICY_BASE,
>          .version = MOD_POLICYDB_VERSION_BASE,
> @@ -334,6 +341,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = OCON_IBENDPORT + 1,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_BASE,
> +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = OCON_IBENDPORT + 1,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>         {
>          .type = POLICY_MOD,
>          .version = MOD_POLICYDB_VERSION_BASE,
> @@ -460,6 +474,13 @@ static const struct policydb_compat_info policydb_compat[] = {
>          .ocon_num = 0,
>          .target_platform = SEPOL_TARGET_SELINUX,
>         },
> +       {
> +        .type = POLICY_MOD,
> +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> +        .sym_num = SYM_NUM,
> +        .ocon_num = 0,
> +        .target_platform = SEPOL_TARGET_SELINUX,
> +       },
>  };
>
>  #if 0
> @@ -909,10 +930,14 @@ int policydb_init(policydb_t * p)
>         if (rc)
>                 goto err;
>
> -       p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
> -       if (!p->filename_trans) {
> -               rc = -ENOMEM;
> -               goto err;
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               p->filename_trans[i] = hashtab_create(filenametr_hash,
> +                                                     filenametr_cmp,
> +                                                     (1 << 10));
> +               if (!p->filename_trans[i]) {
> +                       rc = -ENOMEM;
> +                       goto err;
> +               }
>         }
>
>         p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
> @@ -926,7 +951,9 @@ int policydb_init(policydb_t * p)
>
>         return 0;
>  err:
> -       hashtab_destroy(p->filename_trans);
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               hashtab_destroy(p->filename_trans[i]);
> +       }
>         hashtab_destroy(p->range_tr);
>         for (i = 0; i < SYM_NUM; i++) {
>                 hashtab_destroy(p->symtab[i].table);
> @@ -1564,8 +1591,10 @@ void policydb_destroy(policydb_t * p)
>         if (lra)
>                 free(lra);
>
> -       hashtab_map(p->filename_trans, filenametr_destroy, NULL);
> -       hashtab_destroy(p->filename_trans);
> +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> +               hashtab_map(p->filename_trans[i], filenametr_destroy, NULL);
> +               hashtab_destroy(p->filename_trans[i]);
> +       }
>
>         hashtab_map(p->range_tr, range_tr_destroy, NULL);
>         hashtab_destroy(p->range_tr);
> @@ -2599,7 +2628,7 @@ static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
>  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>                               uint32_t tclass, const char *name,
>                               char **name_alloc, uint32_t otype,
> -                             uint32_t *present_otype)
> +                             uint32_t match_type, uint32_t *present_otype)
>  {
>         filename_trans_key_t *ft, key;
>         filename_trans_datum_t *datum, *last;
> @@ -2609,7 +2638,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>         key.name = (char *)name;
>
>         last = NULL;
> -       datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
> +       datum = hashtab_search(p->filename_trans[match_type],
> +                              (hashtab_key_t)&key);
>         while (datum) {
>                 if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
>                         if (present_otype)
> @@ -2657,7 +2687,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
>                         ft->tclass = tclass;
>                         ft->name = name_dup;
>
> -                       if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> +                       if (hashtab_insert(p->filename_trans[match_type],
> +                                          (hashtab_key_t)ft,
>                                            (hashtab_datum_t)datum)) {
>                                 free(name_dup);
>                                 free(datum);
> @@ -2704,8 +2735,9 @@ static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
>         tclass = le32_to_cpu(buf[2]);
>         otype  = le32_to_cpu(buf[3]);
>
> +       // This version does not contain other than exact filename transitions
>         rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
> -                                      otype, NULL);
> +                                      otype, FILENAME_TRANS_MATCH_EXACT, NULL);
>         if (rc) {
>                 if (rc != SEPOL_EEXIST)
>                         goto err;
> @@ -2753,7 +2785,8 @@ out:
>         return rc;
>  }
>
> -static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> +static int filename_trans_read_one(policydb_t *p, uint32_t match_type,
> +                                  struct policy_file *fp)
>  {
>         filename_trans_key_t *ft = NULL;
>         filename_trans_datum_t **dst, *datum, *first = NULL;
> @@ -2823,7 +2856,7 @@ static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
>         ft->tclass = tclass;
>         ft->name = name;
>
> -       rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> +       rc = hashtab_insert(p->filename_trans[match_type], (hashtab_key_t)ft,
>                             (hashtab_datum_t)first);
>         if (rc)
>                 goto err;
> @@ -2842,7 +2875,8 @@ err:
>         return -1;
>  }
>
> -static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> +static int filename_trans_read(policydb_t *p, struct policy_file *fp,
> +                              uint32_t match_type)
>  {
>         unsigned int i;
>         uint32_t buf[1], nel;
> @@ -2855,13 +2889,17 @@ static int filename_trans_read(policydb_t *p, struct policy_file *fp)
>
>         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
>                 for (i = 0; i < nel; i++) {
> +                       /*
> +                        * this version does not have other than exact match
> +                        * transitions
> +                        */
>                         rc = filename_trans_read_one_compat(p, fp);
>                         if (rc < 0)
>                                 return -1;
>                 }
>         } else {
>                 for (i = 0; i < nel; i++) {
> -                       rc = filename_trans_read_one(p, fp);
> +                       rc = filename_trans_read_one(p, match_type, fp);
>                         if (rc < 0)
>                                 return -1;
>                 }
> @@ -3837,7 +3875,7 @@ static int role_allow_rule_read(role_allow_rule_t ** r, struct policy_file *fp)
>  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                                     struct policy_file *fp)
>  {
> -       uint32_t buf[3], nel, i, len;
> +       uint32_t buf[4], nel, i, len;
>         unsigned int entries;
>         filename_trans_rule_t *ftr, *lftr;
>         int rc;
> @@ -3883,7 +3921,9 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                 if (type_set_read(&ftr->ttypes, fp))
>                         return -1;
>
> -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> +                       entries = 4;
> +               else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
>                         entries = 3;
>                 else
>                         entries = 2;
> @@ -3895,6 +3935,8 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
>                 ftr->otype = le32_to_cpu(buf[1]);
>                 if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
>                         ftr->flags = le32_to_cpu(buf[2]);
> +               if (p->policyvers >=  MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> +                       ftr->match_type = le32_to_cpu(buf[3]);
>         }
>
>         return 0;
> @@ -4470,7 +4512,11 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
>                 if (role_allow_read(&p->role_allow, fp))
>                         goto bad;
>                 if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
> -                   filename_trans_read(p, fp))
> +                   filename_trans_read(p, fp, FILENAME_TRANS_MATCH_EXACT))
> +                       goto bad;
> +               if (r_policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX &&
> +                   (filename_trans_read(p, fp, FILENAME_TRANS_MATCH_PREFIX) ||
> +                    filename_trans_read(p, fp, FILENAME_TRANS_MATCH_SUFFIX)))
>                         goto bad;
>         } else {
>                 /* first read the AV rule blocks, then the scope tables */
> diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
> index 892a0ffd..3f2ba5ea 100644
> --- a/libsepol/src/policydb_validate.c
> +++ b/libsepol/src/policydb_validate.c
> @@ -1,4 +1,3 @@
> -
>  #include <sepol/policydb/conditional.h>
>  #include <sepol/policydb/ebitmap.h>
>  #include <sepol/policydb/policydb.h>
> @@ -1115,14 +1114,27 @@ bad:
>         return -1;
>  }
>
> -static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
> +static int validate_filename_trans_hashtabs(sepol_handle_t *handle,
> +                                           const policydb_t *p,
> +                                           validate_t flavors[])
>  {
> -       if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
> -               ERR(handle, "Invalid filename trans");
> -               return -1;
> +       if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> +                       validate_filename_trans, flavors))
> +               goto bad;
> +
> +       if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> +                               validate_filename_trans, flavors))
> +                       goto bad;
> +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> +                               validate_filename_trans, flavors))
> +                       goto bad;
>         }
>
>         return 0;
> +bad:
> +       ERR(handle, "Invalid filename trans");
> +       return -1;
>  }
>
>  static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
> @@ -1334,6 +1346,15 @@ static int validate_filename_trans_rules(sepol_handle_t *handle, const filename_
>                 if (validate_simpletype(filename_trans->otype, p, flavors))
>                         goto bad;
>
> +               switch (filename_trans->match_type) {
> +               case FILENAME_TRANS_MATCH_EXACT:
> +               case FILENAME_TRANS_MATCH_PREFIX:
> +               case FILENAME_TRANS_MATCH_SUFFIX:
> +                       break;
> +               default:
> +                       goto bad;
> +               }
> +
>                 /* currently only the RULE_SELF flag can be set */
>                 if ((filename_trans->flags & ~RULE_SELF) != 0)
>                         goto bad;
> @@ -1554,9 +1575,10 @@ int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
>                         goto bad;
>                 if (validate_role_allows(handle, p->role_allow, flavors))
>                         goto bad;
> -               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
> -                       if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
> +               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> +                       if (validate_filename_trans_hashtabs(handle, p, flavors))
>                                 goto bad;
> +               }
>         } else {
>                 if (validate_avrule_blocks(handle, p->global, p, flavors))
>                         goto bad;
> diff --git a/libsepol/src/write.c b/libsepol/src/write.c
> index 283d11c8..c9398a98 100644
> --- a/libsepol/src/write.c
> +++ b/libsepol/src/write.c
> @@ -653,7 +653,8 @@ static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
>         return 0;
>  }
>
> -static int filename_trans_write(struct policydb *p, void *fp)
> +static int filename_trans_write(struct policydb *p, uint32_t match_type,
> +                               void *fp)
>  {
>         size_t items;
>         uint32_t buf[1];
> @@ -663,20 +664,25 @@ static int filename_trans_write(struct policydb *p, void *fp)
>                 return 0;
>
>         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> +               /*
> +                * This version does not have other than exact match
> +                * transitions, there is no need to count other ones.
> +                */
>                 buf[0] = cpu_to_le32(p->filename_trans_count);
>                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
>                 if (items != 1)
>                         return POLICYDB_ERROR;
>
> -               rc = hashtab_map(p->filename_trans, filename_write_one_compat,
> -                                fp);
> +               rc = hashtab_map(p->filename_trans[match_type],
> +                                filename_write_one_compat, fp);
>         } else {
> -               buf[0] = cpu_to_le32(p->filename_trans->nel);
> +               buf[0] = cpu_to_le32(p->filename_trans[match_type]->nel);
>                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
>                 if (items != 1)
>                         return POLICYDB_ERROR;
>
> -               rc = hashtab_map(p->filename_trans, filename_write_one, fp);
> +               rc = hashtab_map(p->filename_trans[match_type],
> +                                filename_write_one, fp);
>         }
>         return rc;
>  }
> @@ -1944,7 +1950,7 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
>  {
>         int nel = 0;
>         size_t items, entries;
> -       uint32_t buf[3], len;
> +       uint32_t buf[4], len;
>         filename_trans_rule_t *ftr;
>
>         for (ftr = t; ftr; ftr = ftr->next)
> @@ -1974,8 +1980,11 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
>                 buf[0] = cpu_to_le32(ftr->tclass);
>                 buf[1] = cpu_to_le32(ftr->otype);
>                 buf[2] = cpu_to_le32(ftr->flags);
> +               buf[3] = cpu_to_le32(ftr->match_type);
>
> -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
> +                       entries = 4;
> +               } else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
>                         entries = 3;
>                 } else if (!(ftr->flags & RULE_SELF)) {
>                         entries = 2;
> @@ -2370,12 +2379,29 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
>                 if (role_allow_write(p->role_allow, fp))
>                         return POLICYDB_ERROR;
>                 if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> -                       if (filename_trans_write(p, fp))
> +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_EXACT,
> +                                                fp))
>                                 return POLICYDB_ERROR;
>                 } else {
> -                       if (p->filename_trans)
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_EXACT])
>                                 WARN(fp->handle, "Discarding filename type transition rules");
>                 }
> +               if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_PREFIX,
> +                                                fp) ||
> +                           filename_trans_write(p, FILENAME_TRANS_MATCH_SUFFIX,
> +                                                fp))
> +                               return POLICYDB_ERROR;
> +               } else {
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_PREFIX] &&
> +                           p->filename_trans[FILENAME_TRANS_MATCH_PREFIX]->nel)
> +                               WARN(fp->handle,
> +                                    "Discarding prefix filename type transition rules");
> +                       if (p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX] &&
> +                           p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX]->nel)
> +                               WARN(fp->handle,
> +                                    "Discarding suffix filename type transition rules");
> +               }
>         } else {
>                 if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
>                         return POLICYDB_ERROR;
> --
> 2.41.0
>

I fixed up the validate stuff so I could do some testing.

I found an issue when compiling CIL to policy version 32 (version 33
works fine).

The problem also happens with a binary generated from a policy.conf
file as well.
WIth modules, it is a little more complicated. If I compile the
modules with module version 22 and then expand to a binary version 32,
then I get the same problem.
If I compile the modules to some version less than 22 and then expand
to a binary version 32, there is no problem.


With this CIL policy:

(handleunknown deny)
(class CLASS01 (PERM01))
(class CLASS02 (PERM02))
(class CLASS03 (PERM03))
(classorder (CLASS01 CLASS02 CLASS03))
(sid kernel)
(sidorder (kernel))
(user USER1)
(role ROLE1)
(type TYPE1)
(category CAT1)
(categoryorder (CAT1))
(sensitivity SENS1)
(sensitivityorder (SENS1))
(sensitivitycategory SENS1 (CAT1))
(allow TYPE1 self (CLASS01 (PERM01)))
(roletype ROLE1 TYPE1)
(userrole USER1 ROLE1)
(userlevel USER1 (SENS1))
(userrange USER1 ((SENS1)(SENS1 (CAT1))))
(sidcontext kernel (USER1 ROLE1 TYPE1 ((SENS1)(SENS1))))

(type ta)
(type tb)
(type tc)

(typetransition ta tb CLASS01 "file01" tc)
(typetransition ta tb CLASS02 "file02" prefix tc)
(typetransition ta tb CLASS03 "file03" suffix tc)

Compiling to a binary policy works fine:
~/local/usr/bin/secilc -c 32 -o test.bin test.cil

Trying to convert that binary back to CIL does not:
~/local/usr/bin/checkpolicy -C -b -o test.bin.cil test.bin

I get a generic "error(s) encountered while parsing configuration"
from checkpolicy, so I don't know what is causing the problem yet.
Like I said, this works for policy version 33.

I'll let you know if I figure out more.

Thanks,
Jim
Juraj Marcin Nov. 9, 2023, 3:51 p.m. UTC | #4
On 2023-11-09 09:51, James Carter wrote:
> On Wed, Nov 8, 2023 at 5:35 AM Juraj Marcin <juraj@jurajmarcin.com> wrote:
> >
> > Currently, filename transitions are stored separately from other type
> > enforcement rules and only support 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 implements the equivalent changes made by this kernel
> > patch [1].
> >
> > This patch updates the policydb structure to contain prefix and suffix
> > filename transition tables along normal filename transitions table and
> > updates the code that accesses those tables. Furthermore, it adds
> > match_type attribute to module and CIL structures that store filename
> > transitions and updates functions that parse conf and CIL policy files.
> >
> > This patch does not significantly change the binary policy size when
> > prefix/suffix rules are not used. In addition, with prefix/suffix rules,
> > the number of filename transitions can be reduced, and therefore also
> > binary policy size can be reduced.
> >
> > Syntax of the new prefix/suffix filename transition rule:
> >
> >     type_transition source_type target_type : class default_type object_name match_type;
> >
> >     (typetransition source_type_id target_type_id class_id object_name match_type default_type_id)
> >
> > where match_type is either keyword "prefix" or "suffix"
> >
> > Examples:
> >
> >     type_transition ta tb:CLASS01 tc "file01" prefix;
> >     type_transition td te:CLASS01 tf "file02" suffix;
> >
> >     (typetransition ta tb CLASS01 "file01" prefix td)
> >     (typetransition td te CLASS01 "file02" suffix tf)
> >
> > In the kernel, the rules have the following order of priority, if no
> > matching rule is found, the code moves on to the next category:
> > - exact filename transitions,
> > - prefix filename transitions in the order of the longest prefix match,
> > - suffix filename transitions in the order of the longest suffix match.
> > This ensures the compatibility with older policies.
> >
> > [1]: https://lore.kernel.org/selinux/20231108101427.3514509-1-juraj@jurajmarcin.com/
> >
> > Reviewed-by: Ondrej Mosnacek <omosnace@redhat.com>
> > Signed-off-by: Juraj Marcin <juraj@jurajmarcin.com>
> > ---
> > v3:
> > - reworked the solution from scratch, this time only adding the
> >   prefix/suffix matching feature without moving filename transition
> >   rules to the avtab
> > ---
> >  checkpolicy/policy_define.c                |  7 +-
> >  checkpolicy/policy_define.h                |  2 +-
> >  checkpolicy/policy_parse.y                 | 13 +++-
> >  checkpolicy/policy_scan.l                  |  4 ++
> >  checkpolicy/test/dismod.c                  | 14 +++-
> >  checkpolicy/test/dispol.c                  | 26 ++++++-
> >  libsepol/cil/src/cil.c                     |  6 ++
> >  libsepol/cil/src/cil_binary.c              |  8 +--
> >  libsepol/cil/src/cil_build_ast.c           | 26 +++++--
> >  libsepol/cil/src/cil_copy_ast.c            |  1 +
> >  libsepol/cil/src/cil_internal.h            |  4 ++
> >  libsepol/cil/src/cil_policy.c              | 17 ++++-
> >  libsepol/cil/src/cil_resolve_ast.c         | 12 ++++
> >  libsepol/cil/src/cil_write_ast.c           |  2 +
> >  libsepol/include/sepol/policydb/policydb.h | 20 ++++--
> >  libsepol/src/expand.c                      | 17 ++++-
> >  libsepol/src/kernel_to_cil.c               | 36 +++++++++-
> >  libsepol/src/kernel_to_conf.c              | 36 +++++++++-
> >  libsepol/src/link.c                        |  1 +
> >  libsepol/src/module_to_cil.c               | 21 ++++--
> >  libsepol/src/policydb.c                    | 82 +++++++++++++++++-----
> >  libsepol/src/policydb_validate.c           | 36 ++++++++--
> >  libsepol/src/write.c                       | 44 +++++++++---
> >  23 files changed, 367 insertions(+), 68 deletions(-)
> >
> > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > index 260e609d..fb8325ee 100644
> > --- a/checkpolicy/policy_define.c
> > +++ b/checkpolicy/policy_define.c
> > @@ -3159,7 +3159,7 @@ avrule_t *define_cond_filename_trans(void)
> >         return COND_ERR;
> >  }
> >
> > -int define_filename_trans(void)
> > +int define_filename_trans(uint32_t match_type)
> >  {
> >         char *id, *name = NULL;
> >         type_set_t stypes, ttypes;
> > @@ -3261,7 +3261,7 @@ int define_filename_trans(void)
> >                         ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
> >                                 rc = policydb_filetrans_insert(
> >                                         policydbp, s+1, t+1, c+1, name,
> > -                                       NULL, otype, NULL
> > +                                       NULL, otype, match_type, NULL
> >                                 );
> >                                 if (rc != SEPOL_OK) {
> >                                         if (rc == SEPOL_EEXIST) {
> > @@ -3279,7 +3279,7 @@ int define_filename_trans(void)
> >                         if (self) {
> >                                 rc = policydb_filetrans_insert(
> >                                         policydbp, s+1, s+1, c+1, name,
> > -                                       NULL, otype, NULL
> > +                                       NULL, otype, match_type, NULL
> >                                 );
> >                                 if (rc != SEPOL_OK) {
> >                                         if (rc == SEPOL_EEXIST) {
> > @@ -3317,6 +3317,7 @@ int define_filename_trans(void)
> >                 ftr->tclass = c + 1;
> >                 ftr->otype = otype;
> >                 ftr->flags = self ? RULE_SELF : 0;
> > +               ftr->match_type = match_type;
> >         }
> >
> >         free(name);
> > diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
> > index 075b048d..05869346 100644
> > --- a/checkpolicy/policy_define.h
> > +++ b/checkpolicy/policy_define.h
> > @@ -57,7 +57,7 @@ int define_role_trans(int class_specified);
> >  int define_role_types(void);
> >  int define_role_attr(void);
> >  int define_roleattribute(void);
> > -int define_filename_trans(void);
> > +int define_filename_trans(uint32_t match_type);
> >  int define_sens(void);
> >  int define_te_avtab(int which);
> >  int define_te_avtab_extended_perms(int which);
> > diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
> > index 356626e2..ee4be4ea 100644
> > --- a/checkpolicy/policy_parse.y
> > +++ b/checkpolicy/policy_parse.y
> > @@ -153,6 +153,7 @@ typedef int (* require_func_t)(int pass);
> >  %token FILESYSTEM
> >  %token DEFAULT_USER DEFAULT_ROLE DEFAULT_TYPE DEFAULT_RANGE
> >  %token LOW_HIGH LOW HIGH GLBLUB
> > +%token PREFIX SUFFIX
> >
> >  %left OR
> >  %left XOR
> > @@ -410,6 +411,12 @@ cond_rule_def           : cond_transition_def
> >                         { $$ = NULL; }
> >                          ;
> >  cond_transition_def    : TYPE_TRANSITION names names ':' names identifier filename ';'
> > +                        { $$ = define_cond_filename_trans() ;
> > +                          if ($$ == COND_ERR) return -1;}
> > +                       | TYPE_TRANSITION names names ':' names identifier filename PREFIX ';'
> > +                        { $$ = define_cond_filename_trans() ;
> > +                          if ($$ == COND_ERR) return -1;}
> > +                       | TYPE_TRANSITION names names ':' names identifier filename SUFFIX ';'
> >                          { $$ = define_cond_filename_trans() ;
> >                            if ($$ == COND_ERR) return -1;}
> >                         | TYPE_TRANSITION names names ':' names identifier ';'
> > @@ -449,7 +456,11 @@ cond_dontaudit_def : DONTAUDIT names names ':' names names ';'
> >                         ;
> >                         ;
> >  transition_def         : TYPE_TRANSITION  names names ':' names identifier filename ';'
> > -                       {if (define_filename_trans()) return -1; }
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_EXACT)) return -1; }
> > +                       | TYPE_TRANSITION  names names ':' names identifier filename PREFIX ';'
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_PREFIX)) return -1; }
> > +                       | TYPE_TRANSITION  names names ':' names identifier filename SUFFIX ';'
> > +                       {if (define_filename_trans(FILENAME_TRANS_MATCH_SUFFIX)) return -1; }
> >                         | TYPE_TRANSITION names names ':' names identifier ';'
> >                          {if (define_compute_type(AVRULE_TRANSITION)) return -1;}
> >                          | TYPE_MEMBER names names ':' names identifier ';'
> > diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
> > index c998ff8b..0780ef15 100644
> > --- a/checkpolicy/policy_scan.l
> > +++ b/checkpolicy/policy_scan.l
> > @@ -270,6 +270,10 @@ low |
> >  LOW                            { return(LOW); }
> >  glblub |
> >  GLBLUB                         { return(GLBLUB); }
> > +PREFIX |
> > +prefix                         { return(PREFIX); }
> > +SUFFIX |
> > +suffix                         { return(SUFFIX); }
> >  "/"[^ \n\r\t\f]*               { return(PATH); }
> >  \""/"[^\"\n]*\"                { return(QPATH); }
> >  \"[^"/"\"\n]+\"        { return(FILENAME); }
> > diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
> > index fa7117f5..5995e135 100644
> > --- a/checkpolicy/test/dismod.c
> > +++ b/checkpolicy/test/dismod.c
> > @@ -564,13 +564,25 @@ static void display_role_allow(role_allow_rule_t * ra, policydb_t * p, FILE * fp
> >
> >  static void display_filename_trans(filename_trans_rule_t * tr, policydb_t * p, FILE * fp)
> >  {
> > +       const char *match_str = "";
> >         fprintf(fp, "filename transition");
> >         for (; tr; tr = tr->next) {
> >                 display_type_set(&tr->stypes, 0, p, fp);
> >                 display_type_set(&tr->ttypes, 0, p, fp);
> >                 display_id(p, fp, SYM_CLASSES, tr->tclass - 1, ":");
> >                 display_id(p, fp, SYM_TYPES, tr->otype - 1, "");
> > -               fprintf(fp, " %s\n", tr->name);
> > +               switch (tr->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +               fprintf(fp, " %s%s\n", tr->name, match_str);
> >         }
> >  }
> >
> > diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
> > index b567ce77..4426b844 100644
> > --- a/checkpolicy/test/dispol.c
> > +++ b/checkpolicy/test/dispol.c
> > @@ -450,6 +450,7 @@ static void display_role_trans(policydb_t *p, FILE *fp)
> >
> >  struct filenametr_display_args {
> >         policydb_t *p;
> > +       uint32_t match_type;
> >         FILE *fp;
> >  };
> >
> > @@ -464,6 +465,19 @@ static int filenametr_display(hashtab_key_t key,
> >         FILE *fp = args->fp;
> >         ebitmap_node_t *node;
> >         uint32_t bit;
> > +       const char *match_str = "";
> > +
> > +       switch (args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         do {
> >                 ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
> > @@ -471,7 +485,7 @@ static int filenametr_display(hashtab_key_t key,
> >                         display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
> >                         display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
> >                         display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
> > -                       fprintf(fp, " %s\n", ft->name);
> > +                       fprintf(fp, " %s%s\n", ft->name, match_str);
> >                 }
> >                 ftdatum = ftdatum->next;
> >         } while (ftdatum);
> > @@ -487,7 +501,15 @@ static void display_filename_trans(policydb_t *p, FILE *fp)
> >         fprintf(fp, "filename_trans rules:\n");
> >         args.p = p;
> >         args.fp = fp;
> > -       hashtab_map(p->filename_trans, filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                   filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                   filenametr_display, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                   filenametr_display, &args);
> >  }
> >
> >  static int menu(void)
> > diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
> > index 2021187d..37769fc4 100644
> > --- a/libsepol/cil/src/cil.c
> > +++ b/libsepol/cil/src/cil.c
> > @@ -97,6 +97,8 @@ char *CIL_KEY_TUNABLEIF;
> >  char *CIL_KEY_ALLOW;
> >  char *CIL_KEY_DONTAUDIT;
> >  char *CIL_KEY_TYPETRANSITION;
> > +char *CIL_KEY_PREFIX;
> > +char *CIL_KEY_SUFFIX;
> >  char *CIL_KEY_TYPECHANGE;
> >  char *CIL_KEY_CALL;
> >  char *CIL_KEY_TUNABLE;
> > @@ -269,6 +271,8 @@ static void cil_init_keys(void)
> >         CIL_KEY_ALLOW = cil_strpool_add("allow");
> >         CIL_KEY_DONTAUDIT = cil_strpool_add("dontaudit");
> >         CIL_KEY_TYPETRANSITION = cil_strpool_add("typetransition");
> > +       CIL_KEY_PREFIX = cil_strpool_add("prefix");
> > +       CIL_KEY_SUFFIX = cil_strpool_add("suffix");
> >         CIL_KEY_TYPECHANGE = cil_strpool_add("typechange");
> >         CIL_KEY_CALL = cil_strpool_add("call");
> >         CIL_KEY_TUNABLE = cil_strpool_add("tunable");
> > @@ -2456,6 +2460,8 @@ void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
> >         (*nametypetrans)->obj = NULL;
> >         (*nametypetrans)->name_str = NULL;
> >         (*nametypetrans)->name = NULL;
> > +       (*nametypetrans)->match_type_str = NULL;
> > +       (*nametypetrans)->match_type = FILENAME_TRANS_MATCH_EXACT;
> >         (*nametypetrans)->result_str = NULL;
> >         (*nametypetrans)->result = NULL;
> >  }
> > diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
> > index a8e3616a..75a9e064 100644
> > --- a/libsepol/cil/src/cil_binary.c
> > +++ b/libsepol/cil/src/cil_binary.c
> > @@ -1168,7 +1168,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
> >                                                 type_datum_t *sepol_src,
> >                                                 type_datum_t *sepol_tgt,
> >                                                 struct cil_list *class_list,
> > -                                               char *name,
> > +                                               char *name, uint32_t match_type,
> >                                                 type_datum_t *sepol_result)
> >  {
> >         int rc;
> > @@ -1183,7 +1183,7 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
> >                 rc = policydb_filetrans_insert(
> >                         pdb, sepol_src->s.value, sepol_tgt->s.value,
> >                         sepol_obj->s.value, name, NULL,
> > -                       sepol_result->s.value, &otype
> > +                       sepol_result->s.value, match_type, &otype
> >                 );
> >                 if (rc != SEPOL_OK) {
> >                         if (rc == SEPOL_EEXIST) {
> > @@ -1252,7 +1252,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
> >
> >                         rc = __cil_typetransition_to_avtab_helper(
> >                                 pdb, sepol_src, sepol_src, class_list,
> > -                               name, sepol_result
> > +                               name, typetrans->match_type, sepol_result
> >                         );
> >                         if (rc != SEPOL_OK) goto exit;
> >                 }
> > @@ -1270,7 +1270,7 @@ static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
> >
> >                                 rc = __cil_typetransition_to_avtab_helper(
> >                                         pdb, sepol_src, sepol_tgt, class_list,
> > -                                       name, sepol_result
> > +                                       name, typetrans->match_type, sepol_result
> >                                 );
> >                                 if (rc != SEPOL_OK) goto exit;
> >                         }
> > diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
> > index 8976c254..94dadef8 100644
> > --- a/libsepol/cil/src/cil_build_ast.c
> > +++ b/libsepol/cil/src/cil_build_ast.c
> > @@ -3392,10 +3392,11 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >                 CIL_SYN_STRING,
> >                 CIL_SYN_STRING,
> >                 CIL_SYN_STRING | CIL_SYN_END,
> > -               CIL_SYN_END
> > +               CIL_SYN_STRING | CIL_SYN_END,
> > +               CIL_SYN_END,
> >         };
> >         size_t syntax_len = sizeof(syntax)/sizeof(*syntax);
> > -       char *s1, *s2, *s3, *s4, *s5;
> > +       char *s1, *s2, *s3, *s4, *s5, *s6;
> >
> >         if (db == NULL || parse_current == NULL || ast_node == NULL ) {
> >                 goto exit;
> > @@ -3411,12 +3412,22 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >         s3 = parse_current->next->next->next->data;
> >         s4 = parse_current->next->next->next->next->data;
> >         s5 = NULL;
> > +       s6 = NULL;
> >
> >         if (parse_current->next->next->next->next->next) {
> >                 if (s4 == CIL_KEY_STAR) {
> > -                       s4 = parse_current->next->next->next->next->next->data;
> > +                       if (parse_current->next->next->next->next->next->next) {
> > +                               s4 = parse_current->next->next->next->next->next->next->data;
> > +                       } else {
> > +                               s4 = parse_current->next->next->next->next->next->data;
> > +                       }
> >                 } else {
> > -                       s5 = parse_current->next->next->next->next->next->data;
> > +                       if (parse_current->next->next->next->next->next->next) {
> > +                               s5 = parse_current->next->next->next->next->next->data;
> > +                               s6 = parse_current->next->next->next->next->next->next->data;
> > +                       } else {
> > +                               s5 = parse_current->next->next->next->next->next->data;
> > +                       }
> >                 }
> >         }
> >
> > @@ -3428,8 +3439,13 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
> >                 nametypetrans->src_str = s1;
> >                 nametypetrans->tgt_str = s2;
> >                 nametypetrans->obj_str = s3;
> > -               nametypetrans->result_str = s5;
> >                 nametypetrans->name_str = s4;
> > +               if (s6) {
> > +                       nametypetrans->match_type_str = s5;
> > +                       nametypetrans->result_str = s6;
> > +               } else {
> > +                       nametypetrans->result_str = s5;
> > +               }
> >
> >                 ast_node->data = nametypetrans;
> >                 ast_node->flavor = CIL_NAMETYPETRANSITION;
> > diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
> > index bc972f03..300390ec 100644
> > --- a/libsepol/cil/src/cil_copy_ast.c
> > +++ b/libsepol/cil/src/cil_copy_ast.c
> > @@ -726,6 +726,7 @@ int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
> >         new->tgt_str = orig->tgt_str;
> >         new->obj_str = orig->obj_str;
> >         new->name_str = orig->name_str;
> > +       new->match_type_str = orig->match_type_str;
> >         new->result_str = orig->result_str;
> >
> >
> > diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
> > index 9e492cb9..7f0be652 100644
> > --- a/libsepol/cil/src/cil_internal.h
> > +++ b/libsepol/cil/src/cil_internal.h
> > @@ -114,6 +114,8 @@ extern char *CIL_KEY_TUNABLEIF;
> >  extern char *CIL_KEY_ALLOW;
> >  extern char *CIL_KEY_DONTAUDIT;
> >  extern char *CIL_KEY_TYPETRANSITION;
> > +extern char *CIL_KEY_PREFIX;
> > +extern char *CIL_KEY_SUFFIX;
> >  extern char *CIL_KEY_TYPECHANGE;
> >  extern char *CIL_KEY_CALL;
> >  extern char *CIL_KEY_TUNABLE;
> > @@ -580,6 +582,8 @@ struct cil_nametypetransition {
> >         struct cil_class *obj;
> >         char *name_str;
> >         struct cil_name *name;
> > +       char *match_type_str;
> > +       uint32_t match_type;
> >         char *result_str;
> >         void *result; /* type or alias */
> >
> > diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
> > index feb97868..9776dfd3 100644
> > --- a/libsepol/cil/src/cil_policy.c
> > +++ b/libsepol/cil/src/cil_policy.c
> > @@ -1260,6 +1260,7 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
> >         struct cil_name *name;
> >         struct cil_list *class_list;
> >         struct cil_list_item *i1;
> > +       const char *match_type_str = "";
> >
> >         src = trans->src;
> >         tgt = trans->tgt;
> > @@ -1268,7 +1269,21 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
> >
> >         class_list = cil_expand_class(trans->obj);
> >         cil_list_for_each(i1, class_list) {
> > -               fprintf(out, "type_transition %s %s : %s %s \"%s\";\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn);
> > +               switch (trans->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_type_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_type_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_type_str = " suffix";
> > +                       break;
> > +               default:
> > +                       match_type_str = "???";
> > +                       break;
> > +               }
> > +               fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn, match_type_str);
> >         }
> >         cil_list_destroy(&class_list, CIL_FALSE);
> >  }
> > diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
> > index 33b9d321..3b00f065 100644
> > --- a/libsepol/cil/src/cil_resolve_ast.c
> > +++ b/libsepol/cil/src/cil_resolve_ast.c
> > @@ -717,6 +717,18 @@ int cil_resolve_nametypetransition(struct cil_tree_node *current, void *extra_ar
> >                 nametypetrans->name = (struct cil_name *)name_datum;
> >         }
> >
> > +       if (nametypetrans->match_type_str == NULL) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       } else if (nametypetrans->match_type_str == CIL_KEY_PREFIX) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       } else if (nametypetrans->match_type_str == CIL_KEY_SUFFIX) {
> > +               nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       } else {
> > +               cil_tree_log(current, CIL_ERR, "Invalid name match type \"%s\"", nametypetrans->match_type_str);
> > +               rc = SEPOL_ERR;
> > +               goto exit;
> > +       }
> > +
> >         rc = cil_resolve_name(current, nametypetrans->result_str, CIL_SYM_TYPES, extra_args, &result_datum);
> >         if (rc != SEPOL_OK) {
> >                 goto exit;
> > diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
> > index 4da7a77c..99c82292 100644
> > --- a/libsepol/cil/src/cil_write_ast.c
> > +++ b/libsepol/cil/src/cil_write_ast.c
> > @@ -1178,6 +1178,8 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
> >                 fprintf(out, "%s ", datum_or_str(DATUM(rule->tgt), rule->tgt_str));
> >                 fprintf(out, "%s ", datum_or_str(DATUM(rule->obj), rule->obj_str));
> >                 fprintf(out, "\"%s\" ", datum_or_str(DATUM(rule->name), rule->name_str));
> > +               if (rule->match_type != FILENAME_TRANS_MATCH_EXACT)
> > +                       fprintf(out, "%s ", rule->match_type_str);
> >                 fprintf(out, "%s", datum_or_str(DATUM(rule->result), rule->result_str));
> >                 fprintf(out, ")\n");
> >                 break;
> > diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
> > index 6682069e..7727ca52 100644
> > --- a/libsepol/include/sepol/policydb/policydb.h
> > +++ b/libsepol/include/sepol/policydb/policydb.h
> > @@ -321,6 +321,7 @@ typedef struct filename_trans_rule {
> >         uint32_t tclass;
> >         char *name;
> >         uint32_t otype; /* new type */
> > +       uint32_t match_type;
> >         struct filename_trans_rule *next;
> >  } filename_trans_rule_t;
> >
> > @@ -423,6 +424,14 @@ typedef struct genfs {
> >  /* OCON_NUM needs to be the largest index in any platform's ocontext array */
> >  #define OCON_NUM   9
> >
> > +/* filename transitions table array indices */
> > +enum {
> > +       FILENAME_TRANS_MATCH_EXACT,
> > +       FILENAME_TRANS_MATCH_PREFIX,
> > +       FILENAME_TRANS_MATCH_SUFFIX,
> > +       FILENAME_TRANS_MATCH_NUM,
> > +};
> > +
> >  /* section: module information */
> >
> >  /* scope_index_t holds all of the symbols that are in scope in a
> > @@ -593,7 +602,7 @@ typedef struct policydb {
> >         hashtab_t range_tr;
> >
> >         /* file transitions with the last path component */
> > -       hashtab_t filename_trans;
> > +       hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
> >         uint32_t filename_trans_count;
> >
> >         ebitmap_t *type_attr_map;
> > @@ -657,7 +666,8 @@ extern int policydb_sort_ocontexts(policydb_t *p);
> >  extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
> >                                      uint32_t ttype, uint32_t tclass,
> >                                      const char *name, char **name_alloc,
> > -                                    uint32_t otype, uint32_t *present_otype);
> > +                                    uint32_t otype, uint32_t match_type,
> > +                                    uint32_t *present_otype);
> >
> >  /* Deprecated */
> >  extern int policydb_context_isvalid(const policydb_t * p,
> > @@ -758,10 +768,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
> >  #define POLICYDB_VERSION_INFINIBAND            31 /* Linux-specific */
> >  #define POLICYDB_VERSION_GLBLUB                32
> >  #define POLICYDB_VERSION_COMP_FTRANS   33 /* compressed filename transitions */
> > +#define POLICYDB_VERSION_PREFIX_SUFFIX 34 /* prefix and suffix filename transitions */
> >
> >  /* Range of policy versions we understand*/
> >  #define POLICYDB_VERSION_MIN   POLICYDB_VERSION_BASE
> > -#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_COMP_FTRANS
> > +#define POLICYDB_VERSION_MAX   POLICYDB_VERSION_PREFIX_SUFFIX
> >
> >  /* Module versions and specific changes*/
> >  #define MOD_POLICYDB_VERSION_BASE              4
> > @@ -784,9 +795,10 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
> >  #define MOD_POLICYDB_VERSION_INFINIBAND                19
> >  #define MOD_POLICYDB_VERSION_GLBLUB            20
> >  #define MOD_POLICYDB_VERSION_SELF_TYPETRANS    21
> > +#define MOD_POLICYDB_VERSION_PREFIX_SUFFIX     22
> >
> >  #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
> > -#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_SELF_TYPETRANS
> > +#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_PREFIX_SUFFIX
> >
> >  #define POLICYDB_CONFIG_MLS    1
> >
> > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > index ee5f9185..4df9521a 100644
> > --- a/libsepol/src/expand.c
> > +++ b/libsepol/src/expand.c
> > @@ -1419,18 +1419,31 @@ static int expand_filename_trans_helper(expand_state_t *state,
> >         rc = policydb_filetrans_insert(
> >                 state->out, s + 1, t + 1,
> >                 rule->tclass, rule->name,
> > -               NULL, mapped_otype, &present_otype
> > +               NULL, mapped_otype, rule->match_type, &present_otype
> >         );
> >         if (rc == SEPOL_EEXIST) {
> >                 /* duplicate rule, ignore */
> >                 if (present_otype == mapped_otype)
> >                         return 0;
> >
> > -               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\":  %s vs %s",
> > +               const char *match_str = "";
> > +               switch (rule->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +               ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\"%s:  %s vs %s",
> >                     state->out->p_type_val_to_name[s],
> >                     state->out->p_type_val_to_name[t],
> >                     state->out->p_class_val_to_name[rule->tclass - 1],
> >                     rule->name,
> > +                   match_str,
> >                     state->out->p_type_val_to_name[present_otype - 1],
> >                     state->out->p_type_val_to_name[mapped_otype - 1]);
> >                 return -1;
> > diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
> > index 8fcc385d..81efeaa2 100644
> > --- a/libsepol/src/kernel_to_cil.c
> > +++ b/libsepol/src/kernel_to_cil.c
> > @@ -1869,6 +1869,7 @@ exit:
> >
> >  struct map_filename_trans_args {
> >         struct policydb *pdb;
> > +       uint32_t match_type;
> >         struct strs *strs;
> >  };
> >
> > @@ -1883,6 +1884,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >         struct ebitmap_node *node;
> >         uint32_t bit;
> >         int rc;
> > +       const char *match_str = "";
> > +
> > +       switch (map_args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
> >         class = pdb->p_class_val_to_name[ft->tclass - 1];
> > @@ -1893,8 +1907,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
> >                         src = pdb->p_type_val_to_name[bit];
> >                         rc = strs_create_and_add(strs,
> > -                                                "(typetransition %s %s %s \"%s\" %s)",
> > -                                                5, src, tgt, class, filename, new);
> > +                                                "(typetransition %s %s %s \"%s\"%s %s)",
> > +                                                6, src, tgt, class, filename, match_str, new);
> >                         if (rc)
> >                                 return rc;
> >                 }
> > @@ -1919,7 +1933,23 @@ static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
> >         args.pdb = pdb;
> >         args.strs = strs;
> >
> > -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                        map_filename_trans_to_str, &args);
> >         if (rc != 0) {
> >                 goto exit;
> >         }
> > diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
> > index b0ae16d9..99bef76e 100644
> > --- a/libsepol/src/kernel_to_conf.c
> > +++ b/libsepol/src/kernel_to_conf.c
> > @@ -1845,6 +1845,7 @@ exit:
> >
> >  struct map_filename_trans_args {
> >         struct policydb *pdb;
> > +       uint32_t match_type;
> >         struct strs *strs;
> >  };
> >
> > @@ -1859,6 +1860,19 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >         struct ebitmap_node *node;
> >         uint32_t bit;
> >         int rc;
> > +       const char *match_str = "";
> > +
> > +       switch (map_args->match_type) {
> > +       case FILENAME_TRANS_MATCH_EXACT:
> > +               match_str = "";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_PREFIX:
> > +               match_str = " prefix";
> > +               break;
> > +       case FILENAME_TRANS_MATCH_SUFFIX:
> > +               match_str = " suffix";
> > +               break;
> > +       }
> >
> >         tgt = pdb->p_type_val_to_name[ft->ttype - 1];
> >         class = pdb->p_class_val_to_name[ft->tclass - 1];
> > @@ -1869,8 +1883,8 @@ static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
> >                 ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
> >                         src = pdb->p_type_val_to_name[bit];
> >                         rc = strs_create_and_add(strs,
> > -                                                "type_transition %s %s:%s %s \"%s\";",
> > -                                                5, src, tgt, class, new, filename);
> > +                                                "type_transition %s %s:%s %s \"%s\"%s;",
> > +                                                6, src, tgt, class, new, filename, match_str);
> >                         if (rc)
> >                                 return rc;
> >                 }
> > @@ -1895,7 +1909,23 @@ static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
> >         args.pdb = pdb;
> >         args.strs = strs;
> >
> > -       rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
> > +       args.match_type = FILENAME_TRANS_MATCH_EXACT;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_PREFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                        map_filename_trans_to_str, &args);
> > +       if (rc != 0) {
> > +               goto exit;
> > +       }
> > +
> > +       args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
> > +       rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                        map_filename_trans_to_str, &args);
> >         if (rc != 0) {
> >                 goto exit;
> >         }
> > diff --git a/libsepol/src/link.c b/libsepol/src/link.c
> > index 3b7742bc..f432087f 100644
> > --- a/libsepol/src/link.c
> > +++ b/libsepol/src/link.c
> > @@ -1440,6 +1440,7 @@ static int copy_filename_trans_list(filename_trans_rule_t * list,
> >                 new_rule->name = strdup(cur->name);
> >                 if (!new_rule->name)
> >                         goto err;
> > +               new_rule->match_type = cur->match_type;
> >
> >                 if (type_set_or_convert(&cur->stypes, &new_rule->stypes, module) ||
> >                     type_set_or_convert(&cur->ttypes, &new_rule->ttypes, module))
> > diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> > index d2868019..98e1c7ba 100644
> > --- a/libsepol/src/module_to_cil.c
> > +++ b/libsepol/src/module_to_cil.c
> > @@ -1609,6 +1609,7 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
> >         unsigned int ttype;
> >         struct type_set *ts;
> >         struct filename_trans_rule *rule;
> > +       const char *match_str = "";
> >
> >         for (rule = rules; rule != NULL; rule = rule->next) {
> >                 ts = &rule->stypes;
> > @@ -1623,19 +1624,31 @@ static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
> >                         goto exit;
> >                 }
> >
> > +               switch (rule->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +                       match_str = "";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +                       match_str = " prefix";
> > +                       break;
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       match_str = " suffix";
> > +                       break;
> > +               }
> > +
> >                 for (stype = 0; stype < num_stypes; stype++) {
> >                         for (ttype = 0; ttype < num_ttypes; ttype++) {
> > -                               cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
> > +                               cil_println(indent, "(typetransition %s %s %s \"%s\"%s %s)",
> >                                             stypes[stype], ttypes[ttype],
> >                                             pdb->p_class_val_to_name[rule->tclass - 1],
> > -                                           rule->name,
> > +                                           rule->name, match_str,
> >                                             pdb->p_type_val_to_name[rule->otype - 1]);
> >                         }
> >                         if (rule->flags & RULE_SELF) {
> > -                               cil_println(indent, "(typetransition %s self %s \"%s\" %s)",
> > +                               cil_println(indent, "(typetransition %s self %s \"%s\"%s %s)",
> >                                             stypes[stype],
> >                                             pdb->p_class_val_to_name[rule->tclass - 1],
> > -                                           rule->name,
> > +                                           rule->name, match_str,
> >                                             pdb->p_type_val_to_name[rule->otype - 1]);
> >                         }
> >                 }
> > diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> > index f9537caa..29d7971a 100644
> > --- a/libsepol/src/policydb.c
> > +++ b/libsepol/src/policydb.c
> > @@ -208,6 +208,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = OCON_IBENDPORT + 1,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_KERN,
> > +        .version = POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = OCON_IBENDPORT + 1,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >         {
> >          .type = POLICY_BASE,
> >          .version = MOD_POLICYDB_VERSION_BASE,
> > @@ -334,6 +341,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = OCON_IBENDPORT + 1,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_BASE,
> > +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = OCON_IBENDPORT + 1,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >         {
> >          .type = POLICY_MOD,
> >          .version = MOD_POLICYDB_VERSION_BASE,
> > @@ -460,6 +474,13 @@ static const struct policydb_compat_info policydb_compat[] = {
> >          .ocon_num = 0,
> >          .target_platform = SEPOL_TARGET_SELINUX,
> >         },
> > +       {
> > +        .type = POLICY_MOD,
> > +        .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
> > +        .sym_num = SYM_NUM,
> > +        .ocon_num = 0,
> > +        .target_platform = SEPOL_TARGET_SELINUX,
> > +       },
> >  };
> >
> >  #if 0
> > @@ -909,10 +930,14 @@ int policydb_init(policydb_t * p)
> >         if (rc)
> >                 goto err;
> >
> > -       p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
> > -       if (!p->filename_trans) {
> > -               rc = -ENOMEM;
> > -               goto err;
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               p->filename_trans[i] = hashtab_create(filenametr_hash,
> > +                                                     filenametr_cmp,
> > +                                                     (1 << 10));
> > +               if (!p->filename_trans[i]) {
> > +                       rc = -ENOMEM;
> > +                       goto err;
> > +               }
> >         }
> >
> >         p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
> > @@ -926,7 +951,9 @@ int policydb_init(policydb_t * p)
> >
> >         return 0;
> >  err:
> > -       hashtab_destroy(p->filename_trans);
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               hashtab_destroy(p->filename_trans[i]);
> > +       }
> >         hashtab_destroy(p->range_tr);
> >         for (i = 0; i < SYM_NUM; i++) {
> >                 hashtab_destroy(p->symtab[i].table);
> > @@ -1564,8 +1591,10 @@ void policydb_destroy(policydb_t * p)
> >         if (lra)
> >                 free(lra);
> >
> > -       hashtab_map(p->filename_trans, filenametr_destroy, NULL);
> > -       hashtab_destroy(p->filename_trans);
> > +       for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
> > +               hashtab_map(p->filename_trans[i], filenametr_destroy, NULL);
> > +               hashtab_destroy(p->filename_trans[i]);
> > +       }
> >
> >         hashtab_map(p->range_tr, range_tr_destroy, NULL);
> >         hashtab_destroy(p->range_tr);
> > @@ -2599,7 +2628,7 @@ static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
> >  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >                               uint32_t tclass, const char *name,
> >                               char **name_alloc, uint32_t otype,
> > -                             uint32_t *present_otype)
> > +                             uint32_t match_type, uint32_t *present_otype)
> >  {
> >         filename_trans_key_t *ft, key;
> >         filename_trans_datum_t *datum, *last;
> > @@ -2609,7 +2638,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >         key.name = (char *)name;
> >
> >         last = NULL;
> > -       datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
> > +       datum = hashtab_search(p->filename_trans[match_type],
> > +                              (hashtab_key_t)&key);
> >         while (datum) {
> >                 if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
> >                         if (present_otype)
> > @@ -2657,7 +2687,8 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
> >                         ft->tclass = tclass;
> >                         ft->name = name_dup;
> >
> > -                       if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> > +                       if (hashtab_insert(p->filename_trans[match_type],
> > +                                          (hashtab_key_t)ft,
> >                                            (hashtab_datum_t)datum)) {
> >                                 free(name_dup);
> >                                 free(datum);
> > @@ -2704,8 +2735,9 @@ static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
> >         tclass = le32_to_cpu(buf[2]);
> >         otype  = le32_to_cpu(buf[3]);
> >
> > +       // This version does not contain other than exact filename transitions
> >         rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
> > -                                      otype, NULL);
> > +                                      otype, FILENAME_TRANS_MATCH_EXACT, NULL);
> >         if (rc) {
> >                 if (rc != SEPOL_EEXIST)
> >                         goto err;
> > @@ -2753,7 +2785,8 @@ out:
> >         return rc;
> >  }
> >
> > -static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> > +static int filename_trans_read_one(policydb_t *p, uint32_t match_type,
> > +                                  struct policy_file *fp)
> >  {
> >         filename_trans_key_t *ft = NULL;
> >         filename_trans_datum_t **dst, *datum, *first = NULL;
> > @@ -2823,7 +2856,7 @@ static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
> >         ft->tclass = tclass;
> >         ft->name = name;
> >
> > -       rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
> > +       rc = hashtab_insert(p->filename_trans[match_type], (hashtab_key_t)ft,
> >                             (hashtab_datum_t)first);
> >         if (rc)
> >                 goto err;
> > @@ -2842,7 +2875,8 @@ err:
> >         return -1;
> >  }
> >
> > -static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> > +static int filename_trans_read(policydb_t *p, struct policy_file *fp,
> > +                              uint32_t match_type)
> >  {
> >         unsigned int i;
> >         uint32_t buf[1], nel;
> > @@ -2855,13 +2889,17 @@ static int filename_trans_read(policydb_t *p, struct policy_file *fp)
> >
> >         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> >                 for (i = 0; i < nel; i++) {
> > +                       /*
> > +                        * this version does not have other than exact match
> > +                        * transitions
> > +                        */
> >                         rc = filename_trans_read_one_compat(p, fp);
> >                         if (rc < 0)
> >                                 return -1;
> >                 }
> >         } else {
> >                 for (i = 0; i < nel; i++) {
> > -                       rc = filename_trans_read_one(p, fp);
> > +                       rc = filename_trans_read_one(p, match_type, fp);
> >                         if (rc < 0)
> >                                 return -1;
> >                 }
> > @@ -3837,7 +3875,7 @@ static int role_allow_rule_read(role_allow_rule_t ** r, struct policy_file *fp)
> >  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                                     struct policy_file *fp)
> >  {
> > -       uint32_t buf[3], nel, i, len;
> > +       uint32_t buf[4], nel, i, len;
> >         unsigned int entries;
> >         filename_trans_rule_t *ftr, *lftr;
> >         int rc;
> > @@ -3883,7 +3921,9 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                 if (type_set_read(&ftr->ttypes, fp))
> >                         return -1;
> >
> > -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> > +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> > +                       entries = 4;
> > +               else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> >                         entries = 3;
> >                 else
> >                         entries = 2;
> > @@ -3895,6 +3935,8 @@ static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
> >                 ftr->otype = le32_to_cpu(buf[1]);
> >                 if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
> >                         ftr->flags = le32_to_cpu(buf[2]);
> > +               if (p->policyvers >=  MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
> > +                       ftr->match_type = le32_to_cpu(buf[3]);
> >         }
> >
> >         return 0;
> > @@ -4470,7 +4512,11 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
> >                 if (role_allow_read(&p->role_allow, fp))
> >                         goto bad;
> >                 if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
> > -                   filename_trans_read(p, fp))
> > +                   filename_trans_read(p, fp, FILENAME_TRANS_MATCH_EXACT))
> > +                       goto bad;
> > +               if (r_policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX &&
> > +                   (filename_trans_read(p, fp, FILENAME_TRANS_MATCH_PREFIX) ||
> > +                    filename_trans_read(p, fp, FILENAME_TRANS_MATCH_SUFFIX)))
> >                         goto bad;
> >         } else {
> >                 /* first read the AV rule blocks, then the scope tables */
> > diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
> > index 892a0ffd..3f2ba5ea 100644
> > --- a/libsepol/src/policydb_validate.c
> > +++ b/libsepol/src/policydb_validate.c
> > @@ -1,4 +1,3 @@
> > -
> >  #include <sepol/policydb/conditional.h>
> >  #include <sepol/policydb/ebitmap.h>
> >  #include <sepol/policydb/policydb.h>
> > @@ -1115,14 +1114,27 @@ bad:
> >         return -1;
> >  }
> >
> > -static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
> > +static int validate_filename_trans_hashtabs(sepol_handle_t *handle,
> > +                                           const policydb_t *p,
> > +                                           validate_t flavors[])
> >  {
> > -       if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
> > -               ERR(handle, "Invalid filename trans");
> > -               return -1;
> > +       if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
> > +                       validate_filename_trans, flavors))
> > +               goto bad;
> > +
> > +       if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
> > +                               validate_filename_trans, flavors))
> > +                       goto bad;
> > +               if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
> > +                               validate_filename_trans, flavors))
> > +                       goto bad;
> >         }
> >
> >         return 0;
> > +bad:
> > +       ERR(handle, "Invalid filename trans");
> > +       return -1;
> >  }
> >
> >  static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
> > @@ -1334,6 +1346,15 @@ static int validate_filename_trans_rules(sepol_handle_t *handle, const filename_
> >                 if (validate_simpletype(filename_trans->otype, p, flavors))
> >                         goto bad;
> >
> > +               switch (filename_trans->match_type) {
> > +               case FILENAME_TRANS_MATCH_EXACT:
> > +               case FILENAME_TRANS_MATCH_PREFIX:
> > +               case FILENAME_TRANS_MATCH_SUFFIX:
> > +                       break;
> > +               default:
> > +                       goto bad;
> > +               }
> > +
> >                 /* currently only the RULE_SELF flag can be set */
> >                 if ((filename_trans->flags & ~RULE_SELF) != 0)
> >                         goto bad;
> > @@ -1554,9 +1575,10 @@ int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
> >                         goto bad;
> >                 if (validate_role_allows(handle, p->role_allow, flavors))
> >                         goto bad;
> > -               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
> > -                       if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
> > +               if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> > +                       if (validate_filename_trans_hashtabs(handle, p, flavors))
> >                                 goto bad;
> > +               }
> >         } else {
> >                 if (validate_avrule_blocks(handle, p->global, p, flavors))
> >                         goto bad;
> > diff --git a/libsepol/src/write.c b/libsepol/src/write.c
> > index 283d11c8..c9398a98 100644
> > --- a/libsepol/src/write.c
> > +++ b/libsepol/src/write.c
> > @@ -653,7 +653,8 @@ static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
> >         return 0;
> >  }
> >
> > -static int filename_trans_write(struct policydb *p, void *fp)
> > +static int filename_trans_write(struct policydb *p, uint32_t match_type,
> > +                               void *fp)
> >  {
> >         size_t items;
> >         uint32_t buf[1];
> > @@ -663,20 +664,25 @@ static int filename_trans_write(struct policydb *p, void *fp)
> >                 return 0;
> >
> >         if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
> > +               /*
> > +                * This version does not have other than exact match
> > +                * transitions, there is no need to count other ones.
> > +                */
> >                 buf[0] = cpu_to_le32(p->filename_trans_count);
> >                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
> >                 if (items != 1)
> >                         return POLICYDB_ERROR;
> >
> > -               rc = hashtab_map(p->filename_trans, filename_write_one_compat,
> > -                                fp);
> > +               rc = hashtab_map(p->filename_trans[match_type],
> > +                                filename_write_one_compat, fp);
> >         } else {
> > -               buf[0] = cpu_to_le32(p->filename_trans->nel);
> > +               buf[0] = cpu_to_le32(p->filename_trans[match_type]->nel);
> >                 items = put_entry(buf, sizeof(uint32_t), 1, fp);
> >                 if (items != 1)
> >                         return POLICYDB_ERROR;
> >
> > -               rc = hashtab_map(p->filename_trans, filename_write_one, fp);
> > +               rc = hashtab_map(p->filename_trans[match_type],
> > +                                filename_write_one, fp);
> >         }
> >         return rc;
> >  }
> > @@ -1944,7 +1950,7 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
> >  {
> >         int nel = 0;
> >         size_t items, entries;
> > -       uint32_t buf[3], len;
> > +       uint32_t buf[4], len;
> >         filename_trans_rule_t *ftr;
> >
> >         for (ftr = t; ftr; ftr = ftr->next)
> > @@ -1974,8 +1980,11 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
> >                 buf[0] = cpu_to_le32(ftr->tclass);
> >                 buf[1] = cpu_to_le32(ftr->otype);
> >                 buf[2] = cpu_to_le32(ftr->flags);
> > +               buf[3] = cpu_to_le32(ftr->match_type);
> >
> > -               if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> > +               if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +                       entries = 4;
> > +               } else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
> >                         entries = 3;
> >                 } else if (!(ftr->flags & RULE_SELF)) {
> >                         entries = 2;
> > @@ -2370,12 +2379,29 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
> >                 if (role_allow_write(p->role_allow, fp))
> >                         return POLICYDB_ERROR;
> >                 if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
> > -                       if (filename_trans_write(p, fp))
> > +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_EXACT,
> > +                                                fp))
> >                                 return POLICYDB_ERROR;
> >                 } else {
> > -                       if (p->filename_trans)
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_EXACT])
> >                                 WARN(fp->handle, "Discarding filename type transition rules");
> >                 }
> > +               if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
> > +                       if (filename_trans_write(p, FILENAME_TRANS_MATCH_PREFIX,
> > +                                                fp) ||
> > +                           filename_trans_write(p, FILENAME_TRANS_MATCH_SUFFIX,
> > +                                                fp))
> > +                               return POLICYDB_ERROR;
> > +               } else {
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_PREFIX] &&
> > +                           p->filename_trans[FILENAME_TRANS_MATCH_PREFIX]->nel)
> > +                               WARN(fp->handle,
> > +                                    "Discarding prefix filename type transition rules");
> > +                       if (p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX] &&
> > +                           p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX]->nel)
> > +                               WARN(fp->handle,
> > +                                    "Discarding suffix filename type transition rules");
> > +               }
> >         } else {
> >                 if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
> >                         return POLICYDB_ERROR;
> > --
> > 2.41.0
> >
> 
> I fixed up the validate stuff so I could do some testing.
> 
> I found an issue when compiling CIL to policy version 32 (version 33
> works fine).
> 
> The problem also happens with a binary generated from a policy.conf
> file as well.
> WIth modules, it is a little more complicated. If I compile the
> modules with module version 22 and then expand to a binary version 32,
> then I get the same problem.
> If I compile the modules to some version less than 22 and then expand
> to a binary version 32, there is no problem.

I have looked at it and discovered the cause of the problem. The
filename_trans_count attribute of the policydb structure (which is used
for compatibility when writing from 33 to older policy versions) was
erroneously incremented for all match types, not only exact match.
Therefore, when writing to a binary version 32 with prefix/suffix rules
loaded, the number of filename transitions in the file is incorrect.

I will send updated and rebased patch soon, also with changes suggested
by Christian.

Sorry for the inconvenience with the validate stuff.

> 
> 
> With this CIL policy:
> 
> (handleunknown deny)
> (class CLASS01 (PERM01))
> (class CLASS02 (PERM02))
> (class CLASS03 (PERM03))
> (classorder (CLASS01 CLASS02 CLASS03))
> (sid kernel)
> (sidorder (kernel))
> (user USER1)
> (role ROLE1)
> (type TYPE1)
> (category CAT1)
> (categoryorder (CAT1))
> (sensitivity SENS1)
> (sensitivityorder (SENS1))
> (sensitivitycategory SENS1 (CAT1))
> (allow TYPE1 self (CLASS01 (PERM01)))
> (roletype ROLE1 TYPE1)
> (userrole USER1 ROLE1)
> (userlevel USER1 (SENS1))
> (userrange USER1 ((SENS1)(SENS1 (CAT1))))
> (sidcontext kernel (USER1 ROLE1 TYPE1 ((SENS1)(SENS1))))
> 
> (type ta)
> (type tb)
> (type tc)
> 
> (typetransition ta tb CLASS01 "file01" tc)
> (typetransition ta tb CLASS02 "file02" prefix tc)
> (typetransition ta tb CLASS03 "file03" suffix tc)
> 
> Compiling to a binary policy works fine:
> ~/local/usr/bin/secilc -c 32 -o test.bin test.cil
> 
> Trying to convert that binary back to CIL does not:
> ~/local/usr/bin/checkpolicy -C -b -o test.bin.cil test.bin
> 
> I get a generic "error(s) encountered while parsing configuration"
> from checkpolicy, so I don't know what is causing the problem yet.
> Like I said, this works for policy version 33.
> 
> I'll let you know if I figure out more.
> 
> Thanks,
> Jim

Thank you,
diff mbox series

Patch

diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
index 260e609d..fb8325ee 100644
--- a/checkpolicy/policy_define.c
+++ b/checkpolicy/policy_define.c
@@ -3159,7 +3159,7 @@  avrule_t *define_cond_filename_trans(void)
 	return COND_ERR;
 }
 
-int define_filename_trans(void)
+int define_filename_trans(uint32_t match_type)
 {
 	char *id, *name = NULL;
 	type_set_t stypes, ttypes;
@@ -3261,7 +3261,7 @@  int define_filename_trans(void)
 			ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
 				rc = policydb_filetrans_insert(
 					policydbp, s+1, t+1, c+1, name,
-					NULL, otype, NULL
+					NULL, otype, match_type, NULL
 				);
 				if (rc != SEPOL_OK) {
 					if (rc == SEPOL_EEXIST) {
@@ -3279,7 +3279,7 @@  int define_filename_trans(void)
 			if (self) {
 				rc = policydb_filetrans_insert(
 					policydbp, s+1, s+1, c+1, name,
-					NULL, otype, NULL
+					NULL, otype, match_type, NULL
 				);
 				if (rc != SEPOL_OK) {
 					if (rc == SEPOL_EEXIST) {
@@ -3317,6 +3317,7 @@  int define_filename_trans(void)
 		ftr->tclass = c + 1;
 		ftr->otype = otype;
 		ftr->flags = self ? RULE_SELF : 0;
+		ftr->match_type = match_type;
 	}
 
 	free(name);
diff --git a/checkpolicy/policy_define.h b/checkpolicy/policy_define.h
index 075b048d..05869346 100644
--- a/checkpolicy/policy_define.h
+++ b/checkpolicy/policy_define.h
@@ -57,7 +57,7 @@  int define_role_trans(int class_specified);
 int define_role_types(void);
 int define_role_attr(void);
 int define_roleattribute(void);
-int define_filename_trans(void);
+int define_filename_trans(uint32_t match_type);
 int define_sens(void);
 int define_te_avtab(int which);
 int define_te_avtab_extended_perms(int which);
diff --git a/checkpolicy/policy_parse.y b/checkpolicy/policy_parse.y
index 356626e2..ee4be4ea 100644
--- a/checkpolicy/policy_parse.y
+++ b/checkpolicy/policy_parse.y
@@ -153,6 +153,7 @@  typedef int (* require_func_t)(int pass);
 %token FILESYSTEM
 %token DEFAULT_USER DEFAULT_ROLE DEFAULT_TYPE DEFAULT_RANGE
 %token LOW_HIGH LOW HIGH GLBLUB
+%token PREFIX SUFFIX
 
 %left OR
 %left XOR
@@ -410,6 +411,12 @@  cond_rule_def           : cond_transition_def
 			{ $$ = NULL; }
                         ;
 cond_transition_def	: TYPE_TRANSITION names names ':' names identifier filename ';'
+                        { $$ = define_cond_filename_trans() ;
+                          if ($$ == COND_ERR) return -1;}
+			| TYPE_TRANSITION names names ':' names identifier filename PREFIX ';'
+                        { $$ = define_cond_filename_trans() ;
+                          if ($$ == COND_ERR) return -1;}
+			| TYPE_TRANSITION names names ':' names identifier filename SUFFIX ';'
                         { $$ = define_cond_filename_trans() ;
                           if ($$ == COND_ERR) return -1;}
 			| TYPE_TRANSITION names names ':' names identifier ';'
@@ -449,7 +456,11 @@  cond_dontaudit_def	: DONTAUDIT names names ':' names names ';'
 		        ;
 			;
 transition_def		: TYPE_TRANSITION  names names ':' names identifier filename ';'
-			{if (define_filename_trans()) return -1; }
+			{if (define_filename_trans(FILENAME_TRANS_MATCH_EXACT)) return -1; }
+			| TYPE_TRANSITION  names names ':' names identifier filename PREFIX ';'
+			{if (define_filename_trans(FILENAME_TRANS_MATCH_PREFIX)) return -1; }
+			| TYPE_TRANSITION  names names ':' names identifier filename SUFFIX ';'
+			{if (define_filename_trans(FILENAME_TRANS_MATCH_SUFFIX)) return -1; }
 			| TYPE_TRANSITION names names ':' names identifier ';'
                         {if (define_compute_type(AVRULE_TRANSITION)) return -1;}
                         | TYPE_MEMBER names names ':' names identifier ';'
diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l
index c998ff8b..0780ef15 100644
--- a/checkpolicy/policy_scan.l
+++ b/checkpolicy/policy_scan.l
@@ -270,6 +270,10 @@  low |
 LOW				{ return(LOW); }
 glblub |
 GLBLUB				{ return(GLBLUB); }
+PREFIX |
+prefix				{ return(PREFIX); }
+SUFFIX |
+suffix				{ return(SUFFIX); }
 "/"[^ \n\r\t\f]*	        { return(PATH); }
 \""/"[^\"\n]*\" 		{ return(QPATH); }
 \"[^"/"\"\n]+\"	{ return(FILENAME); }
diff --git a/checkpolicy/test/dismod.c b/checkpolicy/test/dismod.c
index fa7117f5..5995e135 100644
--- a/checkpolicy/test/dismod.c
+++ b/checkpolicy/test/dismod.c
@@ -564,13 +564,25 @@  static void display_role_allow(role_allow_rule_t * ra, policydb_t * p, FILE * fp
 
 static void display_filename_trans(filename_trans_rule_t * tr, policydb_t * p, FILE * fp)
 {
+	const char *match_str = "";
 	fprintf(fp, "filename transition");
 	for (; tr; tr = tr->next) {
 		display_type_set(&tr->stypes, 0, p, fp);
 		display_type_set(&tr->ttypes, 0, p, fp);
 		display_id(p, fp, SYM_CLASSES, tr->tclass - 1, ":");
 		display_id(p, fp, SYM_TYPES, tr->otype - 1, "");
-		fprintf(fp, " %s\n", tr->name);
+		switch (tr->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+			match_str = "";
+			break;
+		case FILENAME_TRANS_MATCH_PREFIX:
+			match_str = " prefix";
+			break;
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			match_str = " suffix";
+			break;
+		}
+		fprintf(fp, " %s%s\n", tr->name, match_str);
 	}
 }
 
diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
index b567ce77..4426b844 100644
--- a/checkpolicy/test/dispol.c
+++ b/checkpolicy/test/dispol.c
@@ -450,6 +450,7 @@  static void display_role_trans(policydb_t *p, FILE *fp)
 
 struct filenametr_display_args {
 	policydb_t *p;
+	uint32_t match_type;
 	FILE *fp;
 };
 
@@ -464,6 +465,19 @@  static int filenametr_display(hashtab_key_t key,
 	FILE *fp = args->fp;
 	ebitmap_node_t *node;
 	uint32_t bit;
+	const char *match_str = "";
+
+	switch (args->match_type) {
+	case FILENAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case FILENAME_TRANS_MATCH_PREFIX:
+		match_str = " prefix";
+		break;
+	case FILENAME_TRANS_MATCH_SUFFIX:
+		match_str = " suffix";
+		break;
+	}
 
 	do {
 		ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
@@ -471,7 +485,7 @@  static int filenametr_display(hashtab_key_t key,
 			display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
 			display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
 			display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
-			fprintf(fp, " %s\n", ft->name);
+			fprintf(fp, " %s%s\n", ft->name, match_str);
 		}
 		ftdatum = ftdatum->next;
 	} while (ftdatum);
@@ -487,7 +501,15 @@  static void display_filename_trans(policydb_t *p, FILE *fp)
 	fprintf(fp, "filename_trans rules:\n");
 	args.p = p;
 	args.fp = fp;
-	hashtab_map(p->filename_trans, filenametr_display, &args);
+	args.match_type = FILENAME_TRANS_MATCH_EXACT;
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+		    filenametr_display, &args);
+	args.match_type = FILENAME_TRANS_MATCH_PREFIX;
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+		    filenametr_display, &args);
+	args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+		    filenametr_display, &args);
 }
 
 static int menu(void)
diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
index 2021187d..37769fc4 100644
--- a/libsepol/cil/src/cil.c
+++ b/libsepol/cil/src/cil.c
@@ -97,6 +97,8 @@  char *CIL_KEY_TUNABLEIF;
 char *CIL_KEY_ALLOW;
 char *CIL_KEY_DONTAUDIT;
 char *CIL_KEY_TYPETRANSITION;
+char *CIL_KEY_PREFIX;
+char *CIL_KEY_SUFFIX;
 char *CIL_KEY_TYPECHANGE;
 char *CIL_KEY_CALL;
 char *CIL_KEY_TUNABLE;
@@ -269,6 +271,8 @@  static void cil_init_keys(void)
 	CIL_KEY_ALLOW = cil_strpool_add("allow");
 	CIL_KEY_DONTAUDIT = cil_strpool_add("dontaudit");
 	CIL_KEY_TYPETRANSITION = cil_strpool_add("typetransition");
+	CIL_KEY_PREFIX = cil_strpool_add("prefix");
+	CIL_KEY_SUFFIX = cil_strpool_add("suffix");
 	CIL_KEY_TYPECHANGE = cil_strpool_add("typechange");
 	CIL_KEY_CALL = cil_strpool_add("call");
 	CIL_KEY_TUNABLE = cil_strpool_add("tunable");
@@ -2456,6 +2460,8 @@  void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
 	(*nametypetrans)->obj = NULL;
 	(*nametypetrans)->name_str = NULL;
 	(*nametypetrans)->name = NULL;
+	(*nametypetrans)->match_type_str = NULL;
+	(*nametypetrans)->match_type = FILENAME_TRANS_MATCH_EXACT;
 	(*nametypetrans)->result_str = NULL;
 	(*nametypetrans)->result = NULL;
 }
diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
index a8e3616a..75a9e064 100644
--- a/libsepol/cil/src/cil_binary.c
+++ b/libsepol/cil/src/cil_binary.c
@@ -1168,7 +1168,7 @@  static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
 						type_datum_t *sepol_src,
 						type_datum_t *sepol_tgt,
 						struct cil_list *class_list,
-						char *name,
+						char *name, uint32_t match_type,
 						type_datum_t *sepol_result)
 {
 	int rc;
@@ -1183,7 +1183,7 @@  static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
 		rc = policydb_filetrans_insert(
 			pdb, sepol_src->s.value, sepol_tgt->s.value,
 			sepol_obj->s.value, name, NULL,
-			sepol_result->s.value, &otype
+			sepol_result->s.value, match_type, &otype
 		);
 		if (rc != SEPOL_OK) {
 			if (rc == SEPOL_EEXIST) {
@@ -1252,7 +1252,7 @@  static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
 
 			rc = __cil_typetransition_to_avtab_helper(
 				pdb, sepol_src, sepol_src, class_list,
-				name, sepol_result
+				name, typetrans->match_type, sepol_result
 			);
 			if (rc != SEPOL_OK) goto exit;
 		}
@@ -1270,7 +1270,7 @@  static int __cil_typetransition_to_avtab(policydb_t *pdb, const struct cil_db *d
 
 				rc = __cil_typetransition_to_avtab_helper(
 					pdb, sepol_src, sepol_tgt, class_list,
-					name, sepol_result
+					name, typetrans->match_type, sepol_result
 				);
 				if (rc != SEPOL_OK) goto exit;
 			}
diff --git a/libsepol/cil/src/cil_build_ast.c b/libsepol/cil/src/cil_build_ast.c
index 8976c254..94dadef8 100644
--- a/libsepol/cil/src/cil_build_ast.c
+++ b/libsepol/cil/src/cil_build_ast.c
@@ -3392,10 +3392,11 @@  int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
 		CIL_SYN_STRING,
 		CIL_SYN_STRING,
 		CIL_SYN_STRING | CIL_SYN_END,
-		CIL_SYN_END
+		CIL_SYN_STRING | CIL_SYN_END,
+		CIL_SYN_END,
 	};
 	size_t syntax_len = sizeof(syntax)/sizeof(*syntax);
-	char *s1, *s2, *s3, *s4, *s5;
+	char *s1, *s2, *s3, *s4, *s5, *s6;
 
 	if (db == NULL || parse_current == NULL || ast_node == NULL ) {
 		goto exit;
@@ -3411,12 +3412,22 @@  int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
 	s3 = parse_current->next->next->next->data;
 	s4 = parse_current->next->next->next->next->data;
 	s5 = NULL;
+	s6 = NULL;
 
 	if (parse_current->next->next->next->next->next) {
 		if (s4 == CIL_KEY_STAR) {
-			s4 = parse_current->next->next->next->next->next->data;
+			if (parse_current->next->next->next->next->next->next) {
+				s4 = parse_current->next->next->next->next->next->next->data;
+			} else {
+				s4 = parse_current->next->next->next->next->next->data;
+			}
 		} else {
-			s5 = parse_current->next->next->next->next->next->data;
+			if (parse_current->next->next->next->next->next->next) {
+				s5 = parse_current->next->next->next->next->next->data;
+				s6 = parse_current->next->next->next->next->next->next->data;
+			} else {
+				s5 = parse_current->next->next->next->next->next->data;
+			}
 		}
 	}
 
@@ -3428,8 +3439,13 @@  int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
 		nametypetrans->src_str = s1;
 		nametypetrans->tgt_str = s2;
 		nametypetrans->obj_str = s3;
-		nametypetrans->result_str = s5;
 		nametypetrans->name_str = s4;
+		if (s6) {
+			nametypetrans->match_type_str = s5;
+			nametypetrans->result_str = s6;
+		} else {
+			nametypetrans->result_str = s5;
+		}
 
 		ast_node->data = nametypetrans;
 		ast_node->flavor = CIL_NAMETYPETRANSITION;
diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
index bc972f03..300390ec 100644
--- a/libsepol/cil/src/cil_copy_ast.c
+++ b/libsepol/cil/src/cil_copy_ast.c
@@ -726,6 +726,7 @@  int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
 	new->tgt_str = orig->tgt_str;
 	new->obj_str = orig->obj_str;
 	new->name_str = orig->name_str;
+	new->match_type_str = orig->match_type_str;
 	new->result_str = orig->result_str;
 
 
diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
index 9e492cb9..7f0be652 100644
--- a/libsepol/cil/src/cil_internal.h
+++ b/libsepol/cil/src/cil_internal.h
@@ -114,6 +114,8 @@  extern char *CIL_KEY_TUNABLEIF;
 extern char *CIL_KEY_ALLOW;
 extern char *CIL_KEY_DONTAUDIT;
 extern char *CIL_KEY_TYPETRANSITION;
+extern char *CIL_KEY_PREFIX;
+extern char *CIL_KEY_SUFFIX;
 extern char *CIL_KEY_TYPECHANGE;
 extern char *CIL_KEY_CALL;
 extern char *CIL_KEY_TUNABLE;
@@ -580,6 +582,8 @@  struct cil_nametypetransition {
 	struct cil_class *obj;
 	char *name_str;
 	struct cil_name *name;
+	char *match_type_str;
+	uint32_t match_type;
 	char *result_str;
 	void *result; /* type or alias */
 
diff --git a/libsepol/cil/src/cil_policy.c b/libsepol/cil/src/cil_policy.c
index feb97868..9776dfd3 100644
--- a/libsepol/cil/src/cil_policy.c
+++ b/libsepol/cil/src/cil_policy.c
@@ -1260,6 +1260,7 @@  static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
 	struct cil_name *name;
 	struct cil_list *class_list;
 	struct cil_list_item *i1;
+	const char *match_type_str = "";
 
 	src = trans->src;
 	tgt = trans->tgt;
@@ -1268,7 +1269,21 @@  static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
 
 	class_list = cil_expand_class(trans->obj);
 	cil_list_for_each(i1, class_list) {
-		fprintf(out, "type_transition %s %s : %s %s \"%s\";\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn);
+		switch (trans->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+			match_type_str = "";
+			break;
+		case FILENAME_TRANS_MATCH_PREFIX:
+			match_type_str = " prefix";
+			break;
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			match_type_str = " suffix";
+			break;
+		default:
+			match_type_str = "???";
+			break;
+		}
+		fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->datum.fqn, match_type_str);
 	}
 	cil_list_destroy(&class_list, CIL_FALSE);
 }
diff --git a/libsepol/cil/src/cil_resolve_ast.c b/libsepol/cil/src/cil_resolve_ast.c
index 33b9d321..3b00f065 100644
--- a/libsepol/cil/src/cil_resolve_ast.c
+++ b/libsepol/cil/src/cil_resolve_ast.c
@@ -717,6 +717,18 @@  int cil_resolve_nametypetransition(struct cil_tree_node *current, void *extra_ar
 		nametypetrans->name = (struct cil_name *)name_datum;
 	}
 
+	if (nametypetrans->match_type_str == NULL) {
+		nametypetrans->match_type = FILENAME_TRANS_MATCH_EXACT;
+	} else if (nametypetrans->match_type_str == CIL_KEY_PREFIX) {
+		nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
+	} else if (nametypetrans->match_type_str == CIL_KEY_SUFFIX) {
+		nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
+	} else {
+		cil_tree_log(current, CIL_ERR, "Invalid name match type \"%s\"", nametypetrans->match_type_str);
+		rc = SEPOL_ERR;
+		goto exit;
+	}
+
 	rc = cil_resolve_name(current, nametypetrans->result_str, CIL_SYM_TYPES, extra_args, &result_datum);
 	if (rc != SEPOL_OK) {
 		goto exit;
diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
index 4da7a77c..99c82292 100644
--- a/libsepol/cil/src/cil_write_ast.c
+++ b/libsepol/cil/src/cil_write_ast.c
@@ -1178,6 +1178,8 @@  void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
 		fprintf(out, "%s ", datum_or_str(DATUM(rule->tgt), rule->tgt_str));
 		fprintf(out, "%s ", datum_or_str(DATUM(rule->obj), rule->obj_str));
 		fprintf(out, "\"%s\" ", datum_or_str(DATUM(rule->name), rule->name_str));
+		if (rule->match_type != FILENAME_TRANS_MATCH_EXACT)
+			fprintf(out, "%s ", rule->match_type_str);
 		fprintf(out, "%s", datum_or_str(DATUM(rule->result), rule->result_str));
 		fprintf(out, ")\n");
 		break;
diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
index 6682069e..7727ca52 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -321,6 +321,7 @@  typedef struct filename_trans_rule {
 	uint32_t tclass;
 	char *name;
 	uint32_t otype;	/* new type */
+	uint32_t match_type;
 	struct filename_trans_rule *next;
 } filename_trans_rule_t;
 
@@ -423,6 +424,14 @@  typedef struct genfs {
 /* OCON_NUM needs to be the largest index in any platform's ocontext array */
 #define OCON_NUM   9
 
+/* filename transitions table array indices */
+enum {
+	FILENAME_TRANS_MATCH_EXACT,
+	FILENAME_TRANS_MATCH_PREFIX,
+	FILENAME_TRANS_MATCH_SUFFIX,
+	FILENAME_TRANS_MATCH_NUM,
+};
+
 /* section: module information */
 
 /* scope_index_t holds all of the symbols that are in scope in a
@@ -593,7 +602,7 @@  typedef struct policydb {
 	hashtab_t range_tr;
 
 	/* file transitions with the last path component */
-	hashtab_t filename_trans;
+	hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
 	uint32_t filename_trans_count;
 
 	ebitmap_t *type_attr_map;
@@ -657,7 +666,8 @@  extern int policydb_sort_ocontexts(policydb_t *p);
 extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
 				     uint32_t ttype, uint32_t tclass,
 				     const char *name, char **name_alloc,
-				     uint32_t otype, uint32_t *present_otype);
+				     uint32_t otype, uint32_t match_type,
+				     uint32_t *present_otype);
 
 /* Deprecated */
 extern int policydb_context_isvalid(const policydb_t * p,
@@ -758,10 +768,11 @@  extern int policydb_set_target_platform(policydb_t *p, int platform);
 #define POLICYDB_VERSION_INFINIBAND		31 /* Linux-specific */
 #define POLICYDB_VERSION_GLBLUB		32
 #define POLICYDB_VERSION_COMP_FTRANS	33 /* compressed filename transitions */
+#define POLICYDB_VERSION_PREFIX_SUFFIX	34 /* prefix and suffix filename transitions */
 
 /* Range of policy versions we understand*/
 #define POLICYDB_VERSION_MIN	POLICYDB_VERSION_BASE
-#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_COMP_FTRANS
+#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_PREFIX_SUFFIX
 
 /* Module versions and specific changes*/
 #define MOD_POLICYDB_VERSION_BASE		4
@@ -784,9 +795,10 @@  extern int policydb_set_target_platform(policydb_t *p, int platform);
 #define MOD_POLICYDB_VERSION_INFINIBAND		19
 #define MOD_POLICYDB_VERSION_GLBLUB		20
 #define MOD_POLICYDB_VERSION_SELF_TYPETRANS	21
+#define MOD_POLICYDB_VERSION_PREFIX_SUFFIX	22
 
 #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
-#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_SELF_TYPETRANS
+#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_PREFIX_SUFFIX
 
 #define POLICYDB_CONFIG_MLS    1
 
diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
index ee5f9185..4df9521a 100644
--- a/libsepol/src/expand.c
+++ b/libsepol/src/expand.c
@@ -1419,18 +1419,31 @@  static int expand_filename_trans_helper(expand_state_t *state,
 	rc = policydb_filetrans_insert(
 		state->out, s + 1, t + 1,
 		rule->tclass, rule->name,
-		NULL, mapped_otype, &present_otype
+		NULL, mapped_otype, rule->match_type, &present_otype
 	);
 	if (rc == SEPOL_EEXIST) {
 		/* duplicate rule, ignore */
 		if (present_otype == mapped_otype)
 			return 0;
 
-		ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\":  %s vs %s",
+		const char *match_str = "";
+		switch (rule->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+			match_str = "";
+			break;
+		case FILENAME_TRANS_MATCH_PREFIX:
+			match_str = " prefix";
+			break;
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			match_str = " suffix";
+			break;
+		}
+		ERR(state->handle, "Conflicting name-based type_transition %s %s:%s \"%s\"%s:  %s vs %s",
 		    state->out->p_type_val_to_name[s],
 		    state->out->p_type_val_to_name[t],
 		    state->out->p_class_val_to_name[rule->tclass - 1],
 		    rule->name,
+		    match_str,
 		    state->out->p_type_val_to_name[present_otype - 1],
 		    state->out->p_type_val_to_name[mapped_otype - 1]);
 		return -1;
diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
index 8fcc385d..81efeaa2 100644
--- a/libsepol/src/kernel_to_cil.c
+++ b/libsepol/src/kernel_to_cil.c
@@ -1869,6 +1869,7 @@  exit:
 
 struct map_filename_trans_args {
 	struct policydb *pdb;
+	uint32_t match_type;
 	struct strs *strs;
 };
 
@@ -1883,6 +1884,19 @@  static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
 	struct ebitmap_node *node;
 	uint32_t bit;
 	int rc;
+	const char *match_str = "";
+
+	switch (map_args->match_type) {
+	case FILENAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case FILENAME_TRANS_MATCH_PREFIX:
+		match_str = " prefix";
+		break;
+	case FILENAME_TRANS_MATCH_SUFFIX:
+		match_str = " suffix";
+		break;
+	}
 
 	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
 	class = pdb->p_class_val_to_name[ft->tclass - 1];
@@ -1893,8 +1907,8 @@  static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
 		ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
 			src = pdb->p_type_val_to_name[bit];
 			rc = strs_create_and_add(strs,
-						 "(typetransition %s %s %s \"%s\" %s)",
-						 5, src, tgt, class, filename, new);
+						 "(typetransition %s %s %s \"%s\"%s %s)",
+						 6, src, tgt, class, filename, match_str, new);
 			if (rc)
 				return rc;
 		}
@@ -1919,7 +1933,23 @@  static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
 	args.pdb = pdb;
 	args.strs = strs;
 
-	rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
+	args.match_type = FILENAME_TRANS_MATCH_EXACT;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_type = FILENAME_TRANS_MATCH_PREFIX;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+	
+	args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+			 map_filename_trans_to_str, &args);
 	if (rc != 0) {
 		goto exit;
 	}
diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
index b0ae16d9..99bef76e 100644
--- a/libsepol/src/kernel_to_conf.c
+++ b/libsepol/src/kernel_to_conf.c
@@ -1845,6 +1845,7 @@  exit:
 
 struct map_filename_trans_args {
 	struct policydb *pdb;
+	uint32_t match_type;
 	struct strs *strs;
 };
 
@@ -1859,6 +1860,19 @@  static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
 	struct ebitmap_node *node;
 	uint32_t bit;
 	int rc;
+	const char *match_str = "";
+
+	switch (map_args->match_type) {
+	case FILENAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case FILENAME_TRANS_MATCH_PREFIX:
+		match_str = " prefix";
+		break;
+	case FILENAME_TRANS_MATCH_SUFFIX:
+		match_str = " suffix";
+		break;
+	}
 
 	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
 	class = pdb->p_class_val_to_name[ft->tclass - 1];
@@ -1869,8 +1883,8 @@  static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
 		ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
 			src = pdb->p_type_val_to_name[bit];
 			rc = strs_create_and_add(strs,
-						 "type_transition %s %s:%s %s \"%s\";",
-						 5, src, tgt, class, new, filename);
+						 "type_transition %s %s:%s %s \"%s\"%s;",
+						 6, src, tgt, class, new, filename, match_str);
 			if (rc)
 				return rc;
 		}
@@ -1895,7 +1909,23 @@  static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
 	args.pdb = pdb;
 	args.strs = strs;
 
-	rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
+	args.match_type = FILENAME_TRANS_MATCH_EXACT;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_type = FILENAME_TRANS_MATCH_PREFIX;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_type = FILENAME_TRANS_MATCH_SUFFIX;
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+			 map_filename_trans_to_str, &args);
 	if (rc != 0) {
 		goto exit;
 	}
diff --git a/libsepol/src/link.c b/libsepol/src/link.c
index 3b7742bc..f432087f 100644
--- a/libsepol/src/link.c
+++ b/libsepol/src/link.c
@@ -1440,6 +1440,7 @@  static int copy_filename_trans_list(filename_trans_rule_t * list,
 		new_rule->name = strdup(cur->name);
 		if (!new_rule->name)
 			goto err;
+		new_rule->match_type = cur->match_type;
 
 		if (type_set_or_convert(&cur->stypes, &new_rule->stypes, module) ||
 		    type_set_or_convert(&cur->ttypes, &new_rule->ttypes, module))
diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
index d2868019..98e1c7ba 100644
--- a/libsepol/src/module_to_cil.c
+++ b/libsepol/src/module_to_cil.c
@@ -1609,6 +1609,7 @@  static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
 	unsigned int ttype;
 	struct type_set *ts;
 	struct filename_trans_rule *rule;
+	const char *match_str = "";
 
 	for (rule = rules; rule != NULL; rule = rule->next) {
 		ts = &rule->stypes;
@@ -1623,19 +1624,31 @@  static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filena
 			goto exit;
 		}
 
+		switch (rule->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+			match_str = "";
+			break;
+		case FILENAME_TRANS_MATCH_PREFIX:
+			match_str = " prefix";
+			break;
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			match_str = " suffix";
+			break;
+		}
+
 		for (stype = 0; stype < num_stypes; stype++) {
 			for (ttype = 0; ttype < num_ttypes; ttype++) {
-				cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
+				cil_println(indent, "(typetransition %s %s %s \"%s\"%s %s)",
 					    stypes[stype], ttypes[ttype],
 					    pdb->p_class_val_to_name[rule->tclass - 1],
-					    rule->name,
+					    rule->name, match_str,
 					    pdb->p_type_val_to_name[rule->otype - 1]);
 			}
 			if (rule->flags & RULE_SELF) {
-				cil_println(indent, "(typetransition %s self %s \"%s\" %s)",
+				cil_println(indent, "(typetransition %s self %s \"%s\"%s %s)",
 					    stypes[stype],
 					    pdb->p_class_val_to_name[rule->tclass - 1],
-					    rule->name,
+					    rule->name, match_str,
 					    pdb->p_type_val_to_name[rule->otype - 1]);
 			}
 		}
diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
index f9537caa..29d7971a 100644
--- a/libsepol/src/policydb.c
+++ b/libsepol/src/policydb.c
@@ -208,6 +208,13 @@  static const struct policydb_compat_info policydb_compat[] = {
 	 .ocon_num = OCON_IBENDPORT + 1,
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
+	{
+	 .type = POLICY_KERN,
+	 .version = POLICYDB_VERSION_PREFIX_SUFFIX,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = OCON_IBENDPORT + 1,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
 	{
 	 .type = POLICY_BASE,
 	 .version = MOD_POLICYDB_VERSION_BASE,
@@ -334,6 +341,13 @@  static const struct policydb_compat_info policydb_compat[] = {
 	 .ocon_num = OCON_IBENDPORT + 1,
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
+	{
+	 .type = POLICY_BASE,
+	 .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = OCON_IBENDPORT + 1,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
 	{
 	 .type = POLICY_MOD,
 	 .version = MOD_POLICYDB_VERSION_BASE,
@@ -460,6 +474,13 @@  static const struct policydb_compat_info policydb_compat[] = {
 	 .ocon_num = 0,
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
+	{
+	 .type = POLICY_MOD,
+	 .version = MOD_POLICYDB_VERSION_PREFIX_SUFFIX,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = 0,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
 };
 
 #if 0
@@ -909,10 +930,14 @@  int policydb_init(policydb_t * p)
 	if (rc)
 		goto err;
 
-	p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
-	if (!p->filename_trans) {
-		rc = -ENOMEM;
-		goto err;
+	for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
+		p->filename_trans[i] = hashtab_create(filenametr_hash,
+						      filenametr_cmp,
+						      (1 << 10));
+		if (!p->filename_trans[i]) {
+			rc = -ENOMEM;
+			goto err;
+		}
 	}
 
 	p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
@@ -926,7 +951,9 @@  int policydb_init(policydb_t * p)
 
 	return 0;
 err:
-	hashtab_destroy(p->filename_trans);
+	for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
+		hashtab_destroy(p->filename_trans[i]);
+	}
 	hashtab_destroy(p->range_tr);
 	for (i = 0; i < SYM_NUM; i++) {
 		hashtab_destroy(p->symtab[i].table);
@@ -1564,8 +1591,10 @@  void policydb_destroy(policydb_t * p)
 	if (lra)
 		free(lra);
 
-	hashtab_map(p->filename_trans, filenametr_destroy, NULL);
-	hashtab_destroy(p->filename_trans);
+	for (i = 0; i < FILENAME_TRANS_MATCH_NUM; i++) {
+		hashtab_map(p->filename_trans[i], filenametr_destroy, NULL);
+		hashtab_destroy(p->filename_trans[i]);
+	}
 
 	hashtab_map(p->range_tr, range_tr_destroy, NULL);
 	hashtab_destroy(p->range_tr);
@@ -2599,7 +2628,7 @@  static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
 int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
 			      uint32_t tclass, const char *name,
 			      char **name_alloc, uint32_t otype,
-			      uint32_t *present_otype)
+			      uint32_t match_type, uint32_t *present_otype)
 {
 	filename_trans_key_t *ft, key;
 	filename_trans_datum_t *datum, *last;
@@ -2609,7 +2638,8 @@  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
 	key.name = (char *)name;
 
 	last = NULL;
-	datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
+	datum = hashtab_search(p->filename_trans[match_type],
+			       (hashtab_key_t)&key);
 	while (datum) {
 		if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
 			if (present_otype)
@@ -2657,7 +2687,8 @@  int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
 			ft->tclass = tclass;
 			ft->name = name_dup;
 
-			if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
+			if (hashtab_insert(p->filename_trans[match_type],
+					   (hashtab_key_t)ft,
 					   (hashtab_datum_t)datum)) {
 				free(name_dup);
 				free(datum);
@@ -2704,8 +2735,9 @@  static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
 	tclass = le32_to_cpu(buf[2]);
 	otype  = le32_to_cpu(buf[3]);
 
+	// This version does not contain other than exact filename transitions
 	rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
-				       otype, NULL);
+				       otype, FILENAME_TRANS_MATCH_EXACT, NULL);
 	if (rc) {
 		if (rc != SEPOL_EEXIST)
 			goto err;
@@ -2753,7 +2785,8 @@  out:
 	return rc;
 }
 
-static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
+static int filename_trans_read_one(policydb_t *p, uint32_t match_type,
+				   struct policy_file *fp)
 {
 	filename_trans_key_t *ft = NULL;
 	filename_trans_datum_t **dst, *datum, *first = NULL;
@@ -2823,7 +2856,7 @@  static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
 	ft->tclass = tclass;
 	ft->name = name;
 
-	rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
+	rc = hashtab_insert(p->filename_trans[match_type], (hashtab_key_t)ft,
 			    (hashtab_datum_t)first);
 	if (rc)
 		goto err;
@@ -2842,7 +2875,8 @@  err:
 	return -1;
 }
 
-static int filename_trans_read(policydb_t *p, struct policy_file *fp)
+static int filename_trans_read(policydb_t *p, struct policy_file *fp,
+			       uint32_t match_type)
 {
 	unsigned int i;
 	uint32_t buf[1], nel;
@@ -2855,13 +2889,17 @@  static int filename_trans_read(policydb_t *p, struct policy_file *fp)
 
 	if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
 		for (i = 0; i < nel; i++) {
+			/*
+			 * this version does not have other than exact match
+			 * transitions
+			 */
 			rc = filename_trans_read_one_compat(p, fp);
 			if (rc < 0)
 				return -1;
 		}
 	} else {
 		for (i = 0; i < nel; i++) {
-			rc = filename_trans_read_one(p, fp);
+			rc = filename_trans_read_one(p, match_type, fp);
 			if (rc < 0)
 				return -1;
 		}
@@ -3837,7 +3875,7 @@  static int role_allow_rule_read(role_allow_rule_t ** r, struct policy_file *fp)
 static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
 				    struct policy_file *fp)
 {
-	uint32_t buf[3], nel, i, len;
+	uint32_t buf[4], nel, i, len;
 	unsigned int entries;
 	filename_trans_rule_t *ftr, *lftr;
 	int rc;
@@ -3883,7 +3921,9 @@  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
 		if (type_set_read(&ftr->ttypes, fp))
 			return -1;
 
-		if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
+		if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
+			entries = 4;
+		else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
 			entries = 3;
 		else
 			entries = 2;
@@ -3895,6 +3935,8 @@  static int filename_trans_rule_read(policydb_t *p, filename_trans_rule_t **r,
 		ftr->otype = le32_to_cpu(buf[1]);
 		if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS)
 			ftr->flags = le32_to_cpu(buf[2]);
+		if (p->policyvers >=  MOD_POLICYDB_VERSION_PREFIX_SUFFIX)
+			ftr->match_type = le32_to_cpu(buf[3]);
 	}
 
 	return 0;
@@ -4470,7 +4512,11 @@  int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
 		if (role_allow_read(&p->role_allow, fp))
 			goto bad;
 		if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
-		    filename_trans_read(p, fp))
+		    filename_trans_read(p, fp, FILENAME_TRANS_MATCH_EXACT))
+			goto bad;
+		if (r_policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX &&
+		    (filename_trans_read(p, fp, FILENAME_TRANS_MATCH_PREFIX) ||
+		     filename_trans_read(p, fp, FILENAME_TRANS_MATCH_SUFFIX)))
 			goto bad;
 	} else {
 		/* first read the AV rule blocks, then the scope tables */
diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index 892a0ffd..3f2ba5ea 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -1,4 +1,3 @@ 
-
 #include <sepol/policydb/conditional.h>
 #include <sepol/policydb/ebitmap.h>
 #include <sepol/policydb/policydb.h>
@@ -1115,14 +1114,27 @@  bad:
 	return -1;
 }
 
-static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
+static int validate_filename_trans_hashtabs(sepol_handle_t *handle,
+					    const policydb_t *p,
+					    validate_t flavors[])
 {
-	if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
-		ERR(handle, "Invalid filename trans");
-		return -1;
+	if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			validate_filename_trans, flavors))
+		goto bad;
+
+	if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+		if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+				validate_filename_trans, flavors))
+			goto bad;
+		if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+				validate_filename_trans, flavors))
+			goto bad;
 	}
 
 	return 0;
+bad:
+	ERR(handle, "Invalid filename trans");
+	return -1;
 }
 
 static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
@@ -1334,6 +1346,15 @@  static int validate_filename_trans_rules(sepol_handle_t *handle, const filename_
 		if (validate_simpletype(filename_trans->otype, p, flavors))
 			goto bad;
 
+		switch (filename_trans->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+		case FILENAME_TRANS_MATCH_PREFIX:
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			break;
+		default:
+			goto bad;
+		}
+
 		/* currently only the RULE_SELF flag can be set */
 		if ((filename_trans->flags & ~RULE_SELF) != 0)
 			goto bad;
@@ -1554,9 +1575,10 @@  int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
 			goto bad;
 		if (validate_role_allows(handle, p->role_allow, flavors))
 			goto bad;
-		if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
-			if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
+		if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
+			if (validate_filename_trans_hashtabs(handle, p, flavors))
 				goto bad;
+		}
 	} else {
 		if (validate_avrule_blocks(handle, p->global, p, flavors))
 			goto bad;
diff --git a/libsepol/src/write.c b/libsepol/src/write.c
index 283d11c8..c9398a98 100644
--- a/libsepol/src/write.c
+++ b/libsepol/src/write.c
@@ -653,7 +653,8 @@  static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
 	return 0;
 }
 
-static int filename_trans_write(struct policydb *p, void *fp)
+static int filename_trans_write(struct policydb *p, uint32_t match_type,
+				void *fp)
 {
 	size_t items;
 	uint32_t buf[1];
@@ -663,20 +664,25 @@  static int filename_trans_write(struct policydb *p, void *fp)
 		return 0;
 
 	if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
+		/*
+		 * This version does not have other than exact match
+		 * transitions, there is no need to count other ones.
+		 */
 		buf[0] = cpu_to_le32(p->filename_trans_count);
 		items = put_entry(buf, sizeof(uint32_t), 1, fp);
 		if (items != 1)
 			return POLICYDB_ERROR;
 
-		rc = hashtab_map(p->filename_trans, filename_write_one_compat,
-				 fp);
+		rc = hashtab_map(p->filename_trans[match_type],
+				 filename_write_one_compat, fp);
 	} else {
-		buf[0] = cpu_to_le32(p->filename_trans->nel);
+		buf[0] = cpu_to_le32(p->filename_trans[match_type]->nel);
 		items = put_entry(buf, sizeof(uint32_t), 1, fp);
 		if (items != 1)
 			return POLICYDB_ERROR;
 
-		rc = hashtab_map(p->filename_trans, filename_write_one, fp);
+		rc = hashtab_map(p->filename_trans[match_type],
+				 filename_write_one, fp);
 	}
 	return rc;
 }
@@ -1944,7 +1950,7 @@  static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
 {
 	int nel = 0;
 	size_t items, entries;
-	uint32_t buf[3], len;
+	uint32_t buf[4], len;
 	filename_trans_rule_t *ftr;
 
 	for (ftr = t; ftr; ftr = ftr->next)
@@ -1974,8 +1980,11 @@  static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
 		buf[0] = cpu_to_le32(ftr->tclass);
 		buf[1] = cpu_to_le32(ftr->otype);
 		buf[2] = cpu_to_le32(ftr->flags);
+		buf[3] = cpu_to_le32(ftr->match_type);
 
-		if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
+		if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
+			entries = 4;
+		} else if (p->policyvers >= MOD_POLICYDB_VERSION_SELF_TYPETRANS) {
 			entries = 3;
 		} else if (!(ftr->flags & RULE_SELF)) {
 			entries = 2;
@@ -2370,12 +2379,29 @@  int policydb_write(policydb_t * p, struct policy_file *fp)
 		if (role_allow_write(p->role_allow, fp))
 			return POLICYDB_ERROR;
 		if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
-			if (filename_trans_write(p, fp))
+			if (filename_trans_write(p, FILENAME_TRANS_MATCH_EXACT,
+						 fp))
 				return POLICYDB_ERROR;
 		} else {
-			if (p->filename_trans)
+			if (p->filename_trans[FILENAME_TRANS_MATCH_EXACT])
 				WARN(fp->handle, "Discarding filename type transition rules");
 		}
+		if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+			if (filename_trans_write(p, FILENAME_TRANS_MATCH_PREFIX,
+						 fp) ||
+			    filename_trans_write(p, FILENAME_TRANS_MATCH_SUFFIX,
+						 fp))
+				return POLICYDB_ERROR;
+		} else {
+			if (p->filename_trans[FILENAME_TRANS_MATCH_PREFIX] &&
+			    p->filename_trans[FILENAME_TRANS_MATCH_PREFIX]->nel)
+				WARN(fp->handle,
+				     "Discarding prefix filename type transition rules");
+			if (p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX] &&
+			    p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX]->nel)
+				WARN(fp->handle,
+				     "Discarding suffix filename type transition rules");
+		}
 	} else {
 		if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
 			return POLICYDB_ERROR;