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

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

 



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/20231121122719.2332137-1-juraj@xxxxxxxxxxxxxxx/

Reviewed-by: Ondrej Mosnacek <omosnace@xxxxxxxxxx>
Signed-off-by: Juraj Marcin <juraj@xxxxxxxxxxxxxxx>
---
v6:
- removed match_type_str from cil structures and moved match type
  parsing to the build phase, as suggested by Jim
- add match string to map arg structs instead of match type, as
  suggested by Jim

v5:
- rebased to the latest main, fixed conflicts in libsepol/cil

v4:
- rebased to the latest main, fixed conflicts in policydb_validate.c
- added syntax tests to checkpolicy and secilc as proposed by Christian
- retested with more test cases

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                     |  14 ++-
 checkpolicy/tests/policy_allonce.conf         |   2 +
 .../tests/policy_allonce.expected.conf        |   8 ++
 .../tests/policy_allonce.expected_opt.conf    |   8 ++
 checkpolicy/tests/policy_allonce_mls.conf     |   2 +
 .../tests/policy_allonce_mls.expected.conf    |   8 ++
 .../policy_allonce_mls.expected_opt.conf      |   8 ++
 libsepol/cil/src/cil.c                        |   5 +
 libsepol/cil/src/cil_binary.c                 |   8 +-
 libsepol/cil/src/cil_build_ast.c              |  36 ++++++-
 libsepol/cil/src/cil_copy_ast.c               |   1 +
 libsepol/cil/src/cil_internal.h               |   3 +
 libsepol/cil/src/cil_policy.c                 |  17 ++-
 libsepol/cil/src/cil_write_ast.c              |  10 ++
 libsepol/include/sepol/policydb/policydb.h    |  23 +++-
 libsepol/src/expand.c                         |  17 ++-
 libsepol/src/kernel_to_cil.c                  |  24 ++++-
 libsepol/src/kernel_to_conf.c                 |  24 ++++-
 libsepol/src/link.c                           |   1 +
 libsepol/src/module_to_cil.c                  |  21 +++-
 libsepol/src/policydb.c                       | 100 ++++++++++++++----
 libsepol/src/policydb_validate.c              |  26 ++++-
 libsepol/src/write.c                          |  67 +++++++++---
 secilc/test/policy.cil                        |   4 +
 29 files changed, 407 insertions(+), 70 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 9f4a669b..931af9da 100644
--- a/checkpolicy/test/dismod.c
+++ b/checkpolicy/test/dismod.c
@@ -571,13 +571,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 944ef7ec..999c26ca 100644
--- a/checkpolicy/test/dispol.c
+++ b/checkpolicy/test/dispol.c
@@ -458,6 +458,7 @@ static void display_role_trans(policydb_t *p, FILE *fp)
 
 struct filenametr_display_args {
 	policydb_t *p;
+	const char *match_str;
 	FILE *fp;
 };
 
@@ -472,6 +473,7 @@ static int filenametr_display(hashtab_key_t key,
 	FILE *fp = args->fp;
 	ebitmap_node_t *node;
 	uint32_t bit;
+	const char *match_str = args->match_str;
 
 	do {
 		ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
@@ -479,7 +481,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);
@@ -495,7 +497,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_str = "";
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+		    filenametr_display, &args);
+	args.match_str = " prefix";
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+		    filenametr_display, &args);
+	args.match_str = " suffix";
+	hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+		    filenametr_display, &args);
 }
 
 static int menu(void)
diff --git a/checkpolicy/tests/policy_allonce.conf b/checkpolicy/tests/policy_allonce.conf
index 34e6402d..fb0a0172 100644
--- a/checkpolicy/tests/policy_allonce.conf
+++ b/checkpolicy/tests/policy_allonce.conf
@@ -30,6 +30,8 @@ tunable TUNABLE1 false;
 tunable TUNABLE2 true;
 type_transition TYPE1 TYPE2 : CLASS1 TYPE3;
 type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME";
+type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME" prefix;
+type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME" suffix;
 type_member TYPE1 TYPE2 : CLASS1 TYPE2;
 type_change TYPE1 TYPE2 : CLASS1 TYPE3;
 allow TYPE1 self : CLASS1 { PERM1 };
