diff --git a/ci/alpine.sh b/ci/alpine.sh
index 6b17942a43f..0ab648e45e8 100755
--- a/ci/alpine.sh
+++ b/ci/alpine.sh
@@ -45,7 +45,8 @@ apk add \
 	util-linux \
 	wget \
 	which \
-	xxd
+	xxd \
+	gawk
 
 if [ ! "$TSS" ]; then
 	apk add git
diff --git a/ci/debian.sh b/ci/debian.sh
index 431203aabb4..76761912a81 100755
--- a/ci/debian.sh
+++ b/ci/debian.sh
@@ -53,7 +53,8 @@ $apt \
 	sudo \
 	util-linux \
 	wget \
-	xsltproc
+	xsltproc \
+	gawk
 
 $apt xxd || $apt vim-common
 $apt libengine-gost-openssl1.1$ARCH || true
diff --git a/ci/tumbleweed.sh b/ci/tumbleweed.sh
index 6f70b0fcc76..c4bd75ea413 100755
--- a/ci/tumbleweed.sh
+++ b/ci/tumbleweed.sh
@@ -42,7 +42,8 @@ zypper --non-interactive install --force-resolution --no-recommends \
 	vim \
 	wget \
 	which \
-	xsltproc
+	xsltproc \
+	gawk
 
 zypper --non-interactive install --force-resolution --no-recommends \
 	gnutls openssl-engine-libp11 softhsm || true
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a0463b7b5b5..9a7d8a1f989 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,7 +2,7 @@ check_SCRIPTS =
 TESTS = $(check_SCRIPTS)
 
 check_SCRIPTS += ima_hash.test sign_verify.test boot_aggregate.test \
-		 fsverity.test portable_signatures.test
+		 fsverity.test portable_signatures.test ima_policy_check.test
 
 .PHONY: check_logs
 check_logs:
diff --git a/tests/ima_policy_check.awk b/tests/ima_policy_check.awk
new file mode 100755
index 00000000000..8d072455a00
--- /dev/null
+++ b/tests/ima_policy_check.awk
@@ -0,0 +1,211 @@
+#! /usr/bin/gawk -f
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2023 Roberto Sassu <roberto.sassu@huawei.com>
+#
+# Check a new rule against the loaded IMA policy.
+#
+# Documentation/ABI/testing/ima_policy (Linux kernel)
+# base:	[[func=] [mask=] [fsmagic=] [fsuuid=] [fsname=]
+#	[uid=] [euid=] [gid=] [egid=]
+#	[fowner=] [fgroup=]]
+# lsm:	[[subj_user=] [subj_role=] [subj_type=]
+#	[obj_user=] [obj_role=] [obj_type=]]
+# option:	[digest_type=] [template=] [permit_directio]
+#		[appraise_type=] [appraise_flag=]
+#		[appraise_algos=] [keyrings=]
+#
+# Rules don't overlap if their actions are unrelated (cannot be matched without
+# dont_) and there is no combination of appraise with another do action (e.g.
+# measure, audit, hash). The second condition is due to the fact that appraise
+# might still forbid other actions expected to be performed by a test that did
+# not setup appraisal. Checking appraise for new rules is not sufficient,
+# because that rule could be added anyway. By checking existing rules as well,
+# a warning will be displayed when tests inserting rules with other do actions
+# are reexecuted.
+#
+# Also, rules don't overlap if both include the same policy keyword(s) (in base
+# or lsm, except func), at least one, with a different value. Different func
+# values don't imply non-overlap, due to the fact that a test command might
+# cause the execution of multiple hooks (e.g. FILE_CHECK in addition to
+# MMAP_CHECK). Despite one test is willing to test a particular hook, this could
+# have side effects on other tests (e.g. one test sets: appraise func=MMAP_CHECK
+# and another: measure func=FILE_CHECK; the second test might see an unexpected
+# measurement due to the first test being executed; or the second test cannot
+# unexpectedly do mmap).
+#
+# Currently, the < > operators are not supported and overlapping is asserted
+# even if intervals are disjoint. If supported, non-overlapping conditions could
+# be found. With the ^ modifier, no disjoint intervals can be found. Overlapping
+# is always reported.
+#
+# Rule equivalence is determined by checking each key/value pair, regardless of
+# their order. However, the action must always be at the beginning of the rules.
+# Rules with aliases are considered equivalent to those with their source (e.g.
+# rules with PATH_CHECK and FILE_MMAP are considered as equivalent to rules with
+# FILE_CHECK and MMAP_CHECK).
+#
+# Return a bit mask with the following values:
+# - 1: invalid new rule;
+# - 2: overlap of the new rule with an existing rule in the IMA policy;
+# - 4: new rule exists in the IMA policy.
+
+BEGIN {
+	# Policy definitions.
+	actions_str="measure dont_measure appraise dont_appraise audit hash dont_hash"
+	split(actions_str, actions_array);
+	keywords_str="func mask fsmagic fsuuid fsname uid euid gid egid fowner fgroup subj_user subj_role subj_type obj_user obj_role obj_type";
+	split(keywords_str, keywords_array);
+	options_str="digest_type template permit_directio appraise_type appraise_flag appraise_algos keyrings";
+	split(options_str, options_array);
+
+	# Key types.
+	key_type_unknown=0;
+	key_type_action=1;
+	key_type_keyword=2;
+	key_type_option=3;
+
+	# Result values.
+	ret_invalid_rule=1;
+	ret_rule_overlap=2;
+	ret_same_rule_exists=4;
+
+	for (action_idx in actions_array)
+		key_types[actions_array[action_idx]]=key_type_action;
+	for (keyword_idx in keywords_array)
+		key_types[keywords_array[keyword_idx]]=key_type_keyword;
+	for (option_idx in options_array)
+		key_types[options_array[option_idx]]=key_type_option;
+
+	new_rule=1;
+	result=0;
+}
+{
+	# Delete arrays from previous rule.
+	if (!new_rule) {
+		delete current_rule_array;
+		delete current_rule_operator_array;
+	}
+
+	# Check empty rules.
+	if (!length($0)) {
+		if (new_rule) {
+			result=or(result, ret_invalid_rule);
+			exit;
+		}
+		next;
+	}
+
+	for (i=1; i<=NF; i++) {
+		# Parse key/value pair.
+		split($i, key_value_array, /[=,>,<]/, separator_array);
+		key=key_value_array[1];
+		value=key_value_array[2];
+
+		if (key == "func") {
+			# Normalize values of IMA hooks to what IMA will print.
+			if (value == "FILE_MMAP")
+				value="MMAP_CHECK";
+			else if (value == "PATH_CHECK")
+				value="FILE_CHECK";
+		}
+
+		# Basic validity check (not necessary in general for the IMA policy, but useful to find typos in the tests).
+		if (key_types[key] == key_type_unknown ||
+		    (i == 1 && key_types[key] != key_type_action)) {
+			result=or(result, ret_invalid_rule);
+			exit;
+		}
+
+		# Store key/value pair and operator into an array.
+		if (new_rule) {
+			new_rule_array[key]=value;
+			new_rule_operator_array[key]=separator_array[1];
+		} else {
+			current_rule_array[key]=value;
+			current_rule_operator_array[key]=separator_array[1];
+		}
+
+		# Store original action and action without dont_.
+		if (i == 1) {
+			if (new_rule) {
+				new_rule_action=key;
+				new_rule_action_sub=key;
+				gsub(/dont_/, "", new_rule_action_sub);
+			} else {
+				current_rule_action=key;
+				current_rule_action_sub=key;
+				gsub(/dont_/, "", current_rule_action_sub);
+			}
+		}
+	}
+
+	# Go to the next line, to compare the new rule with rules in the IMA policy.
+	if (new_rule) {
+		new_rule=0;
+		next;
+	}
+
+	# No overlap by action (unrelated rules and no combination appraise - <do action>), new rule safe to add to the IMA policy.
+	if (current_rule_action_sub != new_rule_action_sub &&
+	    (current_rule_action != "appraise" || new_rule_action ~ /^dont_/) &&
+	    (new_rule_action != "appraise" || current_rule_action ~ /^dont_/))
+		next;
+
+	same_rule=1;
+	overlap_rule=1;
+
+	for (key in key_types) {
+		if (!(key in new_rule_array)) {
+			# Key in current rule but not in new rule.
+			if (key in current_rule_array)
+				same_rule=0;
+			# Key not in new rule and not in current rule.
+			continue;
+		}
+
+		if (!(key in current_rule_array)) {
+			# Key in new rule but not in current rule.
+			if (key in new_rule_array)
+				same_rule=0;
+			# Key not in current rule and not in new rule.
+			continue;
+		}
+
+		# Same value and operator.
+		if (new_rule_array[key] == current_rule_array[key] &&
+		    new_rule_operator_array[key] == current_rule_operator_array[key])
+			continue;
+
+		# Different value and/or operator.
+		same_rule=0;
+
+		# Not a policy keyword, not useful to determine overlap.
+		if (key_types[key] != key_type_keyword)
+			continue;
+
+		# > < operators are not supported, cannot determine overlap.
+		if (new_rule_operator_array[key] != "=" || current_rule_operator_array[key] != "=")
+			continue;
+
+		# ^ modifier does not make disjoint sets, cannot determine overlap.
+		if (new_rule_array[key] ~ /^\^/ || current_rule_array[key] ~ /^\^/)
+			continue;
+
+		# One test command can invoke multiple hooks, cannot determine overlap from func.
+		if (key == "func")
+			continue;
+
+		# No overlap by policy keyword, new rule safe to add to the IMA policy.
+		overlap_rule=0;
+		next;
+	}
+
+	if (same_rule)
+		result=or(result, ret_same_rule_exists);
+	else if (overlap_rule)
+		result=or(result, ret_rule_overlap);
+}
+END {
+	exit result;
+}
diff --git a/tests/ima_policy_check.test b/tests/ima_policy_check.test
new file mode 100755
index 00000000000..179b52e2324
--- /dev/null
+++ b/tests/ima_policy_check.test
@@ -0,0 +1,245 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2023 Roberto Sassu <roberto.sassu@huawei.com>
+#
+# Test for ima_policy_check.awk
+
+trap '_report_exit_and_cleanup' SIGINT SIGTERM EXIT
+
+cd "$(dirname "$0")" || exit 1
+. ./functions.sh
+
+export PATH=$PWD:$PATH
+
+check_result() {
+	local result
+
+	echo -e "\nTest: $1"
+	echo "New rule: $2"
+	echo "IMA policy: $3"
+
+	echo -n "Result (expect $4): "
+
+	echo -e "$2\n$3" | ima_policy_check.awk
+	result=$?
+
+	if [ "$result" -ne "$4" ]; then
+		echo "${RED}$result${NORM}"
+		return "$FAIL"
+	fi
+
+	echo "${GREEN}$result${NORM}"
+	return "$OK"
+}
+
+# ima_policy_check.awk returns a bit mask with the following values:
+# - 1: invalid new rule;
+# - 2: overlap of the new rule with an existing rule in the IMA policy;
+# - 4: new rule exists in the IMA policy.
+
+# Basic checks.
+desc="empty IMA policy"
+rule="measure func=FILE_CHECK"
+ima_policy=""
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Empty new rule"
+rule=""
+ima_policy=""
+expect_pass check_result "$desc" "$rule" "$ima_policy" 1
+
+desc="Unknown policy keyword fun"
+rule="measure fun=FILE_CHECK"
+ima_policy=""
+expect_pass check_result "$desc" "$rule" "$ima_policy" 1
+
+desc="Missing action"
+rule="func=FILE_CHECK"
+ima_policy=""
+expect_pass check_result "$desc" "$rule" "$ima_policy" 1
+
+# Non-overlapping rules.
+desc="Non-overlapping by action measure/dont_appraise, same func"
+rule="measure func=FILE_CHECK"
+ima_policy="dont_appraise func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by action audit/dont_appraise, same func"
+rule="audit func=FILE_CHECK"
+ima_policy="dont_appraise func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by action appraise/dont_measure, same func"
+rule="appraise func=FILE_CHECK"
+ima_policy="dont_measure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by action dont_measure/hash, same func"
+rule="dont_measure func=FILE_CHECK"
+ima_policy="hash func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by uid, func is equal"
+rule="measure func=FILE_CHECK uid=0"
+ima_policy="measure uid=1 func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by uid, func is equal, same policy options"
+rule="measure func=FILE_CHECK uid=0 permit_directio"
+ima_policy="measure uid=1 func=FILE_CHECK permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by mask, func and uid are equal, same policy options"
+rule="measure func=FILE_CHECK uid=0 permit_directio mask=MAY_READ"
+ima_policy="measure uid=0 mask=MAY_EXEC func=FILE_CHECK permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="Non-overlapping by mask, func and uid are equal, different policy options"
+rule="measure func=FILE_CHECK uid=0 permit_directio mask=MAY_READ"
+ima_policy="measure uid=0 mask=MAY_EXEC func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+# Overlapping and different rules.
+desc="same actions, different keywords"
+rule="appraise func=FILE_CHECK"
+ima_policy="appraise uid=0"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="unrelated actions with appraise and a do action, same func"
+rule="appraise func=FILE_CHECK"
+ima_policy="measure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="unrelated actions with appraise and a do action, different func"
+rule="appraise func=FILE_CHECK"
+ima_policy="measure func=MMAP_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="related actions, same func"
+rule="measure func=FILE_CHECK"
+ima_policy="dont_measure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="related actions, same func, different policy options"
+rule="measure func=FILE_CHECK"
+ima_policy="dont_measure func=FILE_CHECK permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="related actions, same func, different policy options"
+rule="measure func=FILE_CHECK permit_directio"
+ima_policy="dont_measure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, same mask with different modifier (no disjoint sets with the ^ modifier)"
+rule="measure func=FILE_CHECK mask=MAY_EXEC"
+ima_policy="measure func=FILE_CHECK mask=^MAY_EXEC"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, different mask with same modifier (no disjoint sets with the ^ modifier)"
+rule="measure func=FILE_CHECK mask=^MAY_READ"
+ima_policy="measure func=FILE_CHECK mask=^MAY_EXEC"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, different policy options"
+rule="measure func=FILE_CHECK"
+ima_policy="measure func=FILE_CHECK permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, different policy options"
+rule="measure func=FILE_CHECK permit_directio"
+ima_policy="measure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, MMAP_CHECK and MMAP_CHECK_REQPROT hooks"
+rule="measure func=MMAP_CHECK"
+ima_policy="measure func=MMAP_CHECK_REQPROT"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="related actions, same func, same mask with same modifier"
+rule="measure func=FILE_CHECK mask=^MAY_EXEC"
+ima_policy="dont_measure func=FILE_CHECK mask=^MAY_EXEC"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, different uid with same operator (overlap because operators are not supported)"
+rule="measure func=FILE_CHECK uid>0"
+ima_policy="measure func=FILE_CHECK uid>1"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+desc="same actions, same func, same uid with different operator (overlap because operators are not supported)"
+rule="measure func=FILE_CHECK uid>1"
+ima_policy="measure func=FILE_CHECK uid<1"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 2
+
+# Overlapping and same rules.
+desc="same actions, same func"
+rule="appraise func=FILE_CHECK"
+ima_policy="appraise func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func, same mask"
+rule="appraise mask=MAY_READ func=FILE_CHECK"
+ima_policy="appraise func=FILE_CHECK mask=MAY_READ"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func, same mask, same policy options"
+rule="appraise mask=MAY_READ func=FILE_CHECK permit_directio appraise_type=imasig"
+ima_policy="appraise func=FILE_CHECK mask=MAY_READ permit_directio appraise_type=imasig"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func"
+rule="measure func=MMAP_CHECK_REQPROT"
+ima_policy="measure func=MMAP_CHECK_REQPROT"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func with alias (PATH_CHECK = FILE_CHECK)"
+rule="measure func=FILE_CHECK"
+ima_policy="measure func=PATH_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func with alias (PATH_CHECK = FILE_CHECK), same mask with same modifiers"
+rule="measure mask=^MAY_READ func=FILE_CHECK"
+ima_policy="measure func=PATH_CHECK mask=^MAY_READ"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func with alias (PATH_CHECK = FILE_CHECK) and same mask with same modifiers, same uid with same operators"
+rule="measure mask=^MAY_READ uid>0 func=FILE_CHECK"
+ima_policy="measure func=PATH_CHECK mask=^MAY_READ uid>0"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+desc="same actions, same func with alias (PATH_CHECK = FILE_CHECK) and same mask with same modifiers, same uid with same operators"
+rule="measure mask=^MAY_READ uid<1 func=FILE_CHECK"
+ima_policy="measure func=PATH_CHECK mask=^MAY_READ uid<1"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
+
+# Overlapping and two rules (one same, one different).
+desc="first: same actions, same func, second: unrelated actions with appraise and a do action"
+rule="appraise func=FILE_CHECK"
+ima_policy="appraise func=FILE_CHECK\nmeasure func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 6
+
+desc="first: unrelated actions with appraise and a do action, same func, second: same actions"
+rule="appraise func=FILE_CHECK"
+ima_policy="measure func=FILE_CHECK\nappraise func=FILE_CHECK"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 6
+
+desc="first: same actions, same func, same mask, second: different policy options"
+rule="appraise mask=MAY_READ func=FILE_CHECK"
+ima_policy="appraise func=FILE_CHECK mask=MAY_READ\nappraise func=FILE_CHECK mask=MAY_READ permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 6
+
+desc="first: same actions, same func with alias (PATH_CHECK = FILE_CHECK), same mask, second: different policy options"
+rule="appraise mask=MAY_READ func=FILE_CHECK"
+ima_policy="appraise func=PATH_CHECK mask=MAY_READ\nappraise func=FILE_CHECK mask=MAY_READ permit_directio"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 6
+
+# Non-overlapping and three rules.
+desc="same actions, same func and mask, different uid"
+rule="appraise mask=MAY_READ func=FILE_CHECK uid=0"
+ima_policy="appraise mask=MAY_READ func=FILE_CHECK uid=1\nappraise mask=MAY_READ func=FILE_CHECK uid=2\nappraise mask=MAY_READ func=FILE_CHECK uid=3"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 0
+
+desc="same actions, same func and mask, different uid, except one that is the same"
+rule="appraise mask=MAY_READ func=FILE_CHECK uid=0"
+ima_policy="appraise mask=MAY_READ func=FILE_CHECK uid=1\nappraise mask=MAY_READ func=FILE_CHECK uid=0\nappraise mask=MAY_READ func=FILE_CHECK uid=3"
+expect_pass check_result "$desc" "$rule" "$ima_policy" 4
