diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
index e86ad2ed..efdd102b 100644
--- a/checkpolicy/test/dispol.c
+++ b/checkpolicy/test/dispol.c
@@ -129,6 +129,7 @@ typedef struct {
 	avtab_key_t *key;
 	policydb_t *p;
 	FILE *fp;
+	name_trans_match_t match;
 } render_name_trans_args_t;
 
 static int render_name_trans_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
@@ -140,7 +141,22 @@ static int render_name_trans_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
 	fprintf(args->fp, "type_transition ");
 	render_key(args->key, args->p, args->fp);
 	render_type(*otype, args->p, args->fp);
-	fprintf(args->fp, " \"%s\";\n", name);
+	const char *match_str = "";
+	switch (args->match) {
+	case NAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case NAME_TRANS_MATCH_PREFIX:
+		match_str = " PREFIX";
+		break;
+	case NAME_TRANS_MATCH_SUFFIX:
+		match_str = " SUFFIX";
+		break;
+	default:
+		fprintf(args->fp, "     ERROR: no valid name match type specified\n");
+		return -1;
+	}
+	fprintf(args->fp, " \"%s\"%s;\n", name, match_str);
 
 	return 0;
 }
@@ -207,9 +223,16 @@ static int render_av_rule(avtab_key_t * key, avtab_datum_t * datum, uint32_t wha
 				.key = key,
 				.p = p,
 				.fp = fp,
+				.match = NAME_TRANS_MATCH_EXACT,
 			};
 			hashtab_map(datum->trans->name_trans.table,
 				    render_name_trans_helper, &args);