diff --git a/checkpolicy/tests/policy_allonce.expected.conf b/checkpolicy/tests/policy_allonce.expected.conf
index 63739e1f..e3db7593 100644
--- a/checkpolicy/tests/policy_allonce.expected.conf
+++ b/checkpolicy/tests/policy_allonce.expected.conf
@@ -40,9 +40,17 @@ dontauditxperm TYPE1 TYPE2:CLASS1 ioctl { 0x3 };
 type_transition TYPE1 TYPE2:CLASS1 TYPE3;
 type_member TYPE1 TYPE2:CLASS1 TYPE2;
 type_change TYPE1 TYPE2:CLASS1 TYPE3;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME";
 if (BOOL1) {
 } else {
diff --git a/checkpolicy/tests/policy_allonce.expected_opt.conf b/checkpolicy/tests/policy_allonce.expected_opt.conf
index 1c969961..94eaf37a 100644
--- a/checkpolicy/tests/policy_allonce.expected_opt.conf
+++ b/checkpolicy/tests/policy_allonce.expected_opt.conf
@@ -40,9 +40,17 @@ dontauditxperm TYPE1 TYPE2:CLASS1 ioctl { 0x3 };
 type_transition TYPE1 TYPE2:CLASS1 TYPE3;
 type_member TYPE1 TYPE2:CLASS1 TYPE2;
 type_change TYPE1 TYPE2:CLASS1 TYPE3;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME";
 if (BOOL1) {
 } else {
diff --git a/checkpolicy/tests/policy_allonce_mls.conf b/checkpolicy/tests/policy_allonce_mls.conf
index c88616b3..2ba88a00 100644
--- a/checkpolicy/tests/policy_allonce_mls.conf
+++ b/checkpolicy/tests/policy_allonce_mls.conf
@@ -41,6 +41,8 @@ tunable TUNABLE1 false;
 tunable TUNABLE2 true;
 type_transition TYPE1 TYPE2 : CLASS1 TYPE3;
 type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME";
+type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME" prefix;
+type_transition { TYPE1 TYPE2 } { TYPE3 TYPE4 } : CLASS1 TYPE1 "FILENAME" suffix;
 type_member TYPE1 TYPE2 : CLASS1 TYPE2;
 type_change TYPE1 TYPE2 : CLASS1 TYPE3;
 range_transition TYPE1 TYPE2 : CLASS1 s1:c0.c1;
diff --git a/checkpolicy/tests/policy_allonce_mls.expected.conf b/checkpolicy/tests/policy_allonce_mls.expected.conf
index 87c36f92..d390aac0 100644
--- a/checkpolicy/tests/policy_allonce_mls.expected.conf
+++ b/checkpolicy/tests/policy_allonce_mls.expected.conf
@@ -51,9 +51,17 @@ dontauditxperm TYPE1 TYPE2:CLASS1 ioctl { 0x3 };
 type_transition TYPE1 TYPE2:CLASS1 TYPE3;
 type_member TYPE1 TYPE2:CLASS1 TYPE2;
 type_change TYPE1 TYPE2:CLASS1 TYPE3;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME";
 range_transition TYPE1 TYPE2:CLASS1 s1:c0,c1 - s1:c0,c1;
 if (BOOL1) {
diff --git a/checkpolicy/tests/policy_allonce_mls.expected_opt.conf b/checkpolicy/tests/policy_allonce_mls.expected_opt.conf
index 38176166..ef683070 100644
--- a/checkpolicy/tests/policy_allonce_mls.expected_opt.conf
+++ b/checkpolicy/tests/policy_allonce_mls.expected_opt.conf
@@ -51,9 +51,17 @@ dontauditxperm TYPE1 TYPE2:CLASS1 ioctl { 0x3 };
 type_transition TYPE1 TYPE2:CLASS1 TYPE3;
 type_member TYPE1 TYPE2:CLASS1 TYPE2;
 type_change TYPE1 TYPE2:CLASS1 TYPE3;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE1 TYPE4:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE3:CLASS1 TYPE1 "FILENAME";
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" prefix;
+type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME" suffix;
 type_transition TYPE2 TYPE4:CLASS1 TYPE1 "FILENAME";
 range_transition TYPE1 TYPE2:CLASS1 s1:c0,c1 - s1:c0,c1;
 if (BOOL1) {
diff --git a/libsepol/cil/src/cil.c b/libsepol/cil/src/cil.c
index 067e28a6..fd1f7bd8 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");
@@ -2461,6 +2465,7 @@ void cil_nametypetransition_init(struct cil_nametypetransition **nametypetrans)
 	(*nametypetrans)->obj = NULL;
 	(*nametypetrans)->name_str = NULL;
 	(*nametypetrans)->name = 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 be260a31..d91b8d20 100644
--- a/libsepol/cil/src/cil_build_ast.c
+++ b/libsepol/cil/src/cil_build_ast.c
@@ -3386,10 +3386,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;
@@ -3405,12 +3406,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;
+			}
 		}
 	}
 
@@ -3426,7 +3437,22 @@ int cil_gen_typetransition(struct cil_db *db, struct cil_tree_node *parse_curren
 		nametypetrans->obj_str = s3;
 		nametypetrans->name_str = s4;
 		nametypetrans->name = cil_gen_declared_string(db, s4, ast_node);
-		nametypetrans->result_str = s5;
+		if (s6) {
+			if (s5 == CIL_KEY_PREFIX) {
+				nametypetrans->match_type = FILENAME_TRANS_MATCH_PREFIX;
+			} else if (s5 == CIL_KEY_SUFFIX) {
+				nametypetrans->match_type = FILENAME_TRANS_MATCH_SUFFIX;
+			} else {
+				rc = SEPOL_ERR;
+				goto exit;
+			}
+			nametypetrans->result_str = s6;
+		} else {
+			nametypetrans->result_str = s5;
+		}
+
+		ast_node->data = nametypetrans;
+		ast_node->flavor = CIL_NAMETYPETRANSITION;
 	} else {
 		struct cil_type_rule *rule = NULL;
 		cil_type_rule_init(&rule);
diff --git a/libsepol/cil/src/cil_copy_ast.c b/libsepol/cil/src/cil_copy_ast.c
index 1507edb4..64e2cacd 100644
--- a/libsepol/cil/src/cil_copy_ast.c
+++ b/libsepol/cil/src/cil_copy_ast.c
@@ -723,6 +723,7 @@ int cil_copy_nametypetransition(__attribute__((unused)) struct cil_db *db, void
 	new->obj_str = orig->obj_str;
 	new->name_str = orig->name_str;
 	new->name = orig->name;
+	new->match_type = orig->match_type;
 	new->result_str = orig->result_str;
 
 
diff --git a/libsepol/cil/src/cil_internal.h b/libsepol/cil/src/cil_internal.h
index 47b67c89..8c9f1151 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;
@@ -588,6 +590,7 @@ struct cil_nametypetransition {
 	struct cil_class *obj;
 	char *name_str;
 	struct cil_symtab_datum *name;
+	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 e9a8f75d..b7f096b7 100644
--- a/libsepol/cil/src/cil_policy.c
+++ b/libsepol/cil/src/cil_policy.c
@@ -1259,15 +1259,30 @@ static void cil_nametypetransition_to_policy(FILE *out, struct cil_nametypetrans
 	struct cil_symtab_datum *src, *tgt, *name, *res;
 	struct cil_list *class_list;
 	struct cil_list_item *i1;
+	const char *match_type_str = "";
 
 	src = trans->src;
 	tgt = trans->tgt;
 	name = trans->name;
 	res = trans->result;
+	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;
+	}
 
 	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->fqn);
+		fprintf(out, "type_transition %s %s : %s %s \"%s\"%s;\n", src->fqn, tgt->fqn, DATUM(i1->data)->fqn, res->fqn, name->fqn, match_type_str);
 	}
 	cil_list_destroy(&class_list, CIL_FALSE);
 }
diff --git a/libsepol/cil/src/cil_write_ast.c b/libsepol/cil/src/cil_write_ast.c
index f4f9f167..dbf01f5a 100644
--- a/libsepol/cil/src/cil_write_ast.c
+++ b/libsepol/cil/src/cil_write_ast.c
@@ -1217,6 +1217,16 @@ void cil_write_ast_node(FILE *out, struct cil_tree_node *node)
 		} else {
 			fprintf(out, "%s ", rule->name_str);
 		}
