Re: [PATCH] semodule-utils: add bad-data tests for sefcontext_compile
Stephen Smalley <[email protected]> Wed, 29 Jul 2026 10:05:19 -0400
| Newsgroups | org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAEjxPJ4aaSPxRHd+i76Kq9cMosJ37d0DdYebjUzBcxCG2-zYew@mail.gmail.com> |
On Tue, Jul 28, 2026 at 9:21 AM Akhil Kohli <[email protected]> wrote: > > Extend the bad-data harness to validate file contexts after packaging: > wrong field count and NUL bytes are rejected by sefcontext_compile, invalid > contexts are rejected with -p, and unpackage'd .pp file contexts are > checked the same way. > > Adds 7 sefcontext_compile test steps on top of the semodule_package > harness. Adds bad_mls.mod.fc fixture. Preserve errexit restoration > (set -e after rc=$?) in shared helpers. > > Signed-off-by: Akhil Kohli <[email protected]> Acked-by: Stephen Smalley <[email protected]> > --- > .../fixtures/file_contexts/bad_mls.mod.fc | 1 + > semodule-utils/tests/bad-data/run.sh | 227 +++++++++++++++++- > 2 files changed, 223 insertions(+), 5 deletions(-) > create mode 100644 semodule-utils/tests/bad-data/fixtures/file_contexts/bad_mls.mod.fc > > diff --git a/semodule-utils/tests/bad-data/fixtures/file_contexts/bad_mls.mod.fc b/semodule-utils/tests/bad-data/fixtures/file_contexts/bad_mls.mod.fc > new file mode 100644 > index 00000000..1913faaa > --- /dev/null > +++ b/semodule-utils/tests/bad-data/fixtures/file_contexts/bad_mls.mod.fc > @@ -0,0 +1 @@ > +/usr/bin/bad_mls -- system_u:object_r:test_good_exec_t:s0:c999 > diff --git a/semodule-utils/tests/bad-data/run.sh b/semodule-utils/tests/bad-data/run.sh > index 7c9f7c7d..1c7d8f51 100755 > --- a/semodule-utils/tests/bad-data/run.sh > +++ b/semodule-utils/tests/bad-data/run.sh > @@ -1,9 +1,10 @@ > #!/bin/sh > # > -# Bad-data tests for semodule_package in the modular policy packaging pipeline. > -# Covers packaging path edge cases (-m/-f) and documents deferred rejection of bad > -# .mod.fc content (enforced later by sefcontext_compile). > -# Unreadable -m/-f cases skip when run as root; CI runs this script as non-root. > +# 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. > # > > set -u > @@ -29,6 +30,8 @@ CHECKMODULE=${CHECKMODULE:-checkmodule} > # Modular TE for MCS/MLS builds (checkmodule -M -m). > 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} > > GOOD_TE="${FIXTURES}/modules/good.te" > GOOD_MOD="${OUTDIR}/test_good.mod" > @@ -250,6 +253,161 @@ expect_package_fail_unreadable_fc() { > pass "${desc} (rejected as expected, rc=${rc})" > } > > +find_policy_file() { > + if [ -n "${POLICY_FILE:-}" ] && [ -f "${POLICY_FILE}" ]; then > + echo "${POLICY_FILE}" > + return 0 > + fi > + > + # Prefer the active policy store (SELINUXTYPE); fall back to targeted, then any > + # installed policy.* (POLICY_FILE overrides all of this). > + selinuxtype=targeted > + if [ -r /etc/selinux/config ]; then > + selinuxtype=$(grep -E '^[[:space:]]*SELINUXTYPE=' /etc/selinux/config 2>/dev/null \ > + | tail -1 | cut -d= -f2- | tr -d ' "') > + [ -n "${selinuxtype}" ] || selinuxtype=targeted > + fi > + > + # shellcheck disable=SC2086 > + set -- /etc/selinux/"${selinuxtype}"/policy/policy.* > + if [ -f "$1" ]; then > + echo "$1" > + return 0 > + fi > + > + for policy in /etc/selinux/*/policy/policy.*; do > + [ -f "${policy}" ] || continue > + echo "${policy}" > + return 0 > + done > + > + return 1 > +} > + > +expect_sefcontext_pass() { > + desc="$1" > + outname="$2" > + fc="$3" > + policy="${4:-}" > + > + outbin="${OUTDIR}/${outname}.bin" > + stderr="${OUTDIR}/${outname}.err" > + > + echo "==== POSITIVE (expect sefcontext_compile success): ${desc}" > + rm -f "${outbin}" > + > + set +e > + if [ -n "${policy}" ]; then > + "${SEFCONTEXT_COMPILE}" -p "${policy}" -o "${outbin}" "${fc}" \ > + 2>"${stderr}" > + else > + "${SEFCONTEXT_COMPILE}" -o "${outbin}" "${fc}" 2>"${stderr}" > + fi > + rc=$? > + set -e > + > + if [ "${rc}" -ne 0 ]; then > + cat "${stderr}" >&2 > + die "${desc}: expected exit 0, got rc=${rc}" > + return 0 > + fi > + if [ ! -s "${outbin}" ]; then > + die "${desc}: expected non-empty ${outbin}" > + return 0 > + fi > + > + pass "${desc} (sefcontext_compile succeeded)" > +} > + > +expect_sefcontext_fail() { > + desc="$1" > + pattern="$2" > + outname="$3" > + fc="$4" > + policy="${5:-}" > + > + outbin="${OUTDIR}/${outname}.bin" > + stderr="${OUTDIR}/${outname}.err" > + > + echo "==== NEGATIVE (expect sefcontext_compile failure): ${desc}" > + rm -f "${outbin}" > + > + set +e > + if [ -n "${policy}" ]; then > + "${SEFCONTEXT_COMPILE}" -p "${policy}" -o "${outbin}" "${fc}" \ > + 2>"${stderr}" > + else > + "${SEFCONTEXT_COMPILE}" -o "${outbin}" "${fc}" 2>"${stderr}" > + fi > + rc=$? > + set -e > + > + if [ "${rc}" -eq 0 ]; then > + die "${desc}: expected non-zero exit, got rc=0" > + return 0 > + fi > + if [ -f "${outbin}" ] && [ -s "${outbin}" ]; then > + die "${desc}: did not expect successful ${outbin}" > + return 0 > + fi > + if ! grep -Eq "${pattern}" "${stderr}"; then > + echo "FAIL: stderr did not match /${pattern}/" >&2 > + cat "${stderr}" >&2 > + FAIL=$((FAIL + 1)) > + return 0 > + fi > + > + pass "${desc} (rejected as expected, rc=${rc})" > +} > + > +expect_sefcontext_fail_or_skip_no_policy() { > + desc="$1" > + pattern="$2" > + outname="$3" > + fc="$4" > + > + policy=$(find_policy_file) || policy= > + if [ -z "${policy}" ]; then > + echo "==== SKIP (no binary policy for -p validation): ${desc}" > + echo "Set POLICY_FILE= to enable this check." > + PASS=$((PASS + 1)) > + echo "" > + return 0 > + fi > + > + expect_sefcontext_fail "${desc}" "${pattern}" "${outname}" "${fc}" \ > + "${policy}" > +} > + > +expect_sefcontext_pass_deferred() { > + desc="$1" > + outname="$2" > + fc="$3" > + > + outbin="${OUTDIR}/${outname}.bin" > + stderr="${OUTDIR}/${outname}.err" > + > + echo "==== DOCUMENT (sefcontext_compile accepts input; complements packaging deferral): ${desc}" > + rm -f "${outbin}" > + > + set +e > + "${SEFCONTEXT_COMPILE}" -o "${outbin}" "${fc}" 2>"${stderr}" > + rc=$? > + set -e > + > + if [ "${rc}" -ne 0 ]; then > + cat "${stderr}" >&2 > + die "${desc}: expected exit 0 at compile stage, got rc=${rc}" > + return 0 > + fi > + if [ ! -s "${outbin}" ]; then > + die "${desc}: expected non-empty ${outbin} at compile stage" > + return 0 > + fi > + > + pass "${desc} (sefcontext_compile exit 0; complements packaging deferral)" > +} > + > build_good_mod > > # Ephemeral path fixtures for packaging path tests. > @@ -258,7 +416,7 @@ ln -sf /nonexistent/test_good.mod.fc "${OUTDIR}/broken_symlink.mod.fc" > printf '' > "${OUTDIR}/empty.mod.fc" > > # NUL byte inside path column (packaging accepts; sefcontext_compile rejects). > -printf '/bin/foo\x00bar\t--\tsystem_u:object_r:test_good_exec_t:s0\n' \ > +printf '/bin/foo\000bar\t--\tsystem_u:object_r:test_good_exec_t:s0\n' \ > > "${OUTDIR}/nul_bytes.mod.fc" > > GOOD_FC="${FIXTURES}/file_contexts/good.mod.fc" > @@ -347,6 +505,65 @@ expect_package_pass_deferred \ > empty_fc \ > -m "${GOOD_MOD}" -f "${OUTDIR}/empty.mod.fc" > > +# --- sefcontext_compile / unpackage validation --- > +expect_sefcontext_pass \ > + "control good .mod.fc through sefcontext_compile" \ > + good_fc \ > + "${GOOD_FC}" > + > +expect_sefcontext_fail \ > + "wrong field count in .mod.fc (after packaging)" \ > + "missing fields|process_file failed" \ > + sefcontext_bad_fields \ > + "${FIXTURES}/file_contexts/bad_fields.mod.fc" > + > +expect_sefcontext_fail \ > + "NUL byte in .mod.fc path field (after packaging)" \ > + "missing fields|process_file failed" \ > + sefcontext_nul_bytes \ > + "${OUTDIR}/nul_bytes.mod.fc" > + > +expect_sefcontext_fail_or_skip_no_policy \ > + "invalid SELinux context requires policy validation (-p)" \ > + "invalid context|malformed context|process_file failed" \ > + sefcontext_bad_context \ > + "${FIXTURES}/file_contexts/bad_context.mod.fc" > + > +expect_sefcontext_pass_deferred \ > + "empty .mod.fc file through sefcontext_compile" \ > + sefcontext_empty_fc \ > + "${OUTDIR}/empty.mod.fc" > + > +expect_sefcontext_fail_or_skip_no_policy \ > + "invalid MLS context requires policy validation (-p)" \ > + "invalid context|malformed context|process_file failed" \ > + sefcontext_bad_mls \ > + "${FIXTURES}/file_contexts/bad_mls.mod.fc" > + > +BAD_PP="${OUTDIR}/bad_context.pp" > +EXTRACTED_MOD="${OUTDIR}/extracted.mod" > +EXTRACTED_FC="${OUTDIR}/extracted.fc" > + > +echo "==== Setup: extract file contexts from packaged module with invalid context" > +rm -f "${EXTRACTED_MOD}" "${EXTRACTED_FC}" > +set +e > +"${SEMODULE_UNPACKAGE}" "${BAD_PP}" "${EXTRACTED_MOD}" "${EXTRACTED_FC}" \ > + 2>"${OUTDIR}/unpackage.err" > +rc=$? > +set -e > +if [ "${rc}" -ne 0 ] || [ ! -s "${EXTRACTED_FC}" ]; then > + die "unpackage setup: could not extract .fc from ${BAD_PP}" > + cat "${OUTDIR}/unpackage.err" >&2 > +else > + echo "==== unpackage setup succeeded" > + echo "" > + expect_sefcontext_fail_or_skip_no_policy \ > + "invalid context from unpackage'd .pp file contexts" \ > + "invalid context|malformed context|process_file failed" \ > + unpackaged_bad_context \ > + "${EXTRACTED_FC}" > +fi > + > echo "========================================" > echo "Results: ${PASS} passed, ${FAIL} failed" > if [ "${FAIL}" -ne 0 ]; then > -- > 2.55.0 > >