[PATCH] semodule-utils: add bad-data tests for link/expand neverallow
Akhil Kohli <[email protected]> Thu, 30 Jul 2026 14:42:51 +0200
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <[email protected]> |
Add link/expand bad-data tests for neverallow and neverallowxperm enforcement at semodule_expand: cross-module base violations, same-module neverallow conflicts, and checkmodule deferral document tests. Adapt same-module neverallowxperm for libsepol 3.11 (enforced at expand) while still passing on older userspace where enforcement is deferred. Refactor link/expand helpers through run_link_expand(). Add a cross-module require test: link rejects a consumer module when a required type is only declared in a separate provider module. Signed-off-by: Akhil Kohli <[email protected]> --- .../bad-data/fixtures/base/minimal_base.conf | 15 + .../bad-data/fixtures/modules/module_a.te | 8 + .../bad-data/fixtures/modules/module_b.te | 8 + .../fixtures/modules/neverallow_good.te | 8 + .../fixtures/modules/neverallow_self.te | 10 + .../fixtures/modules/neverallow_violator.te | 8 + .../fixtures/modules/neverallow_xperm_self.te | 11 + .../modules/neverallow_xperm_violator.te | 9 + semodule-utils/tests/bad-data/run.sh | 342 +++++++++++++++++- 9 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 semodule-utils/tests/bad-data/fixtures/base/minimal_base.conf create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/module_a.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/module_b.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/neverallow_good.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/neverallow_self.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/neverallow_violator.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_self.te create mode 100644 semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_violator.te diff --git a/semodule-utils/tests/bad-data/fixtures/base/minimal_base.conf b/semodule-utils/tests/bad-data/fixtures/base/minimal_base.conf new file mode 100644 index 00000000..4e058c77 --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/base/minimal_base.conf @@ -0,0 +1,15 @@ +# Base .conf: bare class declaration, then permission block (checkmodule requires both). +class file +sid kernel +class file { read write ioctl } + +type base_t; + +allow base_t base_t : file read; +neverallow base_t base_t : file write; +neverallowxperm base_t base_t : file ioctl 0x1000; + +role object_r; +role object_r types { base_t }; +user user_u roles { object_r }; +sid kernel user_u:object_r:base_t diff --git a/semodule-utils/tests/bad-data/fixtures/modules/module_a.te b/semodule-utils/tests/bad-data/fixtures/modules/module_a.te new file mode 100644 index 00000000..04d16238 --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/module_a.te @@ -0,0 +1,8 @@ +module module_a 1.0; + +require { + type other_t; + class file { read }; +} + +allow other_t other_t : file read; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/module_b.te b/semodule-utils/tests/bad-data/fixtures/modules/module_b.te new file mode 100644 index 00000000..2b8caed7 --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/module_b.te @@ -0,0 +1,8 @@ +module module_b 1.0; + +require { + class file { read }; +} + +type other_t; +allow other_t other_t : file read; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/neverallow_good.te b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_good.te new file mode 100644 index 00000000..f9850b8b --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_good.te @@ -0,0 +1,8 @@ +module neverallow_good 1.0; + +require { + type base_t; + class file { read }; +} + +allow base_t base_t : file read; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/neverallow_self.te b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_self.te new file mode 100644 index 00000000..255fb391 --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_self.te @@ -0,0 +1,10 @@ +module neverallow_self 1.0; + +require { + class file { read write }; +} + +type foo_t; + +allow foo_t foo_t : file { read write }; +neverallow foo_t foo_t : file write; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/neverallow_violator.te b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_violator.te new file mode 100644 index 00000000..fb8aade4 --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_violator.te @@ -0,0 +1,8 @@ +module neverallow_violator 1.0; + +require { + type base_t; + class file { read write }; +} + +allow base_t base_t : file write; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_self.te b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_self.te new file mode 100644 index 00000000..965f86af --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_self.te @@ -0,0 +1,11 @@ +module neverallow_xperm_self 1.0; + +require { + class file { read write ioctl }; +} + +type foo_t; + +allow foo_t foo_t : file ioctl; +allowxperm foo_t foo_t : file ioctl 0x2000; +neverallowxperm foo_t foo_t : file ioctl 0x2000; diff --git a/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_violator.te b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_violator.te new file mode 100644 index 00000000..7dbf889f --- /dev/null +++ b/semodule-utils/tests/bad-data/fixtures/modules/neverallow_xperm_violator.te @@ -0,0 +1,9 @@ +module neverallow_xperm_violator 1.0; + +require { + type base_t; + class file { read write ioctl }; +} + +allow base_t base_t : file ioctl; +allowxperm base_t base_t : file ioctl 0x1000; diff --git a/semodule-utils/tests/bad-data/run.sh b/semodule-utils/tests/bad-data/run.sh index 1c7d8f51..d3f89691 100755 --- a/semodule-utils/tests/bad-data/run.sh +++ b/semodule-utils/tests/bad-data/run.sh @@ -1,10 +1,11 @@ #!/bin/sh # -# Bad-data tests for semodule_package and sefcontext_compile in the modular -# policy packaging pipeline. Covers packaging path edge cases (-m/-f), documents -# deferred rejection of bad .mod.fc content at packaging time, and validates -# labeling through sefcontext_compile. Unreadable -m/-f cases skip when run as -# root; CI runs this script as non-root. +# Bad-data tests for semodule_package, sefcontext_compile, and +# semodule_link/semodule_expand in the modular policy packaging pipeline. +# Covers packaging path edge cases (-m/-f), documents deferred rejection of bad +# .mod.fc content at packaging time, validates labeling through sefcontext_compile, +# and exercises neverallow enforcement at link/expand. Unreadable -m/-f cases skip +# when run as root; CI runs this script as non-root. # set -u @@ -32,6 +33,8 @@ CHECKMODULE_MOD_FLAGS=${CHECKMODULE_MOD_FLAGS:--M -m} SEMODULE_PACKAGE=${SEMODULE_PACKAGE:-semodule_package} SEMODULE_UNPACKAGE=${SEMODULE_UNPACKAGE:-semodule_unpackage} SEFCONTEXT_COMPILE=${SEFCONTEXT_COMPILE:-sefcontext_compile} +SEMODULE_LINK=${SEMODULE_LINK:-semodule_link} +SEMODULE_EXPAND=${SEMODULE_EXPAND:-semodule_expand} GOOD_TE="${FIXTURES}/modules/good.te" GOOD_MOD="${OUTDIR}/test_good.mod" @@ -408,6 +411,229 @@ expect_sefcontext_pass_deferred() { pass "${desc} (sefcontext_compile exit 0; complements packaging deferral)" } +build_loadable_package() { + te="$1" + mod="$2" + pp="$3" + + set +e + # Non-MLS modular build to match fixtures/base/minimal_base.conf. + "${CHECKMODULE}" -m -o "${mod}" "${te}" 2>"${mod}.err" + rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + echo "FAIL: checkmodule could not build ${te}" >&2 + cat "${mod}.err" >&2 + exit 1 + fi + + set +e + "${SEMODULE_PACKAGE}" -o "${pp}" -m "${mod}" 2>"${pp}.err" + rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + echo "FAIL: semodule_package could not build ${pp}" >&2 + cat "${pp}.err" >&2 + exit 1 + fi +} + +expect_checkmodule_pass() { + desc="$1" + te="$2" + mod="${OUTDIR}/$(basename "${te}" .te).mod" + + echo "==== DOCUMENT (checkmodule accepts module; enforcement deferred to expand): ${desc}" + rm -f "${mod}" "${mod}.err" + + set +e + "${CHECKMODULE}" -m -o "${mod}" "${te}" 2>"${mod}.err" + rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + cat "${mod}.err" >&2 + die "${desc}: expected checkmodule success, got rc=${rc}" + return 0 + fi + if [ ! -s "${mod}" ]; then + die "${desc}: expected non-empty ${mod}" + return 0 + fi + + pass "${desc} (checkmodule exit 0, .mod created)" +} + +# Sets LINK_EXPAND_LINK_RC and LINK_EXPAND_EXPAND_RC (-1 if expand not run). +run_link_expand() { + link_out="$1" + expand_out="$2" + base_pp="$3" + shift 3 + + rm -f "${link_out}" "${expand_out}" "${link_out}.err" "${expand_out}.err" + + set +e + "${SEMODULE_LINK}" -o "${link_out}" "${base_pp}" "$@" \ + 2>"${link_out}.err" + LINK_EXPAND_LINK_RC=$? + set -e + if [ "${LINK_EXPAND_LINK_RC}" -ne 0 ]; then + cat "${link_out}.err" >&2 + LINK_EXPAND_EXPAND_RC=-1 + return 0 + fi + + set +e + "${SEMODULE_EXPAND}" "${link_out}" "${expand_out}" \ + 2>"${expand_out}.err" + LINK_EXPAND_EXPAND_RC=$? + set -e + if [ "${LINK_EXPAND_EXPAND_RC}" -ne 0 ]; then + cat "${expand_out}.err" >&2 + fi +} + +expect_link_expand_pass() { + desc="$1" + link_out="$2" + expand_out="$3" + base_pp="$4" + shift 4 + + echo "==== POSITIVE (expect link + expand success): ${desc}" + run_link_expand "${link_out}" "${expand_out}" "${base_pp}" "$@" + if [ "${LINK_EXPAND_LINK_RC}" -ne 0 ]; then + die "${desc}: semodule_link failed, rc=${LINK_EXPAND_LINK_RC}" + return 0 + fi + if [ "${LINK_EXPAND_EXPAND_RC}" -ne 0 ]; then + die "${desc}: semodule_expand failed, rc=${LINK_EXPAND_EXPAND_RC}" + return 0 + fi + if [ ! -s "${expand_out}" ]; then + die "${desc}: expected non-empty ${expand_out}" + return 0 + fi + + pass "${desc} (link and expand succeeded)" +} + +expect_link_expand_fail() { + desc="$1" + pattern="$2" + link_out="$3" + expand_out="$4" + base_pp="$5" + shift 5 + + echo "==== NEGATIVE (expect neverallow failure at expand): ${desc}" + run_link_expand "${link_out}" "${expand_out}" "${base_pp}" "$@" + if [ "${LINK_EXPAND_LINK_RC}" -ne 0 ]; then + die "${desc}: expected semodule_link success before expand, rc=${LINK_EXPAND_LINK_RC}" + return 0 + fi + if [ "${LINK_EXPAND_EXPAND_RC}" -eq 0 ]; then + die "${desc}: expected semodule_expand failure, got rc=0" + return 0 + fi + if ! grep -Eq "${pattern}" "${expand_out}.err"; then + echo "FAIL: stderr did not match /${pattern}/" >&2 + cat "${expand_out}.err" >&2 + FAIL=$((FAIL + 1)) + return 0 + fi + + pass "${desc} (expand failed on neverallow as expected, rc=${LINK_EXPAND_EXPAND_RC})" +} + +# Same-module neverallowxperm: enforced at expand on libsepol 3.11+, deferred on older userspace. +expect_link_expand_fail_or_deferred() { + desc="$1" + pattern="$2" + link_out="$3" + expand_out="$4" + base_pp="$5" + shift 5 + + echo "==== NEGATIVE or DOCUMENT (same-module neverallowxperm at expand): ${desc}" + run_link_expand "${link_out}" "${expand_out}" "${base_pp}" "$@" + if [ "${LINK_EXPAND_LINK_RC}" -ne 0 ]; then + die "${desc}: expected semodule_link success, rc=${LINK_EXPAND_LINK_RC}" + return 0 + fi + if [ "${LINK_EXPAND_EXPAND_RC}" -eq 0 ]; then + if [ ! -s "${expand_out}" ]; then + die "${desc}: expected non-empty ${expand_out} when expand deferred" + return 0 + fi + pass "${desc} (link and expand succeeded; enforcement deferred on this userspace)" + return 0 + fi + if ! grep -Eq "${pattern}" "${expand_out}.err"; then + echo "FAIL: stderr did not match /${pattern}/" >&2 + cat "${expand_out}.err" >&2 + FAIL=$((FAIL + 1)) + return 0 + fi + + pass "${desc} (expand failed on neverallowxperm as expected, rc=${LINK_EXPAND_EXPAND_RC})" +} + +build_base_package() { + base_conf="$1" + base_mod="$2" + base_pp="$3" + + set +e + "${CHECKMODULE}" -o "${base_mod}" "${base_conf}" 2>"${base_mod}.err" + rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + echo "FAIL: checkmodule could not build base from ${base_conf}" >&2 + cat "${base_mod}.err" >&2 + exit 1 + fi + + set +e + "${SEMODULE_PACKAGE}" -o "${base_pp}" -m "${base_mod}" 2>"${base_pp}.err" + rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + echo "FAIL: semodule_package could not build ${base_pp}" >&2 + cat "${base_pp}.err" >&2 + exit 1 + fi +} + +expect_link_fail() { + desc="$1" + pattern="$2" + link_out="$3" + base_pp="$4" + shift 4 + + echo "==== NEGATIVE (expect semodule_link failure): ${desc}" + rm -f "${link_out}" "${link_out}.err" + + set +e + "${SEMODULE_LINK}" -o "${link_out}" "${base_pp}" "$@" \ + 2>"${link_out}.err" + rc=$? + set -e + if [ "${rc}" -eq 0 ]; then + die "${desc}: expected semodule_link failure, got rc=0" + return 0 + fi + if ! grep -Eq "${pattern}" "${link_out}.err"; then + echo "FAIL: stderr did not match /${pattern}/" >&2 + cat "${link_out}.err" >&2 + FAIL=$((FAIL + 1)) + return 0 + fi + + pass "${desc} (link failed on unmet require as expected, rc=${rc})" +} + build_good_mod # Ephemeral path fixtures for packaging path tests. @@ -564,6 +790,112 @@ else "${EXTRACTED_FC}" fi +# --- semodule_link / semodule_expand neverallow --- +BASE_CONF="${FIXTURES}/base/minimal_base.conf" +BASE_MOD="${OUTDIR}/minimal_base.mod" +BASE_PP="${OUTDIR}/minimal_base.pp" +GOOD_LOADABLE_MOD="${OUTDIR}/neverallow_good.mod" +GOOD_LOADABLE_PP="${OUTDIR}/neverallow_good.pp" +VIOLATOR_MOD="${OUTDIR}/neverallow_violator.mod" +VIOLATOR_PP="${OUTDIR}/neverallow_violator.pp" +SELF_MOD="${OUTDIR}/neverallow_self.mod" +SELF_PP="${OUTDIR}/neverallow_self.pp" +XPERM_VIOLATOR_MOD="${OUTDIR}/neverallow_xperm_violator.mod" +XPERM_VIOLATOR_PP="${OUTDIR}/neverallow_xperm_violator.pp" +XPERM_SELF_MOD="${OUTDIR}/neverallow_xperm_self.mod" +XPERM_SELF_PP="${OUTDIR}/neverallow_xperm_self.pp" + +echo "==== Setup: build minimal base and loadable module packages (non-MLS)" +build_base_package "${BASE_CONF}" "${BASE_MOD}" "${BASE_PP}" +build_loadable_package "${FIXTURES}/modules/neverallow_good.te" \ + "${GOOD_LOADABLE_MOD}" "${GOOD_LOADABLE_PP}" +build_loadable_package "${FIXTURES}/modules/neverallow_violator.te" \ + "${VIOLATOR_MOD}" "${VIOLATOR_PP}" +build_loadable_package "${FIXTURES}/modules/neverallow_self.te" \ + "${SELF_MOD}" "${SELF_PP}" +build_loadable_package "${FIXTURES}/modules/neverallow_xperm_violator.te" \ + "${XPERM_VIOLATOR_MOD}" "${XPERM_VIOLATOR_PP}" +build_loadable_package "${FIXTURES}/modules/neverallow_xperm_self.te" \ + "${XPERM_SELF_MOD}" "${XPERM_SELF_PP}" +echo "" + +expect_checkmodule_pass \ + "neverallow module passes checkmodule before expand rejects it" \ + "${FIXTURES}/modules/neverallow_self.te" + +expect_checkmodule_pass \ + "neverallowxperm module passes checkmodule before link/expand" \ + "${FIXTURES}/modules/neverallow_xperm_self.te" + +expect_link_expand_pass \ + "control base + good module link and expand" \ + "${OUTDIR}/link_good.pp" \ + "${OUTDIR}/expand_good.bin" \ + "${BASE_PP}" \ + "${GOOD_LOADABLE_PP}" + +expect_link_expand_fail \ + "loadable module violates base neverallow on expand" \ + "neverallow violated|neverallow failures occurred" \ + "${OUTDIR}/link_violator.pp" \ + "${OUTDIR}/expand_violator.bin" \ + "${BASE_PP}" \ + "${VIOLATOR_PP}" + +expect_link_expand_fail \ + "same-module neverallow violated on expand" \ + "neverallow violated|neverallow failures occurred" \ + "${OUTDIR}/link_self.pp" \ + "${OUTDIR}/expand_self.bin" \ + "${BASE_PP}" \ + "${SELF_PP}" + +expect_link_expand_fail \ + "loadable module violates base neverallowxperm on expand" \ + "neverallowxperm|neverallow failures occurred" \ + "${OUTDIR}/link_xperm_violator.pp" \ + "${OUTDIR}/expand_xperm_violator.bin" \ + "${BASE_PP}" \ + "${XPERM_VIOLATOR_PP}" + +expect_link_expand_fail_or_deferred \ + "same-module neverallowxperm violated on expand" \ + "neverallowxperm|neverallow failures occurred" \ + "${OUTDIR}/link_xperm_self.pp" \ + "${OUTDIR}/expand_xperm_self.bin" \ + "${BASE_PP}" \ + "${XPERM_SELF_PP}" + +# --- cross-module require (ยง2c.9) --- +REQUIRE_CONSUMER_MOD="${OUTDIR}/module_a.mod" +REQUIRE_CONSUMER_PP="${OUTDIR}/module_a.pp" +REQUIRE_PROVIDER_MOD="${OUTDIR}/module_b.mod" +REQUIRE_PROVIDER_PP="${OUTDIR}/module_b.pp" + +build_loadable_package "${FIXTURES}/modules/module_a.te" \ + "${REQUIRE_CONSUMER_MOD}" "${REQUIRE_CONSUMER_PP}" +build_loadable_package "${FIXTURES}/modules/module_b.te" \ + "${REQUIRE_PROVIDER_MOD}" "${REQUIRE_PROVIDER_PP}" + +expect_checkmodule_pass \ + "consumer module passes checkmodule before link rejects missing require" \ + "${FIXTURES}/modules/module_a.te" + +expect_link_fail \ + "module requires type only declared in another module" \ + "requirements were not met|global requirements" \ + "${OUTDIR}/link_require_consumer.pp" \ + "${BASE_PP}" \ + "${REQUIRE_CONSUMER_PP}" + +expect_link_expand_pass \ + "consumer and provider modules link and expand together" \ + "${OUTDIR}/link_require_both.pp" \ + "${OUTDIR}/expand_require_both.bin" \ + "${BASE_PP}" \ + "${REQUIRE_CONSUMER_PP}" \ + "${REQUIRE_PROVIDER_PP}" + echo "========================================" echo "Results: ${PASS} passed, ${FAIL} failed" if [ "${FAIL}" -ne 0 ]; then -- 2.55.0