+		switch (rule->match_type) {
+		case FILENAME_TRANS_MATCH_EXACT:
+			break;
+		case FILENAME_TRANS_MATCH_PREFIX:
+			fprintf(out, "prefix ");
+			break;
+		case FILENAME_TRANS_MATCH_SUFFIX:
+			fprintf(out, "suffix ");
+			break;
+		}
 		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..5ee0fa16 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -321,6 +321,8 @@ typedef struct filename_trans_rule {
 	uint32_t tclass;
 	char *name;
 	uint32_t otype;	/* new type */
+ 	/* name match type, values from enum filename_trans_match_type */
+	uint32_t match_type;
 	struct filename_trans_rule *next;
 } filename_trans_rule_t;
 
@@ -423,6 +425,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_type {
+	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,8 +603,8 @@ typedef struct policydb {
 	hashtab_t range_tr;
 
 	/* file transitions with the last path component */
-	hashtab_t filename_trans;
-	uint32_t filename_trans_count;
+	hashtab_t filename_trans[FILENAME_TRANS_MATCH_NUM];
+	uint32_t filename_trans_exact_count;
 
 	ebitmap_t *type_attr_map;
 
@@ -657,7 +667,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 +769,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 +796,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 e63414b1..93170a14 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 8ec79749..5dd6cfdc 100644
--- a/libsepol/src/kernel_to_cil.c
+++ b/libsepol/src/kernel_to_cil.c
@@ -1882,6 +1882,7 @@ exit:
 
 struct map_filename_trans_args {
 	struct policydb *pdb;
+	const char *match_str;
 	struct strs *strs;
 };
 
@@ -1896,6 +1897,7 @@ 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 = map_args->match_str;
 
 	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
 	class = pdb->p_class_val_to_name[ft->tclass - 1];
@@ -1906,8 +1908,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;
 		}
@@ -1932,7 +1934,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_str = "";
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_str = " prefix";
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_str = " 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 b5b530d6..d3631d8d 100644
--- a/libsepol/src/kernel_to_conf.c
+++ b/libsepol/src/kernel_to_conf.c
@@ -1846,6 +1846,7 @@ exit:
 
 struct map_filename_trans_args {
 	struct policydb *pdb;
+	const char *match_str;
 	struct strs *strs;
 };
 
@@ -1860,6 +1861,7 @@ 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 = map_args->match_str;
 
 	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
 	class = pdb->p_class_val_to_name[ft->tclass - 1];
@@ -1870,8 +1872,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;
 		}