+			args.match = NAME_TRANS_MATCH_PREFIX;
+			hashtab_map(datum->trans->prefix_trans.table,
+				    render_name_trans_helper, &args);
+			args.match = NAME_TRANS_MATCH_SUFFIX;
+			hashtab_map(datum->trans->suffix_trans.table,
+				    render_name_trans_helper, &args);
 		}
 		if (key->specified & AVTAB_MEMBER) {
 			fprintf(fp, "type_member ");
diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
index 5dc720cc..870fb08a 100644
--- a/libsepol/include/sepol/policydb/avtab.h
+++ b/libsepol/include/sepol/policydb/avtab.h
@@ -74,6 +74,8 @@ typedef struct avtab_key {
 typedef struct avtab_trans {
 	uint32_t otype;		/* resulting type of the new object */
 	symtab_t name_trans;	/* filename transitions */
+	symtab_t prefix_trans;	/* prefix filename transitions */
+	symtab_t suffix_trans;	/* prefix filename transitions */
 } avtab_trans_t;
 
 typedef struct avtab_extended_perms {
diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
index 5efd0a47..a2df4a62 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -252,6 +252,12 @@ typedef struct av_extended_perms {
 	uint32_t perms[EXTENDED_PERMS_LEN];
 } av_extended_perms_t;
 
+typedef enum name_trans_match {
+	NAME_TRANS_MATCH_EXACT,
+	NAME_TRANS_MATCH_PREFIX,
+	NAME_TRANS_MATCH_SUFFIX,
+} name_trans_match_t;
+
 typedef struct avrule {
 /* these typedefs are almost exactly the same as those in avtab.h - they are
  * here because of the need to include neverallow and dontaudit messages */
@@ -723,10 +729,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
 #define POLICYDB_VERSION_GLBLUB		32
 #define POLICYDB_VERSION_COMP_FTRANS	33 /* compressed filename transitions */
 #define POLICYDB_VERSION_AVTAB_FTRANS	34 /* filename transitions moved to avtab */
+#define POLICYDB_VERSION_PREFIX_SUFFIX	35 /* prefix/suffix support for filename transitions */
 
 /* Range of policy versions we understand*/
 #define POLICYDB_VERSION_MIN	POLICYDB_VERSION_BASE
-#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_AVTAB_FTRANS
+#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_PREFIX_SUFFIX
 
 /* Module versions and specific changes*/
 #define MOD_POLICYDB_VERSION_BASE		4
diff --git a/libsepol/src/avtab.c b/libsepol/src/avtab.c
index 2a9564ba..90cfb90b 100644
--- a/libsepol/src/avtab.c
+++ b/libsepol/src/avtab.c
@@ -327,6 +327,10 @@ void avtab_trans_destroy(avtab_trans_t *trans)
 {
 	hashtab_map(trans->name_trans.table, avtab_trans_destroy_helper, NULL);
 	symtab_destroy(&trans->name_trans);
+	hashtab_map(trans->prefix_trans.table, avtab_trans_destroy_helper, NULL);
+	symtab_destroy(&trans->prefix_trans);
+	hashtab_map(trans->suffix_trans.table, avtab_trans_destroy_helper, NULL);
+	symtab_destroy(&trans->suffix_trans);
 }
 
 void avtab_destroy(avtab_t * h)
@@ -520,6 +524,15 @@ static int avtab_trans_read(policy_file_t *fp, uint32_t vers,
 	if (rc < 0)
 		goto bad;
 
+	if (vers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+		rc = avtab_read_name_trans(fp, &trans->prefix_trans);
+		if (rc < 0)
+			goto bad;
+		rc = avtab_read_name_trans(fp, &trans->suffix_trans);
+		if (rc < 0)
+			goto bad;
+	}
+
 	return SEPOL_OK;
 
 bad:
diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
index e829e235..8824fe01 100644
--- a/libsepol/src/kernel_to_cil.c
+++ b/libsepol/src/kernel_to_cil.c
@@ -1707,9 +1707,24 @@ static int name_trans_to_strs_helper(hashtab_key_t k, hashtab_datum_t d, void *a
 	char *name = k;
 	uint32_t *otype = d;
 	name_trans_to_strs_args_t *args = a;
-	return strs_create_and_add(args->strs, "(%s %s %s %s \"%s\" %s)", 6,
+	const char *match_str = "";
+	switch (args->match) {
+	case NAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case NAME_TRANS_MATCH_PREFIX:
+		match_str = " prefix";
+		break;
+	case NAME_TRANS_MATCH_SUFFIX:
+		match_str = " suffix";
+		break;
+	default:
+		ERR(NULL, "Unknown name match type: %" PRIu8, args->match);
+		return SEPOL_ERR;
+	}
+	return strs_create_and_add(args->strs, "(%s %s %s %s \"%s\"%s %s)", 7,
 				   args->flavor, args->src, args->tgt,
-				   args->class, name,
+				   args->class, name, match_str,
 				   args->pdb->p_type_val_to_name[*otype - 1]);
 }
 
@@ -1797,9 +1812,20 @@ static int avtab_node_to_strs(struct policydb *pdb, avtab_key_t *key, avtab_datu
 			.src = src,
 			.tgt = tgt,
 			.class = class,
+			.match = NAME_TRANS_MATCH_EXACT,
 		};
 		rc = hashtab_map(datum->trans->name_trans.table,
 				 name_trans_to_strs_helper, &args);
+		if (rc < 0)
+			return rc;
+		args.match = NAME_TRANS_MATCH_PREFIX;
+		rc = hashtab_map(datum->trans->prefix_trans.table,
+				 name_trans_to_strs_helper, &args);
+		if (rc < 0)
+			return rc;
+		args.match = NAME_TRANS_MATCH_SUFFIX;
+		rc = hashtab_map(datum->trans->suffix_trans.table,
+				 name_trans_to_strs_helper, &args);
 	} else {
 		new = pdb->p_type_val_to_name[data - 1];
 
diff --git a/libsepol/src/kernel_to_common.h b/libsepol/src/kernel_to_common.h
index 524a4b0f..96169228 100644
--- a/libsepol/src/kernel_to_common.h
+++ b/libsepol/src/kernel_to_common.h
@@ -90,6 +90,7 @@ typedef struct {
 	const char *src;
 	const char *tgt;
 	const char *class;
+	name_trans_match_t match;
 } name_trans_to_strs_args_t;
 
 void sepol_indent(FILE *out, int indent);
diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
index a637c207..5f232f47 100644
--- a/libsepol/src/kernel_to_conf.c
+++ b/libsepol/src/kernel_to_conf.c
@@ -1685,11 +1685,26 @@ static int name_trans_to_strs_helper(hashtab_key_t k, hashtab_datum_t d, void *a
 	char *name = k;
 	uint32_t *otype = d;
 	name_trans_to_strs_args_t *args = a;
-	return strs_create_and_add(args->strs, "%s %s %s:%s %s \"%s\";", 6,
+	const char *match_str = "";
+	switch (args->match) {
+	case NAME_TRANS_MATCH_EXACT:
+		match_str = "";
+		break;
+	case NAME_TRANS_MATCH_PREFIX:
+		match_str = " PREFIX";
+		break;
+	case NAME_TRANS_MATCH_SUFFIX:
+		match_str = " SUFFIX";
+		break;
+	default:
+		ERR(NULL, "Unknown name match type: %" PRIu8, args->match);
+		return SEPOL_ERR;
+	}
+	return strs_create_and_add(args->strs, "%s %s %s:%s %s \"%s\"%s;", 7,
 				   args->flavor, args->src, args->tgt,
 				   args->class,
 				   args->pdb->p_type_val_to_name[*otype - 1],
-				   name);
+				   name, match_str);
 }
 
 static int avtab_node_to_strs(struct policydb *pdb, avtab_key_t *key, avtab_datum_t *datum, struct strs *strs)
@@ -1773,9 +1788,20 @@ static int avtab_node_to_strs(struct policydb *pdb, avtab_key_t *key, avtab_datu
 			.src = src,
 			.tgt = tgt,
 			.class = class,
+			.match = NAME_TRANS_MATCH_EXACT,
 		};
 		rc = hashtab_map(datum->trans->name_trans.table,
 				 name_trans_to_strs_helper, &args);
+		if (rc < 0)
+			return rc;
+		args.match = NAME_TRANS_MATCH_PREFIX;
+		rc = hashtab_map(datum->trans->prefix_trans.table,
+				 name_trans_to_strs_helper, &args);
+		if (rc < 0)
+			return rc;
+		args.match = NAME_TRANS_MATCH_SUFFIX;
+		rc = hashtab_map(datum->trans->suffix_trans.table,
+				 name_trans_to_strs_helper, &args);
 	} else {
 		new = pdb->p_type_val_to_name[data - 1];
 
diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
index 37bb97a1..f1f6cec6 100644
--- a/libsepol/src/policydb.c
+++ b/libsepol/src/policydb.c
@@ -215,6 +215,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,
diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index 0b8e8eee..08b4a477 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -855,11 +855,18 @@ static int validate_avtab_key_and_datum(avtab_key_t *k, avtab_datum_t *d, void *
 
 		/* also each transition must be non empty */
 		if (!d->trans->otype &&
-		    !hashtab_nel(d->trans->name_trans.table))
+		    !hashtab_nel(d->trans->name_trans.table) &&
+		    !hashtab_nel(d->trans->name_trans.table) &&
+		    !hashtab_nel(d->trans->prefix_trans.table) &&
+		    !hashtab_nel(d->trans->suffix_trans.table))
 			return -1;
 
-		/* and each filename transition must be also valid */
+		/* and each name transition must be also valid */
 		if (hashtab_map(d->trans->name_trans.table,
+				validate_name_trans_helper, margs) ||
+		    hashtab_map(d->trans->prefix_trans.table,
+				validate_name_trans_helper, margs) ||
+		    hashtab_map(d->trans->suffix_trans.table,
 				validate_name_trans_helper, margs))
 			return -1;
 	} else if ((k->specified & AVTAB_TYPE) && validate_simpletype(d->data, margs->policy, margs->flavors)) {
diff --git a/libsepol/src/write.c b/libsepol/src/write.c
index d7f47c8d..df47197c 100644
--- a/libsepol/src/write.c
+++ b/libsepol/src/write.c
@@ -133,16 +133,43 @@ static int avtab_trans_write(policydb_t *p, const avtab_trans_t *cur,
 	uint32_t buf32[2];
 
 	if (p->policyvers >= POLICYDB_VERSION_AVTAB_FTRANS) {
-		/* write otype and number of filename transitions */
+		/* write otype and number of name transitions */
 		buf32[0] = cpu_to_le32(cur->otype);
 		buf32[1] = cpu_to_le32(hashtab_nel(cur->name_trans.table));
 		items = put_entry(buf32, sizeof(uint32_t), 2, fp);
 		if (items != 2)
 			return -1;
 
-		/* write filename transitions */
-		return hashtab_map(cur->name_trans.table,
-				   avtab_trans_write_helper, fp);
+		/* write name transitions */
+		if (hashtab_map(cur->name_trans.table,
+				avtab_trans_write_helper, fp))
+			return -1;
+
+		if (p->policyvers >= POLICYDB_VERSION_PREFIX_SUFFIX) {
+			/* write number of prefix transitions */
+			buf32[0] = cpu_to_le32(hashtab_nel(
+					cur->prefix_trans.table));
+			items = put_entry(buf32, sizeof(uint32_t), 1, fp);
+			if (items != 1)
+				return -1;
+
+			/* write prefix transitions */
+			if (hashtab_map(cur->prefix_trans.table,
+					avtab_trans_write_helper, fp))
+				return -1;
+
+			/* write number of suffix transitions */
+			buf32[0] = cpu_to_le32(hashtab_nel(
+					cur->suffix_trans.table));
+			items = put_entry(buf32, sizeof(uint32_t), 1, fp);
+			if (items != 1)
+				return -1;
+
+			/* write suffix transitions */
+			if (hashtab_map(cur->suffix_trans.table,
+					avtab_trans_write_helper, fp))
+				return -1;
+		}
 	} else if (cur->otype) {
 		buf32[0] = cpu_to_le32(cur->otype);
 		items = put_entry(buf32, sizeof(uint32_t), 1, fp);
@@ -168,14 +195,26 @@ static int avtab_write_item(policydb_t * p,
 
 	/*
 	 * skip entries which only contain filename transitions in versions
-	 * before filename transitions were moved to avtab
+	 * before filename transitions were moved to avtab,
+	 * skip entries which only contain prefix/suffix transitions in versions
+	 * before prefix/suffix filename transitions
 	 */
-	if (p->policyvers < POLICYDB_VERSION_AVTAB_FTRANS &&
-	    cur->key.specified & AVTAB_TRANSITION && !cur->datum.trans->otype) {
-		/* if oldvers, reduce nel, because this node will be skipped */
-		if (oldvers && nel)
-			(*nel)--;
-		return 0;
+	if (cur->key.specified & AVTAB_TRANSITION) {
+		if (p->policyvers < POLICYDB_VERSION_AVTAB_FTRANS &&
+		    cur->key.specified & AVTAB_TRANSITION &&
+		    !cur->datum.trans->otype) {
+                        /*
+                         * if oldvers, reduce nel, because this node will be
+                         * skipped
+			 */
+                        if (oldvers && nel)
+				(*nel)--;
+			return 0;
+		}
+		if (p->policyvers < POLICYDB_VERSION_PREFIX_SUFFIX &&
+		    !cur->datum.trans->otype &&
+		    !hashtab_nel(cur->datum.trans->name_trans.table))
+			return 0;
 	}
 
 	if (oldvers) {
@@ -378,17 +417,27 @@ static int avtab_write(struct policydb *p, avtab_t * a, struct policy_file *fp)
 		 * filename transitions.
 		 */
 		nel = a->nel;
-		if (p->policyvers < POLICYDB_VERSION_AVTAB_FTRANS) {
-			/*
-			 * entries containing only filename transitions are
-			 * skipped and written out later
-			 */
-			for (i = 0; i < a->nslot; i++) {
-				for (cur = a->htable[i]; cur; cur = cur->next) {
-					if ((cur->key.specified
-					     & AVTAB_TRANSITION) &&
-					    !cur->datum.trans->otype)
-						nel--;
+		for (i = 0; i < a->nslot; i++) {
+			for (cur = a->htable[i]; cur; cur = cur->next) {
+				if (!(cur->key.specified & AVTAB_TRANSITION))
+					continue;
+                                if (p->policyvers < POLICYDB_VERSION_AVTAB_FTRANS &&
+				    !cur->datum.trans->otype) {
+                                        /*
+                                         * entries containing only filename
+                                         * transitions are skipped and written
+                                         * out later
+                                         */
+                                        nel--;
+				} else if (p->policyvers < POLICYDB_VERSION_PREFIX_SUFFIX &&
+					   !cur->datum.trans->otype &&
+					   !hashtab_nel(cur->datum.trans->name_trans.table)) {
+					/*
+					 * entries containing only prefix/suffix
+					 * transitions are not supported in
+					 * previous versions
+					 */
+					nel--;
 				}
 			}
 		}
@@ -2520,6 +2569,22 @@ static int avtab_has_filename_transitions(avtab_t *a)
 	return 0;
 }
 
+static int avtab_has_prefix_suffix_filename_transitions(avtab_t *a)
+{
+	uint32_t i;
+	struct avtab_node *cur;
+	for (i = 0; i < a->nslot; i++) {
+		for (cur = a->htable[i]; cur; cur = cur->next) {
+			if (cur->key.specified & AVTAB_TRANSITION) {
+				if (hashtab_nel(cur->datum.trans->prefix_trans.table)
+				    || hashtab_nel(cur->datum.trans->suffix_trans.table))
+					return 1;
+			}
+		}
+	}
+	return 0;
+}
+
 /*
  * Write the configuration data in a policy database
  * structure to a policy database binary representation
@@ -2686,6 +2751,10 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
 	if (p->policy_type == POLICY_KERN) {
 		if (avtab_write(p, &p->te_avtab, fp))
 			return POLICYDB_ERROR;
+		if (avtab_has_prefix_suffix_filename_transitions(&p->te_avtab)) {
+			WARN(fp->handle,
+			     "Discarding filename prefix/suffix type transition rules");
+		}
 		if (p->policyvers < POLICYDB_VERSION_BOOL) {
 			if (p->p_bools.nprim)
 				WARN(fp->handle, "Discarding "
