Linux Security Modules development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
Cc: linux-security-module@vger.kernel.org
Subject: Re: [PATCH 2/2] security: lockdown: Prevent boot with lockdown but no IOMMU
Date: Mon, 02 Nov 2026 14:28:18 +0000	[thread overview]
Message-ID: <sashiko-outbox-193572@kernel.org> (raw)
In-Reply-To: <20261102-lockdown-iommu-v1-2-00f3c2be4c9f@kernel.org>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] security: lockdown: unconditional boot panic in lockdown_check_iommu()
- [Medium] security: lockdown: runtime activation in lockdown_write() skips IOMMU check
- [Medium] iommu: iommu_available stays true after iommu_device_unregister() on failure
- [Medium] security: lockdown: passthrough mode bypasses lockdown_check_iommu()
- [Medium] security: lockdown: raw late_initcall() runs when lockdown LSM is disabled
- [Medium] security: lockdown: premature no_iommu panic in lockdown_lsm_init()
- [Low] security: lockdown: unreachable no_iommu check in lockdown_check_iommu()
- [Low] security: lockdown: commit message claims change to iommu_device_register()
--

commit 437687711e0f75d4ef1930e13f9b1f994ec32b30
Author: Mario Limonciello (AMD) <superm1@kernel.org>

security: lockdown: Prevent boot with lockdown but no IOMMU

This commit adds an early check on x86 and a generic late_initcall check to
panic during boot if kernel lockdown is enabled while an IOMMU is disabled
or unavailable.

