* Re: [PATCH v3 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
From: Jihong Min @ 2026-05-08 16:56 UTC (permalink / raw)
To: Mario Limonciello, Guenter Roeck, Jihong Min
Cc: Greg Kroah-Hartman, Mathias Nyman, Jonathan Corbet, Shuah Khan,
Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
linux-kernel
In-Reply-To: <70035490-eafb-4610-8889-9e04931c8b32@amd.com>
>
> Another thing to mention is that you are going too fast between patch
> versions. All your patches show up in a ton of people's inboxes.
>
> It's great you've gotten feedback on them but I suggest you give it a
> few days or a week between versions to gather more feedback.
Thanks for the guidance. I understand, and I agree that I have been sending
new revisions too quickly.
I will slow down the revision cadence from now on and wait a few days, or
until the review discussion settles, before sending the next version.
>
> If you haven't already; you should take a look at what Sahiko finds on
> your patches too. Be sure to look at the feedback critically and take
> it with a grain of salt; but it often finds a few nuggets that are
> worthwhile to consider.
>
> Here is the Sahiko link for v4 you can review if you weren't already
> looking at it.
>
> https://sashiko.dev/#/patchset/20260508143910.14673-1-hurryman2212%40gmail.com
>
>
I have been checking the Sashiko feedback and have been incorporating the
actionable issues it found where they made sense. I will continue to review
it critically before sending future revisions.
For reference only, and not as a substitute for the mailing-list
patches, I am
keeping my current work-in-progress branch here:
https://github.com/hurryman2212/linux/tree/prom21_hwmon
Sincerely,
Jihong Min
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-08 16:54 UTC (permalink / raw)
To: Mathieu Desnoyers
Cc: Joshua Peisach, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <5616fbd5-612d-4c23-aa10-d6b8de81f9eb@efficios.com>
On Fri, May 08, 2026 at 12:26:45PM -0400, Mathieu Desnoyers wrote:
>On 2026-05-08 12:23, Sasha Levin wrote:
>>On Fri, May 08, 2026 at 12:18:28PM -0400, Mathieu Desnoyers wrote:
>>>On 2026-05-08 12:13, Sasha Levin wrote:
>>>[...]
>>>>>One possible approach to prevent "footgun" type of killswitch use would
>>>>>be to first apply a statistics collection killswitch handler that does
>>>>>not change the behavior: it checks whether the target function is
>>>>>invoked at all on the system for a given period of time. Then
>>>>>it applies
>>>>>the killswitch if it was not invoked during that period. Overall
>>>>>sequence:
>>>>>
>>>>>- pre-soak killswitch for e.g. 30s, checking whether the function is
>>>>> invoked at all. (period would be user-configurable)
>>>>>- if no calls were detected, engage killswitch, else report failure to
>>>>> the user.
>>>>>
>>>>>This should prevent footguns such as trying to killswitch fork, malloc
>>>>>or other core functions which are inherently required.
>>>>
>>>>Why not just use our good old tracing infra? Set tracepoints
>>>>where ever you
>>>>want, collect any data you might need, and engage the killswitch
>>>>when you're
>>>>happy with the data you have?
>>>>
>>>>It feels a bit weird adding something like this into killswitch.
>>>
>>>It really depends on whether you want to include some basic safety nets
>>>directly within killswitch, or leave that entirely to the end user.
>>>
>>>I don't have a strong opinion either way. I was just pointing out the
>>>feasibility of a pre-soak sanity check before applying the killswitch.
>>
>>What about doing this with some userspace tooling (maybe under scripts/) to
>>facilitate this?
>>
>
>That could work, although it requires additional infrastructure to be in
>place (tracing) on production environments, which may not be the case
>everywhere. In comparison, if the sanity check is done within
>killswitch, you only need killswitch and kprobe to be compiled into
>the production system, which is more lightweight.
So CONFIG_KPROBES but no CONFIG_KPROBES_EVENTS or BPF?
This sounds like a pretty small crowd :)
I think it makes sense to document/script a workflow around tracing or
bpftrace, but I don't think it's worth adding all that logic in the kernel.
I'd also note that you can always reverse the killswitch if you observe issues.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH v4 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
From: Mario Limonciello @ 2026-05-08 16:51 UTC (permalink / raw)
To: Jihong Min, Greg Kroah-Hartman, Mathias Nyman
Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Basavaraj Natikar,
linux-usb, linux-hwmon, linux-doc, linux-pci, linux-kernel
In-Reply-To: <20260508143910.14673-3-hurryman2212@gmail.com>
On 5/8/26 09:39, Jihong Min wrote:
> Add an auxiliary-bus hwmon driver for the temperature sensor exposed by
> AMD Promontory 21 (PROM21) xHCI PCI functions. The driver binds to the
> "hwmon" auxiliary device published by the PROM21 xHCI PCI glue and
> exposes the sensor as temp1_input under the prom21_xhci hwmon device.
>
> The sensor is accessed through a PROM21 vendor index/data register pair
> in the xHCI PCI MMIO BAR. The read path restores the previous vendor
> index value after sampling and does not runtime-resume the parent PCI
> device; reads from a suspended parent return -ENODATA.
>
> Document the supported device, register access, runtime PM behavior, and
> sysfs lookup method. The documentation also records the observation
> method used to identify the register pair and derive the conversion
> formula.
>
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> ---
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/prom21-xhci.rst | 99 +++++++++++
> drivers/hwmon/Kconfig | 10 ++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/prom21-xhci.c | 250 ++++++++++++++++++++++++++++
> 5 files changed, 361 insertions(+)
> create mode 100644 Documentation/hwmon/prom21-xhci.rst
> create mode 100644 drivers/hwmon/prom21-xhci.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8b655e5d6b68..324208f1faa2 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -216,6 +216,7 @@ Hardware Monitoring Kernel Drivers
> pmbus
> powerz
> powr1220
> + prom21-xhci
> pt5161l
> pxe1610
> pwm-fan
> diff --git a/Documentation/hwmon/prom21-xhci.rst b/Documentation/hwmon/prom21-xhci.rst
> new file mode 100644
> index 000000000000..10d03c4476c3
> --- /dev/null
> +++ b/Documentation/hwmon/prom21-xhci.rst
> @@ -0,0 +1,99 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver prom21-xhci
> +=========================
> +
> +Supported chips:
> +
> + * AMD Promontory 21 (PROM21) xHCI
> +
> + Prefix: 'prom21_xhci'
> +
> + PCI ID: 1022:43fd
> +
> +Author:
> +
> + - Jihong Min <hurryman2212@gmail.com>
> +
> +Description
> +-----------
> +
> +This driver exposes the temperature sensor in AMD PROM21 xHCI controllers.
> +
> +The driver binds to an auxiliary device created by the xHCI PCI driver for
> +supported controllers. The sensor value is accessed through a vendor-specific
> +index/data register pair in the controller's PCI MMIO BAR.
> +The auxiliary device is created by the ``xhci-pci-prom21`` PCI glue driver.
> +USB host operation is otherwise delegated to the common ``xhci-pci`` code.
> +
> +PROM21 is an AMD chipset IP used in single-chip or daisy-chained configurations
> +to build AMD 6xx/8xx series chipsets. Since the xHCI controllers are
> +integrated in PROM21, this temperature can also be used as a monitor for a
> +temperature close to the AMD chipset temperature.
> +
> +Register access
> +---------------
> +
> +The temperature value is read through a vendor-specific index/data register
> +pair in the xHCI PCI MMIO BAR. The driver uses the following byte offsets from
> +the MMIO BAR base:
> +
> +======================= =====================================================
> +0x3000 Vendor index register
> +0x3008 Vendor data register
> +======================= =====================================================
> +
> +The driver saves the current vendor index register value, writes the
> +temperature selector ``0x0001e520`` to the vendor index register, reads the
> +vendor data register, and restores the previous vendor index value before
> +returning. The raw temperature value is the low 8 bits of the vendor data
> +register value.
> +
> +No public AMD reference is available for the register pair or the raw value.
> +The register pair was identified on an X870E system with two PROM21 xHCI
> +controllers. One controller was passed through to a Windows VM, and the same
> +controller's PCI MMIO BAR was observed from the Linux host while HWiNFO64 was
> +reporting the PROM21 xHCI temperature. In the test environment, the reported
> +temperature was very stable at idle and the displayed sensor resolution was
> +low, which made it possible to look for a consistently repeating MMIO response
> +for the same reported temperature. During observation, offset 0x3000 repeatedly
> +contained selector ``0x0001e520``. Writing the same selector to offset 0x3000
> +from Linux and then reading offset 0x3008 reproduced the same raw value, so the
> +offsets are treated as a vendor index/data register pair.
> +
> +The conversion formula was empirically inferred by matching observed raw
> +8-bit values against HWiNFO64's reported PROM21 xHCI temperature for the same
> +controller. The observed mapping is:
> +
> + temp[C] = raw * 0.9066 - 78.624
> +
> +Runtime PM
> +----------
> +
> +The driver does not wake the xHCI PCI device for hwmon reads. It reads the
> +temperature only when the parent device is already active. A read from a
> +suspended device returns ``-ENODATA``. Sensor reads do not mark the xHCI PCI
> +device as busy or schedule autosuspend, so polling the sensor does not delay
> +runtime suspend.
> +
> +Sysfs entries
> +-------------
> +
> +======================= =====================================================
> +temp1_input Temperature in millidegrees Celsius
> +======================= =====================================================
> +
> +The hwmon device name is ``prom21_xhci``. The sysfs path depends on the hwmon
> +device number assigned by the kernel. Userspace can locate the device by
> +matching the ``name`` attribute:
> +
> +.. code-block:: sh
> +
> + for hwmon in /sys/class/hwmon/hwmon*; do
> + [ "$(cat "$hwmon/name")" = "prom21_xhci" ] || continue
> + cat "$hwmon/temp1_input"
> + done
> +
> +``temp1_input`` reports millidegrees Celsius, so a value of ``50113`` means
> +50.113 degrees Celsius. If the raw register value is invalid, ``temp1_input``
> +returns ``-ENODATA``.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 14e4cea48acc..fe0f14e247b5 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -951,6 +951,16 @@ config SENSORS_POWR1220
> This driver can also be built as a module. If so, the module
> will be called powr1220.
>
> +config SENSORS_PROM21_XHCI
> + tristate "AMD Promontory 21 xHCI temperature sensor"
> + depends on USB_XHCI_PCI_PROM21
> + help
> + If you say yes here you get support for the AMD Promontory 21
> + (PROM21) xHCI temperature sensor.
> +
> + This driver can also be built as a module. If so, the module
> + will be called prom21-xhci.
> +
> config SENSORS_LAN966X
> tristate "Microchip LAN966x Hardware Monitoring"
> depends on SOC_LAN966 || COMPILE_TEST
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 4788996aa137..0bda542e8e2b 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -196,6 +196,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
> obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
> obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
> obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
> +obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o
> obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
> obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
> obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
> diff --git a/drivers/hwmon/prom21-xhci.c b/drivers/hwmon/prom21-xhci.c
> new file mode 100644
> index 000000000000..f91303ce3428
> --- /dev/null
> +++ b/drivers/hwmon/prom21-xhci.c
> @@ -0,0 +1,250 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * AMD Promontory 21 xHCI Hwmon Implementation
> + * (only temperature monitoring is supported)
> + *
> + * This can be effectively used as the alternative chipset temperature monitor.
> + *
> + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/io.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +#include <linux/usb/hcd.h>
> +
> +#define PROM21_INDEX 0x3000
> +#define PROM21_DATA 0x3008
> +#define PROM21_TEMP_REG 0x0001e520
> +
> +struct prom21_xhci {
> + struct pci_dev *pdev;
> + struct device *hwmon_dev;
> + void __iomem *regs;
> + struct mutex lock; /* serializes index/data register access */
> +};
> +
> +static int prom21_xhci_pm_get(struct prom21_xhci *hwmon, bool *pm_ref)
> +{
> + struct device *dev = &hwmon->pdev->dev;
> + int ret;
> +
> + *pm_ref = false;
> +
> + /*
> + * PROM21 temperature register access does not return a valid value while
> + * the parent xHCI PCI function is suspended. Do not wake the device from
> + * a hwmon read; only read when runtime PM reports the device as active,
> + * or when runtime PM is disabled and the device is not marked as
> + * suspended.
> + */
> + ret = pm_runtime_get_if_active(dev);
> + if (ret > 0) {
> + *pm_ref = true;
> + return 0;
> + }
> +
> + if (ret == -EINVAL && !pm_runtime_status_suspended(dev))
> + return 0;
> +
> + if (!ret || pm_runtime_status_suspended(dev))
> + return -ENODATA;
> +
> + return ret;
> +}
> +
> +/*
> + * This is not a pure MMIO read. The PROM21 vendor data register is selected
> + * by temporarily writing PROM21_TEMP_REG to the vendor index register.
> + * Serialize the sequence, keep it short, and restore the previous index before
> + * returning so this driver does not leave the vendor index/data register pair
> + * in a different state for other possible users.
> + */
> +static int prom21_xhci_read_temp_raw_restore_index(struct prom21_xhci *hwmon,
> + u8 *raw)
> +{
> + struct device *dev = &hwmon->pdev->dev;
> + bool pm_ref;
> + u32 index;
> + u32 data;
> + int ret;
> +
> + ret = prom21_xhci_pm_get(hwmon, &pm_ref);
> + if (ret)
> + return ret;
Rather than using pm_ref as an output variable to indicate whether you
took a ref - how about you instead always take a ref on success and
return an error on fail? This would feel more logical to me.
> +
> + mutex_lock(&hwmon->lock);
guard(mutex) perhaps?
> + index = readl(hwmon->regs + PROM21_INDEX);
> + /* Select the PROM21 temperature register through the vendor index. */
> + writel(PROM21_TEMP_REG, hwmon->regs + PROM21_INDEX);
> + data = readl(hwmon->regs + PROM21_DATA);
You only care about the first byte, right? Just use readb() and make
data a u8.
> + /* Restore the previous vendor index register value. */
> + writel(index, hwmon->regs + PROM21_INDEX);
> + readl(hwmon->regs + PROM21_INDEX);
> + mutex_unlock(&hwmon->lock);
> +
> + if (pm_ref) {
> + /*
> + * Drop only the reference taken by pm_runtime_get_if_active().
> + * Do not mark the device busy or schedule autosuspend from the
> + * hwmon path; sensor polling must not keep the xHCI PCI device
> + * active.
> + */
> + pm_runtime_put_noidle(dev);
> + }
> +
> + *raw = data & 0xff;
I personally don't really like changing the pointer when there is
potentially an error case with it for -ENODATA.
> + if (!*raw || *raw == 0xff)
Does 0xff actually happen with your runtime PM handling? Between my
suggestion above to use readb() this can turn into:
if (!data)
return -ENODDATA;
*raw = data;
return 0;
> + return -ENODATA;
> +
> + return 0;
> +}
> +
> +static long prom21_xhci_raw_to_millicelsius(u8 raw)
> +{
> + /*
> + * No public AMD reference is available for this value.
> + * The scale was derived from observed PROM21 xHCI temperature readings:
> + * temp[C] = raw * 0.9066 - 78.624
> + */
> + return DIV_ROUND_CLOSEST(raw * 9066, 10) - 78624;
> +}
> +
> +static umode_t prom21_xhci_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + if (type != hwmon_temp || channel)
> + return 0;
> +
> + switch (attr) {
> + case hwmon_temp_input:
> + return 0444;
> + default:
> + return 0;
> + }
> +}
> +
> +static int prom21_xhci_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct prom21_xhci *hwmon = dev_get_drvdata(dev);
> + u8 raw;
> + int ret;
> +
> + if (type != hwmon_temp || attr != hwmon_temp_input || channel)
> + return -EOPNOTSUPP;
> +
> + ret = prom21_xhci_read_temp_raw_restore_index(hwmon, &raw);
> + if (ret)
> + return ret;
> +
> + *val = prom21_xhci_raw_to_millicelsius(raw);
> + return 0;
> +}
> +
> +static const struct hwmon_ops prom21_xhci_ops = {
> + .is_visible = prom21_xhci_is_visible,
> + .read = prom21_xhci_read,
> +};
> +
> +static const struct hwmon_channel_info *const prom21_xhci_info[] = {
> + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
> + NULL,
> +};
> +
> +static const struct hwmon_chip_info prom21_xhci_chip_info = {
> + .ops = &prom21_xhci_ops,
> + .info = prom21_xhci_info,
> +};
> +
> +static int prom21_xhci_probe(struct auxiliary_device *auxdev,
> + const struct auxiliary_device_id *id)
> +{
> + struct device *dev = &auxdev->dev;
> + struct device *parent = dev->parent;
> + struct prom21_xhci *hwmon;
> + struct pci_dev *pdev;
> + struct usb_hcd *hcd;
> + int ret;
> +
> + if (!parent || !dev_is_pci(parent))
> + return -ENODEV;
> +
> + pdev = to_pci_dev(parent);
> + hcd = pci_get_drvdata(pdev);
> + if (!hcd)
> + return dev_err_probe(dev, -ENODEV,
> + "xHCI HCD data unavailable\n");
> +
> + if (!hcd->regs || hcd->rsrc_len < PROM21_DATA + sizeof(u32))
> + return dev_err_probe(dev, -ENODEV, "invalid MMIO resource\n");
> +
> + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
> + if (!hwmon)
> + return -ENOMEM;
> +
> + ret = devm_mutex_init(dev, &hwmon->lock);
> + if (ret)
> + return ret;
> +
> + hwmon->pdev = pdev;
> + hwmon->regs = hcd->regs;
> + auxiliary_set_drvdata(auxdev, hwmon);
> +
> + /*
> + * Use the PCI function as the hwmon parent so user space reports it as
> + * a PCI adapter. Lifetime is still owned by this auxiliary driver;
> + * remove() unregisters the hwmon device before xhci-pci tears down the
> + * HCD.
> + */
> + hwmon->hwmon_dev =
> + hwmon_device_register_with_info(&pdev->dev, "prom21_xhci",
> + hwmon, &prom21_xhci_chip_info,
> + NULL);
> + if (IS_ERR(hwmon->hwmon_dev))
> + return PTR_ERR(hwmon->hwmon_dev);
> +
> + return 0;
> +}
> +
> +static void prom21_xhci_remove(struct auxiliary_device *auxdev)
> +{
> + struct prom21_xhci *hwmon = auxiliary_get_drvdata(auxdev);
> +
> + /*
> + * The PROM21 PCI glue destroys the auxiliary device before HCD teardown.
> + * Unregister the hwmon device here so sysfs removes the attributes,
> + * stops new reads, and drains active hwmon callbacks before the xHCI
> + * MMIO mapping is released.
> + */
> + hwmon_device_unregister(hwmon->hwmon_dev);
> +}
> +
> +static const struct auxiliary_device_id prom21_xhci_id_table[] = {
> + { .name = "xhci_pci_prom21.hwmon" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(auxiliary, prom21_xhci_id_table);
> +
> +static struct auxiliary_driver prom21_xhci_driver = {
> + .name = "prom21-xhci",
> + .probe = prom21_xhci_probe,
> + .remove = prom21_xhci_remove,
> + .id_table = prom21_xhci_id_table,
> +};
> +module_auxiliary_driver(prom21_xhci_driver);
> +
> +MODULE_AUTHOR("Jihong Min <hurryman2212@gmail.com>");
> +MODULE_DESCRIPTION("AMD Promontory 21 xHCI temperature sensor driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply
* Re: [PATCH v4 1/2] usb: xhci-pci: add AMD Promontory 21 PCI glue
From: Mario Limonciello @ 2026-05-08 16:40 UTC (permalink / raw)
To: Jihong Min, Greg Kroah-Hartman, Mathias Nyman
Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Basavaraj Natikar,
linux-usb, linux-hwmon, linux-doc, linux-pci, linux-kernel
In-Reply-To: <20260508143910.14673-2-hurryman2212@gmail.com>
On 5/8/26 09:39, Jihong Min wrote:
> AMD Promontory 21 (PROM21) xHCI controllers use generic xHCI
> operation, but the PCI function also exposes optional
> controller-specific sensor functionality. Add a small PROM21 PCI glue
> driver for AMD 1022:43fd controllers.
>
> The driver delegates USB host operation to the common xhci-pci core and
> creates a "hwmon" auxiliary device for optional child drivers. Failure
> to create the auxiliary device is logged but does not fail the xHCI
> probe, since the auxiliary device is only needed for sensor support.
>
> Keep the PROM21 PCI glue built-in only when enabled because it owns the
> PCI binding for PROM21 xHCI controllers and must be available whenever
> the common built-in xhci-pci driver hands those controllers off. This
> avoids an early boot case where generic xhci-pci rejects a PROM21
> controller but a modular xhci-pci-prom21 driver is not available in the
> initramfs, leaving USB devices behind that controller unavailable.
>
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Jihong Min <hurryman2212@gmail.com>
> ---
> drivers/usb/host/Kconfig | 18 +++++
> drivers/usb/host/Makefile | 1 +
> drivers/usb/host/xhci-pci-prom21.c | 111 +++++++++++++++++++++++++++++
> drivers/usb/host/xhci-pci.c | 11 +++
> 4 files changed, 141 insertions(+)
> create mode 100644 drivers/usb/host/xhci-pci-prom21.c
>
> diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
> index 0a277a07cf70..74eedef1440d 100644
> --- a/drivers/usb/host/Kconfig
> +++ b/drivers/usb/host/Kconfig
> @@ -42,6 +42,24 @@ config USB_XHCI_PCI
> depends on USB_PCI
> default y
>
> +config USB_XHCI_PCI_PROM21
> + bool "AMD Promontory 21 xHCI PCI support"
> + depends on USB_XHCI_PCI=y
> + select AUXILIARY_BUS
> + help
> + Say 'Y' to enable support for the AMD Promontory 21 xHCI PCI
> + controller with optional sensor support. This driver does not add
> + PROM21-specific USB or xHCI operation. It binds PROM21 xHCI PCI
> + functions, delegates USB host operation to the common xHCI PCI core,
> + and creates auxiliary devices for optional sensor drivers.
> +
> + This driver is built-in only because it owns the PCI binding for
> + PROM21 xHCI controllers when enabled and must be available whenever
> + the common xHCI PCI driver is available. The optional sensor driver
> + can still be built as a module.
> +
> + If unsure, say 'N'.
> +
> config USB_XHCI_PCI_RENESAS
> tristate "Support for additional Renesas xHCI controller with firmware"
> depends on USB_XHCI_PCI
> diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
> index a07e7ba9cd53..174580c1281a 100644
> --- a/drivers/usb/host/Makefile
> +++ b/drivers/usb/host/Makefile
> @@ -71,6 +71,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
> obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
> obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o
> obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
> +obj-$(CONFIG_USB_XHCI_PCI_PROM21) += xhci-pci-prom21.o
> obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
> obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
> obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
> diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c
> new file mode 100644
> index 000000000000..7354a898732e
> --- /dev/null
> +++ b/drivers/usb/host/xhci-pci-prom21.c
> @@ -0,0 +1,111 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * AMD Promontory 21 xHCI host controller PCI Bus Glue.
> + *
> + * This does not add any PROM21-specific USB or xHCI operation. It exists only
> + * to publish an auxiliary device for integrated temperature sensor support.
> + *
> + * Copyright (C) 2026 Jihong Min <hurryman2212@gmail.com>
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/device/devres.h>
> +#include <linux/errno.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/slab.h>
> +#include <linux/usb.h>
> +#include <linux/usb/hcd.h>
> +
> +#include "xhci-pci.h"
> +
> +struct prom21_xhci_auxdev {
> + struct auxiliary_device *auxdev;
> +};
> +
> +static void prom21_xhci_auxdev_release(struct device *dev, void *res)
> +{
> + struct prom21_xhci_auxdev *prom21_auxdev = res;
> +
> + auxiliary_device_destroy(prom21_auxdev->auxdev);
> +}
> +
> +static int prom21_xhci_create_auxdev(struct pci_dev *pdev)
> +{
> + struct prom21_xhci_auxdev *prom21_auxdev;
> +
> + prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release,
> + sizeof(*prom21_auxdev), GFP_KERNEL);
> + if (!prom21_auxdev)
> + return -ENOMEM;
> +
> + prom21_auxdev->auxdev =
> + auxiliary_device_create(&pdev->dev, KBUILD_MODNAME, "hwmon",
> + NULL, (pci_domain_nr(pdev->bus) << 16) |
> + pci_dev_id(pdev));
> + if (!prom21_auxdev->auxdev) {
> + devres_free(prom21_auxdev);
> + return -ENOMEM;
> + }
> +
> + devres_add(&pdev->dev, prom21_auxdev);
> + return 0;
> +}
> +
> +static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev)
> +{
> + devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL);
> +}
> +
> +static int prom21_xhci_probe(struct pci_dev *dev,
> + const struct pci_device_id *id)
> +{
> + int retval;
> +
> + retval = xhci_pci_common_probe(dev, id);
> + if (retval)
> + return retval;
> +
> + retval = prom21_xhci_create_auxdev(dev);
> + if (retval) {
> + /*
> + * The auxiliary device only provides optional temperature sensor
> + * support. Keep the xHCI controller usable if it fails.
> + */
> + dev_err(&dev->dev,
> + "failed to create PROM21 hwmon auxiliary device: %d\n",
> + retval);
> + }
> +
> + return 0;
> +}
> +
> +static void prom21_xhci_remove(struct pci_dev *dev)
> +{
> + prom21_xhci_destroy_auxdev(dev);
> + xhci_pci_remove(dev);
> +}
> +
> +static const struct pci_device_id pci_ids[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x43fd) }, /* PROM21 xHCI */
> + { /* end: all zeroes */ }
> +};
> +MODULE_DEVICE_TABLE(pci, pci_ids);
> +
> +static struct pci_driver prom21_xhci_driver = {
> + .name = "xhci-pci-prom21",
> + .id_table = pci_ids,
> +
> + .probe = prom21_xhci_probe,
> + .remove = prom21_xhci_remove,
> +
> + .shutdown = usb_hcd_pci_shutdown,
> + .driver = {
> + .pm = pm_ptr(&usb_hcd_pci_pm_ops),
> + },
> +};
> +module_pci_driver(prom21_xhci_driver);
> +
> +MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver");
> +MODULE_IMPORT_NS("xhci");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
> index 585b2f3117b0..5db427ad0422 100644
> --- a/drivers/usb/host/xhci-pci.c
> +++ b/drivers/usb/host/xhci-pci.c
> @@ -84,6 +84,7 @@
> #define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba
> #define PCI_DEVICE_ID_AMD_PROMONTORYA_2 0x43bb
> #define PCI_DEVICE_ID_AMD_PROMONTORYA_1 0x43bc
> +#define PCI_DEVICE_ID_AMD_PROM21_XHCI 0x43fd
This define should be in a common header used by xhci-pci.c and
xhci-pci-prom21.c both.
>
> #define PCI_DEVICE_ID_ATI_NAVI10_7316_XHCI 0x7316
>
> @@ -696,12 +697,22 @@ static const struct pci_device_id pci_ids_renesas[] = {
> { /* end: all zeroes */ }
> };
>
> +/* handled by xhci-pci-prom21 if enabled */
> +static const struct pci_device_id pci_ids_prom21[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI) },
> + { /* end: all zeroes */ }
> +};
> +
> static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
> {
> if (IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) &&
> pci_match_id(pci_ids_renesas, dev))
> return -ENODEV;
>
> + if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) &&
> + pci_match_id(pci_ids_prom21, dev))
> + return -ENODEV;
> +
> return xhci_pci_common_probe(dev, id);
> }
>
^ permalink raw reply
* Re: [PATCH v2 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Willy Tarreau @ 2026-05-08 16:39 UTC (permalink / raw)
To: Greg KH
Cc: Linus Torvalds, leon, security, Jonathan Corbet, skhan, workflows,
linux-doc, linux-kernel
In-Reply-To: <af4IW_ycR2RpAjhy@1wt.eu>
Greg,
does this addition on top of the current patch address your concerns ?
--- a/Documentation/process/security-bugs.rst
+++ b/Documentation/process/security-bugs.rst
@@ -88,6 +88,14 @@ can be easily exploited, representing an imminent threat to many users. Before
reporting, consider whether the issue actually crosses a trust boundary on such
a system.
+**If you resorted to AI assistance to identify a bug, you must treat it as
+public**. While you may have valid reasons to believe it is not, the security
+team's experience shows that bugs discovered this way systematically surface
+simultaneously across multiple researchers, often on the same day. In this
+case, do not publicly share a reproducer, as this could cause unintended harm;
+just mention that one is available and maintainers might ask for it privately
+if they need it.
+
If you are unsure whether an issue qualifies, err on the side of reporting
privately: the security team would rather triage a borderline report than miss
a real vulnerability. Reporting ordinary bugs to the security list, however,
@@ -102,7 +110,7 @@ affected subsystem's maintainers and Cc: the Linux kernel security team. Do
not send it to a public list at this stage, unless you have good reasons to
consider the issue as being public or trivial to discover (e.g. result of a
widely available automated vulnerability scanning tool that can be repeated by
-anyone).
+anyone, or use of AI-based tools).
If you're sending a report for issues affecting multiple parts in the kernel,
even if they're fairly similar issues, please send individual messages (think
If so I can resend with it.
Thanks,
Willy
^ permalink raw reply
* [PATCH 5/5] docs: fix repeated word 'at' in journalling.rst
From: Adrien Reynard @ 2026-05-08 16:38 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION, open list
Cc: Adrien Reynard
Signed-off-by: Adrien Reynard <reynard.adrien.08@gmail.com>
---
Documentation/filesystems/journalling.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/filesystems/journalling.rst b/Documentation/filesystems/journalling.rst
index 863e93e623f7..2825f6c030c2 100644
--- a/Documentation/filesystems/journalling.rst
+++ b/Documentation/filesystems/journalling.rst
@@ -93,7 +93,7 @@ easily as on jbd2_journal_start().
Try to reserve the right number of blocks the first time. ;-). This will
be the maximum number of blocks you are going to touch in this
-transaction. I advise having a look at at least ext4_jbd.h to see the
+transaction. I advise having a look at least ext4_jbd.h to see the
basis on which ext4 uses to make these decisions.
Another wriggle to watch out for is your on-disk block allocation
--
2.54.0
^ permalink raw reply related
* [PATCH 4/5] docs: fix repeated prepositions across documentation
From: Adrien Reynard @ 2026-05-08 16:38 UTC (permalink / raw)
To: Andrey Ryabinin, Alexander Potapenko, Andrey Konovalov,
Dmitry Vyukov, Vincenzo Frascino, Jonathan Corbet, Shuah Khan,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Richard Weinberger, Anton Ivanov, Johannes Berg,
open list:KASAN, open list:DOCUMENTATION PROCESS,
open list:DOCUMENTATION, open list,
open list:NETWORKING [GENERAL], open list:USER-MODE LINUX (UML)
Cc: Adrien Reynard
Signed-off-by: Adrien Reynard <reynard.adrien.08@gmail.com>
---
Documentation/dev-tools/kasan.rst | 2 +-
Documentation/networking/switchdev.rst | 2 +-
Documentation/virt/uml/user_mode_linux_howto_v2.rst | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
index 4968b2aa60c8..3a8bd40ad905 100644
--- a/Documentation/dev-tools/kasan.rst
+++ b/Documentation/dev-tools/kasan.rst
@@ -392,7 +392,7 @@ reserved to tag freed memory regions.
If the hardware does not support MTE (pre ARMv8.5), Hardware Tag-Based KASAN
will not be enabled. In this case, all KASAN boot parameters are ignored.
-Note that enabling CONFIG_KASAN_HW_TAGS always results in in-kernel TBI being
+Note that enabling CONFIG_KASAN_HW_TAGS always results in-kernel TBI being
enabled. Even when ``kasan.mode=off`` is provided or when the hardware does not
support MTE (but supports TBI).
diff --git a/Documentation/networking/switchdev.rst b/Documentation/networking/switchdev.rst
index 2966b7122f05..948bce44ca9b 100644
--- a/Documentation/networking/switchdev.rst
+++ b/Documentation/networking/switchdev.rst
@@ -162,7 +162,7 @@ The switchdev driver can know a particular port's position in the topology by
monitoring NETDEV_CHANGEUPPER notifications. For example, a port moved into a
bond will see its upper master change. If that bond is moved into a bridge,
the bond's upper master will change. And so on. The driver will track such
-movements to know what position a port is in in the overall topology by
+movements to know what position a port is in the overall topology by
registering for netdevice events and acting on NETDEV_CHANGEUPPER.
L2 Forwarding Offload
diff --git a/Documentation/virt/uml/user_mode_linux_howto_v2.rst b/Documentation/virt/uml/user_mode_linux_howto_v2.rst
index c37e8e594d12..7b08738c30aa 100644
--- a/Documentation/virt/uml/user_mode_linux_howto_v2.rst
+++ b/Documentation/virt/uml/user_mode_linux_howto_v2.rst
@@ -1092,7 +1092,7 @@ be formatted as plain text.
Developing always goes hand in hand with debugging. First of all,
you can always run UML under gdb and there will be a whole section
-later on on how to do that. That, however, is not the only way to
+later on how to do that. That, however, is not the only way to
debug a Linux kernel. Quite often adding tracing statements and/or
using UML specific approaches such as ptracing the UML kernel process
are significantly more informative.
--
2.54.0
^ permalink raw reply related
* [PATCH 3/5] docs: fix repeated word 'as' in dax-hv-api
From: Adrien Reynard @ 2026-05-08 16:38 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, open list:DOCUMENTATION, open list
Cc: Adrien Reynard
Signed-off-by: Adrien Reynard <reynard.adrien.08@gmail.com>
---
Documentation/arch/sparc/oradax/dax-hv-api.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Documentation/arch/sparc/oradax/dax-hv-api.txt b/Documentation/arch/sparc/oradax/dax-hv-api.txt
index ef1a4c2bf08b..297515ceb697 100644
--- a/Documentation/arch/sparc/oradax/dax-hv-api.txt
+++ b/Documentation/arch/sparc/oradax/dax-hv-api.txt
@@ -485,7 +485,7 @@ Offset Size Field Description
the virtual machine to use when accessing this data stream
(checking is only guaranteed to be performed when using API
version 1.1 and later). If using a virtual address, this field will
- be used as as primary input address bits [59:56].
+ be used as primary input address bits [59:56].
[55:0] Primary input address bits [55:0]. Address type is determined
by CCB header.
24 8 Data Access Control
@@ -576,7 +576,7 @@ Offset Size Field Description
the virtual machine to use when accessing this data stream
(checking is only guaranteed to be performed when using API
version 1.1 and later). If using a virtual address, this field will
- be used as as symbol table address bits [59:56].
+ be used as symbol table address bits [59:56].
[55:4] Symbol table address bits [55:4]. Address type is determined
by CCB header.
[3:0] Symbol table version
@@ -815,7 +815,7 @@ Offset Size Field Description
the virtual machine to use when accessing this data stream
(checking is only guaranteed to be performed when using API
version 1.1 and later). If using a virtual address, this field will
- be used as as bit table address bits [59:56]
+ be used as bit table address bits [59:56]
[55:4] Bit table address bits [55:4]. Address type is determined by
CCB header. Address must be 64-byte aligned (CCB version
0) or 16-byte aligned (CCB version 1).
--
2.54.0
^ permalink raw reply related
* [PATCH 2/5] docs: fix repeated word 'that' across documentation
From: Adrien Reynard @ 2026-05-08 16:37 UTC (permalink / raw)
To: Paul E. McKenney, Frederic Weisbecker, Neeraj Upadhyay,
Joel Fernandes, Josh Triplett, Boqun Feng, Uladzislau Rezki,
Steven Rostedt, Mathieu Desnoyers, Lai Jiangshan, Zqiang,
Jonathan Corbet, Shuah Khan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, David Howells,
Paulo Alcantara, Masami Hiramatsu,
open list:READ-COPY UPDATE (RCU), open list:DOCUMENTATION,
open list, open list:DRIVER CORE, KOBJECTS, DEBUGFS AND SYSFS,
open list:FILESYSTEMS [NETFS LIBRARY],
open list:FILESYSTEMS [NETFS LIBRARY], open list:TRACING
Cc: Adrien Reynard
Signed-off-by: Adrien Reynard <reynard.adrien.08@gmail.com>
---
Documentation/RCU/rcu.rst | 2 +-
Documentation/driver-api/driver-model/overview.rst | 2 +-
Documentation/filesystems/netfs_library.rst | 2 +-
Documentation/trace/histogram-design.rst | 2 +-
Documentation/trace/histogram.rst | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Documentation/RCU/rcu.rst b/Documentation/RCU/rcu.rst
index bf6617b330a7..320ad3292b75 100644
--- a/Documentation/RCU/rcu.rst
+++ b/Documentation/RCU/rcu.rst
@@ -32,7 +32,7 @@ Frequently Asked Questions
Just as with spinlocks, RCU readers are not permitted to
block, switch to user-mode execution, or enter the idle loop.
Therefore, as soon as a CPU is seen passing through any of these
- three states, we know that that CPU has exited any previous RCU
+ three states, we know that CPU has exited any previous RCU
read-side critical sections. So, if we remove an item from a
linked list, and then wait until all CPUs have switched context,
executed in user mode, or executed in the idle loop, we can
diff --git a/Documentation/driver-api/driver-model/overview.rst b/Documentation/driver-api/driver-model/overview.rst
index b3f447bf9f07..c1966d506d55 100644
--- a/Documentation/driver-api/driver-model/overview.rst
+++ b/Documentation/driver-api/driver-model/overview.rst
@@ -55,7 +55,7 @@ struct pci_dev now looks like this::
Note first that the struct device dev within the struct pci_dev is
statically allocated. This means only one allocation on device discovery.
-Note also that that struct device dev is not necessarily defined at the
+Note also that struct device dev is not necessarily defined at the
front of the pci_dev structure. This is to make people think about what
they're doing when switching between the bus driver and the global driver,
and to discourage meaningless and incorrect casts between the two.
diff --git a/Documentation/filesystems/netfs_library.rst b/Documentation/filesystems/netfs_library.rst
index ddd799df6ce3..4033de4535ac 100644
--- a/Documentation/filesystems/netfs_library.rst
+++ b/Documentation/filesystems/netfs_library.rst
@@ -626,7 +626,7 @@ A number of members are available for access/use by the filesystem:
These are set by the filesystem or the cache in ->prepare_read() or
->prepare_write() for each subrequest to indicate the maximum number of
- bytes and, optionally, the maximum number of segments (if not 0) that that
+ bytes and, optionally, the maximum number of segments (if not 0) that
subrequest can support.
* ``submit_extendable_to``
diff --git a/Documentation/trace/histogram-design.rst b/Documentation/trace/histogram-design.rst
index e92f56ebd0b5..949bbfdb0f16 100644
--- a/Documentation/trace/histogram-design.rst
+++ b/Documentation/trace/histogram-design.rst
@@ -738,7 +738,7 @@ creates its own variable, wakeup_lat, but nothing yet uses it::
Looking at the sched_waking 'hist_debug' output, in addition to the
normal key and value hist_fields, in the val fields section we see a
-field with the HIST_FIELD_FL_VAR flag, which indicates that that field
+field with the HIST_FIELD_FL_VAR flag, which indicates that field
represents a variable. Note that in addition to the variable name,
contained in the var.name field, it includes the var.idx, which is the
index into the tracing_map_elt.vars[] array of the actual variable
diff --git a/Documentation/trace/histogram.rst b/Documentation/trace/histogram.rst
index 340bcb5099e7..5b303fabdf32 100644
--- a/Documentation/trace/histogram.rst
+++ b/Documentation/trace/histogram.rst
@@ -1700,7 +1700,7 @@ to that rule is that any variable used in an expression is essentially
'read-once' - once it's used by an expression in a subsequent event,
it's reset to its 'unset' state, which means it can't be used again
unless it's set again. This ensures not only that an event doesn't
-use an uninitialized variable in a calculation, but that that variable
+use an uninitialized variable in a calculation, but that variable
is used only once and not for any unrelated subsequent match.
The basic syntax for saving a variable is to simply prefix a unique
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3 2/2] hwmon: add AMD Promontory 21 xHCI temperature sensor support
From: Mario Limonciello @ 2026-05-08 16:27 UTC (permalink / raw)
To: Jihong Min, Guenter Roeck, Jihong Min
Cc: Greg Kroah-Hartman, Mathias Nyman, Jonathan Corbet, Shuah Khan,
Basavaraj Natikar, linux-usb, linux-hwmon, linux-doc, linux-pci,
linux-kernel
In-Reply-To: <198ae20f-49c9-4f81-87e2-e16e81053f08@icloud.com>
On 5/8/26 09:21, Jihong Min wrote:
>> Yes.
>>
>> Please note that you keep top-posting. I don't mind that much, but
>> top-posting is (sometimes strongly) discouraged for linux kernel
>> discussions.
>
> Sorry, this is my first kernel contribution and I was not familiar with the
> mailing list convention around top-posting. I will avoid top-posting and
> use
> inline replies from now on.
Another thing to mention is that you are going too fast between patch
versions. All your patches show up in a ton of people's inboxes.
It's great you've gotten feedback on them but I suggest you give it a
few days or a week between versions to gather more feedback.
If you haven't already; you should take a look at what Sahiko finds on
your patches too. Be sure to look at the feedback critically and take
it with a grain of salt; but it often finds a few nuggets that are
worthwhile to consider.
Here is the Sahiko link for v4 you can review if you weren't already
looking at it.
https://sashiko.dev/#/patchset/20260508143910.14673-1-hurryman2212%40gmail.com
>
> I have addressed the review comments in v4, including runtime PM behavior,
> temp1_label removal, -ENODATA return, the PROM21-specific xHCI PCI glue
> split,
> and making the PROM21 PCI glue built-in only when enabled. I also
> adopted the
> naming scheme discussed above:
>
> - drivers/hwmon/prom21-xhci.c
> - CONFIG_SENSORS_PROM21_XHCI
> - hwmon name: prom21_xhci
>
> I will send v4 now.
>
> Sincerely,
> Jihong Min
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Mathieu Desnoyers @ 2026-05-08 16:26 UTC (permalink / raw)
To: Sasha Levin
Cc: Joshua Peisach, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <af4N9_YOgDQziRCR@laps>
On 2026-05-08 12:23, Sasha Levin wrote:
> On Fri, May 08, 2026 at 12:18:28PM -0400, Mathieu Desnoyers wrote:
>> On 2026-05-08 12:13, Sasha Levin wrote:
>> [...]
>>>> One possible approach to prevent "footgun" type of killswitch use would
>>>> be to first apply a statistics collection killswitch handler that does
>>>> not change the behavior: it checks whether the target function is
>>>> invoked at all on the system for a given period of time. Then it
>>>> applies
>>>> the killswitch if it was not invoked during that period. Overall
>>>> sequence:
>>>>
>>>> - pre-soak killswitch for e.g. 30s, checking whether the function is
>>>> invoked at all. (period would be user-configurable)
>>>> - if no calls were detected, engage killswitch, else report failure to
>>>> the user.
>>>>
>>>> This should prevent footguns such as trying to killswitch fork, malloc
>>>> or other core functions which are inherently required.
>>>
>>> Why not just use our good old tracing infra? Set tracepoints where
>>> ever you
>>> want, collect any data you might need, and engage the killswitch when
>>> you're
>>> happy with the data you have?
>>>
>>> It feels a bit weird adding something like this into killswitch.
>>
>> It really depends on whether you want to include some basic safety nets
>> directly within killswitch, or leave that entirely to the end user.
>>
>> I don't have a strong opinion either way. I was just pointing out the
>> feasibility of a pre-soak sanity check before applying the killswitch.
>
> What about doing this with some userspace tooling (maybe under scripts/) to
> facilitate this?
>
That could work, although it requires additional infrastructure to be in
place (tracing) on production environments, which may not be the case
everywhere. In comparison, if the sanity check is done within
killswitch, you only need killswitch and kprobe to be compiled into
the production system, which is more lightweight.
Thanks,
Mathieu
--
Mathieu Desnoyers
EfficiOS Inc.
https://www.efficios.com
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-08 16:23 UTC (permalink / raw)
To: Mathieu Desnoyers
Cc: Joshua Peisach, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <b14d54c0-0fe8-4d98-a2ea-2dd830cd5869@efficios.com>
On Fri, May 08, 2026 at 12:18:28PM -0400, Mathieu Desnoyers wrote:
>On 2026-05-08 12:13, Sasha Levin wrote:
>[...]
>>>One possible approach to prevent "footgun" type of killswitch use would
>>>be to first apply a statistics collection killswitch handler that does
>>>not change the behavior: it checks whether the target function is
>>>invoked at all on the system for a given period of time. Then it applies
>>>the killswitch if it was not invoked during that period. Overall
>>>sequence:
>>>
>>>- pre-soak killswitch for e.g. 30s, checking whether the function is
>>> invoked at all. (period would be user-configurable)
>>>- if no calls were detected, engage killswitch, else report failure to
>>> the user.
>>>
>>>This should prevent footguns such as trying to killswitch fork, malloc
>>>or other core functions which are inherently required.
>>
>>Why not just use our good old tracing infra? Set tracepoints where ever you
>>want, collect any data you might need, and engage the killswitch
>>when you're
>>happy with the data you have?
>>
>>It feels a bit weird adding something like this into killswitch.
>
>It really depends on whether you want to include some basic safety nets
>directly within killswitch, or leave that entirely to the end user.
>
>I don't have a strong opinion either way. I was just pointing out the
>feasibility of a pre-soak sanity check before applying the killswitch.
What about doing this with some userspace tooling (maybe under scripts/) to
facilitate this?
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH net-next v3 6/8] selftests: drv-net: refactor devmem command builders into lib module
From: Bobby Eshleman @ 2026-05-08 16:19 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan, dw,
mohsin.bashr, willemb, jiang.kun2, xu.xin16, wang.yaxin, netdev,
linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <af37Eoq2TLjhI7kx@devvm7509.cco0.facebook.com>
On Fri, May 08, 2026 at 08:03:11AM -0700, Stanislav Fomichev wrote:
> On 05/07, Bobby Eshleman wrote:
> > From: Bobby Eshleman <bobbyeshleman@meta.com>
> >
> > Adding netkit-based devmem tests is a straight-forward copy of devmem
> > test commands plus some args for the nk cases, so this patch breaks out
> > these command builders into helpers used by both.
> >
> > Though we tried to avoid libraries to avoid increasing the barrier of
> > entry/complexity (see selftests/drivers/net/README.md, section "Avoid
> > libraries and frameworks"), factoring out these functions seemed like
> > the lesser of two evils in this case of using the same commands, just
> > with slightly different args per environment.
> >
> > I experimented with just having all of the tests in the same file to
> > avoid having helpers in a library file, but because ksft_run() is
> > limited to a single call per file, and the new tests will require
> > different environments (NetDrvContEnv/NetDrvEpEnv), it would have been
> > necessary to have each test set up its own environment instead of
> > sharing one for the entire ksft_run() run. This came at the cost of
> > ballooning the test time (from under 5s to 30s on my test system), so to
> > strike a balance these tests were placed in separate files so they could
> > keep a shared environment across a single ksft_run() run shared across
> > all tests using the same env type (introduced in subsequent patches).
> >
> > The helpers work transparently with both plain and netkit environments
> > by inspecting cfg for netkit-specific attributes (netns, nk_queue,
> > etc...).
> >
> > Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
> > ---
>
> [..]
>
> > Changes in v4:
>
> This is a v3, but you already have changes for v4 :-p
oops, will change on respin.
Thanks,
Bobby
^ permalink raw reply
* Re: [PATCH net-next v3 3/8] net: devmem: support TX over NETMEM_TX_NO_DMA devices
From: Bobby Eshleman @ 2026-05-08 16:19 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan, dw,
mohsin.bashr, willemb, jiang.kun2, xu.xin16, wang.yaxin, netdev,
linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <af3593dYeiEeMzC2@devvm7509.cco0.facebook.com>
On Fri, May 08, 2026 at 08:01:17AM -0700, Stanislav Fomichev wrote:
> On 05/07, Bobby Eshleman wrote:
> > From: Bobby Eshleman <bobbyeshleman@meta.com>
> >
> > When a netkit virtual device leases queues from a physical NIC, devmem
> > TX bindings created on the netkit device must still result in the dmabuf
> > being mapped for dma by the physical device. This patch accomplishes
> > this by teaching the bind handler to search for the underlying
> > DMA-capable device by looking it up via leased rx queues. The function
> > netdev_find_netmem_tx_dev(), used for finding the underlying DMA-capable
> > device, can be extended to support other non-netkit NETMEM_TX_NO_DMA
> > devices in the future if needed.
> >
> > Additionally, this patch extends validate_xmit_unreadable_skb() to
> > support the netkit case, where the skb is validated twice: once on the
> > netkit guest device and again on the physical NIC after BPF redirect or
> > ip forwarding.
> >
> > Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
> > ---
> > Changes in v3:
> > - Fix validate_xmit_unreadable_skb() bug for non-devmem
> > unreadable niovs (should not be dropped)
> > - Major simplification of validate_xmit_unreadable_skb()
> > - Fix prematurely released lock in bind-tx handler (Jakub)
> >
> > Changes in v2:
> > - In validate_xmit_unreadable_skb() to check netmem_tx mode before
> > inspecting frags (Jakub)
> > - Lock bind_dev around netdev_queue_get_dma_dev() when bind_dev !=
> > netdev to fix lockdep (Sashiko)
> > ---
> > net/core/dev.c | 3 +++
> > net/core/devmem.c | 6 +++--
> > net/core/devmem.h | 9 ++++++--
> > net/core/netdev-genl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++----
> > 4 files changed, 72 insertions(+), 9 deletions(-)
> >
> > diff --git a/net/core/dev.c b/net/core/dev.c
> > index fbe4c328a367..268417c9ef22 100644
> > --- a/net/core/dev.c
> > +++ b/net/core/dev.c
> > @@ -3999,6 +3999,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
> > if (dev->netmem_tx == NETMEM_TX_NONE)
> > goto out_free;
> >
> > + if (dev->netmem_tx == NETMEM_TX_NO_DMA)
> > + goto out;
> > +
>
> Since this is a good case, maybe fold it into skb_frags_readable check above?
>
> if (likely(skb_frags_readable() || netmem_tx == NETMEM_TX_NO_DMA))
>
> Otherwise it's a bit confusing to have:
>
> if (xxx)
> goto out;
> if (yyy)
> goto out_free;
> if (zzz)
> goto out;
>
> (or, reorder to be out/out/out_free)
>
> Acked-by: Stanislav Fomichev <sdf@fomichev.me>
Makes sense, will use the combined conditional.
Best,
Bobby
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Mathieu Desnoyers @ 2026-05-08 16:18 UTC (permalink / raw)
To: Sasha Levin
Cc: Joshua Peisach, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <af4LvqtLu-yeor-v@laps>
On 2026-05-08 12:13, Sasha Levin wrote:
[...]
>> One possible approach to prevent "footgun" type of killswitch use would
>> be to first apply a statistics collection killswitch handler that does
>> not change the behavior: it checks whether the target function is
>> invoked at all on the system for a given period of time. Then it applies
>> the killswitch if it was not invoked during that period. Overall
>> sequence:
>>
>> - pre-soak killswitch for e.g. 30s, checking whether the function is
>> invoked at all. (period would be user-configurable)
>> - if no calls were detected, engage killswitch, else report failure to
>> the user.
>>
>> This should prevent footguns such as trying to killswitch fork, malloc
>> or other core functions which are inherently required.
>
> Why not just use our good old tracing infra? Set tracepoints where ever you
> want, collect any data you might need, and engage the killswitch when
> you're
> happy with the data you have?
>
> It feels a bit weird adding something like this into killswitch.
It really depends on whether you want to include some basic safety nets
directly within killswitch, or leave that entirely to the end user.
I don't have a strong opinion either way. I was just pointing out the
feasibility of a pre-soak sanity check before applying the killswitch.
Thanks,
Mathieu
--
Mathieu Desnoyers
EfficiOS Inc.
https://www.efficios.com
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Sasha Levin @ 2026-05-08 16:13 UTC (permalink / raw)
To: Mathieu Desnoyers
Cc: Joshua Peisach, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <af4Fwxndqv2knLov@localhost.localdomain>
On Fri, May 08, 2026 at 11:48:19AM -0400, Mathieu Desnoyers wrote:
>On 08-May-2026 11:40:54 AM, Joshua Peisach wrote:
>> On Thu May 7, 2026 at 3:05 AM EDT, Sasha Levin wrote:
>> >
>> > For many such issues the simplest mitigation is to stop calling the buggy
>> > function. Killswitch provides that. An admin writes:
>> >
>> > echo "engage af_alg_sendmsg -1" \
>> > > /sys/kernel/security/killswitch/control
>> >
>> > After this, af_alg_sendmsg() returns -EPERM on every call without
>> > running its body. The mitigation takes effect immediately, and is dropped on
>> > the next reboot.
>> >
>> > A lot of recent kernel issues sit in code paths most installs only have enabled
>> > to support a relative minority of users: AF_ALG, ksmbd, nf_tables, vsock, ax25,
>> > and friends.
>> >
>> > For most users, the cost of "this socket family stops working for the day" is
>> > much smaller than the cost of running a known vulnerable kernel until the fix
>> > land.
>>
>> I like the concept - but is there any way to ensure that important functions
>> like malloc aren't being patched? Or some way to make sure that what is being
>> patched will not kill the entire kernel?
>>
>> I know this probably boils down to "operator has to use it correctly", but just
>> wondering if this was considered.
So I thought about it, and realized it would be very difficult to decide on
this list of functions, and even more difficult to keep it maintained over time
(functions get added, removed, refactored, ...).
>I like the idea too, it should help prevent a few devops burnouts in the
>coming weeks.
>
>One possible approach to prevent "footgun" type of killswitch use would
>be to first apply a statistics collection killswitch handler that does
>not change the behavior: it checks whether the target function is
>invoked at all on the system for a given period of time. Then it applies
>the killswitch if it was not invoked during that period. Overall
>sequence:
>
>- pre-soak killswitch for e.g. 30s, checking whether the function is
> invoked at all. (period would be user-configurable)
>- if no calls were detected, engage killswitch, else report failure to
> the user.
>
>This should prevent footguns such as trying to killswitch fork, malloc
>or other core functions which are inherently required.
Why not just use our good old tracing infra? Set tracepoints where ever you
want, collect any data you might need, and engage the killswitch when you're
happy with the data you have?
It feels a bit weird adding something like this into killswitch.
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH net-next v3 1/8] net: convert netmem_tx flag to enum
From: Bobby Eshleman @ 2026-05-08 16:11 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan, dw,
mohsin.bashr, willemb, jiang.kun2, xu.xin16, wang.yaxin, netdev,
linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <af35ekIjYDXIDWVR@devvm7509.cco0.facebook.com>
On Fri, May 08, 2026 at 07:56:42AM -0700, Stanislav Fomichev wrote:
> On 05/07, Bobby Eshleman wrote:
> > From: Bobby Eshleman <bobbyeshleman@meta.com>
> >
> > Devices that support netmem TX previously set dev->netmem_tx = true.
> > This was checked in validate_xmit_unreadable_skb() to drop unreadable
> > skbs (skbs with dmabuf-backed frags) before they reach drivers that
> > would mishandle them or devices that would not have the iommu mappings
> > for them.
> >
> > A subsequent patch will introduce a third state for virtual devices
> > that forward unreadable skbs without ever performing DMA on them. To
> > prepare for that, convert the boolean dev->netmem_tx into an enum:
> >
> > NETMEM_TX_NONE - no netmem TX support (drop unreadable skbs)
> > NETMEM_TX_DMA - full support, device does DMA
> >
> > Update the existing NIC drivers (bnxt, gve, mlx5, fbnic) and the
> > validators in net/core to use the new enum. No functional change.
> >
> > Acked-by: Harshitha Ramamurthy <hramamurthy@google.com>
> > Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
> > ---
> > Changes in v3:
> > - Split NO_DMA changes into subsequent commit (Jakub)
> > - Move !netdev->netmem_tx -> netdev->netmem_tx ==
> > NETMEM_TX_NONE conversions to this patch (Jakub)
> >
> > Changes in v2:
> > - Squash driver conversion patches (2-5) into patch 1 (Jakub)
> > ---
> > Documentation/networking/netmem.rst | 5 ++++-
> > Documentation/translations/zh_CN/networking/netmem.rst | 4 +++-
> > drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +-
> > drivers/net/ethernet/google/gve/gve_main.c | 2 +-
> > drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 +-
> > drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 2 +-
> > include/linux/netdevice.h | 8 +++++++-
> > net/core/dev.c | 2 +-
> > net/core/netdev-genl.c | 2 +-
> > 9 files changed, 20 insertions(+), 9 deletions(-)
> >
> > diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst
> > index b63aded46337..5ccadba4f373 100644
> > --- a/Documentation/networking/netmem.rst
> > +++ b/Documentation/networking/netmem.rst
> > @@ -95,4 +95,7 @@ Driver TX Requirements
> > netdev@, or reach out to the maintainers and/or almasrymina@google.com for
> > help adding the netmem API.
> >
> > -2. Driver should declare support by setting `netdev->netmem_tx = true`
> > +2. Driver should declare support by setting `netdev->netmem_tx` to the
> > + appropriate mode:
> > +
> > + - `NETMEM_TX_DMA`: for physical devices that perform DMA.
> > diff --git a/Documentation/translations/zh_CN/networking/netmem.rst b/Documentation/translations/zh_CN/networking/netmem.rst
> > index fe351a240f02..9c84423b7528 100644
> > --- a/Documentation/translations/zh_CN/networking/netmem.rst
> > +++ b/Documentation/translations/zh_CN/networking/netmem.rst
> > @@ -89,4 +89,6 @@ dma-mapping API 去处理。
> > 使用某个还不存在的 netmem API,你可以自行添加并提交到 netdev@,也可以联系维护
> > 人员或者发送邮件至 almasrymina@google.com 寻求帮助。
> >
> > -2. 驱动程序应通过设置 netdev->netmem_tx = true 来表明自身支持 netmem 功能。
> > +2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式:
> > +
> > + - `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。
> > diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> > index 8c55874f44ca..ed9c22dc4a5a 100644
> > --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> > +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
> > @@ -17120,7 +17120,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
> > dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops_unsupp;
> > if (BNXT_SUPPORTS_QUEUE_API(bp))
> > dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops;
> > - dev->netmem_tx = true;
> > + dev->netmem_tx = NETMEM_TX_DMA;
> >
> > rc = register_netdev(dev);
> > if (rc)
> > diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
> > index 424d973c97f2..dd2b8f087163 100644
> > --- a/drivers/net/ethernet/google/gve/gve_main.c
> > +++ b/drivers/net/ethernet/google/gve/gve_main.c
> > @@ -2894,7 +2894,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> > goto abort_with_wq;
> >
> > if (!gve_is_gqi(priv) && !gve_is_qpl(priv))
> > - dev->netmem_tx = true;
> > + dev->netmem_tx = NETMEM_TX_DMA;
> >
> > err = register_netdev(dev);
> > if (err)
> > diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
> > index 5a46870c4b74..fc49aae38807 100644
> > --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
> > +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
> > @@ -5924,7 +5924,7 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev)
> >
> > netdev->priv_flags |= IFF_UNICAST_FLT;
> >
> > - netdev->netmem_tx = true;
> > + netdev->netmem_tx = NETMEM_TX_DMA;
> >
> > netif_set_tso_max_size(netdev, GSO_MAX_SIZE);
> > mlx5e_set_xdp_feature(priv);
> > diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
> > index c406a3b56b37..138e522ef9b9 100644
> > --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
> > +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
> > @@ -752,7 +752,7 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)
> > netdev->netdev_ops = &fbnic_netdev_ops;
> > netdev->stat_ops = &fbnic_stat_ops;
> > netdev->queue_mgmt_ops = &fbnic_queue_mgmt_ops;
> > - netdev->netmem_tx = true;
> > + netdev->netmem_tx = NETMEM_TX_DMA;
> >
> > fbnic_set_ethtool_ops(netdev);
> >
> > diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> > index 0e1e581efc5a..580bccb118a0 100644
> > --- a/include/linux/netdevice.h
> > +++ b/include/linux/netdevice.h
> > @@ -1788,6 +1788,11 @@ enum netdev_stat_type {
> > NETDEV_PCPU_STAT_DSTATS, /* struct pcpu_dstats */
> > };
> >
> > +enum netmem_tx_mode {
> > + NETMEM_TX_NONE, /* no netmem TX support */
> > + NETMEM_TX_DMA, /* DMA-capable netmem TX (real HW) */
> > +};
> > +
> > enum netdev_reg_state {
> > NETREG_UNINITIALIZED = 0,
> > NETREG_REGISTERED, /* completed register_netdevice */
> > @@ -1809,7 +1814,8 @@ enum netdev_reg_state {
> > * @lltx: device supports lockless Tx. Deprecated for real HW
> > * drivers. Mainly used by logical interfaces, such as
> > * bonding and tunnels
> > - * @netmem_tx: device support netmem_tx.
> > + * @netmem_tx: device netmem TX mode (NETMEM_TX_NONE or
> > + * NETMEM_TX_DMA).
>
>
> nit: if you happen to repost, listing enum values here seems too much?
>
> "device netmem TX mode" should be enough
Will do!
Thanks,
Bobby
^ permalink raw reply
* Re: [PATCH v2 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Willy Tarreau @ 2026-05-08 16:07 UTC (permalink / raw)
To: Joshua Peisach
Cc: Greg KH, Linus Torvalds, leon, security, Jonathan Corbet, skhan,
workflows, linux-doc, linux-kernel
In-Reply-To: <DIDF0Y3YTGL4.31WQZEPS1JOQW@ubuntu.com>
On Fri, May 08, 2026 at 11:54:32AM -0400, Joshua Peisach wrote:
> On Fri May 8, 2026 at 11:35 AM EDT, Greg KH wrote:
> > On Wed, May 06, 2026 at 08:46:07AM -0700, Linus Torvalds wrote:
> > > [ Coming back to this after a week of trying to clean up the disaster
> > > that is my inbox after the merge window ]
> > >
> > > On Sun, 3 May 2026 at 04:35, Willy Tarreau <w@1wt.eu> wrote:
> > > >
> > > > The use of automated tools to find bugs in random locations of the kernel
> > > > induces a raise of security reports even if most of them should just be
> > > > reported as regular bugs. This patch is an attempt at drawing a line
> > > > between what qualifies as a security bug and what does not, hoping to
> > > > improve the situation and ease decision on the reporter's side.
> > >
> > > I actually think we may want to go further than this.
> > >
> > > I think we should simply make it a rule that "a 'security' bug that is
> > > found by AI is public".
>
> Whether my opinion is cared about or not, I feel it should be put in here:
>
> Yes, *in theory* the bug is public. Anyone can find it. But just like bugs
> sitting in open source code repositories, anyone can look for if it they try.
>
> The only difference is that a LLM is making it more apparent and noticable
> to people, if you ask it to.
>
> The choice to then decide "therefore we can disclose it immediately", in my
> opinion, is not great. Because then you are bringing attention to a bug that
> nobody, or at most, relatively few people knew about (even in small circles)
> to a broader audience.
It's no longer needed, please trust us. Last week we've seen about one
duplicate every day and some bugs had up to 2 duplicates. I tried myself
to ask my *local* LLM to find bugs in a certain class over the whole net
tree, and it found one of the recently pubished ones without me having to
give it any hint about this.
Really, these days LLMs can swallow huge amounts of data and correlate
complex patterns very easily over an immense context. You don't need to
ask them to analyze a patch anymore nor to work on this or that file.
An issue found by an LLM is just a proof that this issue CAN BE FOUND by
an LLM, thus a good indication that someone else will find it, and very
likely that someone else might already be using it.
> Take Dirty Frag - even though the embargo is said to have been broken, and
> all parties agreed to release the disclosure, it was put on GitHub. Of course,
> information that is public, is public. But putting it on GitHub and then
> buying the domain dirtyfrag.io makes it easy to bring attention to the bug
> that was disclosed **with no patch or CVE.**
It's really not how it works nor how it worked.
> Even if the mitigation is "just disable the module", I still think that by
> giving up the embargo entirely, we are creating more attention, and more
> opportunity for exploitation. Even if it's a PoC and not an exploit for
> malicious purposes.
What is important is that we insist on no longer sharing PoCs publicly.
This will slow down script kiddies (who are the ones doing the most
damage because they don't need the bug for their business yet they cause
harm using it). Criminals are probably already playing with it and might
have been for weeks or months already.
> > After the past 2 weeks, and the past 2 months, I am going to violently
> > agree with you here. We've seen so many "duplicate" bug reports it's
> > not funny. All of the modern LLMs are feeding the output back into the
> > model for future runs, which makes the data totally public. Even if
> > not, the output is being monitored by external companies at the very
> > least.
> >
>
> I think that's more "unresponsible disclosure" - maybe there is some way
> that LLM emails can be filtered?
No :-( If you saw the flood we're receiving on s@k.o, feels like
taking a shower under the niagara falls. Sometimes we just say "trim
this and repost it, it's too long we can't read it".
> And again, yes, the data is being trained. But **you have to look for it.**.
> It is still a needle in a haystack, but it's not a black hole absorbing
> said haystack.
No, really not at all. Not since the last month at least.
> > > So why should be consider it special and have it be on the security list?
> >
> > I don't think we should anymore.
> >
> > Yes, having a full reproducer in public is not good, but the general
> > "this is a bug" comments we should start redirecting to public lists
> > more. That's the only way we are going to handle this influx as our
> > "normal" bug workflow works very well, especially when it comes with a
> > fix, as these LLM tools can provide very easily.
> >
>
> Could this at least be temporary? There are only a finite number of bugs
> that can exist in a codebase.
We regularly update the doc based on circumstances so we don't need to
care now. It looks like AI-based reports consume the doc and this will
make them less painful to maintainers, which is already a great thing.
Willy
^ permalink raw reply
* Re: [PATCH v2 2/3] Documentation: security-bugs: explain what is and is not a security bug
From: Willy Tarreau @ 2026-05-08 15:59 UTC (permalink / raw)
To: Greg KH
Cc: Linus Torvalds, leon, security, Jonathan Corbet, skhan, workflows,
linux-doc, linux-kernel
In-Reply-To: <2026050801-semifinal-expulsion-9af6@gregkh>
On Fri, May 08, 2026 at 05:35:39PM +0200, Greg KH wrote:
> On Wed, May 06, 2026 at 08:46:07AM -0700, Linus Torvalds wrote:
> > [ Coming back to this after a week of trying to clean up the disaster
> > that is my inbox after the merge window ]
> >
> > On Sun, 3 May 2026 at 04:35, Willy Tarreau <w@1wt.eu> wrote:
> > >
> > > The use of automated tools to find bugs in random locations of the kernel
> > > induces a raise of security reports even if most of them should just be
> > > reported as regular bugs. This patch is an attempt at drawing a line
> > > between what qualifies as a security bug and what does not, hoping to
> > > improve the situation and ease decision on the reporter's side.
> >
> > I actually think we may want to go further than this.
> >
> > I think we should simply make it a rule that "a 'security' bug that is
> > found by AI is public".
> >
> > Now, I may be influenced by that "my inbox is a disaster during the
> > merge window" thing, but I do think this is pretty fundamental: if
> > somebody finds a bug with more or less standard AI tools (ie we're not
> > talking magical special hardware and nation-state level efforts), then
> > that bug pretty much by definition IS NOT SECRET.
>
> After the past 2 weeks, and the past 2 months, I am going to violently
> agree with you here. We've seen so many "duplicate" bug reports it's
> not funny. All of the modern LLMs are feeding the output back into the
> model for future runs, which makes the data totally public. Even if
> not, the output is being monitored by external companies at the very
> least.
>
> > So why should be consider it special and have it be on the security list?
>
> I don't think we should anymore.
>
> Yes, having a full reproducer in public is not good, but the general
> "this is a bug" comments we should start redirecting to public lists
> more. That's the only way we are going to handle this influx as our
> "normal" bug workflow works very well, especially when it comes with a
> fix, as these LLM tools can provide very easily.
>
> So if this could be reworded somehow to reflect that, maybe?
What I'm trying to do is to make sure the reports don't flood just to
maintainers (some of whom never got a report, and getting an intimidating
one written by an LLM can be really painful). And in parallel we're trying
to limit public reports for non-AI. So I think the split point revolves
to:
- all bugs (AI and non-AI) affecting the threat model are security bugs,
but AI reports must be considered public as others will find them in
parallel (and we do know that pretty well now).
- if non-AI, send to maintainers and Cc: security, send all repros
you can share
- if AI, the report must be considered public so send to maintainers
and Cc: public lists AND always LKML, and never security@, and do
not send the repros publicly.
=> this reinforces the role of security@ to be for triage, coordination
and assitance to maintainers so that they're never left to themselves
(i.e. private bugs=maint+s@k.o; public bugs=maint+public list).
Also, I'll add "for AI, please see the points below" (the 3rd patch with
all the rules).
There remains a gray zone with the repros from AI tools (since they're
good at writing them). They should sent to maintainers only (no need to
involve s@k.o) but it requires a second message.
> But the "what is and is not a security bug" is a good thing overall. We
> need a solid definition of our threat model if for no other reason to
> keep me from having to write over and over "Once a driver is bound to
> the kernel, we trust the hardware"...
Over the last two weeks I felt like you needed a macro on your keyboard
that would post a link to that doc in lore!
Thanks,
Willy
^ permalink raw reply
* Re: [PATCH] killswitch: add per-function short-circuit mitigation primitive
From: Mathieu Desnoyers @ 2026-05-08 15:48 UTC (permalink / raw)
To: Joshua Peisach
Cc: Sasha Levin, corbet, akpm, skhan, linux-doc, linux-kernel,
linux-kselftest, gregkh
In-Reply-To: <DIDEQIFQF1EW.11CESAK4JL4PR@ubuntu.com>
On 08-May-2026 11:40:54 AM, Joshua Peisach wrote:
> On Thu May 7, 2026 at 3:05 AM EDT, Sasha Levin wrote:
> >
> > For many such issues the simplest mitigation is to stop calling the buggy
> > function. Killswitch provides that. An admin writes:
> >
> > echo "engage af_alg_sendmsg -1" \
> > > /sys/kernel/security/killswitch/control
> >
> > After this, af_alg_sendmsg() returns -EPERM on every call without
> > running its body. The mitigation takes effect immediately, and is dropped on
> > the next reboot.
> >
> > A lot of recent kernel issues sit in code paths most installs only have enabled
> > to support a relative minority of users: AF_ALG, ksmbd, nf_tables, vsock, ax25,
> > and friends.
> >
> > For most users, the cost of "this socket family stops working for the day" is
> > much smaller than the cost of running a known vulnerable kernel until the fix
> > land.
>
> I like the concept - but is there any way to ensure that important functions
> like malloc aren't being patched? Or some way to make sure that what is being
> patched will not kill the entire kernel?
>
> I know this probably boils down to "operator has to use it correctly", but just
> wondering if this was considered.
I like the idea too, it should help prevent a few devops burnouts in the
coming weeks.
One possible approach to prevent "footgun" type of killswitch use would
be to first apply a statistics collection killswitch handler that does
not change the behavior: it checks whether the target function is
invoked at all on the system for a given period of time. Then it applies
the killswitch if it was not invoked during that period. Overall
sequence:
- pre-soak killswitch for e.g. 30s, checking whether the function is
invoked at all. (period would be user-configurable)
- if no calls were detected, engage killswitch, else report failure to
the user.
This should prevent footguns such as trying to killswitch fork, malloc
or other core functions which are inherently required.
Thanks,
Mathieu
--
Mathieu Desnoyers
EfficiOS Inc.
http://www.efficios.com
^ permalink raw reply
* [PATCH v2 14/14] Documentation/userfaultfd: document RWP working set tracking
From: Kiryl Shutsemau (Meta) @ 2026-05-08 15:55 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team,
Kiryl Shutsemau (Meta)
In-Reply-To: <cover.1778254670.git.kas@kernel.org>
Add an admin-guide section covering UFFDIO_REGISTER_MODE_RWP:
- sync and async fault models;
- UFFDIO_RWPROTECT semantics;
- UFFD_FEATURE_RWP_ASYNC;
- UFFDIO_SET_MODE runtime mode flips.
It also covers typical VMM working-set-tracking workflow from detection
loop through sync-mode eviction and back to async.
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
---
Documentation/admin-guide/mm/userfaultfd.rst | 226 ++++++++++++++++++-
1 file changed, 220 insertions(+), 6 deletions(-)
diff --git a/Documentation/admin-guide/mm/userfaultfd.rst b/Documentation/admin-guide/mm/userfaultfd.rst
index 1e533639fd50..5ac4ae3dff1b 100644
--- a/Documentation/admin-guide/mm/userfaultfd.rst
+++ b/Documentation/admin-guide/mm/userfaultfd.rst
@@ -275,16 +275,16 @@ tracking and it can be different in a few ways:
- Dirty information will not get lost if the pte was zapped due to
various reasons (e.g. during split of a shmem transparent huge page).
- - Due to a reverted meaning of soft-dirty (page clean when uffd-wp bit
- set; dirty when uffd-wp bit cleared), it has different semantics on
- some of the memory operations. For example: ``MADV_DONTNEED`` on
+ - Due to a reverted meaning of soft-dirty (page clean when the uffd bit
+ is set; dirty when the uffd bit is cleared), it has different semantics
+ on some of the memory operations. For example: ``MADV_DONTNEED`` on
anonymous (or ``MADV_REMOVE`` on a file mapping) will be treated as
- dirtying of memory by dropping uffd-wp bit during the procedure.
+ dirtying of memory by dropping the uffd bit during the procedure.
The user app can collect the "written/dirty" status by looking up the
-uffd-wp bit for the pages being interested in /proc/pagemap.
+uffd bit for the pages being interested in /proc/pagemap.
-The page will not be under track of uffd-wp async mode until the page is
+The page will not be under track of userfaultfd-wp async mode until the page is
explicitly write-protected by ``ioctl(UFFDIO_WRITEPROTECT)`` with the mode
flag ``UFFDIO_WRITEPROTECT_MODE_WP`` set. Trying to resolve a page fault
that was tracked by async mode userfaultfd-wp is invalid.
@@ -307,6 +307,220 @@ transparent to the guest, we want that same address range to act as if it was
still poisoned, even though it's on a new physical host which ostensibly
doesn't have a memory error in the exact same spot.
+Read-Write Protection
+---------------------
+
+``UFFDIO_REGISTER_MODE_RWP`` enables read-write protection tracking on a
+memory range. It is similar to (but faster than) ``mprotect(PROT_NONE)``
+combined with a signal handler; unlike ``mprotect(PROT_NONE)``, RWP only
+traps accesses to *present* PTEs, so accesses to unpopulated addresses in a
+protected range fall through to the normal missing-page path. It uses the
+PROT_NONE hinting mechanism (same as NUMA balancing) to make pages
+inaccessible while keeping them resident in memory. Works on anonymous,
+shmem, and hugetlbfs memory.
+
+This is designed for VM memory managers that need to track the working set
+of guest memory for cold page eviction to tiered or remote storage.
+
+**Setup:**
+
+1. Open a userfaultfd and enable ``UFFD_FEATURE_RWP`` via ``UFFDIO_API``.
+ Optionally request ``UFFD_FEATURE_RWP_ASYNC`` as well — it requires
+ ``UFFD_FEATURE_RWP`` to be set in the same ``UFFDIO_API`` call.
+
+2. Register the guest memory range with ``UFFDIO_REGISTER_MODE_RWP``
+ (and ``UFFDIO_REGISTER_MODE_MISSING`` if evicted pages will need to be
+ fetched back from storage).
+
+**Feature availability:**
+
+RWP is built on top of two kernel primitives: a spare PTE bit owned by
+userfaultfd (``CONFIG_HAVE_ARCH_USERFAULTFD_WP``) and arch support for
+present-but-inaccessible PTEs (``CONFIG_ARCH_HAS_PTE_PROTNONE``). When both
+are available on a 64-bit kernel, the build selects
+``CONFIG_USERFAULTFD_RWP=y`` and the ``VM_UFFD_RWP`` VMA flag becomes
+available.
+
+``UFFD_FEATURE_RWP`` and ``UFFD_FEATURE_RWP_ASYNC`` are masked out of the
+features returned by ``UFFDIO_API`` when the running kernel or architecture
+cannot support them — for example 32-bit kernels (where ``VM_UFFD_RWP`` is
+unavailable), kernels built without ``CONFIG_USERFAULTFD_RWP``, and
+architectures whose ptes cannot carry the uffd bit at runtime (e.g. riscv
+without the ``SVRSW60T59B`` extension). ``UFFDIO_API`` does not fail;
+unsupported bits are simply absent from ``uffdio_api.features`` on return.
+VMMs should inspect the returned ``features`` after ``UFFDIO_API`` and fall
+back to another tracking method when RWP is unavailable.
+
+**Protecting and Unprotecting:**
+
+Use ``UFFDIO_RWPROTECT`` to protect or unprotect a range, mirroring the
+``UFFDIO_WRITEPROTECT`` interface::
+
+ struct uffdio_rwprotect rwp = {
+ .range = { .start = addr, .len = len },
+ .mode = UFFDIO_RWPROTECT_MODE_RWP, /* protect */
+ };
+ ioctl(uffd, UFFDIO_RWPROTECT, &rwp);
+
+Setting ``UFFDIO_RWPROTECT_MODE_RWP`` sets PROT_NONE on present PTEs in the
+range. Pages stay resident and their physical frames are preserved — only
+access permissions are removed.
+
+Clearing ``UFFDIO_RWPROTECT_MODE_RWP`` restores normal VMA permissions and
+wakes any faulting threads (unless ``UFFDIO_RWPROTECT_MODE_DONTWAKE`` is set).
+
+**Scope of protection:**
+
+RWP protection is a property of *present* PTEs. ``UFFDIO_RWPROTECT`` only
+affects entries that are already populated. Unpopulated addresses within
+the range remain unpopulated; when first accessed they fault through the
+normal missing path (``do_anonymous_page()``, ``do_swap_page()``,
+``finish_fault()``) and the resulting PTE is not RWP-protected. To observe
+the population itself, co-register the range with
+``UFFDIO_REGISTER_MODE_MISSING``.
+
+Protection is preserved across page reclaim: a page swapped out while
+RWP-protected carries the marker on its swap entry, and swap-in restores
+the PROT_NONE state so the first access after swap-in still faults. The
+same applies to pages temporarily replaced by migration entries.
+
+Operations that drop the PTE entirely — ``MADV_DONTNEED`` on anonymous
+memory, hole-punch on shmem, truncation of a file mapping — also drop the
+RWP marker: the next access re-populates the range without protection.
+Unlike WP (which persists via ``PTE_MARKER_UFFD_WP``), there is no
+persistent RWP marker today. The VMM needs to re-arm the range with
+``UFFDIO_RWPROTECT`` after any operation that explicitly frees PTEs.
+
+**Fault Handling:**
+
+When a protected page is accessed:
+
+- **Sync mode** (default): The faulting thread blocks and a
+ ``UFFD_PAGEFAULT_FLAG_RWP`` message is delivered to the userfaultfd
+ handler. The handler resolves the fault with ``UFFDIO_RWPROTECT``
+ (clearing ``MODE_RWP``), which restores the PTE permissions and wakes
+ the faulting thread.
+
+- **Async mode** (``UFFD_FEATURE_RWP_ASYNC``): The kernel automatically
+ restores PTE permissions and the thread continues without blocking. No
+ message is delivered to the handler.
+
+**Runtime Mode Switching:**
+
+``UFFDIO_SET_MODE`` toggles ``UFFD_FEATURE_RWP_ASYNC`` at runtime, allowing
+the VMM to switch between lightweight async detection and safe sync
+eviction without re-registering. The toggle takes ``mmap_write_lock()`` to
+ensure all in-flight faults complete before the mode change takes effect.
+
+**Cold Page Detection with PAGEMAP_SCAN:**
+
+RWP-protected PTEs carry the uffd PTE bit; the fault-resolution path
+clears it. ``PAGEMAP_SCAN`` reports ``PAGE_IS_ACCESSED`` once the bit is
+clear on a ``VM_UFFD_RWP`` VMA, so inverting it efficiently reports the
+still-protected (cold) pages::
+
+ struct pm_scan_arg arg = {
+ .size = sizeof(arg),
+ .start = guest_mem_start,
+ .end = guest_mem_end,
+ .vec = (uint64_t)regions,
+ .vec_len = regions_len,
+ .category_mask = PAGE_IS_ACCESSED,
+ .category_inverted = PAGE_IS_ACCESSED,
+ .return_mask = PAGE_IS_ACCESSED,
+ };
+ long n = ioctl(pagemap_fd, PAGEMAP_SCAN, &arg);
+
+The returned ``page_region`` array contains contiguous cold ranges that can
+then be evicted.
+
+**Cleanup:**
+
+When the userfaultfd is closed or the range is unregistered, all PROT_NONE
+PTEs are automatically restored to their normal VMA permissions. This
+prevents pages from becoming permanently inaccessible.
+
+**VMM Working Set Tracking Workflow:**
+
+A typical VMM lifecycle for cold page eviction to tiered storage. Two
+mappings of the same shmem (or hugetlbfs) file are used: ``guest_mem`` is
+the RWP-registered mapping that vCPUs access through, and ``io_mem`` is a
+private mapping for VMM-side I/O. Reading ``io_mem`` does not go through
+the RWP-protected PTEs of ``guest_mem``, so the VMM's own ``pwrite()``
+never traps on its own ::
+
+ /* One-time setup */
+ fd = memfd_create("guest", MFD_CLOEXEC);
+ ftruncate(fd, guest_size);
+ guest_mem = mmap(NULL, guest_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0); /* vCPU view, RWP-registered */
+ io_mem = mmap(NULL, guest_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0); /* VMM I/O view, unprotected */
+
+ uffd = userfaultfd(O_CLOEXEC | O_NONBLOCK);
+ ioctl(uffd, UFFDIO_API, &(struct uffdio_api){
+ .api = UFFD_API,
+ .features = UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ });
+ ioctl(uffd, UFFDIO_REGISTER, &(struct uffdio_register){
+ .range = { guest_mem, guest_size },
+ .mode = UFFDIO_REGISTER_MODE_RWP |
+ UFFDIO_REGISTER_MODE_MISSING,
+ });
+
+ /* Tracking loop */
+ while (vm_running) {
+ /* 1. Detection phase (async — no vCPU stalls) */
+ ioctl(uffd, UFFDIO_RWPROTECT, &(struct uffdio_rwprotect){
+ .range = full_range,
+ .mode = UFFDIO_RWPROTECT_MODE_RWP });
+ sleep(tracking_interval);
+
+ /* 2. Find cold pages (uffd bit still set) */
+ ioctl(pagemap_fd, PAGEMAP_SCAN, &(struct pm_scan_arg){
+ .category_mask = PAGE_IS_ACCESSED,
+ .category_inverted = PAGE_IS_ACCESSED,
+ .return_mask = PAGE_IS_ACCESSED,
+ ...
+ });
+
+ /* 3. Switch to sync for safe eviction */
+ ioctl(uffd, UFFDIO_SET_MODE,
+ &(struct uffdio_set_mode){
+ .disable = UFFD_FEATURE_RWP_ASYNC });
+
+ /* 4. Evict cold pages (vCPU faults block on guest_mem) */
+ for each cold range:
+ /* Read from io_mem -- bypasses RWP, no fault. */
+ pwrite(storage_fd, io_mem + cold_offset, len, offset);
+ /* Drop the page from the shared file. */
+ fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ cold_offset, len);
+ /*
+ * Wake any vCPU blocked on the RWP fault for this range:
+ * fallocate() does not iterate ctx->fault_pending_wqh.
+ */
+ ioctl(uffd, UFFDIO_WAKE, &(struct uffdio_range){
+ .start = (uintptr_t)guest_mem + cold_offset,
+ .len = len });
+
+ /* 5. Resume async tracking */
+ ioctl(uffd, UFFDIO_SET_MODE,
+ &(struct uffdio_set_mode){
+ .enable = UFFD_FEATURE_RWP_ASYNC });
+ }
+
+During step 4, a vCPU that accesses ``guest_mem + cold_offset`` blocks
+with a ``UFFD_PAGEFAULT_FLAG_RWP`` fault while the eviction is in
+progress. After ``fallocate()`` punches the page out and ``UFFDIO_WAKE``
+fires, the vCPU retries the access, faults as ``MISSING``, and the
+handler resolves it with ``UFFDIO_COPY`` from storage.
+
+This workflow targets shmem and hugetlbfs (both support a private
+``io_mem`` mapping over the same fd). Anonymous-memory backings need a
+different inner-loop strategy because the VMM has no way to read the
+page without going through the RWP-protected mapping.
+
QEMU/KVM
========
--
2.51.2
^ permalink raw reply related
* [PATCH v2 13/14] selftests/mm: add userfaultfd RWP tests
From: Kiryl Shutsemau (Meta) @ 2026-05-08 15:55 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team,
Kiryl Shutsemau (Meta)
In-Reply-To: <cover.1778254670.git.kas@kernel.org>
Coverage for UFFDIO_REGISTER_MODE_RWP and UFFDIO_RWPROTECT:
rwp-async async mode — touch pages, verify permissions are
auto-restored without a message
rwp-sync sync mode — access blocks, handler resolves via
UFFDIO_RWPROTECT
rwp-pagemap PAGEMAP_SCAN reports still-cold pages via
inverted PAGE_IS_ACCESSED
rwp-mprotect RWP survives mprotect(PROT_NONE) ->
mprotect(PROT_READ|PROT_WRITE) round-trip
rwp-gup GUP walks through a protnone RWP PTE (pipe
write/read drives the GUP path)
rwp-async-toggle UFFDIO_SET_MODE flips between sync and async
without re-registering
rwp-close closing the uffd restores page permissions
rwp-fork RWP survives fork() with EVENT_FORK; child's
PTEs keep the uffd bit
rwp-fork-pin RWP survives fork() on an RO-longterm-pinned
anon page (forces copy_present_page()); child
read auto-resolves and clears the bit, proving
PAGE_NONE was in place
rwp-wp-exclusive register with MODE_WP|MODE_RWP returns -EINVAL
All tests run against anon, shmem, shmem-private, hugetlb, and
hugetlb-private memory, except rwp-fork-pin which is anon-only —
copy_present_page() is the private-anon pinned-exclusive fork path.
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
---
tools/testing/selftests/mm/uffd-unit-tests.c | 774 +++++++++++++++++++
1 file changed, 774 insertions(+)
diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index 6f5e404a446c..a35fb677e4cc 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -7,6 +7,7 @@
#include "uffd-common.h"
+#include <linux/fs.h>
#include "../../../../mm/gup_test.h"
#ifdef __NR_userfaultfd
@@ -167,6 +168,23 @@ static int test_uffd_api(bool use_dev)
goto out;
}
+ /* Verify returned fd-level ioctls bitmask */
+ {
+ uint64_t expected_ioctls =
+ BIT_ULL(_UFFDIO_REGISTER) |
+ BIT_ULL(_UFFDIO_UNREGISTER) |
+ BIT_ULL(_UFFDIO_API) |
+ BIT_ULL(_UFFDIO_SET_MODE);
+
+ if ((uffdio_api.ioctls & expected_ioctls) != expected_ioctls) {
+ uffd_test_fail("UFFDIO_API missing expected ioctls: "
+ "got=0x%"PRIx64", expected=0x%"PRIx64,
+ (uint64_t)uffdio_api.ioctls,
+ expected_ioctls);
+ goto out;
+ }
+ }
+
/* Test double requests of UFFDIO_API with a random feature set */
uffdio_api.features = BIT_ULL(0);
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
@@ -623,6 +641,691 @@ void uffd_minor_collapse_test(uffd_global_test_opts_t *gopts, uffd_test_args_t *
uffd_minor_test_common(gopts, true, false);
}
+static int uffd_register_rwp(int uffd, void *addr, uint64_t len)
+{
+ struct uffdio_register reg = {
+ .range = { .start = (unsigned long)addr, .len = len },
+ .mode = UFFDIO_REGISTER_MODE_RWP,
+ };
+
+ if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1)
+ return -errno;
+ return 0;
+}
+
+static void rwprotect_range(int uffd, __u64 start, __u64 len, bool protect)
+{
+ struct uffdio_rwprotect rwp = {
+ .range = { .start = start, .len = len },
+ .mode = protect ? UFFDIO_RWPROTECT_MODE_RWP : 0,
+ };
+
+ if (ioctl(uffd, UFFDIO_RWPROTECT, &rwp))
+ err("UFFDIO_RWPROTECT failed");
+}
+
+static void set_async_mode(int uffd, bool enable)
+{
+ struct uffdio_set_mode mode = { };
+
+ if (enable)
+ mode.enable = UFFD_FEATURE_RWP_ASYNC;
+ else
+ mode.disable = UFFD_FEATURE_RWP_ASYNC;
+
+ if (ioctl(uffd, UFFDIO_SET_MODE, &mode))
+ err("UFFDIO_SET_MODE failed");
+}
+
+/*
+ * Test async RWP faults on anonymous memory.
+ * Populate pages, register MODE_RWP with RWP_ASYNC,
+ * RW-protect, re-access, verify content preserved and no faults delivered.
+ */
+static void uffd_rwp_async_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+
+ /* Populate all pages with known content */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ /* Register MODE_RWP */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* RW-protect all pages (sets protnone) */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Access all pages — should auto-resolve, no faults */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ unsigned char expected = p % 255 + 1;
+
+ if (page[0] != expected) {
+ uffd_test_fail("page %lu content mismatch: %u != %u",
+ p, page[0], expected);
+ return;
+ }
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Fault handler for RWP — unprotect the page via UFFDIO_RWPROTECT.
+ */
+static void uffd_handle_rwp_fault(uffd_global_test_opts_t *gopts,
+ struct uffd_msg *msg,
+ struct uffd_args *uargs)
+{
+ if (!(msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_RWP))
+ err("expected RWP fault, got 0x%llx",
+ msg->arg.pagefault.flags);
+
+ rwprotect_range(gopts->uffd, msg->arg.pagefault.address,
+ gopts->page_size, false);
+ uargs->minor_faults++;
+}
+
+/*
+ * Test sync RWP faults on anonymous memory.
+ * Populate pages, register MODE_RWP (sync), RW-protect,
+ * access from worker thread, verify fault delivered, UFFDIO_RWPROTECT resolves.
+ */
+static void uffd_rwp_sync_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ pthread_t uffd_mon;
+ struct uffd_args uargs = { };
+ bool failed = false;
+ char c = '\0';
+ unsigned long p;
+
+ uargs.gopts = gopts;
+ uargs.handle_fault = uffd_handle_rwp_fault;
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ /* Register MODE_RWP */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* RW-protect all pages */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Start fault handler thread */
+ if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &uargs))
+ err("uffd_poll_thread create");
+
+ /* Access all pages — triggers sync RWP faults, handler unprotects */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+
+ if (page[0] != (p % 255 + 1)) {
+ uffd_test_fail("page %lu content mismatch", p);
+ failed = true;
+ goto out;
+ }
+ }
+
+out:
+ /*
+ * Stop the handler before reading minor_faults: the last fault
+ * resolution rwprotect_range()s before incrementing the counter,
+ * so the main thread can race ahead of the increment.
+ */
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+
+ if (failed)
+ return;
+ if (uargs.minor_faults == 0)
+ uffd_test_fail("expected RWP faults, got 0");
+ else
+ uffd_test_pass();
+}
+
+/*
+ * Test PAGEMAP_SCAN detection of RW-protected (cold) pages.
+ */
+static void uffd_rwp_pagemap_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+ struct page_region regions[16];
+ struct pm_scan_arg pm_arg;
+ int pagemap_fd;
+ long ret;
+
+ /* Need at least 4 pages */
+ if (nr_pages < 4) {
+ uffd_test_skip("need at least 4 pages");
+ return;
+ }
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, 0xab, page_size);
+
+ /* Register and RW-protect */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Touch first half of pages to re-activate them (async auto-resolve) */
+ for (p = 0; p < nr_pages / 2; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ /* Scan for cold (still RW-protected) pages */
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ err("open pagemap");
+
+ /*
+ * PAGE_IS_ACCESSED is set once the uffd-wp bit has been cleared
+ * (access happened, or the user resolved). Invert it to select
+ * still-protected (cold) pages.
+ */
+ memset(&pm_arg, 0, sizeof(pm_arg));
+ pm_arg.size = sizeof(pm_arg);
+ pm_arg.start = (uint64_t)gopts->area_dst;
+ pm_arg.end = (uint64_t)gopts->area_dst + nr_pages * page_size;
+ pm_arg.vec = (uint64_t)regions;
+ pm_arg.vec_len = 16;
+ pm_arg.category_mask = PAGE_IS_ACCESSED;
+ pm_arg.category_inverted = PAGE_IS_ACCESSED;
+ pm_arg.return_mask = PAGE_IS_ACCESSED;
+
+ ret = ioctl(pagemap_fd, PAGEMAP_SCAN, &pm_arg);
+ close(pagemap_fd);
+
+ if (ret < 0) {
+ uffd_test_fail("PAGEMAP_SCAN failed: %s", strerror(errno));
+ return;
+ }
+
+ /*
+ * The second half of pages should be reported as RW-protected.
+ * They may be coalesced into one region.
+ */
+ if (ret < 1) {
+ uffd_test_fail("expected cold pages, got %ld regions", ret);
+ return;
+ }
+
+ /* Verify the cold region covers the second half */
+ uint64_t cold_start = regions[0].start;
+ uint64_t expected_start = (uint64_t)gopts->area_dst +
+ (nr_pages / 2) * page_size;
+
+ if (cold_start != expected_start) {
+ uffd_test_fail("cold region starts at 0x%lx, expected 0x%lx",
+ (unsigned long)cold_start,
+ (unsigned long)expected_start);
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that RWP protection survives a mprotect(PROT_NONE) ->
+ * mprotect(PROT_READ|PROT_WRITE) round-trip. The uffd-wp bit on a
+ * VM_UFFD_RWP VMA must continue to carry PROT_NONE semantics after
+ * mprotect() changes the base protection; otherwise accesses would
+ * silently succeed and the pagemap bit would stick without a fault
+ * ever clearing it.
+ */
+static void uffd_rwp_mprotect_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+ struct page_region regions[16];
+ struct pm_scan_arg pm_arg;
+ int pagemap_fd;
+ long ret;
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, 0xab, page_size);
+
+ /* Register and RW-protect the whole range */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Round-trip mprotect(): PROT_NONE -> PROT_READ|PROT_WRITE */
+ if (mprotect(gopts->area_dst, nr_pages * page_size, PROT_NONE))
+ err("mprotect() PROT_NONE");
+ if (mprotect(gopts->area_dst, nr_pages * page_size,
+ PROT_READ | PROT_WRITE))
+ err("mprotect() PROT_READ|PROT_WRITE");
+
+ /* Touch every page. Async RWP must auto-resolve each fault. */
+ for (p = 0; p < nr_pages; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ /*
+ * After touching, no page should remain RW-protected. A stuck
+ * uffd-wp bit would mean mprotect() silently dropped PROT_NONE and
+ * the access never faulted.
+ */
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ err("open pagemap");
+
+ memset(&pm_arg, 0, sizeof(pm_arg));
+ pm_arg.size = sizeof(pm_arg);
+ pm_arg.start = (uint64_t)gopts->area_dst;
+ pm_arg.end = (uint64_t)gopts->area_dst + nr_pages * page_size;
+ pm_arg.vec = (uint64_t)regions;
+ pm_arg.vec_len = 16;
+ pm_arg.category_mask = PAGE_IS_ACCESSED;
+ pm_arg.category_inverted = PAGE_IS_ACCESSED;
+ pm_arg.return_mask = PAGE_IS_ACCESSED;
+
+ ret = ioctl(pagemap_fd, PAGEMAP_SCAN, &pm_arg);
+ close(pagemap_fd);
+
+ if (ret < 0) {
+ uffd_test_fail("PAGEMAP_SCAN failed: %s", strerror(errno));
+ return;
+ }
+ if (ret != 0) {
+ uffd_test_fail("expected no cold pages after mprotect()+touch, got %ld regions",
+ ret);
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that GUP resolves through protnone PTEs (async mode).
+ * RW-protect pages, then use a pipe to exercise GUP on the RW-protected
+ * memory. write() from RW-protected pages triggers GUP which must fault
+ * through the protnone PTE.
+ */
+static void uffd_rwp_gup_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long page_size = gopts->page_size;
+ char *buf;
+ int pipefd[2];
+
+ buf = malloc(page_size);
+ if (!buf)
+ err("malloc");
+
+ /* Populate first page with known content */
+ memset(gopts->area_dst, 0xCD, page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst, page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst, page_size, true);
+
+ if (pipe(pipefd))
+ err("pipe");
+
+ /*
+ * write() from the RW-protected page into the pipe. This triggers
+ * GUP on the protnone PTE; in async mode the kernel auto-restores
+ * permissions and GUP succeeds. One byte is enough to exercise
+ * the GUP path and avoids any concern about pipe buffer sizing on
+ * large-page archs.
+ */
+ if (write(pipefd[1], gopts->area_dst, 1) != 1) {
+ uffd_test_fail("write from RW-protected page failed: %s",
+ strerror(errno));
+ goto out;
+ }
+
+ if (read(pipefd[0], buf, 1) != 1) {
+ uffd_test_fail("read from pipe failed");
+ goto out;
+ }
+
+ if (buf[0] != (char)0xCD) {
+ uffd_test_fail("content mismatch: got 0x%02x, expected 0xCD",
+ (unsigned char)buf[0]);
+ goto out;
+ }
+
+ uffd_test_pass();
+out:
+ close(pipefd[0]);
+ close(pipefd[1]);
+ free(buf);
+}
+
+/*
+ * Test runtime toggle between async and sync modes.
+ * Start in async mode (detection), flip to sync (eviction), verify faults
+ * block, resolve them, flip back to async.
+ */
+static void uffd_rwp_async_toggle_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ struct uffd_args uargs = { };
+ pthread_t uffd_mon;
+ bool started = false;
+ char c = '\0';
+ unsigned long p;
+
+ uargs.gopts = gopts;
+ uargs.handle_fault = uffd_handle_rwp_fault;
+
+ /* Populate */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* Phase 1: async detection — RW-protect, access first half */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ for (p = 0; p < nr_pages / 2; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page; /* auto-resolves in async mode */
+ }
+
+ /* Phase 2: flip to sync for eviction */
+ set_async_mode(gopts->uffd, false);
+
+ /* Start handler — will receive faults for cold pages */
+ if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &uargs))
+ err("uffd_poll_thread create");
+ started = true;
+
+ /* Access second half (cold pages) — should trigger sync faults */
+ for (p = nr_pages / 2; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ if (page[0] != (p % 255 + 1)) {
+ uffd_test_fail("page %lu content mismatch", p);
+ goto out;
+ }
+ }
+
+ /*
+ * Stop the handler before reading minor_faults: the last fault
+ * resolution rwprotect_range()s before incrementing the counter,
+ * so the main thread can race ahead of the increment. Stopping
+ * here also makes Phase 3 a clean async-only test -- with the
+ * handler still running it would silently resolve any sync fault
+ * the kernel erroneously delivers, masking a regression.
+ */
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+ started = false;
+
+ if (uargs.minor_faults == 0) {
+ uffd_test_fail("expected sync faults, got 0");
+ goto out;
+ }
+
+ /* Phase 3: flip back to async */
+ set_async_mode(gopts->uffd, true);
+
+ /* RW-protect and access again — should auto-resolve */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ for (p = 0; p < nr_pages; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ uffd_test_pass();
+out:
+ if (started) {
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+ }
+}
+
+/*
+ * Test that RW-protected pages become accessible after closing uffd.
+ */
+static void uffd_rwp_close_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+
+ /* Populate */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Close uffd — should restore protnone PTEs */
+ close(gopts->uffd);
+ gopts->uffd = -1;
+
+ /* All pages should be accessible with original content */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ unsigned char expected = p % 255 + 1;
+
+ if (page[0] != expected) {
+ uffd_test_fail("page %lu not accessible after close", p);
+ return;
+ }
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that RWP protection is preserved across fork() when
+ * UFFD_FEATURE_EVENT_FORK is enabled. Without preservation, the child's
+ * PTEs would lose the uffd-wp marker and RWP-protected accesses would
+ * silently fall through to do_numa_page().
+ */
+static void uffd_rwp_fork_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ int pagemap_fd;
+ uint64_t value;
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failed");
+
+ /* Populate + RWP-protect */
+ *gopts->area_dst = 1;
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ page_size, true);
+
+ /* Parent: verify uffd-wp bit is set before fork */
+ pagemap_fd = pagemap_open();
+ value = pagemap_get_entry(pagemap_fd, gopts->area_dst);
+ pagemap_check_wp(value, true);
+
+ /*
+ * Fork with EVENT_FORK: child inherits VM_UFFD_RWP. Child reads
+ * its own pagemap and must still see the uffd-wp bit set.
+ */
+ if (pagemap_test_fork(gopts, true, false)) {
+ uffd_test_fail("RWP marker lost in child after fork");
+ goto out;
+ }
+
+ uffd_test_pass();
+out:
+ close(pagemap_fd);
+}
+
+/*
+ * Test that RWP protection on a pinned anon page is preserved across fork().
+ * Pinning forces copy_present_page() in the child path, which must restore
+ * PAGE_NONE on top of the uffd bit. Using async mode, a read in the child
+ * auto-resolves if — and only if — the PTE was actually protnone+uffd; the
+ * cleared uffd bit afterward proves the fault path ran.
+ */
+static void uffd_rwp_fork_pin_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long page_size = gopts->page_size;
+ fork_event_args fevent_args = { .gopts = gopts, .child_uffd = -1 };
+ pin_args pin_args = {};
+ int pagemap_fd, status;
+ pthread_t fevent_thread;
+ uint64_t value;
+ pid_t child;
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst, page_size))
+ err("register failed");
+
+ /* Populate. */
+ *gopts->area_dst = 1;
+
+ /* RO-longterm pin so fork() takes copy_present_page() for this PTE. */
+ if (pin_pages(&pin_args, gopts->area_dst, page_size)) {
+ uffd_test_skip("Possibly CONFIG_GUP_TEST missing or unprivileged");
+ uffd_unregister(gopts->uffd, gopts->area_dst, page_size);
+ return;
+ }
+
+ /* RWP-protect: PTE is now PAGE_NONE + uffd bit. */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst, page_size, true);
+
+ pagemap_fd = pagemap_open();
+ value = pagemap_get_entry(pagemap_fd, gopts->area_dst);
+ pagemap_check_wp(value, true);
+
+ /*
+ * UFFD_FEATURE_EVENT_FORK is required so the child inherits
+ * VM_UFFD_RWP and the marker; without it dup_userfaultfd() resets
+ * the child VMA and the test would pass for the wrong reason.
+ * dup_userfaultfd() blocks until the EVENT_FORK message is consumed,
+ * so spawn a reader before the fork().
+ */
+ gopts->ready_for_fork = false;
+ if (pthread_create(&fevent_thread, NULL, fork_event_consumer,
+ &fevent_args))
+ err("pthread_create() for fork event consumer");
+ while (!gopts->ready_for_fork)
+ ; /* Wait for consumer to start polling. */
+
+ child = fork();
+ if (child < 0)
+ err("fork");
+ if (child == 0) {
+ volatile char c;
+ int cfd;
+
+ /*
+ * Read the pinned page. Only reaches the fault path if the
+ * child PTE is protnone + uffd; async mode auto-resolves and
+ * clears the uffd bit. If copy_present_page() dropped
+ * PAGE_NONE, the read would silently succeed and the bit
+ * would still be set.
+ */
+ c = *(volatile char *)gopts->area_dst;
+ (void)c;
+
+ cfd = pagemap_open();
+ value = pagemap_get_entry(cfd, gopts->area_dst);
+ close(cfd);
+ _exit((value & PM_UFFD_WP) ? 1 : 0);
+ }
+ if (waitpid(child, &status, 0) < 0)
+ err("waitpid");
+ if (pthread_join(fevent_thread, NULL))
+ err("pthread_join() for fork event consumer");
+ if (fevent_args.child_uffd >= 0)
+ close(fevent_args.child_uffd);
+
+ unpin_pages(&pin_args);
+ close(pagemap_fd);
+ if (uffd_unregister(gopts->uffd, gopts->area_dst, page_size))
+ err("unregister failed");
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ uffd_test_fail("RWP not enforced in child after pinned fork");
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * WP and RWP share the uffd-wp PTE bit and cannot coexist in the same VMA.
+ * Registration requesting both modes must be rejected.
+ */
+static void uffd_rwp_wp_exclusive_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ struct uffdio_register reg = { };
+
+ reg.range.start = (unsigned long)gopts->area_dst;
+ reg.range.len = nr_pages * page_size;
+ reg.mode = UFFDIO_REGISTER_MODE_WP | UFFDIO_REGISTER_MODE_RWP;
+
+ if (ioctl(gopts->uffd, UFFDIO_REGISTER, ®) == 0) {
+ uffd_test_fail("register with WP|RWP unexpectedly succeeded");
+ return;
+ }
+ if (errno != EINVAL) {
+ uffd_test_fail("register with WP|RWP: expected EINVAL, got %d",
+ errno);
+ return;
+ }
+ uffd_test_pass();
+}
+
static sigjmp_buf jbuf, *sigbuf;
static void sighndl(int sig, siginfo_t *siginfo, void *ptr)
@@ -1625,6 +2328,77 @@ uffd_test_case_t uffd_tests[] = {
/* We can't test MADV_COLLAPSE, so try our luck */
.uffd_feature_required = UFFD_FEATURE_MINOR_SHMEM,
},
+ {
+ .name = "rwp-async",
+ .uffd_fn = uffd_rwp_async_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-sync",
+ .uffd_fn = uffd_rwp_sync_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required = UFFD_FEATURE_RWP,
+ },
+ {
+ .name = "rwp-pagemap",
+ .uffd_fn = uffd_rwp_pagemap_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-mprotect",
+ .uffd_fn = uffd_rwp_mprotect_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-gup",
+ .uffd_fn = uffd_rwp_gup_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-async-toggle",
+ .uffd_fn = uffd_rwp_async_toggle_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-close",
+ .uffd_fn = uffd_rwp_close_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required = UFFD_FEATURE_RWP,
+ },
+ {
+ .name = "rwp-fork",
+ .uffd_fn = uffd_rwp_fork_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_EVENT_FORK,
+ },
+ {
+ .name = "rwp-fork-pin",
+ .uffd_fn = uffd_rwp_fork_pin_test,
+ .mem_targets = MEM_ANON,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC |
+ UFFD_FEATURE_EVENT_FORK,
+ },
+ {
+ .name = "rwp-wp-exclusive",
+ .uffd_fn = uffd_rwp_wp_exclusive_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP |
+ UFFD_FEATURE_PAGEFAULT_FLAG_WP |
+ UFFD_FEATURE_WP_HUGETLBFS_SHMEM,
+ },
{
.name = "sigbus",
.uffd_fn = uffd_sigbus_test,
--
2.51.2
^ permalink raw reply related
* [PATCH v2 12/14] userfaultfd: add UFFDIO_SET_MODE for runtime sync/async toggle
From: Kiryl Shutsemau (Meta) @ 2026-05-08 15:55 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team,
Kiryl Shutsemau (Meta)
In-Reply-To: <cover.1778254670.git.kas@kernel.org>
Add an ioctl to toggle async mode at runtime without re-registering
the userfaultfd. This allows a VMM to switch between sync and async
RWP modes on-the-fly -- for example, starting in async mode for
working set scanning, then switching to sync mode to intercept faults
during page eviction.
UFFDIO_SET_MODE takes an enable/disable bitmask of UFFD_FEATURE_*
flags. Only UFFD_FEATURE_RWP_ASYNC is toggleable today; the ioctl
rejects any other bit with -EINVAL. Enabling RWP_ASYNC also requires
RWP to have been negotiated at UFFDIO_API time, mirroring the
UFFDIO_API invariant.
Fault-path readers of ctx->features run under mmap_read_lock or a
per-VMA lock; the RMW takes mmap_write_lock and calls
vma_start_write() on every UFFD-armed VMA, so those readers are fully
excluded. userfaultfd_show_fdinfo(), however, reads ctx->features
without any lock, so the RMW is written as a single WRITE_ONCE and
fdinfo reads it with READ_ONCE. That keeps the lockless observer from
seeing a mid-RMW intermediate and removes the audit burden when new
toggleable bits are added later.
When switching to async, pending sync waiters are woken so they retry
and auto-resolve under the new mode.
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
---
fs/userfaultfd.c | 150 +++++++++++++++++++++++++------
include/uapi/linux/userfaultfd.h | 14 +++
2 files changed, 136 insertions(+), 28 deletions(-)
diff --git a/fs/userfaultfd.c b/fs/userfaultfd.c
index 4a701ac830f4..908e63304706 100644
--- a/fs/userfaultfd.c
+++ b/fs/userfaultfd.c
@@ -79,19 +79,29 @@ struct userfaultfd_wake_range {
/* internal indication that UFFD_API ioctl was successfully executed */
#define UFFD_FEATURE_INITIALIZED (1u << 31)
+/*
+ * UFFDIO_SET_MODE updates ctx->features under mmap_write_lock with
+ * WRITE_ONCE; readers that run outside mmap_read_lock or the per-VMA
+ * lock (poll/read_iter/ioctl, fdinfo) must pair with READ_ONCE.
+ */
+static unsigned int userfaultfd_features(struct userfaultfd_ctx *ctx)
+{
+ return READ_ONCE(ctx->features);
+}
+
static bool userfaultfd_is_initialized(struct userfaultfd_ctx *ctx)
{
- return ctx->features & UFFD_FEATURE_INITIALIZED;
+ return userfaultfd_features(ctx) & UFFD_FEATURE_INITIALIZED;
}
static bool userfaultfd_wp_async_ctx(struct userfaultfd_ctx *ctx)
{
- return ctx && (ctx->features & UFFD_FEATURE_WP_ASYNC);
+ return ctx && (userfaultfd_features(ctx) & UFFD_FEATURE_WP_ASYNC);
}
static bool userfaultfd_rwp_async_ctx(struct userfaultfd_ctx *ctx)
{
- return ctx && (ctx->features & UFFD_FEATURE_RWP_ASYNC);
+ return ctx && (userfaultfd_features(ctx) & UFFD_FEATURE_RWP_ASYNC);
}
/*
@@ -106,7 +116,7 @@ bool userfaultfd_wp_unpopulated(struct vm_area_struct *vma)
if (!ctx)
return false;
- return ctx->features & UFFD_FEATURE_WP_UNPOPULATED;
+ return userfaultfd_features(ctx) & UFFD_FEATURE_WP_UNPOPULATED;
}
static int userfaultfd_wake_function(wait_queue_entry_t *wq, unsigned mode,
@@ -1871,6 +1881,109 @@ static int userfaultfd_rwprotect(struct userfaultfd_ctx *ctx,
return ret;
}
+/* Subset of UFFD_API_FEATURES actually supported by this kernel/arch */
+static __u64 uffd_api_available_features(void)
+{
+ __u64 f = UFFD_API_FEATURES;
+
+ if (!IS_ENABLED(CONFIG_HAVE_ARCH_USERFAULTFD_MINOR))
+ f &= ~(UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM);
+ if (!pgtable_supports_uffd())
+ f &= ~UFFD_FEATURE_PAGEFAULT_FLAG_WP;
+ if (!uffd_supports_wp_marker())
+ f &= ~(UFFD_FEATURE_WP_HUGETLBFS_SHMEM |
+ UFFD_FEATURE_WP_UNPOPULATED |
+ UFFD_FEATURE_WP_ASYNC);
+ /*
+ * RWP needs both PROT_NONE support and the uffd PTE bit. The
+ * VM_UFFD_RWP check covers compile-time unavailability; the
+ * pgtable_supports_uffd() check covers runtime (e.g. riscv
+ * without the SVRSW60T59B extension) where the PTE bit is declared
+ * but not actually usable.
+ */
+ if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd())
+ f &= ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC);
+ return f;
+}
+
+/* Async features that can be toggled at runtime via UFFDIO_SET_MODE */
+#define UFFD_FEATURE_TOGGLEABLE UFFD_FEATURE_RWP_ASYNC
+
+static int userfaultfd_set_mode(struct userfaultfd_ctx *ctx,
+ unsigned long arg)
+{
+ struct uffdio_set_mode mode;
+ struct mm_struct *mm = ctx->mm;
+
+ if (copy_from_user(&mode, (void __user *)arg, sizeof(mode)))
+ return -EFAULT;
+
+ /* enable and disable must not overlap */
+ if (mode.enable & mode.disable)
+ return -EINVAL;
+
+ /* only toggleable features that this kernel/arch actually supports */
+ if ((mode.enable | mode.disable) &
+ ~(uffd_api_available_features() & UFFD_FEATURE_TOGGLEABLE))
+ return -EINVAL;
+
+ /* RWP_ASYNC can only be enabled on contexts that negotiated RWP */
+ if ((mode.enable & UFFD_FEATURE_RWP_ASYNC) &&
+ !(ctx->features & UFFD_FEATURE_RWP))
+ return -EINVAL;
+
+ if (!mmget_not_zero(mm))
+ return -ESRCH;
+
+ /*
+ * Drain in-flight faults before flipping features. mmap_write_lock()
+ * blocks new mmap_read_lock() callers, but per-VMA locked faults
+ * (lock_vma_under_rcu() + FAULT_FLAG_VMA_LOCK) that acquired before
+ * this point keep running. Calling vma_start_write() on each UFFD-
+ * armed VMA waits for those readers to drop, so no in-flight fault
+ * can observe the old features after mmap_write_unlock().
+ */
+ mmap_write_lock(mm);
+ {
+ struct vm_area_struct *vma;
+ VMA_ITERATOR(vmi, mm, 0);
+
+ for_each_vma(vmi, vma) {
+ if (vma->vm_userfaultfd_ctx.ctx == ctx)
+ vma_start_write(vma);
+ }
+ }
+ /*
+ * Single WRITE_ONCE so lockless readers (fdinfo, poll/read_iter
+ * via userfaultfd_is_initialized(), and the userfaultfd_features()
+ * helper used elsewhere) can't observe a mid-RMW intermediate
+ * value. Hot-path readers already serialise through the mmap lock
+ * + vma_start_write() drain above, so their load doesn't need an
+ * annotation.
+ */
+ WRITE_ONCE(ctx->features,
+ (ctx->features | mode.enable) & ~mode.disable);
+ mmap_write_unlock(mm);
+
+ /*
+ * If switching to async, wake threads blocked in handle_userfault().
+ * They will retry the fault and auto-resolve under the new mode.
+ * len=0 means wake all pending faults on this context.
+ */
+ if (mode.enable & UFFD_FEATURE_RWP_ASYNC) {
+ struct userfaultfd_wake_range range = { .len = 0 };
+
+ spin_lock_irq(&ctx->fault_pending_wqh.lock);
+ __wake_up_locked_key(&ctx->fault_pending_wqh, TASK_NORMAL,
+ &range);
+ __wake_up(&ctx->fault_wqh, TASK_NORMAL, 1, &range);
+ spin_unlock_irq(&ctx->fault_pending_wqh.lock);
+ }
+
+ mmput(mm);
+ return 0;
+}
+
static int userfaultfd_continue(struct userfaultfd_ctx *ctx, unsigned long arg)
{
__s64 ret;
@@ -2109,29 +2222,7 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx,
goto err_out;
/* report all available features and ioctls to userland */
- uffdio_api.features = UFFD_API_FEATURES;
-#ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR
- uffdio_api.features &=
- ~(UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM);
-#endif
- if (!pgtable_supports_uffd())
- uffdio_api.features &= ~UFFD_FEATURE_PAGEFAULT_FLAG_WP;
-
- if (!uffd_supports_wp_marker()) {
- uffdio_api.features &= ~UFFD_FEATURE_WP_HUGETLBFS_SHMEM;
- uffdio_api.features &= ~UFFD_FEATURE_WP_UNPOPULATED;
- uffdio_api.features &= ~UFFD_FEATURE_WP_ASYNC;
- }
- /*
- * RWP needs both PROT_NONE support and the uffd-wp PTE bit. The
- * VM_UFFD_RWP check covers compile-time unavailability; the
- * pgtable_supports_uffd() check covers runtime (e.g. riscv
- * without the SVRSW60T59B extension) where the PTE bit is declared
- * but not actually usable.
- */
- if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd())
- uffdio_api.features &=
- ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC);
+ uffdio_api.features = uffd_api_available_features();
ret = -EINVAL;
if (features & ~uffdio_api.features)
@@ -2201,6 +2292,9 @@ static long userfaultfd_ioctl(struct file *file, unsigned cmd,
case UFFDIO_RWPROTECT:
ret = userfaultfd_rwprotect(ctx, arg);
break;
+ case UFFDIO_SET_MODE:
+ ret = userfaultfd_set_mode(ctx, arg);
+ break;
}
return ret;
}
@@ -2228,7 +2322,7 @@ static void userfaultfd_show_fdinfo(struct seq_file *m, struct file *f)
* protocols: aa:... bb:...
*/
seq_printf(m, "pending:\t%lu\ntotal:\t%lu\nAPI:\t%Lx:%x:%Lx\n",
- pending, total, UFFD_API, ctx->features,
+ pending, total, UFFD_API, userfaultfd_features(ctx),
UFFD_API_IOCTLS|UFFD_API_RANGE_IOCTLS);
}
#endif
diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h
index c10f08f8a618..cea11aad6b54 100644
--- a/include/uapi/linux/userfaultfd.h
+++ b/include/uapi/linux/userfaultfd.h
@@ -49,6 +49,7 @@
#define UFFD_API_IOCTLS \
((__u64)1 << _UFFDIO_REGISTER | \
(__u64)1 << _UFFDIO_UNREGISTER | \
+ (__u64)1 << _UFFDIO_SET_MODE | \
(__u64)1 << _UFFDIO_API)
#define UFFD_API_RANGE_IOCTLS \
((__u64)1 << _UFFDIO_WAKE | \
@@ -85,6 +86,7 @@
#define _UFFDIO_CONTINUE (0x07)
#define _UFFDIO_POISON (0x08)
#define _UFFDIO_RWPROTECT (0x09)
+#define _UFFDIO_SET_MODE (0x0A)
#define _UFFDIO_API (0x3F)
/* userfaultfd ioctl ids */
@@ -111,6 +113,8 @@
struct uffdio_poison)
#define UFFDIO_RWPROTECT _IOWR(UFFDIO, _UFFDIO_RWPROTECT, \
struct uffdio_rwprotect)
+#define UFFDIO_SET_MODE _IOW(UFFDIO, _UFFDIO_SET_MODE, \
+ struct uffdio_set_mode)
/* read() structure */
struct uffd_msg {
@@ -406,6 +410,16 @@ struct uffdio_move {
__s64 move;
};
+struct uffdio_set_mode {
+ /*
+ * Toggle async mode for features at runtime.
+ * Supported: UFFD_FEATURE_RWP_ASYNC.
+ * Setting a bit in both enable and disable is invalid.
+ */
+ __u64 enable;
+ __u64 disable;
+};
+
/*
* Flags for the userfaultfd(2) system call itself.
*/
--
2.51.2
^ permalink raw reply related
* [PATCH v2 11/14] userfaultfd: add UFFD_FEATURE_RWP_ASYNC for async fault resolution
From: Kiryl Shutsemau (Meta) @ 2026-05-08 15:55 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team,
Kiryl Shutsemau (Meta)
In-Reply-To: <cover.1778254670.git.kas@kernel.org>
Sync RWP delivers a message and blocks the faulting thread until the
handler resolves the fault. For working-set tracking the VMM does not
need the message: it just needs to know, at scan time, which pages
were touched. Async RWP serves that use case — the kernel restores
access in-place and the faulting thread continues without blocking.
The VMM reconstructs the access pattern after the fact via
PAGEMAP_SCAN: pages whose uffd bit is still set (inverted
PAGE_IS_ACCESSED) were not re-accessed since the last RWP cycle.
Worth calling out: async resolution upgrades writable private anon
PTEs via pte_mkwrite() when can_change_pte_writable() allows, mirroring
do_numa_page(). Without it, every re-access of an RWP'd writable page
would COW-fault a second time.
UFFD_FEATURE_RWP_ASYNC requires UFFD_FEATURE_RWP.
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
---
fs/userfaultfd.c | 19 ++++++++++++++++++-
include/linux/userfaultfd_k.h | 6 ++++++
include/uapi/linux/userfaultfd.h | 11 ++++++++++-
mm/huge_memory.c | 25 ++++++++++++++++++++++++-
mm/hugetlb.c | 32 +++++++++++++++++++++++++++++++-
mm/memory.c | 27 +++++++++++++++++++++++++--
6 files changed, 114 insertions(+), 6 deletions(-)
diff --git a/fs/userfaultfd.c b/fs/userfaultfd.c
index 6e577c4ac4dd..4a701ac830f4 100644
--- a/fs/userfaultfd.c
+++ b/fs/userfaultfd.c
@@ -89,6 +89,11 @@ static bool userfaultfd_wp_async_ctx(struct userfaultfd_ctx *ctx)
return ctx && (ctx->features & UFFD_FEATURE_WP_ASYNC);
}
+static bool userfaultfd_rwp_async_ctx(struct userfaultfd_ctx *ctx)
+{
+ return ctx && (ctx->features & UFFD_FEATURE_RWP_ASYNC);
+}
+
/*
* Whether WP_UNPOPULATED is enabled on the uffd context. It is only
* meaningful when userfaultfd_wp()==true on the vma and when it's
@@ -1989,6 +1994,11 @@ bool userfaultfd_wp_async(struct vm_area_struct *vma)
return userfaultfd_wp_async_ctx(vma->vm_userfaultfd_ctx.ctx);
}
+bool userfaultfd_rwp_async(struct vm_area_struct *vma)
+{
+ return userfaultfd_rwp_async_ctx(vma->vm_userfaultfd_ctx.ctx);
+}
+
static inline unsigned int uffd_ctx_features(__u64 user_features)
{
/*
@@ -2092,6 +2102,12 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx,
if (features & UFFD_FEATURE_WP_ASYNC)
features |= UFFD_FEATURE_WP_UNPOPULATED;
+ ret = -EINVAL;
+ /* RWP_ASYNC requires RWP */
+ if ((features & UFFD_FEATURE_RWP_ASYNC) &&
+ !(features & UFFD_FEATURE_RWP))
+ goto err_out;
+
/* report all available features and ioctls to userland */
uffdio_api.features = UFFD_API_FEATURES;
#ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR
@@ -2114,7 +2130,8 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx,
* but not actually usable.
*/
if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd())
- uffdio_api.features &= ~UFFD_FEATURE_RWP;
+ uffdio_api.features &=
+ ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC);
ret = -EINVAL;
if (features & ~uffdio_api.features)
diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h
index 37e8d0d29353..777e332edeff 100644
--- a/include/linux/userfaultfd_k.h
+++ b/include/linux/userfaultfd_k.h
@@ -295,6 +295,7 @@ extern void userfaultfd_unmap_complete(struct mm_struct *mm,
struct list_head *uf);
extern bool userfaultfd_wp_unpopulated(struct vm_area_struct *vma);
extern bool userfaultfd_wp_async(struct vm_area_struct *vma);
+extern bool userfaultfd_rwp_async(struct vm_area_struct *vma);
void userfaultfd_reset_ctx(struct vm_area_struct *vma);
@@ -492,6 +493,11 @@ static inline bool userfaultfd_wp_async(struct vm_area_struct *vma)
return false;
}
+static inline bool userfaultfd_rwp_async(struct vm_area_struct *vma)
+{
+ return false;
+}
+
static inline bool vma_has_uffd_without_event_remap(struct vm_area_struct *vma)
{
return false;
diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h
index d803e76d47ad..c10f08f8a618 100644
--- a/include/uapi/linux/userfaultfd.h
+++ b/include/uapi/linux/userfaultfd.h
@@ -44,7 +44,8 @@
UFFD_FEATURE_POISON | \
UFFD_FEATURE_WP_ASYNC | \
UFFD_FEATURE_MOVE | \
- UFFD_FEATURE_RWP)
+ UFFD_FEATURE_RWP | \
+ UFFD_FEATURE_RWP_ASYNC)
#define UFFD_API_IOCTLS \
((__u64)1 << _UFFDIO_REGISTER | \
(__u64)1 << _UFFDIO_UNREGISTER | \
@@ -243,6 +244,13 @@ struct uffdio_api {
* UFFDIO_REGISTER_MODE_RWP for read-write protection tracking.
* Pages are made inaccessible via UFFDIO_RWPROTECT and faults
* are delivered when the pages are re-accessed.
+ *
+ * UFFD_FEATURE_RWP_ASYNC indicates asynchronous mode for
+ * UFFDIO_REGISTER_MODE_RWP. When set, faults on read-write
+ * protected pages are auto-resolved by the kernel (PTE
+ * permissions restored immediately) without delivering a message
+ * to the userfaultfd handler. Use PAGEMAP_SCAN with inverted
+ * PAGE_IS_ACCESSED to find pages that were not re-accessed.
*/
#define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)
#define UFFD_FEATURE_EVENT_FORK (1<<1)
@@ -262,6 +270,7 @@ struct uffdio_api {
#define UFFD_FEATURE_WP_ASYNC (1<<15)
#define UFFD_FEATURE_MOVE (1<<16)
#define UFFD_FEATURE_RWP (1<<17)
+#define UFFD_FEATURE_RWP_ASYNC (1<<18)
__u64 features;
__u64 ioctls;
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 631e0355919f..d49facfdb16b 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2266,7 +2266,30 @@ static inline bool can_change_pmd_writable(struct vm_area_struct *vma,
vm_fault_t do_huge_pmd_uffd_rwp(struct vm_fault *vmf)
{
- return handle_userfault(vmf, VM_UFFD_RWP);
+ struct vm_area_struct *vma = vmf->vma;
+ pmd_t pmd;
+
+ if (!userfaultfd_rwp_async(vma))
+ return handle_userfault(vmf, VM_UFFD_RWP);
+
+ vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
+ if (unlikely(!pmd_same(pmdp_get(vmf->pmd), vmf->orig_pmd))) {
+ spin_unlock(vmf->ptl);
+ return 0;
+ }
+ pmd = pmd_modify(vmf->orig_pmd, vma->vm_page_prot);
+ /* pmd_modify() preserves _PAGE_UFFD; drop it on resolution */
+ pmd = pmd_clear_uffd(pmd);
+ pmd = pmd_mkyoung(pmd);
+ if (!pmd_write(pmd) &&
+ vma_wants_manual_pte_write_upgrade(vma) &&
+ can_change_pmd_writable(vma, vmf->address, pmd))
+ pmd = pmd_mkwrite(pmd, vma);
+ set_pmd_at(vma->vm_mm, vmf->address & HPAGE_PMD_MASK,
+ vmf->pmd, pmd);
+ update_mmu_cache_pmd(vma, vmf->address, vmf->pmd);
+ spin_unlock(vmf->ptl);
+ return 0;
}
/* NUMA hinting page fault entry point for trans huge pmds */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index bac9aa852f6b..dc581adcb0ab 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -6075,7 +6075,37 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
*/
if (pte_protnone(vmf.orig_pte) && vma_is_accessible(vma) &&
userfaultfd_rwp(vma) && huge_pte_uffd(vmf.orig_pte)) {
- return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP);
+ spinlock_t *ptl;
+ pte_t pte;
+
+ /* Sync: drop hugetlb locks before blocking in handle_userfault() */
+ if (!userfaultfd_rwp_async(vma))
+ return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP);
+
+ ptl = huge_pte_lock(h, mm, vmf.pte);
+ pte = huge_ptep_get(mm, vmf.address, vmf.pte);
+ if (pte_protnone(pte) && huge_pte_uffd(pte)) {
+ unsigned int shift = huge_page_shift(h);
+
+ pte = huge_pte_modify(pte, vma->vm_page_prot);
+ pte = arch_make_huge_pte(pte, shift, vma->vm_flags);
+ /* huge_pte_modify() preserves _PAGE_UFFD; drop it on resolution */
+ pte = huge_pte_clear_uffd(pte);
+ pte = pte_mkyoung(pte);
+ /*
+ * Unlike do_uffd_rwp(), do not upgrade to writable
+ * here. Hugetlb lacks a can_change_huge_pte_writable()
+ * equivalent, so a write access will take a separate
+ * COW fault — acceptable for the rare private hugetlb
+ * case.
+ */
+ set_huge_pte_at(mm, vmf.address, vmf.pte, pte,
+ huge_page_size(h));
+ update_mmu_cache(vma, vmf.address, vmf.pte);
+ }
+ spin_unlock(ptl);
+ ret = 0;
+ goto out_mutex;
}
/*
diff --git a/mm/memory.c b/mm/memory.c
index e0dcf2c28d9d..bfe6f218fb16 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -6174,8 +6174,31 @@ static void numa_rebuild_large_mapping(struct vm_fault *vmf, struct vm_area_stru
static vm_fault_t do_uffd_rwp(struct vm_fault *vmf)
{
- pte_unmap(vmf->pte);
- return handle_userfault(vmf, VM_UFFD_RWP);
+ pte_t pte;
+
+ if (!userfaultfd_rwp_async(vmf->vma)) {
+ /* Sync mode: unmap PTE and deliver to userfaultfd handler */
+ pte_unmap(vmf->pte);
+ return handle_userfault(vmf, VM_UFFD_RWP);
+ }
+
+ spin_lock(vmf->ptl);
+ if (unlikely(!pte_same(ptep_get(vmf->pte), vmf->orig_pte))) {
+ pte_unmap_unlock(vmf->pte, vmf->ptl);
+ return 0;
+ }
+ pte = pte_modify(vmf->orig_pte, vmf->vma->vm_page_prot);
+ /* pte_modify() preserves _PAGE_UFFD; drop it on resolution */
+ pte = pte_clear_uffd(pte);
+ pte = pte_mkyoung(pte);
+ if (!pte_write(pte) &&
+ vma_wants_manual_pte_write_upgrade(vmf->vma) &&
+ can_change_pte_writable(vmf->vma, vmf->address, pte))
+ pte = pte_mkwrite(pte, vmf->vma);
+ set_pte_at(vmf->vma->vm_mm, vmf->address, vmf->pte, pte);
+ update_mmu_cache(vmf->vma, vmf->address, vmf->pte);
+ pte_unmap_unlock(vmf->pte, vmf->ptl);
+ return 0;
}
static vm_fault_t do_numa_page(struct vm_fault *vmf)
--
2.51.2
^ permalink raw reply related
* [PATCH v2 10/14] mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking
From: Kiryl Shutsemau (Meta) @ 2026-05-08 15:55 UTC (permalink / raw)
To: akpm, rppt, peterx, david
Cc: ljs, surenb, vbabka, Liam.Howlett, ziy, corbet, skhan, seanjc,
pbonzini, jthoughton, aarcange, sj, usama.arif, linux-mm,
linux-kernel, linux-doc, linux-kselftest, kvm, kernel-team,
Kiryl Shutsemau (Meta)
In-Reply-To: <cover.1778254670.git.kas@kernel.org>
PAGEMAP_SCAN already reports PAGE_IS_WRITTEN from the inverted uffd
PTE bit, targeting the UFFDIO_WRITEPROTECT workflow. UFFDIO_RWPROTECT
reuses the same PTE bit as a marker for read-write protection, but
"has been written" and "has been accessed" are distinct semantic
signals — they happen to share one PTE bit today only because the two
implementations share infrastructure.
Give RWP its own pagemap category so the UAPI does not conflate them:
PAGE_IS_WRITTEN reported on VM_UFFD_WP VMAs, !pte_uffd(pte)
PAGE_IS_ACCESSED reported on VM_UFFD_RWP VMAs, !pte_uffd(pte)
Both still read the same PTE bit today, but each is scoped to the VMA
whose registered mode makes the bit meaningful. If a future
implementation moves RWP to a separate PTE bit, only PAGE_IS_ACCESSED
switches over.
This is a UAPI narrowing. Outside VM_UFFD_WP VMAs the uffd bit is
always clear, so PAGEMAP_SCAN used to flag PAGE_IS_WRITTEN on every
present PTE there — a meaningless duplicate of PAGE_IS_PRESENT. Now
PAGE_IS_WRITTEN fires only inside VM_UFFD_WP VMAs.
pagemap_hugetlb_category() now takes the vma like its PTE/PMD peers.
Signed-off-by: Kiryl Shutsemau <kas@kernel.org>
Assisted-by: Claude:claude-opus-4-6
---
Documentation/admin-guide/mm/pagemap.rst | 13 ++++-
fs/proc/task_mmu.c | 73 ++++++++++++++++++------
include/uapi/linux/fs.h | 1 +
tools/include/uapi/linux/fs.h | 1 +
4 files changed, 67 insertions(+), 21 deletions(-)
diff --git a/Documentation/admin-guide/mm/pagemap.rst b/Documentation/admin-guide/mm/pagemap.rst
index c57e61b5d8aa..ffa690a171c8 100644
--- a/Documentation/admin-guide/mm/pagemap.rst
+++ b/Documentation/admin-guide/mm/pagemap.rst
@@ -19,8 +19,11 @@ There are four components to pagemap:
* Bit 55 pte is soft-dirty (see
Documentation/admin-guide/mm/soft-dirty.rst)
* Bit 56 page exclusively mapped (since 4.2)
- * Bit 57 pte is uffd-wp write-protected (since 5.13) (see
- Documentation/admin-guide/mm/userfaultfd.rst)
+ * Bit 57 pte is tracked by userfaultfd (since 5.13) — in a
+ ``VM_UFFD_WP`` VMA this indicates a write-protected PTE; in a
+ ``VM_UFFD_RWP`` VMA it indicates an RWP-protected PTE. WP and
+ RWP are mutually exclusive per VMA, so the meaning is
+ unambiguous. See Documentation/admin-guide/mm/userfaultfd.rst.
* Bit 58 pte is a guard region (since 6.15) (see madvise (2) man page)
* Bits 59-60 zero
* Bit 61 page is file-page or shared-anon (since 3.5)
@@ -244,7 +247,8 @@ in this IOCTL:
Following flags about pages are currently supported:
- ``PAGE_IS_WPALLOWED`` - Page has async-write-protection enabled
-- ``PAGE_IS_WRITTEN`` - Page has been written to from the time it was write protected
+- ``PAGE_IS_WRITTEN`` - Page in a ``UFFDIO_REGISTER_MODE_WP`` VMA has been
+ written to since it was write-protected. Only reported inside such VMAs.
- ``PAGE_IS_FILE`` - Page is file backed
- ``PAGE_IS_PRESENT`` - Page is present in the memory
- ``PAGE_IS_SWAPPED`` - Page is in swapped
@@ -252,6 +256,9 @@ Following flags about pages are currently supported:
- ``PAGE_IS_HUGE`` - Page is PMD-mapped THP or Hugetlb backed
- ``PAGE_IS_SOFT_DIRTY`` - Page is soft-dirty
- ``PAGE_IS_GUARD`` - Page is a part of a guard region
+- ``PAGE_IS_ACCESSED`` - Page in a ``UFFDIO_REGISTER_MODE_RWP`` VMA has been
+ accessed since RWP was applied. Only reported inside such VMAs. See
+ Documentation/admin-guide/mm/userfaultfd.rst for the RWP workflow.
The ``struct pm_scan_arg`` is used as the argument of the IOCTL.
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index fbaede228201..4e207b6216b1 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -2197,7 +2197,7 @@ static const struct mm_walk_ops pagemap_ops = {
* Bits 5-54 swap offset if swapped
* Bit 55 pte is soft-dirty (see Documentation/admin-guide/mm/soft-dirty.rst)
* Bit 56 page exclusively mapped
- * Bit 57 pte is uffd-wp write-protected
+ * Bit 57 pte is tracked by userfaultfd (uffd-wp or RWP)
* Bit 58 pte is a guard region
* Bits 59-60 zero
* Bit 61 page is file-page or shared-anon
@@ -2332,7 +2332,7 @@ static int pagemap_release(struct inode *inode, struct file *file)
PAGE_IS_FILE | PAGE_IS_PRESENT | \
PAGE_IS_SWAPPED | PAGE_IS_PFNZERO | \
PAGE_IS_HUGE | PAGE_IS_SOFT_DIRTY | \
- PAGE_IS_GUARD)
+ PAGE_IS_GUARD | PAGE_IS_ACCESSED)
#define PM_SCAN_FLAGS (PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC)
struct pagemap_scan_private {
@@ -2357,8 +2357,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p,
categories = PAGE_IS_PRESENT;
- if (!pte_uffd(pte))
- categories |= PAGE_IS_WRITTEN;
+ if (!pte_uffd(pte)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
if (p->masks_of_interest & PAGE_IS_FILE) {
page = vm_normal_page(vma, addr, pte);
@@ -2375,8 +2379,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p,
categories = PAGE_IS_SWAPPED;
- if (!pte_swp_uffd_any(pte))
- categories |= PAGE_IS_WRITTEN;
+ if (!pte_swp_uffd_any(pte)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
entry = softleaf_from_pte(pte);
if (softleaf_is_guard_marker(entry))
@@ -2425,8 +2433,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p,
struct page *page;
categories |= PAGE_IS_PRESENT;
- if (!pmd_uffd(pmd))
- categories |= PAGE_IS_WRITTEN;
+ if (!pmd_uffd(pmd)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
if (p->masks_of_interest & PAGE_IS_FILE) {
page = vm_normal_page_pmd(vma, addr, pmd);
@@ -2440,8 +2452,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p,
categories |= PAGE_IS_SOFT_DIRTY;
} else {
categories |= PAGE_IS_SWAPPED;
- if (!pmd_swp_uffd(pmd))
- categories |= PAGE_IS_WRITTEN;
+ if (!pmd_swp_uffd(pmd)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
if (pmd_swp_soft_dirty(pmd))
categories |= PAGE_IS_SOFT_DIRTY;
@@ -2474,7 +2490,8 @@ static void make_uffd_wp_pmd(struct vm_area_struct *vma,
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
#ifdef CONFIG_HUGETLB_PAGE
-static unsigned long pagemap_hugetlb_category(pte_t pte)
+static unsigned long pagemap_hugetlb_category(struct vm_area_struct *vma,
+ pte_t pte)
{
unsigned long categories = PAGE_IS_HUGE;
@@ -2489,8 +2506,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte)
if (pte_present(pte)) {
categories |= PAGE_IS_PRESENT;
- if (!huge_pte_uffd(pte))
- categories |= PAGE_IS_WRITTEN;
+ if (!huge_pte_uffd(pte)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
if (!PageAnon(pte_page(pte)))
categories |= PAGE_IS_FILE;
if (is_zero_pfn(pte_pfn(pte)))
@@ -2500,8 +2521,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte)
} else {
categories |= PAGE_IS_SWAPPED;
- if (!pte_swp_uffd_any(pte))
- categories |= PAGE_IS_WRITTEN;
+ if (!pte_swp_uffd_any(pte)) {
+ if (userfaultfd_wp(vma))
+ categories |= PAGE_IS_WRITTEN;
+ if (userfaultfd_rwp(vma))
+ categories |= PAGE_IS_ACCESSED;
+ }
if (pte_swp_soft_dirty(pte))
categories |= PAGE_IS_SOFT_DIRTY;
}
@@ -2586,6 +2611,16 @@ static int pagemap_scan_test_walk(unsigned long start, unsigned long end,
bool wp_allowed = userfaultfd_wp_async(vma) &&
userfaultfd_wp_use_markers(vma);
+ /*
+ * PM_SCAN_WP_MATCHING is the atomic read-and-reset flavour of the
+ * scan and is implemented for the WP marker only. Reject it on
+ * VM_UFFD_RWP VMAs explicitly so userspace gets a clear error
+ * instead of a silently-skipped range; re-arming is done with
+ * UFFDIO_RWPROTECT(MODE_RWP).
+ */
+ if (userfaultfd_rwp(vma) && (p->arg.flags & PM_SCAN_WP_MATCHING))
+ return -EINVAL;
+
if (!wp_allowed) {
/* User requested explicit failure over wp-async capability */
if (p->arg.flags & PM_SCAN_CHECK_WPASYNC)
@@ -2773,7 +2808,8 @@ static int pagemap_scan_pmd_entry(pmd_t *pmd, unsigned long start,
goto flush_and_return;
}
- if (!p->arg.category_anyof_mask && !p->arg.category_inverted &&
+ if (userfaultfd_wp(vma) && !p->arg.category_anyof_mask &&
+ !p->arg.category_inverted &&
p->arg.category_mask == PAGE_IS_WRITTEN &&
p->arg.return_mask == PAGE_IS_WRITTEN) {
for (addr = start; addr < end; pte++, addr += PAGE_SIZE) {
@@ -2848,7 +2884,8 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask,
/* Go the short route when not write-protecting pages. */
pte = huge_ptep_get(walk->mm, start, ptep);
- categories = p->cur_vma_category | pagemap_hugetlb_category(pte);
+ categories = p->cur_vma_category |
+ pagemap_hugetlb_category(vma, pte);
if (!pagemap_scan_is_interesting_page(categories, p))
return 0;
@@ -2860,7 +2897,7 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask,
ptl = huge_pte_lock(hstate_vma(vma), vma->vm_mm, ptep);
pte = huge_ptep_get(walk->mm, start, ptep);
- categories = p->cur_vma_category | pagemap_hugetlb_category(pte);
+ categories = p->cur_vma_category | pagemap_hugetlb_category(vma, pte);
if (!pagemap_scan_is_interesting_page(categories, p))
goto out_unlock;
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 13f71202845e..c4aeaa0c31c7 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -455,6 +455,7 @@ typedef int __bitwise __kernel_rwf_t;
#define PAGE_IS_HUGE (1 << 6)
#define PAGE_IS_SOFT_DIRTY (1 << 7)
#define PAGE_IS_GUARD (1 << 8)
+#define PAGE_IS_ACCESSED (1 << 9)
/*
* struct page_region - Page region with flags
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index 24ddf7bc4f25..f0a26309b6d5 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -364,6 +364,7 @@ typedef int __bitwise __kernel_rwf_t;
#define PAGE_IS_HUGE (1 << 6)
#define PAGE_IS_SOFT_DIRTY (1 << 7)
#define PAGE_IS_GUARD (1 << 8)
+#define PAGE_IS_ACCESSED (1 << 9)
/*
* struct page_region - Page region with flags
--
2.51.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox