diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig
index 95a186ec0fcb..78cdc8d4232d 100644
--- a/security/selinux/Kconfig
+++ b/security/selinux/Kconfig
@@ -68,3 +68,20 @@ config SECURITY_SELINUX_SID2STR_CACHE_SIZE
 	  conversion.  Setting this option to 0 disables the cache completely.
 
 	  If unsure, keep the default value.
+
+config SECURITY_SELINUX_KUNIT_TEST
+	tristate "Tests for SELinux" if !KUNIT_ALL_TESTS
+	depends on KUNIT && SECURITY_SELINUX
+	default KUNIT_ALL_TESTS
+	help
+	  This builds the SELinux KUnit tests.
+
+	  KUnit tests run during boot and output the results to the debug log
+	  in TAP format (https://testanything.org/). Only useful for kernel devs
+	  running KUnit test harness and are not for inclusion into a
+	  production build.
+
+	  For more information on KUnit and unit tests in general please refer
+	  to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+	  If unsure, say N.
diff --git a/security/selinux/Makefile b/security/selinux/Makefile
index ab8c3093d5fd..d3d63b28edcf 100644
--- a/security/selinux/Makefile
+++ b/security/selinux/Makefile
@@ -27,3 +27,5 @@ quiet_cmd_genhdrs = GEN     $(addprefix $(obj)/,$(genhdrs))
 targets += $(genhdrs)
 $(addprefix $(obj)/,$(genhdrs)) &: scripts/selinux/genheaders/genheaders FORCE
 	$(call if_changed,genhdrs)
+
+obj-$(CONFIG_SECURITY_SELINUX_KUNIT_TEST) += ss/avtab_test.o
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index 2cd9eabf796c..db7123670ef8 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -223,6 +223,9 @@ struct avtab_datum *avtab_search(struct avtab *h, const struct avtab_key *key)
 	return NULL;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_search);
+
 /* This search function returns a node pointer, and can be used in
  * conjunction with avtab_search_next_node()
  */
@@ -331,6 +334,9 @@ void avtab_destroy(struct avtab *h)
 	h->mask = 0;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_destroy);
+
 void avtab_init(struct avtab *h)
 {
 	h->htable = NULL;
@@ -378,6 +384,9 @@ int avtab_alloc(struct avtab *h, u32 nrules)
 	return 0;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_alloc);
+
 int avtab_alloc_dup(struct avtab *new, const struct avtab *orig)
 {
 	return avtab_alloc_common(new, orig->nslot);
@@ -766,6 +775,9 @@ int avtab_read(struct avtab *a, void *fp, struct policydb *pol)
 	goto out;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_read);
+
 static int avtab_trans_write_helper(void *k, void *d, void *fp)
 {
 	char *name = k;
@@ -900,6 +912,9 @@ int avtab_write(struct policydb *p, struct avtab *a, void *fp)
 	return rc;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_write);
+
 void __init avtab_cache_init(void)
 {
 	avtab_node_cachep = kmem_cache_create("avtab_node",
@@ -1105,6 +1120,8 @@ int avtab_filename_trans_read(struct avtab *a, void *fp, struct policydb *p)
 	return 0;
 }
 
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_filename_trans_read);
 
 struct filenametr_write_args {
 	void *fp;
@@ -1406,3 +1423,6 @@ int avtab_filename_trans_write(struct policydb *p, struct avtab *a, void *fp)
 
 	return rc;
 }
+
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(avtab_filename_trans_write);
diff --git a/security/selinux/ss/avtab_test.c b/security/selinux/ss/avtab_test.c
new file mode 100644
index 000000000000..daa8e4cfaeb2
--- /dev/null
+++ b/security/selinux/ss/avtab_test.c
@@ -0,0 +1,729 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for access vector table type Implementation
+ *
+ * Author: Juraj Marcin <juraj@jurajmarcin.com>
+ */
+
+#include <kunit/test.h>
+#include "policydb.h"
+#include "security.h"
+#include "avtab.h"
+
+static void filename_trans_read__pre_filename_trans(struct kunit *test)
+{
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS - 1,
+	};
+	struct policy_file fp = {
+		.data = NULL,
+		.len = 0,
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_EXPECT_EQ(test, 0,
+			avtab_filename_trans_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 0, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+}
+
+static void filename_trans_read__empty(struct kunit *test)
+{
+	char data[] = {0, 0, 0, 0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_EXPECT_EQ(test, 0,
+			avtab_filename_trans_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 0, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+}
+
+static void filename_trans_read__comp_empty(struct kunit *test)
+{
+	char data[] = {0, 0, 0, 0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_EXPECT_EQ(test, 0,
+			avtab_filename_trans_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 0, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+}
+
+static void filename_trans_read__simple(struct kunit *test)
+{
+	char data[] = {
+		3, 0, 0, 0,	/* count */
+
+		5, 0, 0, 0,			/* entry 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 name */
+		42, 0, 0, 0,			/* entry 1 stype */
+		43, 0, 0, 0,			/* entry 1 ttype */
+		44, 0, 0, 0,			/* entry 1 tclass */
+		45, 0, 0, 0,			/* entry 1 otype */
+
+		5, 0, 0, 0,			/* entry 2 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 2 name */
+		46, 0, 0, 0,			/* entry 2 stype */
+		47, 0, 0, 0,			/* entry 2 ttype */
+		48, 0, 0, 0,			/* entry 2 tclass */
+		49, 0, 0, 0,			/* entry 2 otype */
+
+		5, 0, 0, 0,			/* entry 3 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 3 name */
+		46, 0, 0, 0,			/* entry 3 stype */
+		47, 0, 0, 0,			/* entry 3 ttype */
+		48, 0, 0, 0,			/* entry 3 tclass */
+		53, 0, 0, 0,			/* entry 3 otype */
+	};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	struct avtab_key key;
+	struct avtab_datum *node;
+	u32 *otype;
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	KUNIT_ASSERT_EQ(test, 0, avtab_alloc(&p.te_avtab, 3));
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 2, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+
+	key = (struct avtab_key){42, 43, 44, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file1");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 45, *otype);
+
+	key = (struct avtab_key){46, 47, 48, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file2");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 49, *otype);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file3");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 53, *otype);
+
+	avtab_destroy(&p.te_avtab);
+}
+
+static void filename_trans_read__comp_simple(struct kunit *test)
+{
+	char data[] = {
+		3, 0, 0, 0,	/* count */
+
+		5, 0, 0, 0,			/* entry 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 name */
+		43, 0, 0, 0,			/* entry 1 ttype */
+		44, 0, 0, 0,			/* entry 1 tclass */
+		1, 0, 0, 0,			/* entry 1 ndatum */
+		64, 0, 0, 0,			/* entry 1 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 2, 0, 0,
+		45, 0, 0, 0,			/* entry 1 otype */
+
+		5, 0, 0, 0,			/* entry 2 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 2 name */
+		47, 0, 0, 0,			/* entry 2 ttype */
+		48, 0, 0, 0,			/* entry 2 tclass */
+		1, 0, 0, 0,			/* entry 2 ndatum */
+		64, 0, 0, 0,			/* entry 2 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 32, 0, 0,
+		49, 0, 0, 0,			/* entry 2 otype */
+
+		5, 0, 0, 0,			/* entry 3 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 3 name */
+		47, 0, 0, 0,			/* entry 3 ttype */
+		48, 0, 0, 0,			/* entry 3 tclass */
+		1, 0, 0, 0,			/* entry 2 ndatum */
+		64, 0, 0, 0,			/* entry 2 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 32, 0, 0,
+		53, 0, 0, 0,			/* entry 3 otype */
+	};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	struct avtab_key key;
+	struct avtab_datum *node;
+	u32 *otype;
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	KUNIT_ASSERT_EQ(test, 0, avtab_alloc(&p.te_avtab, 3));
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 2, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+
+	key = (struct avtab_key){42, 43, 44, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file1");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 45, *otype);
+
+	key = (struct avtab_key){46, 47, 48, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file2");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 49, *otype);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file3");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 53, *otype);
+
+	avtab_destroy(&p.te_avtab);
+}
+
+static void filename_trans_write__pre_filename_trans(struct kunit *test)
+{
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS - 1,
+	};
+	struct policy_file fp = {
+		.data = NULL,
+		.len = 0,
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_EXPECT_EQ(test, 0,
+			avtab_filename_trans_write(&p, &p.te_avtab, &fp));
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+}
+
+static void filename_trans_write__empty(struct kunit *test)
+{
+	char expected_data[] = {0, 0, 0, 0};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static void filename_trans_write__comp_empty(struct kunit *test)
+{
+	char expected_data[] = {0, 0, 0, 0};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static void filename_trans_write__simple(struct kunit *test)
+{
+	char expected_data[] = {
+		3, 0, 0, 0,	/* count */
+
+		5, 0, 0, 0,			/* entry 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 name */
+		42, 0, 0, 0,			/* entry 1 stype */
+		43, 0, 0, 0,			/* entry 1 ttype */
+		44, 0, 0, 0,			/* entry 1 tclass */
+		45, 0, 0, 0,			/* entry 1 otype */
+
+		5, 0, 0, 0,			/* entry 2 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 2 name */
+		46, 0, 0, 0,			/* entry 2 stype */
+		47, 0, 0, 0,			/* entry 2 ttype */
+		48, 0, 0, 0,			/* entry 2 tclass */
+		49, 0, 0, 0,			/* entry 2 otype */
+
+		5, 0, 0, 0,			/* entry 3 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 3 name */
+		46, 0, 0, 0,			/* entry 3 stype */
+		47, 0, 0, 0,			/* entry 3 ttype */
+		48, 0, 0, 0,			/* entry 3 tclass */
+		53, 0, 0, 0,			/* entry 3 otype */
+	};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_FILENAME_TRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	u32 otypes[] = {45, 49, 53};
+	struct hashtab_node nhnodes[] = {
+		{"file1", &otypes[0], NULL},
+		{"file2", &otypes[1], &nhnodes[2]},
+		{"file3", &otypes[2], NULL},
+	};
+	struct hashtab_node *nhtable[] = {&nhnodes[0], &nhnodes[1]};
+	struct avtab_trans transs[] = {
+		{0, {{&nhtable[0], 1, 1}, 0}},
+		{0, {{&nhtable[1], 1, 2}, 0}},
+	};
+	struct avtab_node nodes[] = {
+		{{42, 43, 44, AVTAB_TRANSITION},
+			{.u.trans = &transs[0]}, NULL},
+		{{46, 47, 48, AVTAB_TRANSITION},
+			{.u.trans = &transs[1]}, NULL},
+	};
+	struct avtab_node *htable[] = {&nodes[0], &nodes[1]};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	p.te_avtab.htable = htable;
+	p.te_avtab.nslot = 2;
+	p.te_avtab.nel = 2;
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_ASSERT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static void filename_trans_write__comp_simple(struct kunit *test)
+{
+	char expected_data[] = {
+		3, 0, 0, 0,	/* count */
+
+		5, 0, 0, 0,			/* entry 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 name */
+		43, 0, 0, 0,			/* entry 1 ttype */
+		44, 0, 0, 0,			/* entry 1 tclass */
+		1, 0, 0, 0,			/* entry 1 ndatum */
+		64, 0, 0, 0,			/* entry 1 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 2, 0, 0,
+		45, 0, 0, 0,			/* entry 1 otype */
+
+		5, 0, 0, 0,			/* entry 2 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 2 name */
+		47, 0, 0, 0,			/* entry 2 ttype */
+		48, 0, 0, 0,			/* entry 2 tclass */
+		1, 0, 0, 0,			/* entry 2 ndatum */
+		64, 0, 0, 0,			/* entry 2 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 32, 0, 0,
+		49, 0, 0, 0,			/* entry 2 otype */
+
+		5, 0, 0, 0,			/* entry 3 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 3 name */
+		47, 0, 0, 0,			/* entry 3 ttype */
+		48, 0, 0, 0,			/* entry 3 tclass */
+		1, 0, 0, 0,			/* entry 2 ndatum */
+		64, 0, 0, 0,			/* entry 2 datum 1 stypes */
+		64, 0, 0, 0,
+		1, 0, 0, 0,
+		0, 0, 0, 0,
+		0, 0, 0, 0, 0, 32, 0, 0,
+		53, 0, 0, 0,			/* entry 3 otype */
+	};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	u32 otypes[] = {45, 49, 53};
+	struct hashtab_node nhnodes[] = {
+		{"file1", &otypes[0], NULL},
+		{"file2", &otypes[1], &nhnodes[2]},
+		{"file3", &otypes[2], NULL},
+	};
+	struct hashtab_node *nhtable[] = {&nhnodes[0], &nhnodes[1]};
+	struct avtab_trans transs[] = {
+		{0, {{&nhtable[0], 1, 1}, 0}},
+		{0, {{&nhtable[1], 1, 2}, 0}},
+	};
+	struct avtab_node nodes[] = {
+		{{42, 43, 44, AVTAB_TRANSITION},
+			{.u.trans = &transs[0]}, NULL},
+		{{46, 47, 48, AVTAB_TRANSITION},
+			{.u.trans = &transs[1]}, NULL},
+	};
+	struct avtab_node *htable[] = {&nodes[0], &nodes[1]};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	p.te_avtab.htable = htable;
+	p.te_avtab.nslot = 2;
+	p.te_avtab.nel = 2;
+
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_filename_trans_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_ASSERT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static void read__pre_avtab_ftrans(struct kunit *test)
+{
+	char data[] = {
+		2, 0, 0, 0,	/* nel */
+
+		42, 0,			/* entry 1 source type */
+		43, 0,			/* entry 1 target type */
+		44, 0,			/* entry 1 target class */
+		AVTAB_TRANSITION, 0,	/* entry 1 specified */
+		45, 0, 0, 0,		/* entry 1 otype */
+
+		46, 0,			/* entry 2 source type */
+		47, 0,			/* entry 2 target type */
+		48, 0,			/* entry 2 target class */
+		AVTAB_TRANSITION, 0,	/* entry 2 specified */
+		49, 0, 0, 0,		/* entry 2 otype */
+	};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	struct avtab_key key;
+	struct avtab_datum *node;
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 2, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+
+	key = (struct avtab_key){42, 43, 44, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 45, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->name_trans.table.nel);
+
+	key = (struct avtab_key){46, 47, 48, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 49, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 0, node->u.trans->name_trans.table.nel);
+
+	avtab_destroy(&p.te_avtab);
+}
+
+static void read__simple(struct kunit *test)
+{
+	char data[] = {
+		2, 0, 0, 0,	/* nel */
+
+		42, 0,			/* entry 1 source type */
+		43, 0,			/* entry 1 target type */
+		44, 0,			/* entry 1 target class */
+		AVTAB_TRANSITION, 0,	/* entry 1 specified */
+		41, 0, 0, 0,		/* entry 1 otype */
+		1, 0, 0, 0,		/* entry 1 nfnts */
+		45, 0, 0, 0,			/* entry 1 fnt 1 otype */
+		5, 0, 0, 0,			/* entry 1 fnt 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 fnt 1 name */
+
+		46, 0,			/* entry 2 source type */
+		47, 0,			/* entry 2 target type */
+		48, 0,			/* entry 2 target class */
+		AVTAB_TRANSITION, 0,	/* entry 2 specified */
+		40, 0, 0, 0,		/* entry 2 otype */
+		2, 0, 0, 0,		/* entry 2 nfnts */
+		49, 0, 0, 0,			/* entry 2 fnt 1 otype */
+		5, 0, 0, 0,			/* entry 2 fnt 1 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 2 fnt 1 name */
+		50, 0, 0, 0,			/* entry 2 fnt 2 otype */
+		5, 0, 0, 0,			/* entry 2 fnt 2 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 2 fnt 2 name */
+	};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_AVTAB_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	struct avtab_key key;
+	struct avtab_datum *node;
+	u32 *otype;
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	KUNIT_ASSERT_EQ(test, 0,
+			avtab_read(&p.te_avtab, &fp, &p));
+	KUNIT_EXPECT_EQ(test, 2, p.te_avtab.nel);
+	KUNIT_EXPECT_EQ(test, 0, fp.len);
+
+	key = (struct avtab_key){42, 43, 44, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 41, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 1, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file1");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 45, *otype);
+
+	key = (struct avtab_key){46, 47, 48, AVTAB_TRANSITION};
+	node = avtab_search(&p.te_avtab, &key);
+	KUNIT_ASSERT_NOT_NULL(test, node);
+	KUNIT_EXPECT_EQ(test, 40, node->u.trans->otype);
+	KUNIT_EXPECT_EQ(test, 2, node->u.trans->name_trans.table.nel);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file2");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 49, *otype);
+
+	otype = symtab_search(&node->u.trans->name_trans, "file3");
+	KUNIT_ASSERT_NOT_NULL(test, otype);
+	KUNIT_EXPECT_EQ(test, 50, *otype);
+
+	avtab_destroy(&p.te_avtab);
+}
+
+static void write__pre_avtab_ftrans(struct kunit *test)
+{
+	char expected_data[] = {
+		1, 0, 0, 0,	/* nel */
+
+		46, 0,			/* entry 2 source type */
+		47, 0,			/* entry 2 target type */
+		48, 0,			/* entry 2 target class */
+		AVTAB_TRANSITION, 0,	/* entry 2 specified */
+		40, 0, 0, 0,		/* entry 2 otype */
+	};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_COMP_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	u32 otypes[] = {45, 49, 53};
+	struct hashtab_node nhnodes[] = {
+		{"file1", &otypes[0], NULL},
+		{"file2", &otypes[1], &nhnodes[2]},
+		{"file3", &otypes[2], NULL},
+	};
+	struct hashtab_node *nhtable[] = {&nhnodes[0], &nhnodes[1]};
+	struct avtab_trans transs[] = {
+		{0, {{&nhtable[0], 1, 1}, 0}},
+		{40, {{&nhtable[1], 1, 2}, 0}},
+	};
+	struct avtab_node nodes[] = {
+		{{42, 43, 44, AVTAB_TRANSITION},
+			{.u.trans = &transs[0]}, NULL},
+		{{46, 47, 48, AVTAB_TRANSITION},
+			{.u.trans = &transs[1]}, NULL},
+	};
+	struct avtab_node *htable[] = {&nodes[0], &nodes[1]};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	p.te_avtab.htable = htable;
+	p.te_avtab.nslot = 2;
+	p.te_avtab.nel = 2;
+
+	KUNIT_ASSERT_EQ(test, 0, avtab_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_ASSERT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static void write__simple(struct kunit *test)
+{
+	char expected_data[] = {
+		2, 0, 0, 0,	/* nel */
+
+		42, 0,			/* entry 1 source type */
+		43, 0,			/* entry 1 target type */
+		44, 0,			/* entry 1 target class */
+		AVTAB_TRANSITION, 0,	/* entry 1 specified */
+		41, 0, 0, 0,		/* entry 1 otype */
+		1, 0, 0, 0,		/* entry 1 nfnts */
+		45, 0, 0, 0,			/* entry 1 fnt 1 otype */
+		5, 0, 0, 0,			/* entry 1 fnt 1 name len */
+		'f', 'i', 'l', 'e', '1',	/* entry 1 fnt 1 name */
+
+		46, 0,			/* entry 2 source type */
+		47, 0,			/* entry 2 target type */
+		48, 0,			/* entry 2 target class */
+		AVTAB_TRANSITION, 0,	/* entry 2 specified */
+		40, 0, 0, 0,		/* entry 2 otype */
+		2, 0, 0, 0,		/* entry 2 nfnts */
+		49, 0, 0, 0,			/* entry 1 fnt 1 otype */
+		5, 0, 0, 0,			/* entry 1 fnt 1 name len */
+		'f', 'i', 'l', 'e', '2',	/* entry 1 fnt 1 name */
+		50, 0, 0, 0,			/* entry 1 fnt 1 otype */
+		5, 0, 0, 0,			/* entry 1 fnt 1 name len */
+		'f', 'i', 'l', 'e', '3',	/* entry 1 fnt 1 name */
+	};
+	char data[sizeof(expected_data)] = {0};
+	struct policydb p = {
+		.te_avtab = {0},
+		.policyvers = POLICYDB_VERSION_AVTAB_FTRANS,
+	};
+	struct policy_file fp = {
+		.data = data,
+		.len = sizeof(data),
+	};
+	u32 otypes[] = {45, 49, 50};
+	struct hashtab_node nhnodes[] = {
+		{"file1", &otypes[0], NULL},
+		{"file2", &otypes[1], &nhnodes[2]},
+		{"file3", &otypes[2], NULL},
+	};
+	struct hashtab_node *nhtable[] = {&nhnodes[0], &nhnodes[1]};
+	struct avtab_trans transs[] = {
+		{41, {{&nhtable[0], 1, 1}, 0}},
+		{40, {{&nhtable[1], 1, 2}, 0}},
+	};
+	struct avtab_node nodes[] = {
+		{{42, 43, 44, AVTAB_TRANSITION},
+			{.u.trans = &transs[0]}, NULL},
+		{{46, 47, 48, AVTAB_TRANSITION},
+			{.u.trans = &transs[1]}, NULL},
+	};
+	struct avtab_node *htable[] = {&nodes[0], &nodes[1]};
+
+	p.p_types.nprim = 54;
+	p.p_classes.nprim = 49;
+	p.te_avtab.htable = htable;
+	p.te_avtab.nslot = 2;
+	p.te_avtab.nel = 2;
+
+	KUNIT_ASSERT_EQ(test, 0, avtab_write(&p, &p.te_avtab, &fp));
+
+	KUNIT_ASSERT_EQ(test, 0, fp.len);
+	KUNIT_EXPECT_TRUE(test,
+			  !memcmp(expected_data, data, sizeof(expected_data)));
+}
+
+static struct kunit_case avtab_test_cases[] = {
+	KUNIT_CASE(filename_trans_read__pre_filename_trans),
+	KUNIT_CASE(filename_trans_read__empty),
+	KUNIT_CASE(filename_trans_read__comp_empty),
+	KUNIT_CASE(filename_trans_read__simple),
+	KUNIT_CASE(filename_trans_read__comp_simple),
+
+	KUNIT_CASE(filename_trans_write__pre_filename_trans),
+	KUNIT_CASE(filename_trans_write__empty),
+	KUNIT_CASE(filename_trans_write__comp_empty),
+	KUNIT_CASE(filename_trans_write__simple),
+	KUNIT_CASE(filename_trans_write__comp_simple),
+
+	KUNIT_CASE(read__pre_avtab_ftrans),
+	KUNIT_CASE(read__simple),
+
+	KUNIT_CASE(write__pre_avtab_ftrans),
+	KUNIT_CASE(write__simple),
+	{0},
+};
+
+static struct kunit_suite avtab_test_suite = {
+	.name = "security-selinux-avtab",
+	.test_cases = avtab_test_cases,
+};
+
+kunit_test_suite(avtab_test_suite);
+
+MODULE_LICENSE("GPL");
diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c
index c42a6648a07d..c53863d5ed40 100644
--- a/security/selinux/ss/symtab.c
+++ b/security/selinux/ss/symtab.c
@@ -52,3 +52,6 @@ void *symtab_search(struct symtab *s, const char *name)
 {
 	return hashtab_search(&s->table, name, symtab_key_params);
 }
+
+/* Export for avtab KUnit tests */
+EXPORT_SYMBOL_GPL(symtab_search);
