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
prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.