@@ -1896,7 +1898,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_str = "";
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_str = " prefix";
+	rc = hashtab_map(pdb->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+			 map_filename_trans_to_str, &args);
+	if (rc != 0) {
+		goto exit;
+	}
+
+	args.match_str = " 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 ee22dbbd..a90ee85d 100644
--- a/libsepol/src/module_to_cil.c
+++ b/libsepol/src/module_to_cil.c
@@ -1611,6 +1611,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;
@@ -1625,19 +1626,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 6ba4f916..a1796844 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);
@@ -2566,7 +2595,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;
@@ -2576,7 +2605,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)
@@ -2624,7 +2654,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);
@@ -2634,7 +2665,14 @@ int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
 		}
 	}
 
-	p->filename_trans_count++;
+        /*
+         * We need to keep track of the number of exact match filename
+	 * transitions for writing them in uncompressed format in older binary
+	 * policy versions. Other match types were not supported back then, so
+	 * it is not needed.
+         */
+        if (match_type == FILENAME_TRANS_MATCH_EXACT)
+		p->filename_trans_exact_count++;
 	return ebitmap_set_bit(&datum->stypes, stype - 1, 1);
 }
 
@@ -2665,8 +2703,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;
@@ -2714,7 +2753,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;
@@ -2762,7 +2802,14 @@ static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
 
 		datum->otype = le32_to_cpu(buf[0]);
 
-		p->filename_trans_count += ebitmap_cardinality(&datum->stypes);
+		/*
+		 * We need to keep track of the number of exact match filename
+		 * transitions for writing them in uncompressed format in older
+		 * binary policy versions. Other match types were not supported
+		 * back then, so it is not needed
+		 */
+		if (match_type == FILENAME_TRANS_MATCH_EXACT)
+			p->filename_trans_exact_count += ebitmap_cardinality(&datum->stypes);
 
 		dst = &datum->next;
 	}
@@ -2778,7 +2825,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;
@@ -2797,7 +2844,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;
@@ -2810,13 +2858,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;
 		}
@@ -3743,7 +3795,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;
@@ -3782,7 +3834,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;
@@ -3794,6 +3848,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;
@@ -4356,7 +4412,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 8b87675f..d94f49aa 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -1147,12 +1147,23 @@ static int validate_filename_trans_hashtab(sepol_handle_t *handle, const policyd
 {
 	map_arg_t margs = { flavors, handle, p };
 
-	if (hashtab_map(p->filename_trans, validate_filename_trans, &margs)) {
-		ERR(handle, "Invalid filename trans");
-		return -1;
+	if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_EXACT],
+			validate_filename_trans, &margs))
+		goto bad;
+
+	if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+		if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_PREFIX],
+				validate_filename_trans, &margs))
+			goto bad;
+		if (hashtab_map(p->filename_trans[FILENAME_TRANS_MATCH_SUFFIX],
+				validate_filename_trans, &margs))
+			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)
@@ -1374,6 +1385,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;
diff --git a/libsepol/src/write.c b/libsepol/src/write.c
index 283d11c8..6bcd7535 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) {
-		buf[0] = cpu_to_le32(p->filename_trans_count);
+		/*
+		 * 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_exact_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,11 +1950,25 @@ 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;
+	int discarding = 0;
 
-	for (ftr = t; ftr; ftr = ftr->next)
-		nel++;
+	if (p->policyvers >= MOD_POLICYDB_VERSION_PREFIX_SUFFIX) {
+		for (ftr = t; ftr; ftr = ftr->next)
+			nel++;
+	} else {
+		for (ftr = t; ftr; ftr = ftr->next) {
+			if (ftr->match_type == FILENAME_TRANS_MATCH_EXACT)
+				nel++;
+			else
+				discarding = 1;
+		}
+		if (discarding) {
+			WARN(fp->handle,
+			     "Discarding prefix/suffix filename type transition rules");
+		}
+	}
 
 	buf[0] = cpu_to_le32(nel);
 	items = put_entry(buf, sizeof(uint32_t), 1, fp);
@@ -1956,6 +1976,9 @@ static int filename_trans_rule_write(policydb_t *p, filename_trans_rule_t *t,
 		return POLICYDB_ERROR;
 
 	for (ftr = t; ftr; ftr = ftr->next) {
+		if (p->policyvers < MOD_POLICYDB_VERSION_PREFIX_SUFFIX &&
+		    ftr->match_type != FILENAME_TRANS_MATCH_EXACT)
+			continue;
 		len = strlen(ftr->name);
 		buf[0] = cpu_to_le32(len);
 		items = put_entry(buf, sizeof(uint32_t), 1, fp);
@@ -1974,8 +1997,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 +2396,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;
diff --git a/secilc/test/policy.cil b/secilc/test/policy.cil
index e6b78618..73e678a7 100644
--- a/secilc/test/policy.cil
+++ b/secilc/test/policy.cil
@@ -134,6 +134,8 @@
 	(typetransition device_t exec_type files console_device_t)
 	(typetransition exec_type self files console_device_t)
 	(typetransition exec_type self files "filename" console_device_t)
+	(typetransition exec_type self files "filename" prefix console_device_t)
+	(typetransition exec_type self files "filename" suffix console_device_t)
 	(typechange console_device_t device_t file user_tty_device_t)
 	(typechange exec_type device_t file user_tty_device_t)
 	(typechange exec_type self file console_device_t)
@@ -153,6 +155,8 @@
 	(rangetransition device_t kernel_t file ((s0) (s3 (not c3))))
 
 	(typetransition device_t console_t file "some_file" getty_t)
+	(typetransition device_t console_t file "some_file" prefix getty_t)
+	(typetransition device_t console_t file "some_file" suffix getty_t)
 	
 	(allow foo_type self (file (execute)))
 	(allow bin_t device_t (file (execute)))
-- 
2.42.0





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

  Powered by Linux