>     security: lockdown: Prevent boot with lockdown but no IOMMU
>     
>     Lockdown is a kernel LSM that restricts access to sensitive kernel
>     resources (e.g. /dev/mem, kernel modules, perf, tracing, kprobes,
>     BPF access to kernel memory). However, without IOMMU, any PCI/PCIe
>     device can perform DMA to arbitrary physical memory, effectively
>     bypassing all lockdown protections.
>     
>     A malicious or compromised device can:
>     - Read kernel memory directly (encryption keys, credentials, secrets)
>     - Write to kernel memory directly (code patches, rootkits, backdoors)
>     - Circumvent all lockdown LSM checks (which run inside the kernel)
>     
>     Add a two-phase check to prevent booting with lockdown enabled and
>     IOMMU disabled:
>     
>     1. Early check (in lockdown_lsm_init()): On x86, check the no_iommu
>        flag, which is set early by the 'iommu=off' kernel cmdline option.
>        If lockdown is active and no_iommu is set, panic with a clear error.
>     
>     2. Late check (late_initcall): On all architectures, check
>        iommu_is_available() which returns true if any IOMMU driver
>        registered successfully. This catches cases where:
>        - IOMMU hardware is absent on the platform
>        - IOMMU was disabled by driver initialization failure
>        - IOMMU was not detected (broken firmware, missing ACPI tables)
>     
>     This follows the precedent set by s390 UV code which panics when
>     security cannot be guaranteed ("System security cannot be guaranteed
>     unless the system panics now").
>     
>     Also update iommu_device_register() to call iommu_set_available() on
>     successful registration, so all IOMMU drivers automatically signal
>     their availability.
>     
>     Signed-off-by: Mario Limonciello (AMD) <superm1@kernel.org>

[Severity: Low]
This isn't a bug, but this commit only touches security/lockdown/lockdown.c,
while the change to iommu_device_register() in drivers/iommu/iommu.c was
made in the preceding commit ("iommu: Add iommu_is_available() API"). Should
the last paragraph be removed from this commit message?

> diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c
> index 8d46886d2cca0..6ce1490188d5d 100644
> --- a/security/lockdown/lockdown.c
> +++ b/security/lockdown/lockdown.c
[ ... ]
> @@ -88,6 +94,25 @@ static int __init lockdown_lsm_init(void)
>  #elif defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY)
>  	lock_kernel_down("Kernel configuration", LOCKDOWN_CONFIDENTIALITY_MAX);
>  #endif
> +
> +	/*
> +	 * If lockdown was activated (via command line or kernel config)
> +	 * and IOMMU is disabled, refuse to boot. Without IOMMU, any
> +	 * PCI/PCIe device can perform DMA to arbitrary physical memory,
> +	 * completely bypassing lockdown protections.
> +	 *
> +	 * On x86, 'no_iommu' is set early by the 'iommu=off' cmdline
> +	 * option during parse_early_param(), before this init function.
> +	 */
> +#if IS_ENABLED(CONFIG_X86)
> +	if (kernel_locked_down > LOCKDOWN_NONE && no_iommu) {
> +		pr_err("FATAL: Lockdown is active but IOMMU is disabled.\n");
> +		pr_err("FATAL: PCI devices can perform DMA to arbitrary memory,\n");
> +		pr_err("FATAL: bypassing all lockdown protections.\n");
> +		panic("Lockdown enabled but IOMMU is disabled (no_iommu)\n");
> +	}
> +#endif

[Severity: Medium]
Can checking no_iommu in lockdown_lsm_init() cause a premature boot panic on
Intel TXT (tboot) systems?

During start_kernel(), parse_early_param() runs iommu_setup() in
arch/x86/kernel/pci-dma.c, which sets no_iommu = 1 if iommu=off was passed on
the command line. Then early_security_init() or security_init() calls
lockdown_lsm_init() before do_initcalls() executes.

However, on Intel TXT launches, rootfs_initcall(pci_iommu_init) later calls
intel_iommu_init() -> tboot_force_iommu() in drivers/iommu/intel/iommu.c,
which forces DMA remapping on and clears no_iommu:

    static __init void tboot_force_iommu(void)
    {
        if (!tboot_enabled() || intel_iommu_tboot_noforce)
            return;
        ...
        dmar_policy = DMAR_FORCE_ON;
        no_iommu = 0;
    }

Because lockdown_lsm_init() runs before rootfs_initcall(pci_iommu_init), will
it panic before tboot_force_iommu() has a chance to clear no_iommu = 0?

> +
>  	security_add_hooks(lockdown_hooks, ARRAY_SIZE(lockdown_hooks),
>  			   &lockdown_lsmid);
>  	return 0;
> @@ -170,3 +195,43 @@ DEFINE_LSM(lockdown) = {
>  	.init = lockdown_lsm_init,
>  	.initcall_core = lockdown_secfs_init,
>  };
> +
> +/*
> + * Late init call to verify IOMMU availability when lockdown is active.
> + * This catches cases where:
> + *  - IOMMU hardware is absent on the platform
> + *  - IOMMU was disabled by driver initialization failure
> + *  - IOMMU was not detected (e.g. broken firmware, missing ACPI tables)
> + *
> + * On x86, we also check no_iommu here as a double-check. The early check
> + * in lockdown_lsm_init() catches 'iommu=off' from the cmdline before
> + * lockdown_lsm_init() runs, but this late check covers any missed cases.
> + */
> +static int __init lockdown_check_iommu(void)
> +{
> +	if (kernel_locked_down > LOCKDOWN_NONE) {

[Severity: Medium]
What happens when lockdown is enabled at runtime via securityfs rather than
at boot?

When the kernel is built with CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y and booted
without a lockdown= parameter, kernel_locked_down is LOCKDOWN_NONE when both
lockdown_lsm_init() and lockdown_check_iommu() run.

If userspace subsequently enables lockdown by writing to
/sys/kernel/security/lockdown, lockdown_write() in
security/lockdown/lockdown.c calls lock_kernel_down("securityfs", level):

    static ssize_t lockdown_write(struct file *file, const char __user *buf,
                                  size_t n, loff_t *ppos)
    {
        ...
                if (label && !strcmp(state, label))
                        err = lock_kernel_down("securityfs", level);
        ...
    }

Since lock_kernel_down() updates kernel_locked_down without checking
iommu_is_available() or no_iommu, doesn't runtime activation bypass the IOMMU
requirement?

> +		/*
> +		 * Check generic IOMMU availability (all architectures).
> +		 * iommu_is_available() returns true if any IOMMU driver
> +		 * registered successfully.
> +		 */
> +		if (!iommu_is_available()) {
> +			pr_err("FATAL: Lockdown is active but no IOMMU driver\n");
> +			pr_err("FATAL: registered. PCI devices can DMA to\n");
> +			pr_err("FATAL: arbitrary memory, bypassing lockdown.\n");
> +			panic("Lockdown enabled but IOMMU not available\n");
> +		}

[Severity: High]
Won't panicking unconditionally when !iommu_is_available() break boot across
several valid configurations, architectures, and driver models?

1. Kernels with CONFIG_IOMMU_API=n (or CONFIG_IOMMU_SUPPORT=n):
CONFIG_SECURITY_LOCKDOWN_LSM in security/lockdown/Kconfig does not depend on
CONFIG_IOMMU_API, and include/linux/iommu.h defines the fallback stub as:

    static inline bool iommu_is_available(void)
    {
        return false;
    }

For example, arch/powerpc/configs/skiroot_defconfig disables
CONFIG_IOMMU_SUPPORT while enabling CONFIG_SECURITY_LOCKDOWN_LSM=y and
CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y, so it will now panic on every
boot. On PowerPC more generally, arch/powerpc/kernel/iommu.c enforces
hardware TCE DMA translation even when CONFIG_SPAPR_TCE_IOMMU (which selects
CONFIG_IOMMU_API and registers IOMMU devices in
spapr_tce_setup_phb_iommus_initcall() only when hose_list is non-empty) is
disabled.

2. Architectures that only call iommu_device_register() per attached PCI
device:
On s390, zpci_init_iommu() in drivers/iommu/s390-iommu.c is only called when
a zPCI device is discovered via zpci_add_device():

    int zpci_init_iommu(struct zpci_dev *zdev)
    {
        ...
        if (zdev->rtr_avail) {
            rc = iommu_device_register(&zdev->iommu_dev,
                                       &s390_iommu_rtr_ops, NULL);
        } else {
            rc = iommu_device_register(&zdev->iommu_dev, &s390_iommu_ops,
                                       NULL);
        }
        ...
    }

On any s390 LPAR or guest without attached zPCI devices (such as systems
using only CCW or virtio-ccw I/O, or with CONFIG_PCI=n),
iommu_device_register() is never called, causing lockdown_check_iommu() to
panic claiming PCI devices can DMA to arbitrary memory when no PCI devices
are present.

3. Modular and deferred-probe IOMMU drivers:
Several IOMMU drivers in drivers/iommu/Kconfig are tristate (such as
CONFIG_ARM_SMMU, CONFIG_ARM_SMMU_V3, CONFIG_VIRTIO_IOMMU, CONFIG_APPLE_DART,
and CONFIG_MTK_IOMMU). If built as modules (=m), they cannot be loaded from
initramfs before late_initcall(lockdown_check_iommu) runs. In addition,
because security/ is linked before drivers/ in the top-level Kbuild,
late_initcall(lockdown_check_iommu) in security/lockdown/lockdown.c runs
before late_initcall(deferred_probe_initcall) in drivers/base/dd.c, so any
built-in IOMMU driver whose probe returned -EPROBE_DEFER will not have been
re-probed yet when lockdown_check_iommu() panics.

4. Virtual machines and non-PCI systems:
Guests (such as KVM/QEMU, Xen, or cloud instances) and non-PCI platforms
booting with UEFI Secure Boot or lockdown=integrity without an emulated
vIOMMU will also unconditionally panic at boot.

[Severity: Medium]
Can iommu_is_available() still return true after an IOMMU driver fails
initialization following iommu_device_register()?

In drivers/iommu/iommu.c, iommu_device_register() sets iommu_available = true
via iommu_set_available(), but iommu_device_unregister() removes the device
from iommu_device_list without clearing or updating iommu_available when the
list becomes empty:

    void iommu_device_unregister(struct iommu_device *iommu)
    {
        for (int i = 0; i < ARRAY_SIZE(iommu_buses); i++)
            bus_for_each_dev(iommu_buses[i], NULL, iommu, remove_iommu_group);

        spin_lock(&iommu_device_lock);
        list_del(&iommu->list);
        spin_unlock(&iommu_device_lock);
    }

Several IOMMU drivers register the iommu_device before initialization is
complete and call iommu_device_unregister() in their error unwind paths:

- In drivers/iommu/amd/init.c, iommu_init_pci() calls
iommu_device_register() before pci_enable_device() and before
amd_iommu_enable_interrupts() in state_next(). If either fails, state_next()
calls disable_iommus() and free_iommu_resources() -> free_iommu_all() ->
free_sysfs() -> iommu_device_unregister(), leaving iommu_available set to
true. Additionally, iommu_init_pci() still calls iommu_device_register() when
amd_iommu_pgtable == PD_MODE_NONE (DMA translation unsupported).

- Platform IOMMU drivers such as mtk_iommu_probe() in
drivers/iommu/mtk_iommu.c, mtk_iommu_v1_probe(), qcom_iommu_device_probe(),
sprd_iommu_probe(), and sun50i_iommu_probe() also call
iommu_device_register() and roll back via iommu_device_unregister() if a
subsequent probe step fails.

Should iommu_is_available() check whether any active IOMMU devices remain
registered (or update the state in iommu_device_unregister()) rather than
using a one-way flag?

[Severity: Medium]
Does lockdown_check_iommu() also need to check iommu_default_passthrough()?

When booting with iommu=pt (handled in iommu_setup() in
arch/x86/kernel/pci-dma.c), iommu.passthrough=1 (in drivers/iommu/iommu.c),
or CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y, no_iommu remains 0 and the IOMMU
driver still registers via iommu_device_register(), so iommu_is_available()
returns true. However, default domains use IOMMU_DOMAIN_IDENTITY
(iommu_default_passthrough() returns true), leaving PCI devices with
unrestricted identity-mapped DMA access to physical memory.

> +#if IS_ENABLED(CONFIG_X86)
> +		/*
> +		 * Also double-check x86-specific flag.
> +		 */
> +		if (no_iommu) {
> +			pr_err("FATAL: Lockdown is active but IOMMU disabled\n");
> +			pr_err("FATAL: via kernel command line (iommu=off).\n");
> +			panic("Lockdown enabled but IOMMU disabled (iommu=off)\n");
> +		}
> +#endif

[Severity: Low]
Is this no_iommu check in lockdown_check_iommu() reachable?

On x86, when no_iommu is non-zero, no IOMMU driver registers an IOMMU device,
so iommu_is_available() is false and the preceding !iommu_is_available()
check will always panic before reaching if (no_iommu).

> +	}
> +	return 0;
> +}
> +late_initcall(lockdown_check_iommu);

[Severity: Medium]
Should lockdown_check_iommu() be wired up via .initcall_late in
DEFINE_LSM(lockdown) instead of a raw late_initcall()?

In security/lockdown/lockdown.c, early_param("lockdown", lockdown_param) sets
kernel_locked_down during parse_early_param(), before security_init()
determines which LSMs are enabled:

    static int __init lockdown_param(char *level)
    {
        ...
        if (strcmp(level, "integrity") == 0)
            lock_kernel_down("command line", LOCKDOWN_INTEGRITY_MAX);
        else if (strcmp(level, "confidentiality") == 0)
            lock_kernel_down("command line", LOCKDOWN_CONFIDENTIALITY_MAX);
        ...
    }
    early_param("lockdown", lockdown_param);

If CONFIG_SECURITY_LOCKDOWN_LSM=y and CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=n,
and the lockdown LSM is disabled via CONFIG_LSM or the lsm= command-line
parameter while lockdown=integrity is passed on the command line,
lockdown_lsm_init() and .initcall_core = lockdown_secfs_init are skipped, but
a raw late_initcall(lockdown_check_iommu) will still run and panic. Using
.initcall_late in DEFINE_LSM(lockdown) would ensure it only runs when the
lockdown LSM is actually enabled.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-lockdown-iommu-v1-0-00f3c2be4c9f@kernel.org?part=2

      reply	other threads:[~2026-11-02 14:28 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 14:00 [PATCH 0/2] lockdown: Prevent booting with lockdown enabled but IOMMU disabled Mario Limonciello (AMD)
2026-11-02 14:00 ` [PATCH 1/2] iommu: Add iommu_is_available() API Mario Limonciello (AMD)
2026-11-02 14:11   ` sashiko-bot
2026-11-02 14:00 ` [PATCH 2/2] security: lockdown: Prevent boot with lockdown but no IOMMU Mario Limonciello (AMD)
2026-11-02 14:28   ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-193572@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox