* Re: [PATCH] PM: sleep: Allow disabling DPM watchdog by default
From: Tomasz Figa @ 2026-06-02 12:57 UTC (permalink / raw)
To: Tzung-Bi Shih
Cc: Rafael J. Wysocki, Jonathan Corbet, Greg Kroah-Hartman,
Danilo Krummrich, Shuah Khan, Pavel Machek, Len Brown, linux-doc,
linux-kernel, linux-pm, driver-core
In-Reply-To: <ah607VQjZeVKqOP0@google.com>
On Tue, Jun 2, 2026 at 7:48 PM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
>
> On Tue, Jun 02, 2026 at 05:37:13PM +0900, Tomasz Figa wrote:
> > On Tue, Jun 2, 2026 at 11:09 AM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
> > >
> > > On Mon, Jun 01, 2026 at 08:39:42PM +0200, Rafael J. Wysocki wrote:
> > > > On Thu, May 28, 2026 at 12:32 PM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
> > > > > diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> > > > > index e1b550664bab..4f92905f3edf 100644
> > > > > --- a/drivers/base/power/main.c
> > > > > +++ b/drivers/base/power/main.c
> > > > > @@ -527,6 +527,20 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644);
> > > > > MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace,
> > > > > "Backtrace all CPUs on DPM watchdog timeout");
> > > > >
> > > > > +#ifdef CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED
> > > > > +static unsigned int __read_mostly dpm_watchdog_enabled = 1;
> > > > > +#else
> > > > > +static unsigned int __read_mostly dpm_watchdog_enabled;
> > > > > +#endif
> > > > > +
> > > > > +static int __init dpm_watchdog_setup(char *str)
> > > > > +{
> > > > > + if (kstrtouint(str, 0, &dpm_watchdog_enabled) == 0)
> > > > > + return 1;
> > > > > + return 0;
> > > > > +}
> > > > > +__setup("dpm_watchdog_enabled=", dpm_watchdog_setup);
> > > >
> > > > You might as well use a module parameter to allow this to be set or
> > > > clear at run time. Is there a particular reason why you only want it
> > > > to be enabled or disabled via the kernel command line?
> > >
> > > Thanks for the suggestion. Mainly because in our use cases, we only need
> > > to set it once at boot time.
> > >
> > > Also, I was wondering if we need to consider potential races if the flag
> > > can be set at runtime. E.g.:
> > > 1) The flag is set.
> > > 2) dpm_watchdog_set() is called and the timer is started.
> > > 3) The flag is then unset.
> > > 4) The subsequent dpm_watchdog_clear() isn't stop the timer.
> > >
> > > Given this, would you still suggest providing the module parameter for
> > > completeness?
> >
> > Would that lead to anything bad, though? If I'm remembering correctly,
> > would that mean that any already ongoing PM operations would still be
> > subject to the timer, but not any new ones.
>
> I was overthinking the race. All userspace programs should be frozen when
> the DPM watchdog is active. The race I was concerned about isn't possible.
Right. Somehow I was convinced that the DPM watchdog is also used for
runtime PM, but after checking the code it doesn't look like so.
>
> Would you still suggest providing the module parameter for completeness?
I'd also vote for a module parameter. Being able to toggle this at
runtime would be useful for troubleshooting problems without needing
to change the command line arguments (which isn't trivial on some
systems).
>
> >
> > My suggestion would also be to actually make the timeout and warning
> > timeout configurable at runtime.
>
> I'm working on that and I think that should be in a separate patch.
Great, thanks. I'll leave the splitting and other logistics to the maintainers.
Best,
Tomasz
^ permalink raw reply
* Re: [PATCH v3] cpu/hotplug: Fix NULL kobject warning in cpuhp_smt_enable()
From: Will Deacon @ 2026-06-02 12:47 UTC (permalink / raw)
To: Jinjie Ruan
Cc: catalin.marinas, corbet, skhan, punit.agrawal, jic23,
osama.abdelkader, chenl311, fengchengwen, suzuki.poulose, maz,
lpieralisi, timothy.hayes, sascha.bischoff, arnd,
mrigendra.chaubey, pierre.gondois, dietmar.eggemann, yangyicong,
sudeep.holla, linux-arm-kernel, linux-doc, linux-kernel
In-Reply-To: <37bc3f0c-adeb-41b8-9b26-fabe43f47e6a@huawei.com>
On Tue, Jun 02, 2026 at 08:14:26PM +0800, Jinjie Ruan wrote:
> On 6/2/2026 7:09 PM, Will Deacon wrote:
> > Please can you check the Sashiko review comment?
> >
> > https://sashiko.dev/#/patchset/20260520022023.126670-1-ruanjinjie@huawei.com
>
> On arm64, arch_unregister_cpu() enforces a safety block against physical
> CPU hot-removal by aborting early if cpu_present() is true but the
> device is no longer physically present, thereby skipping
> unregister_cpu() and leaking the sysfs device node.
> If acpi_unmap_cpu() blindly clears the present bit while the sysfs
> device remains registered, a subsequent hot-add attempt will see the
> valid leaked device pointer, skip acpi_processor_hotadd_init() (and thus
> skip acpi_map_cpu()), leaving the hot-added CPU permanently absent and
> deadlocked.
>
> Hi, Will,
>
> what do you think about fix it like this?
>
> --- a/arch/arm64/kernel/acpi.c
> +++ b/arch/arm64/kernel/acpi.c
> @@ -455,7 +455,6 @@ EXPORT_SYMBOL(acpi_map_cpu);
>
> int acpi_unmap_cpu(int cpu)
> {
> - set_cpu_present(cpu, false);
> return 0;
> }
> EXPORT_SYMBOL(acpi_unmap_cpu);
> diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
> index 5932e5b30b71..507c6d761434 100644
> --- a/arch/arm64/kernel/smp.c
> +++ b/arch/arm64/kernel/smp.c
> @@ -554,6 +554,7 @@ void arch_unregister_cpu(int cpu)
> }
>
> unregister_cpu(c);
> + set_cpu_present(cpu, false);
> }
> #endif /* CONFIG_ACPI_HOTPLUG_CPU */
Hmm, not sure. Doesn't that break error handling cleanup paths that
expect acpi_unmap_cpu() to undo acpi_map_cpu()? See
acpi_processor_hotadd_init(), for example.
Will
^ permalink raw reply
* Re: [PATCH v3] cpu/hotplug: Fix NULL kobject warning in cpuhp_smt_enable()
From: Jinjie Ruan @ 2026-06-02 12:14 UTC (permalink / raw)
To: Will Deacon
Cc: catalin.marinas, corbet, skhan, punit.agrawal, jic23,
osama.abdelkader, chenl311, fengchengwen, suzuki.poulose, maz,
lpieralisi, timothy.hayes, sascha.bischoff, arnd,
mrigendra.chaubey, pierre.gondois, dietmar.eggemann, yangyicong,
sudeep.holla, linux-arm-kernel, linux-doc, linux-kernel
In-Reply-To: <ah65zXlOH6a9geD9@willie-the-truck>
On 6/2/2026 7:09 PM, Will Deacon wrote:
> On Wed, May 20, 2026 at 10:20:23AM +0800, Jinjie Ruan wrote:
>> On arm64, when booting with `maxcpus` greater than the number of present
>> CPUs (e.g., QEMU -smp cpus=4,maxcpus=8), some CPUs are marked as 'present'
>> but have not yet been registered via register_cpu(). Consequently,
>> the per-cpu device objects for these CPUs are not yet initialized.
>>
>> In cpuhp_smt_enable(), the code iterates over all present CPUs. Calling
>> _cpu_up() for these unregistered CPUs eventually leads to
>> sysfs_create_group() being called with a NULL kobject (or a kobject
>> without a directory), triggering the following warning in
>> fs/sysfs/group.c:
>>
>> if (WARN_ON(!kobj || (!update && !kobj->sd)))
>> return -EINVAL;
>>
>> When booting with ACPI, arm64 smp_prepare_cpus() currently sets all
>> enumerated CPUs as "present" regardless of their status in the MADT. This
>> causes issues with SMT hotplug control. For instance, with QEMU's
>> "-smp 4,maxcpus=8" configuration, the MADT GICC entries are populated as
>> follows: the first four CPUs are marked Enabled while the remaining four
>> are marked Online Capable to support potential hot-plugging.
>>
>> Fix this by:
>>
>> 1. When booting with ACPI, checking the ACPI_MADT_ENABLED flag in the GICC
>> entry before calling set_cpu_present() during SMP initialization.
>>
>> 2. Properly managing the present mask in acpi_map_cpu() and
>> acpi_unmap_cpu() to support actual CPU hotplug events, This aligns with
>> other architectures like x86 and LoongArch.
>>
>> 3. Update the arm64 CPU hotplug documentation to no longer state that all
>> online-capable vCPUs are marked as present by the kernel at boot time.
>>
>> This ensures that only physically available or explicitly enabled CPUs
>> are in the present mask, keeping the SMT control logic consistent with
>> the actual hardware state.
>
> Please can you check the Sashiko review comment?
>
> https://sashiko.dev/#/patchset/20260520022023.126670-1-ruanjinjie@huawei.com
On arm64, arch_unregister_cpu() enforces a safety block against physical
CPU hot-removal by aborting early if cpu_present() is true but the
device is no longer physically present, thereby skipping
unregister_cpu() and leaking the sysfs device node.
If acpi_unmap_cpu() blindly clears the present bit while the sysfs
device remains registered, a subsequent hot-add attempt will see the
valid leaked device pointer, skip acpi_processor_hotadd_init() (and thus
skip acpi_map_cpu()), leaving the hot-added CPU permanently absent and
deadlocked.
Hi, Will,
what do you think about fix it like this?
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -455,7 +455,6 @@ EXPORT_SYMBOL(acpi_map_cpu);
int acpi_unmap_cpu(int cpu)
{
- set_cpu_present(cpu, false);
return 0;
}
EXPORT_SYMBOL(acpi_unmap_cpu);
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 5932e5b30b71..507c6d761434 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -554,6 +554,7 @@ void arch_unregister_cpu(int cpu)
}
unregister_cpu(c);
+ set_cpu_present(cpu, false);
}
#endif /* CONFIG_ACPI_HOTPLUG_CPU */
>
> Cheers,
>
> Will
>
^ permalink raw reply related
* Re: [RFC PATCH v1 00/13] exec: add spawn templates for repeated executable startup
From: Li Chen @ 2026-06-02 12:07 UTC (permalink / raw)
To: Andy Lutomirski
Cc: Christian Brauner, Kees Cook, Alexander Viro, linux-fsdevel,
linux-api, linux-kernel, linux-mm, linux-arch, linux-doc,
linux-kselftest, x86, Arnd Bergmann, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
Jonathan Corbet, Shuah Khan
In-Reply-To: <CALCETrXqWcqn_79sMKnkyKOSAjg4AmcSHsuyH83oW8zJFoV6Dw@mail.gmail.com>
Hi Andy,
---- On Fri, 29 May 2026 02:27:00 +0800 Andy Lutomirski <luto@kernel.org> wrote ---
> On Thu, May 28, 2026 at 2:55 AM Li Chen <me@linux.beauty> wrote:
> >
>
> >
> > The template pins the executable and denies writes to that file while the
> > template fd is alive,
>
> Please don't. *Maybe* detect when it gets modified and clear your cache.
>
> Or develop a generic way to open a new fd that's an immutable view
> into an existing file such that the fd retains its contents even if
> the file changes. (Think a reflink that's not persistent and has no
> name -- you'll need some way to avoid resource exhaustion.)
I agree that deny-write is not a good long-term invalidation model. I had
considered clear-cache-on-modify, but kept this RFC smaller.
> >
> > Workload Calls subprocess spawn_template time_s Delta
> > (workers) calls calls/s calls/s seconds
> > 1x16 6144 411.04 420.32 14.95/14.62 +2.26%
> > 2x8 6144 666.78 690.08 9.21/8.90 +3.49%
> > 4x4 6144 955.61 1003.25 6.43/6.12 +4.99%
> > 8x2 6144 1048.25 1069.18 5.86/5.75 +2.00%
>
> This is a lot of complexity in the kernel for a teeny tiny gain.
>
> I'm with Christian -- a better spawn API would be great (and much
> faster than fork/vfork + exec), but that's a different patch.
Thanks, I agree. A pidfd/pidfs spawn builder looks like the much better API shape.
The cover letter numbers were from a mixed agent-tool workload. For very short
single-tool runs I saw larger wins, about +14% for printf-style work.
I should have called that out separately.
I will work toward a pidfd_config-style builder next.
Regards,
Li
^ permalink raw reply
* Re: [PATCH v3 0/2] arm64: cpufeature: Add WORKAROUND_DISABLE_CNP capability
From: Wei Xu @ 2026-06-02 11:54 UTC (permalink / raw)
To: Zeng Heng, vladimir.murzin, xuwei5, wangyushan12, yangyicong, maz,
yeoreum.yun, miko.lenczewski, james.clark, corbet, skhan,
kuninori.morimoto.gx, lucaswei, catalin.marinas, broonie,
lpieralisi, thuth, kevin.brodsky, tongtiangen, oupton,
ryan.roberts, mark.rutland, will, Sascha.Bischoff
Cc: linux-arm-kernel, wangkefeng.wang, linux-doc, linux-kernel,
xuwei5
In-Reply-To: <20260601112000.1145391-1-zengheng@huaweicloud.com>
Hi Zeng Heng,
On 2026/6/1 19:19, Zeng Heng wrote:
> From: Zeng Heng <zengheng4@huawei.com>
>
> v2: https://lore.kernel.org/all/20260529063132.766491-1-zengheng@huaweicloud.com/
> v1: https://lore.kernel.org/all/20260526015720.206854-1-zengheng@huaweicloud.com/
>
> Changes in v3:
> - Keep CONFIG_ARM64_WORKAROUND_DISABLE_CNP config and generalise
> ARM64_WORKAROUND_DISABLE_CNP capability.
>
> Changes in v2:
> - Unify CNP disable workaround into ARM64_WORKAROUND_DISABLE_CNP
>
> Zeng Heng (2):
> arm64: cpufeature: Add WORKAROUND_DISABLE_CNP capability
> arm64: kernel: Disable CNP on HiSilicon HIP09
>
> Documentation/arch/arm64/silicon-errata.rst | 2 ++
> arch/arm64/Kconfig | 20 ++++++++++++++++++++
> arch/arm64/include/asm/cpucaps.h | 4 ++--
> arch/arm64/kernel/cpu_errata.c | 17 ++++++++++++-----
> arch/arm64/kernel/cpufeature.c | 2 +-
> arch/arm64/tools/cpucaps | 2 +-
> 6 files changed, 38 insertions(+), 9 deletions(-)
>
> --
> 2.43.0
>
> .
>
Thanks, all looks good to me and tested with the 7.1.0-rc1 kernel.
Acked-by: Wei Xu <xuwei5@hisilicon.com>
Tested-by: Wei Xu <xuwei5@hisilicon.com>
Best Regards,
Wei
^ permalink raw reply
* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-06-02 11:33 UTC (permalink / raw)
To: Yosry Ahmed
Cc: akpm, tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <ah4ZZGl7GYJf54Wz@google.com>
On 2026/6/2 08:31, Yosry Ahmed wrote:
> On Mon, Jun 01, 2026 at 07:07:45PM +0800, Hao Jia wrote:
>>
>>
>> On 2026/5/30 09:24, Yosry Ahmed wrote:
>>> On Tue, May 26, 2026 at 07:45:58PM +0800, Hao Jia wrote:
>>>> From: Hao Jia <jiahao1@lixiang.com>
>>>>
>>>> The zswap background writeback worker shrink_worker() uses a global
>>>> cursor zswap_next_shrink, protected by zswap_shrink_lock, to round-robin
>>>> across the online memcgs under root_mem_cgroup.
>>>>
>>>> Proactive writeback also wants a similar per-memcg cursor that is
>>>> scoped to the specified memcg, so that repeated invocations against
>>>> the same memcg make forward progress across its descendant memcgs
>>>> instead of restarting from the first child memcg each time.
>>>
>>> Is this a problem in practice?
>>>
>>> Is the concern the overhead of scanning memcgs repeatedly, or lack of
>>> fairness? I wonder if we should just do writeback in batches from all
>>> memcgs, similar to how reclaim does it, then evaluate at the end if we
>>> need to start over?
>>>
>>
>> Not using a per-cgroup cursor will cause issues for "repeated small-budget
>> calls" cases. For example, repeatedly triggering a 2MB writeback might
>> result in only writing back pages from the first few child memcgs every
>> time. In the worst-case scenario (where the writeback amount is less than
>> WB_BATCH), it might only ever write back from the first child memcg.
>
> Right, so a fairness concern?
>
> I wonder if we should just reclaim a batch from each memcg, then check
> if we reached the goal, otherwise start over. If the batch size is small
> enough that should work?
Even with a small batch size, for small writeback requests triggered by
user-space (e.g., 2MB, which is batch size * N), it might still
repeatedly write back from only the first N child memcgs. This could
cause the user-space agent to prematurely give up on zswap writeback.
>
>>
>> Similar to how memory reclaim uses mem_cgroup_iter() (via struct
>> mem_cgroup_reclaim_iter) and the old shrink_worker() used zswap_next_shrink,
>> we need a shared cursor here.
>
> Right, I understand that in theory we need a cursor. I am just wondering
> if the complexity is justified in practice. Reclaim is a much larger
> beast than zswap writeback. I wonder if we can just get away with
> scanning a batch from each child memcg -- for per-memcg reclaim, not
> global.
>
> We can always improve it later with a cursor if there's an actual need.
>
>>
>>
>>>>
>>>> Naturally, group the cursor and its protecting spinlock into a
>>>> zswap_wb_iter struct, and make it a member of struct mem_cgroup to
>>>> realize per-memcg cursor management. Accordingly, shrink_worker() now
>>>> uses the lock and cursor in root_mem_cgroup->zswap_wb_iter.
>>>
>>> If we really need to have per-memcg cursors (I am not a big fan), I
>>> think we can minimize the overhead by making the cursor updates use
>>> atomic cmpxchg instead of having a per-memcg lock.
>>>
>>
>> Because mem_cgroup_iter() always calls css_put(&prev->css), we cannot simply
>> update zswap_wb_iter.pos via cmpxchg() after calling it. Doing so could lead
>> to a double css_put() issue on prev->css.
>>
>> Therefore, if we switch to the cmpxchg() approach, we wouldn't be able to
>> reuse the existing mem_cgroup_iter() logic. We would have to write a new
>> function similar to cgroup_iter(), and its implementation might end up
>> looking a bit obscure/complex.
>
> What if we do something like this (for the global cursor):
>
> do {
> memcg = xchg(zswap_next_shrink, NULL);
> memcg = mem_cgroup_iter(NULL, memcg, NULL);
> /* If the cursor was advanced from under us, try again */
> if (!try_cmpxchg(zswap_next_shrink, NULL, memcg))
> continue;
> } while (..);
>
>
Regarding the code above, IIRC, both the global and per-cgroup cursors
suffer from race conditions. This race can cause mem_cgroup_iter(NULL,
NULL, NULL) to return the root memcg or its descendants, leading zswap
to write back pages from the wrong memcg.
Additionally, since mem_cgroup_iter() puts the prev memcg ref and gets
the next memcg ref, a try_cmpxchg() failure on CPU1 might also lead to a
ref leak for memcg1.
CPU1 CPU2
memcg1 = xchg(pos, NULL)
memcg2 = xchg(pos, NULL) memcg2 = NULL;
memcg1 = mem_cgroup_iter()
mem_cgroup_iter(NULL, **NULL**, NULL) error memcg
try_cmpxchg(pos,NULL,memcg2) succeed
try_cmpxchg(pos,NULL,memcg1) **fail**
I took a stab at implementing a cmpxchg()-based zswap_mem_cgroup_iter()
modeled after mem_cgroup_iter(), and it actually doesn't look that
complex after all :)
Of course, as Nhat mentioned, we definitely need to add plenty of
comments for this function.
static struct mem_cgroup *zswap_mem_cgroup_iter(struct mem_cgroup *root)
{
struct cgroup_subsys_state *css;
struct mem_cgroup *pos, *next;
if (mem_cgroup_disabled())
return NULL;
if (!root)
root = root_mem_cgroup;
rcu_read_lock();
restart:
pos = READ_ONCE(root->zswap_wb_iter.pos);
css = pos ? &pos->css : NULL;
next = NULL;
while ((css = css_next_descendant_pre(css, &root->css))) {
if (css_tryget_online(css))
break;
}
next = css ? mem_cgroup_from_css(css) : NULL;
if (cmpxchg(&root->zswap_wb_iter.pos, pos, next) != pos) {
if (next)
css_put(&next->css);
goto restart;
}
rcu_read_unlock();
return next;
}
> There is a window where a racing shrinker will see the cursor as NULL
> and start over, but that should be fine. We can generalize this for the
> per-memcg cursor.
>
> That being said..
>
>>
>> Currently, this lock is only used in shrink_memcg(), proactive writeback,
>> and mem_cgroup_css_offline(). Note that shrink_memcg() only acquires the
>> lock of the root cgroup, and mem_cgroup_css_offline() is unlikely to be a
>> hot path.
>
> ..this made me realize it's probably fine to just use a global lock for
> now?
>
> IIUC the only additional contention to the existing lock will be from
> userspace proactive writeback, and that shouldn't be a big deal
> especially with the critical section being short?
>
In the current patch implementation, this lock protects the cgroup's own
cursor variable. During each writeback, we only acquire the spin_lock of
the target cgroup itself; we do not attempt to **spin on any child
cgroup's lock while iterating through the descendants**.
Specifically:
- shrink_memcg() will only attempt to acquire the root cgroup's lock
throughout the entire process.
- Proactive writeback will only acquire the lock of the target cgroup
**itself**.
- Only mem_cgroup_css_offline() might attempt to hold locks of other
cgroups, but normally, this shouldn't be a hot path.
Therefore, even if proactive writebacks are triggered concurrently on a
parent cgroup and its child cgroup, there will be **no** lock contention
at all (specifically referring to zswap_wb_iter.lock).
Lock contention would only occur if user-space **concurrently** triggers
proactive writeback on the exact **same** cgroup. And IIRC, in such a
scenario, the bottleneck is more likely to be on other locks anyway.
>>
>> So, should we keep the spin_lock or go with the cmpxchg() approach?
>> Yosry and Nhat, what are your thoughts on this?
>
> I think we should experiment with the global lock first. See if you
> observe any regressions with workloads that put a lot of pressure on the
> lock (a lot of threads in reclaim doing writeback + a few userspace
> threads doing proactive writeback). See if the userspace threads
> actually cause a meaningful regression.
Sorry, it seems there are some implementation issues with the global
lock approach.
In practice, our user-space agent mostly operates in the following two
scenarios:
- Triggering proactive writeback on the same cgroup at different times
(sequentially).
- Triggering proactive writeback on different cgroups at the same time
(concurrently).
In both cases, there is no lock contention. So, the current lock works
perfectly fine for us.
However, if we really hate zswap_wb_iter.lock, I can try replacing it
with the cmpxchg() approach.
Thanks,
Hao
^ permalink raw reply
* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-06-02 11:32 UTC (permalink / raw)
To: Nhat Pham
Cc: Yosry Ahmed, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=NoQNXOMDD0uTSOPWHQX-CMNU1dw=zEuFj=eLcS3fB-ow@mail.gmail.com>
On 2026/6/2 01:08, Nhat Pham wrote:
> On Mon, Jun 1, 2026 at 4:07 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
>>
>>
>>
>> On 2026/5/30 09:24, Yosry Ahmed wrote:
>>> On Tue, May 26, 2026 at 07:45:58PM +0800, Hao Jia wrote:
>>>> From: Hao Jia <jiahao1@lixiang.com>
>>>>
>>>> The zswap background writeback worker shrink_worker() uses a global
>>>> cursor zswap_next_shrink, protected by zswap_shrink_lock, to round-robin
>>>> across the online memcgs under root_mem_cgroup.
>>>>
>>>> Proactive writeback also wants a similar per-memcg cursor that is
>>>> scoped to the specified memcg, so that repeated invocations against
>>>> the same memcg make forward progress across its descendant memcgs
>>>> instead of restarting from the first child memcg each time.
>>>
>>> Is this a problem in practice?
>>>
>>> Is the concern the overhead of scanning memcgs repeatedly, or lack of
>>> fairness? I wonder if we should just do writeback in batches from all
>>> memcgs, similar to how reclaim does it, then evaluate at the end if we
>>> need to start over?
>>>
>>
>> Not using a per-cgroup cursor will cause issues for "repeated
>> small-budget calls" cases. For example, repeatedly triggering a 2MB
>> writeback might result in only writing back pages from the first few
>> child memcgs every time. In the worst-case scenario (where the writeback
>> amount is less than WB_BATCH), it might only ever write back from the
>> first child memcg.
>>
>> Similar to how memory reclaim uses mem_cgroup_iter() (via struct
>> mem_cgroup_reclaim_iter) and the old shrink_worker() used
>> zswap_next_shrink, we need a shared cursor here.
>
> I think each proactive reclaim invocation just walk the entire subtree
> for page reclaim right (see shrink_node_memcgs())? Would that be
> acceptable for you?
Our current approach is very similar to how proactive memory reclaim
works in shrink_node_memcgs().
shrink_node_memcgs() first calls memcg = mem_cgroup_iter(target_memcg,
NULL, partial);. By doing this, it uses
target_memcg->nodeinfo[nid]->iter->position to retrieve the child memcg
where the last reclaim left off, and then resumes the iteration.
The catch is that zswap can't just reuse
memcg->nodeinfo[nid]->iter->position, as that would mess up the cursor
used by the memory reclaim.
>
> I also wonder if we can at least make this structure dynamically
> allocated... In a system, you only really invoke proactive reclaim
> against a few target cgroups, no?
It is possible to allocate it dynamically, but I am concerned that it
might introduce a slight performance overhead. We would need to add a
**check** like if (READ_ONCE(memcg->zswap_wb_iter)) every time
zswap_mem_cgroup_iter() is called. Furthermore, to handle concurrent
allocations, we might also need to introduce cmpxchg() to resolve race
conditions.
The additional code would look something like this:
static struct zswap_wb_iter *get_zswap_wb_iter(struct mem_cgroup *memcg)
{
struct zswap_wb_iter *iter, *new_iter;
iter = READ_ONCE(memcg->zswap_wb_iter);
if (likely(iter))
return iter;
new_iter = kzalloc(sizeof(*new_iter), GFP_KERNEL);
if (!new_iter)
return NULL;
spin_lock_init(&new_iter->lock);
if (cmpxchg(&memcg->zswap_wb_iter, NULL, new_iter) != NULL) {
/* Lost the race, someone else installed first. */
kfree(new_iter);
}
return READ_ONCE(memcg->zswap_wb_iter);
}
Thanks,
Hao
^ permalink raw reply
* Re: [PATCH v6 11/12] ima: Support staging and deleting N measurements records
From: Roberto Sassu @ 2026-06-02 11:22 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-12-roberto.sassu@huaweicloud.com>
On Tue, 2026-06-02 at 13:14 +0200, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
>
> Add support for sending a value N between 1 and ULONG_MAX to the IMA
> original measurement interface. This value represents the number of
> measurements that should be deleted from the current measurements list. In
> this case, measurements are staged in an internal non-user visible list,
> and immediately deleted.
>
> This staging method allows the remote attestation agents to easily separate
> the measurements that were verified (staged and deleted) from those that
> weren't due to the race between taking a TPM quote and reading the
> measurements list.
>
> In order to minimize the locking time of ima_extend_list_mutex, deleting
> N records is realized by doing a lockless walk in the current measurements
> list to determine the N-th entry to cut, to cut the current measurements
> list under the lock, and by deleting the excess records after releasing the
> lock.
>
> Flushing the hash table is not supported for N records, since it would
> require removing the N records one by one from the hash table under the
> ima_extend_list_mutex lock, which would increase the locking time.
>
> Link: https://github.com/linux-integrity/linux/issues/1
> Co-developed-by: Steven Chen <chenste@linux.microsoft.com>
Hi Steven
I did see your tags, but since I added a Co-developed-by, probably they
are redundant. If you agree, please reply with your Signed-off-by, so
that the tags are complete.
Thanks
Roberto
> Co-developed-by: Roberto Sassu <roberto.sassu@huawei.com>
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
> security/integrity/ima/Kconfig | 3 ++
> security/integrity/ima/ima.h | 1 +
> security/integrity/ima/ima_fs.c | 32 +++++++++++++--
> security/integrity/ima/ima_queue.c | 63 ++++++++++++++++++++++++++++++
> 4 files changed, 96 insertions(+), 3 deletions(-)
>
> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> index 02436670f746..f4d25e045808 100644
> --- a/security/integrity/ima/Kconfig
> +++ b/security/integrity/ima/Kconfig
> @@ -341,6 +341,9 @@ config IMA_STAGING
> It allows user space to stage the measurements list for deletion and
> to delete the staged measurements after confirmation.
>
> + Or, alternatively, it allows user space to specify N measurements
> + records to stage internally, so that they can be immediately deleted.
> +
> On kexec, staging is aborted and any staged measurement records are
> copied to the secondary kernel.
>
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index d2e740c8ff75..7a1b2d6a8b59 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -320,6 +320,7 @@ struct ima_template_desc *lookup_template_desc(const char *name);
> bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> int ima_queue_stage(void);
> int ima_queue_staged_delete_all(void);
> +int ima_queue_delete_partial(unsigned long req_value);
> int ima_restore_measurement_entry(struct ima_template_entry *entry);
> int ima_restore_measurement_list(loff_t bufsize, void *buf);
> int ima_measurements_show(struct seq_file *m, void *v);
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 96d7503a605b..174a94740da1 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -28,6 +28,7 @@
> * Requests:
> * 'A\n': stage the entire measurements list
> * 'D\n': delete all staged measurements
> + * '[1, ULONG_MAX]\n' delete N measurements records
> */
> #define STAGED_REQ_LENGTH 21
>
> @@ -343,6 +344,7 @@ static ssize_t _ima_measurements_write(struct file *file,
> loff_t *ppos, bool staged_interface)
> {
> char req[STAGED_REQ_LENGTH];
> + unsigned long req_value;
> int ret;
>
> if (datalen < 2 || datalen > STAGED_REQ_LENGTH)
> @@ -370,7 +372,24 @@ static ssize_t _ima_measurements_write(struct file *file,
> ret = ima_queue_staged_delete_all();
> break;
> default:
> - ret = -EINVAL;
> + if (staged_interface)
> + return -EINVAL;
> +
> + if (ima_flush_htable) {
> + pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
> + return -EINVAL;
> + }
> +
> + ret = kstrtoul(req, 10, &req_value);
> + if (ret < 0)
> + return ret;
> +
> + if (req_value == 0) {
> + pr_debug("Must delete at least one entry\n");
> + return -EINVAL;
> + }
> +
> + ret = ima_queue_delete_partial(req_value);
> }
>
> if (ret < 0)
> @@ -379,6 +398,12 @@ static ssize_t _ima_measurements_write(struct file *file,
> return datalen;
> }
>
> +static ssize_t ima_measurements_write(struct file *file, const char __user *buf,
> + size_t datalen, loff_t *ppos)
> +{
> + return _ima_measurements_write(file, buf, datalen, ppos, false);
> +}
> +
> static ssize_t ima_measurements_staged_write(struct file *file,
> const char __user *buf,
> size_t datalen, loff_t *ppos)
> @@ -389,6 +414,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
> static const struct file_operations ima_measurements_ops = {
> .open = ima_measurements_open,
> .read = seq_read,
> + .write = ima_measurements_write,
> .llseek = seq_lseek,
> .release = ima_measurements_release,
> };
> @@ -470,6 +496,7 @@ static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
> static const struct file_operations ima_ascii_measurements_ops = {
> .open = ima_ascii_measurements_open,
> .read = seq_read,
> + .write = ima_measurements_write,
> .llseek = seq_lseek,
> .release = ima_measurements_release,
> };
> @@ -603,14 +630,13 @@ static int __init create_securityfs_measurement_lists(bool staging)
> {
> const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
> const struct file_operations *binary_ops = &ima_measurements_ops;
> - umode_t permissions = (S_IRUSR | S_IRGRP);
> + umode_t permissions = (S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP);
> const char *file_suffix = "";
> int count = NR_BANKS(ima_tpm_chip);
>
> if (staging) {
> ascii_ops = &ima_ascii_measurements_staged_ops;
> binary_ops = &ima_measurements_staged_ops;
> - permissions |= (S_IWUSR | S_IWGRP);
> file_suffix = "_staged";
> }
>
> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> index af0502f27d57..718991ba8bcd 100644
> --- a/security/integrity/ima/ima_queue.c
> +++ b/security/integrity/ima/ima_queue.c
> @@ -405,6 +405,69 @@ int ima_queue_staged_delete_all(void)
> return 0;
> }
>
> +/**
> + * ima_queue_delete_partial - Delete current measurements
> + * @req_value: Number of measurements to delete
> + *
> + * Delete the requested number of measurements from the current measurements
> + * list, and update the number of records and the binary run-time size
> + * accordingly.
> + *
> + * Refuse to delete current measurements if measurement is suspended, so that
> + * dump can be done in a lockless way and user space is notified about current
> + * measurements being carried over to the secondary kernel, so that it does not
> + * save them twice.
> + *
> + * Return: Zero on success, a negative value otherwise.
> + */
> +int ima_queue_delete_partial(unsigned long req_value)
> +{
> + unsigned long req_value_copy = req_value;
> + unsigned long size_to_remove = 0, num_to_remove = 0;
> + LIST_HEAD(ima_measurements_trim);
> + struct ima_queue_entry *qe;
> + int ret = 0;
> +
> + /*
> + * list_for_each_entry_rcu() without rcu_read_lock() is fine because
> + * only list append can happen concurrently. No list replace due to the
> + * staging/delete writers mutual exclusion.
> + */
> + list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
> + size_to_remove += get_binary_runtime_size(qe->entry);
> + num_to_remove++;
> +
> + if (--req_value_copy == 0)
> + break;
> + }
> +
> + /* Not enough records to delete. */
> + if (req_value_copy > 0)
> + return -ENOENT;
> +
> + mutex_lock(&ima_extend_list_mutex);
> + if (ima_measurements_suspended) {
> + mutex_unlock(&ima_extend_list_mutex);
> + return -ESTALE;
> + }
> +
> + /*
> + * qe remains valid because ima_fs.c enforces single-writer exclusion.
> + */
> + __list_cut_position(&ima_measurements_trim, &ima_measurements,
> + &qe->later);
> +
> + atomic_long_sub(num_to_remove, &ima_num_records[BINARY]);
> +
> + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> + binary_runtime_size[BINARY] -= size_to_remove;
> +
> + mutex_unlock(&ima_extend_list_mutex);
> +
> + ima_queue_delete(&ima_measurements_trim, false);
> + return ret;
> +}
> +
> /**
> * ima_queue_delete - Delete measurements
> * @head: List head measurements are deleted from
^ permalink raw reply
* [PATCH v6 12/12] doc: security: Add documentation of exporting and deleting IMA measurements
From: Roberto Sassu @ 2026-06-02 11:14 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Add the documentation of exporting and deleting IMA measurements in
Documentation/security/IMA-export-delete.rst.
Also add the missing Documentation/security/IMA-templates.rst file in
MAINTAINERS.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
Documentation/security/IMA-export-delete.rst | 190 +++++++++++++++++++
Documentation/security/index.rst | 1 +
MAINTAINERS | 2 +
3 files changed, 193 insertions(+)
create mode 100644 Documentation/security/IMA-export-delete.rst
diff --git a/Documentation/security/IMA-export-delete.rst b/Documentation/security/IMA-export-delete.rst
new file mode 100644
index 000000000000..a9e1d3f8ed47
--- /dev/null
+++ b/Documentation/security/IMA-export-delete.rst
@@ -0,0 +1,190 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==================================
+IMA Measurements Export and Delete
+==================================
+
+
+Introduction
+============
+
+The IMA measurements list is currently stored in the kernel memory. Memory
+occupation grows linearly with the number of records, and can become a
+problem especially in environments with reduced resources.
+
+While there is an advantage in keeping the IMA measurements list in kernel
+memory, so that it is always available for reading from the securityfs
+interfaces, storing it elsewhere would make it possible to free precious
+memory for other kernel usage.
+
+The IMA measurements list needs to be retained and safely stored for new
+attestation servers to validate it. Assuming the IMA measurements list is
+properly saved, storing it outside the kernel does not introduce security
+issues, since its integrity is anyway protected by the TPM.
+
+Hence, the new IMA staging mechanism is introduced to export IMA
+measurements to user space and delete them from kernel space.
+
+Staging consists in atomically moving the current measurements list to a
+temporary list, so that measurements can be deleted afterwards. The staging
+operation locks the hot path (racing with addition of new measurements) for
+a very short time, only for swapping the list pointers. Deletion of the
+measurements instead is done locklessly, away from the hot path.
+
+There are two flavors of the staging mechanism. In the staging with prompt,
+all current measurements are staged, read and deleted upon confirmation. In
+the staging and deleting flavor, N measurements are staged from the
+beginning of the current measurements list and immediately deleted without
+confirmation.
+
+
+Management of Staged Measurements
+=================================
+
+Since with the staging mechanism measurement records are removed from the
+kernel, the staged measurements need to be saved in a storage and
+concatenated together, so that they can be presented to remote attestation
+agents as if staging was never done. This task can be accomplished by a
+system service.
+
+Coordination is necessary in the case where there are multiple actors
+requesting measurements to be staged.
+
+In the staging with prompt case, the measurement interfaces can be accessed
+only by one actor (writer) at a time, so the others will get an error until
+the former closes it. Since the actors don't care about N, when they gain
+access to the interface, they will get all the staged measurements at the
+time of their request.
+
+In the case of staging and deleting, coordination is more important, since
+there is the risk that two actors unaware of each other compute the value N
+on the current measurements list and request IMA to stage N twice.
+
+
+Remote Attestation Agent Workflow
+=================================
+
+Users can choose the staging method they find more appropriate for their
+workflow.
+
+If, as an example, a remote attestation agent would like to present to the
+remote attestation server only the measurements that are required to
+verify the TPM quote, its workflow would be the following.
+
+With staging with prompt, the agent stages the current measurements list,
+reads and stores the measurements in a storage and immediately requests
+IMA to delete the staged measurements from kernel memory. Afterwards, it
+calculates N by replaying the PCR extend on the stored measurements until
+the calculated PCRs match the quoted PCRs. It then keeps the measurements
+in excess for the next attestation request.
+
+At the next attestation request, the agent performs the same steps above,
+and concatenates the new measurements to the ones in excess from the
+previous request. Also in this case, the agent replays the PCR extend until
+it matches the currently quoted PCRs, keeps the measurements in excess and
+presents the new N measurement records to the remote attestation server.
+
+With the staging and deleting method, the agent reads the current
+measurements list, calculates N and requests IMA to delete only those. The
+measurements in excess are kept in the IMA measurements list and can be
+retrieved at the next remote attestation request.
+
+
+Usage
+=====
+
+The IMA staging mechanism can be enabled from the kernel configuration with
+the CONFIG_IMA_STAGING option. This option prevents inadvertently removing
+the IMA measurement list on systems which do not properly save it.
+
+If the option is enabled, IMA duplicates the current securityfs
+measurements interfaces (both binary and ASCII), by adding the ``_staged``
+file suffix. Both the original and the staging interfaces gain the write
+permission for the root user and group, but require the process to have
+CAP_SYS_ADMIN set.
+
+The staging mechanism supports two flavors.
+
+
+Staging with prompt
+~~~~~~~~~~~~~~~~~~~
+
+The current measurements list is moved to a temporary staging area,
+allowing it to be saved to external storage, before being deleted upon
+confirmation.
+
+This staging process is achieved with the following steps.
+
+ 1. ``echo A > <_staged interface>``: the user requests IMA to stage the
+ entire measurements list;
+ 2. ``cat <_staged interface>``: the user reads the staged measurements;
+ 3. ``echo D > <_staged interface>``: the user requests IMA to delete
+ staged measurements.
+
+
+Staging and deleting
+~~~~~~~~~~~~~~~~~~~~
+
+N measurements are staged to a temporary staging area, and immediately
+deleted without further confirmation.
+
+This staging process is achieved with the following steps.
+
+ 1. ``cat <original interface>``: the user reads the current measurements
+ list and determines what the value N for staging should be;
+ 2. ``echo N > <original interface>``: the user requests IMA to delete N
+ measurements from the current measurements list.
+
+
+Interface Access
+================
+
+In order to avoid the IMA measurements list being suddenly truncated by the
+staging mechanism during a read, or having multiple concurrent staging, a
+semaphore-like locking scheme has been implemented on all the measurements
+list interfaces.
+
+Multiple readers can access concurrently the original and staged
+interfaces, and they can be in mutual exclusion with one writer. In order
+to see the same state across all the measurement interfaces, the same
+writer is allowed to open multiple interfaces for write or read/write.
+
+If an illegal access occurs, the open to the measurements list interface is
+denied.
+
+
+Kexec
+=====
+
+In the event a kexec() system call occurs between staging and deleting, the
+staged measurement records are marshalled before the current measurements
+list, so that they are both available when the secondary kernel starts.
+
+If measurement is suspended before requesting to delete staged or current
+measurements, IMA returns an error to user space to let it know that
+marshalling is already in progress, so that it does not save the
+measurements twice.
+
+IMA also disallows staging when suspending measurement, to avoid the
+situation where neither measurements are carried over to the secondary
+kernel, nor they are saved by user space to the storage.
+
+
+Hash table
+==========
+
+By default, the template digest of staged measurement records are kept in
+kernel memory (only template data are freed), to be able to detect
+duplicate records independently of staging.
+
+The new kernel option ``ima_flush_htable`` has been introduced to
+explicitly request a complete deletion of the staged measurements, for
+maximum kernel memory saving. If the option has been specified, duplicate
+records are still avoided on records of the current measurements list,
+but there can be duplicates between different groups of staged
+measurements.
+
+Flushing the hash table is supported only for the staging with prompt
+flavor. For the staging and deleting flavor, it would have been necessary
+to lock the hot path adding new measurements for the time needed to remove
+each selected measurement individually.
diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
index 3e0a7114a862..00650dcf38cb 100644
--- a/Documentation/security/index.rst
+++ b/Documentation/security/index.rst
@@ -8,6 +8,7 @@ Security Documentation
credentials
snp-tdx-threat-model
IMA-templates
+ IMA-export-delete
keys/index
lsm
lsm-development
diff --git a/MAINTAINERS b/MAINTAINERS
index 461a3eed6129..70ff6bae3493 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12752,6 +12752,8 @@ R: Eric Snowberg <eric.snowberg@oracle.com>
L: linux-integrity@vger.kernel.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
+F: Documentation/security/IMA-export-delete.rst
+F: Documentation/security/IMA-templates.rst
F: include/linux/secure_boot.h
F: security/integrity/
F: security/integrity/ima/
--
2.43.0
^ permalink raw reply related
* [PATCH v6 11/12] ima: Support staging and deleting N measurements records
From: Roberto Sassu @ 2026-06-02 11:14 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Add support for sending a value N between 1 and ULONG_MAX to the IMA
original measurement interface. This value represents the number of
measurements that should be deleted from the current measurements list. In
this case, measurements are staged in an internal non-user visible list,
and immediately deleted.
This staging method allows the remote attestation agents to easily separate
the measurements that were verified (staged and deleted) from those that
weren't due to the race between taking a TPM quote and reading the
measurements list.
In order to minimize the locking time of ima_extend_list_mutex, deleting
N records is realized by doing a lockless walk in the current measurements
list to determine the N-th entry to cut, to cut the current measurements
list under the lock, and by deleting the excess records after releasing the
lock.
Flushing the hash table is not supported for N records, since it would
require removing the N records one by one from the hash table under the
ima_extend_list_mutex lock, which would increase the locking time.
Link: https://github.com/linux-integrity/linux/issues/1
Co-developed-by: Steven Chen <chenste@linux.microsoft.com>
Co-developed-by: Roberto Sassu <roberto.sassu@huawei.com>
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
security/integrity/ima/Kconfig | 3 ++
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_fs.c | 32 +++++++++++++--
security/integrity/ima/ima_queue.c | 63 ++++++++++++++++++++++++++++++
4 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 02436670f746..f4d25e045808 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -341,6 +341,9 @@ config IMA_STAGING
It allows user space to stage the measurements list for deletion and
to delete the staged measurements after confirmation.
+ Or, alternatively, it allows user space to specify N measurements
+ records to stage internally, so that they can be immediately deleted.
+
On kexec, staging is aborted and any staged measurement records are
copied to the secondary kernel.
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index d2e740c8ff75..7a1b2d6a8b59 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -320,6 +320,7 @@ struct ima_template_desc *lookup_template_desc(const char *name);
bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
int ima_queue_stage(void);
int ima_queue_staged_delete_all(void);
+int ima_queue_delete_partial(unsigned long req_value);
int ima_restore_measurement_entry(struct ima_template_entry *entry);
int ima_restore_measurement_list(loff_t bufsize, void *buf);
int ima_measurements_show(struct seq_file *m, void *v);
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 96d7503a605b..174a94740da1 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -28,6 +28,7 @@
* Requests:
* 'A\n': stage the entire measurements list
* 'D\n': delete all staged measurements
+ * '[1, ULONG_MAX]\n' delete N measurements records
*/
#define STAGED_REQ_LENGTH 21
@@ -343,6 +344,7 @@ static ssize_t _ima_measurements_write(struct file *file,
loff_t *ppos, bool staged_interface)
{
char req[STAGED_REQ_LENGTH];
+ unsigned long req_value;
int ret;
if (datalen < 2 || datalen > STAGED_REQ_LENGTH)
@@ -370,7 +372,24 @@ static ssize_t _ima_measurements_write(struct file *file,
ret = ima_queue_staged_delete_all();
break;
default:
- ret = -EINVAL;
+ if (staged_interface)
+ return -EINVAL;
+
+ if (ima_flush_htable) {
+ pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
+ return -EINVAL;
+ }
+
+ ret = kstrtoul(req, 10, &req_value);
+ if (ret < 0)
+ return ret;
+
+ if (req_value == 0) {
+ pr_debug("Must delete at least one entry\n");
+ return -EINVAL;
+ }
+
+ ret = ima_queue_delete_partial(req_value);
}
if (ret < 0)
@@ -379,6 +398,12 @@ static ssize_t _ima_measurements_write(struct file *file,
return datalen;
}
+static ssize_t ima_measurements_write(struct file *file, const char __user *buf,
+ size_t datalen, loff_t *ppos)
+{
+ return _ima_measurements_write(file, buf, datalen, ppos, false);
+}
+
static ssize_t ima_measurements_staged_write(struct file *file,
const char __user *buf,
size_t datalen, loff_t *ppos)
@@ -389,6 +414,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
+ .write = ima_measurements_write,
.llseek = seq_lseek,
.release = ima_measurements_release,
};
@@ -470,6 +496,7 @@ static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
static const struct file_operations ima_ascii_measurements_ops = {
.open = ima_ascii_measurements_open,
.read = seq_read,
+ .write = ima_measurements_write,
.llseek = seq_lseek,
.release = ima_measurements_release,
};
@@ -603,14 +630,13 @@ static int __init create_securityfs_measurement_lists(bool staging)
{
const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
const struct file_operations *binary_ops = &ima_measurements_ops;
- umode_t permissions = (S_IRUSR | S_IRGRP);
+ umode_t permissions = (S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP);
const char *file_suffix = "";
int count = NR_BANKS(ima_tpm_chip);
if (staging) {
ascii_ops = &ima_ascii_measurements_staged_ops;
binary_ops = &ima_measurements_staged_ops;
- permissions |= (S_IWUSR | S_IWGRP);
file_suffix = "_staged";
}
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index af0502f27d57..718991ba8bcd 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -405,6 +405,69 @@ int ima_queue_staged_delete_all(void)
return 0;
}
+/**
+ * ima_queue_delete_partial - Delete current measurements
+ * @req_value: Number of measurements to delete
+ *
+ * Delete the requested number of measurements from the current measurements
+ * list, and update the number of records and the binary run-time size
+ * accordingly.
+ *
+ * Refuse to delete current measurements if measurement is suspended, so that
+ * dump can be done in a lockless way and user space is notified about current
+ * measurements being carried over to the secondary kernel, so that it does not
+ * save them twice.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_delete_partial(unsigned long req_value)
+{
+ unsigned long req_value_copy = req_value;
+ unsigned long size_to_remove = 0, num_to_remove = 0;
+ LIST_HEAD(ima_measurements_trim);
+ struct ima_queue_entry *qe;
+ int ret = 0;
+
+ /*
+ * list_for_each_entry_rcu() without rcu_read_lock() is fine because
+ * only list append can happen concurrently. No list replace due to the
+ * staging/delete writers mutual exclusion.
+ */
+ list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
+ size_to_remove += get_binary_runtime_size(qe->entry);
+ num_to_remove++;
+
+ if (--req_value_copy == 0)
+ break;
+ }
+
+ /* Not enough records to delete. */
+ if (req_value_copy > 0)
+ return -ENOENT;
+
+ mutex_lock(&ima_extend_list_mutex);
+ if (ima_measurements_suspended) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ESTALE;
+ }
+
+ /*
+ * qe remains valid because ima_fs.c enforces single-writer exclusion.
+ */
+ __list_cut_position(&ima_measurements_trim, &ima_measurements,
+ &qe->later);
+
+ atomic_long_sub(num_to_remove, &ima_num_records[BINARY]);
+
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size[BINARY] -= size_to_remove;
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ ima_queue_delete(&ima_measurements_trim, false);
+ return ret;
+}
+
/**
* ima_queue_delete - Delete measurements
* @head: List head measurements are deleted from
--
2.43.0
^ permalink raw reply related
* [PATCH v6 10/12] ima: Add support for flushing the hash table when staging measurements
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
During staging and delete, measurements are not completely deallocated.
Their entry digest portion is kept and is still reachable with the hash
table to detect duplicate records. If the number of records is significant,
this reduces the memory saving benefit of staging.
Some users might be interested in achieving the best memory saving (the
measurements are completely deallocated) at the cost of having duplicate
records across the staged measurement lists. Duplicate records are still
avoided within the current measurement list.
Introduce the new kernel option ima_flush_htable to decide whether or not
the digests of staged measurement records are flushed from the hash table,
when they are deleted, to achieve the maximum memory saving.
When the option is enabled, replace the old hash table with a new one,
by calling ima_alloc_replace_htable(), and completely delete the
measurements records.
Note: This code derives from the Alt-IMA Huawei project, whose license is
GPL-2.0 OR MIT.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
.../admin-guide/kernel-parameters.txt | 6 +++
security/integrity/ima/ima.h | 1 +
security/integrity/ima/ima_queue.c | 41 ++++++++++++++++---
3 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..aad318803f82 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2343,6 +2343,12 @@ Kernel parameters
Use the canonical format for the binary runtime
measurements, instead of host native format.
+ ima_flush_htable [IMA]
+ Flush the IMA hash table when deleting all the
+ staged measurement records, to achieve maximum
+ memory saving at the cost of having duplicate
+ records across the staged measurement lists.
+
ima_hash= [IMA]
Format: { md5 | sha1 | rmd160 | sha256 | sha384
| sha512 | ... }
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index a05db5b18982..d2e740c8ff75 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -343,6 +343,7 @@ extern atomic_long_t ima_num_records[BINARY__LAST];
extern atomic_long_t ima_num_violations;
extern struct hlist_head __rcu *ima_htable;
extern struct mutex ima_extend_list_mutex;
+extern bool ima_flush_htable;
static inline unsigned int ima_hash_key(u8 *digest)
{
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index a1aa141756e1..af0502f27d57 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -22,6 +22,20 @@
#define AUDIT_CAUSE_LEN_MAX 32
+bool ima_flush_htable;
+
+static int __init ima_flush_htable_setup(char *str)
+{
+ if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
+ pr_warn("Hash table not enabled, ignoring request to flush\n");
+ return 1;
+ }
+
+ ima_flush_htable = true;
+ return 1;
+}
+__setup("ima_flush_htable", ima_flush_htable_setup);
+
/* pre-allocated array of tpm_digest structures to extend a PCR */
static struct tpm_digest *digests;
@@ -332,7 +346,7 @@ int ima_queue_stage(void)
return ret;
}
-static void ima_queue_delete(struct list_head *head);
+static void ima_queue_delete(struct list_head *head, bool flush_htable);
/**
* ima_queue_staged_delete_all - Delete staged measurements
@@ -350,6 +364,7 @@ static void ima_queue_delete(struct list_head *head);
*/
int ima_queue_staged_delete_all(void)
{
+ struct hlist_head *old_queue = NULL;
LIST_HEAD(ima_measurements_trim);
mutex_lock(&ima_extend_list_mutex);
@@ -371,21 +386,35 @@ int ima_queue_staged_delete_all(void)
if (IS_ENABLED(CONFIG_IMA_KEXEC))
binary_runtime_size[BINARY_STAGED] = 0;
+ if (ima_flush_htable) {
+ old_queue = ima_alloc_replace_htable();
+ if (IS_ERR(old_queue)) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return PTR_ERR(old_queue);
+ }
+ }
+
mutex_unlock(&ima_extend_list_mutex);
- ima_queue_delete(&ima_measurements_trim);
+ if (ima_flush_htable) {
+ synchronize_rcu();
+ kfree(old_queue);
+ }
+
+ ima_queue_delete(&ima_measurements_trim, ima_flush_htable);
return 0;
}
/**
* ima_queue_delete - Delete measurements
* @head: List head measurements are deleted from
+ * @flush_htable: Whether or not the hash table is being flushed
*
* Delete the measurements from the passed list head completely if the
- * hash table is not enabled, or partially (only the template data), if the
- * hash table is used.
+ * hash table is not enabled or is being flushed, or partially (only the
+ * template data), if the hash table is used.
*/
-static void ima_queue_delete(struct list_head *head)
+static void ima_queue_delete(struct list_head *head, bool flush_htable)
{
struct ima_queue_entry *qe, *qe_tmp;
unsigned int i;
@@ -407,7 +436,7 @@ static void ima_queue_delete(struct list_head *head)
list_del(&qe->later);
/* No leak if condition is false, referenced by ima_htable. */
- if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
+ if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE) || flush_htable) {
kfree(qe->entry->digests);
kfree(qe->entry);
kfree(qe);
--
2.43.0
^ permalink raw reply related
* [PATCH v6 09/12] ima: Add support for staging measurements with prompt
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu, Stefan Berger
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce the ability of staging the IMA measurement list and deleting them
with a prompt.
Staging means moving the current measurement list records to a separate
location, and allowing users to read and delete it. This causes the current
measurement list to be emptied (since records were moved) and new
measurements to be added on the empty list. Staging can be done only once
at a time. In the event of kexec(), staging is aborted and staged records
will be carried over to the new kernel.
Introduce ascii_runtime_measurements_<algo>_staged and
binary_runtime_measurements_<algo>_staged interfaces to access and delete
the measurements.
Use 'echo A > <IMA _staged interface>' and
'echo D > <IMA _staged interface>' to respectively stage and delete the
entire measurements list. Locking of these interfaces is also mediated with
a call to _ima_measurements_open() and with ima_measurements_release().
Implement the staging functionality by introducing the new global
measurements list ima_measurements_staged, and ima_queue_stage() and
ima_queue_staged_delete_all() to respectively move measurements from the
current measurements list to the staged one, and to move staged
measurements to the ima_measurements_trim list for deletion. Introduce
ima_queue_delete() to delete the measurements.
Staging is forbidden after measurement is suspended, and between staging
and deleting, so that walking the staged and current measurements list can
be done locklessly in ima_dump_measurement_list(). Strict ordering of
suspending and dumping is enforced by two reboot notifiers with different
priority. Refusing to delete staged measurements also signals to user space
that those measurements are already carried over to the secondary kernel,
so that it does not save them twice.
Finally, introduce the BINARY_STAGED and BINARY_FULL binary measurements
list types, to maintain the counters and the binary size of staged
measurements and the full measurements list (including records that were
staged). BINARY still represents the current binary measurements list.
Use the binary size for the BINARY + BINARY_STAGED types in
ima_add_kexec_buffer(), since both measurements list types are copied to
the secondary kernel during kexec. Use BINARY_FULL in
ima_measure_kexec_event(), to generate a critical data record.
It should be noted that the BINARY_FULL counter is not passed through
kexec. Thus, the number of records included in the kexec critical data
records refers to the records since the critical data records generated
from the previous kexec event.
Note: This code derives from the Alt-IMA Huawei project, whose license is
GPL-2.0 OR MIT.
Link: https://github.com/linux-integrity/linux/issues/1
Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Tested-by: Stefan Berger <stefanb@linux.ibm.com>
---
security/integrity/ima/Kconfig | 12 ++
security/integrity/ima/ima.h | 8 +-
security/integrity/ima/ima_fs.c | 174 ++++++++++++++++++++++++++---
security/integrity/ima/ima_kexec.c | 20 +++-
security/integrity/ima/ima_queue.c | 142 ++++++++++++++++++++++-
5 files changed, 335 insertions(+), 21 deletions(-)
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 862fbee2b174..02436670f746 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -332,4 +332,16 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
If set to the default value of 0, an extra half page of memory for those
additional measurements will be allocated.
+config IMA_STAGING
+ bool "Support for staging the measurements list"
+ default n
+ help
+ Add support for staging the measurements list.
+
+ It allows user space to stage the measurements list for deletion and
+ to delete the staged measurements after confirmation.
+
+ On kexec, staging is aborted and any staged measurement records are
+ copied to the secondary kernel.
+
endif
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index c00c133a140f..a05db5b18982 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -30,9 +30,11 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
/*
* BINARY: current binary measurements list
+ * BINARY_STAGED: staged binary measurements list
+ * BINARY_FULL: binary measurements list since IMA init (lost after kexec)
*/
enum binary_lists {
- BINARY, BINARY__LAST
+ BINARY, BINARY_STAGED, BINARY_FULL, BINARY__LAST
};
/* digest size for IMA, fits SHA1 or MD5 */
@@ -125,6 +127,7 @@ struct ima_queue_entry {
struct ima_template_entry *entry;
};
extern struct list_head ima_measurements; /* list of all measurements */
+extern struct list_head ima_measurements_staged; /* list of staged meas. */
/* Some details preceding the binary serialized measurement list */
struct ima_kexec_hdr {
@@ -315,6 +318,8 @@ struct ima_template_desc *ima_template_desc_current(void);
struct ima_template_desc *ima_template_desc_buf(void);
struct ima_template_desc *lookup_template_desc(const char *name);
bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
+int ima_queue_stage(void);
+int ima_queue_staged_delete_all(void);
int ima_restore_measurement_entry(struct ima_template_entry *entry);
int ima_restore_measurement_list(loff_t bufsize, void *buf);
int ima_measurements_show(struct seq_file *m, void *v);
@@ -337,6 +342,7 @@ extern atomic_long_t ima_num_records[BINARY__LAST];
/* Total number of violations since hard boot. */
extern atomic_long_t ima_num_violations;
extern struct hlist_head __rcu *ima_htable;
+extern struct mutex ima_extend_list_mutex;
static inline unsigned int ima_hash_key(u8 *digest)
{
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index f6ecee2d7699..96d7503a605b 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -24,6 +24,13 @@
#include "ima.h"
+/*
+ * Requests:
+ * 'A\n': stage the entire measurements list
+ * 'D\n': delete all staged measurements
+ */
+#define STAGED_REQ_LENGTH 21
+
static DEFINE_MUTEX(ima_write_mutex);
static DEFINE_MUTEX(ima_measure_mutex);
static long ima_measure_users;
@@ -99,6 +106,11 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
return _ima_measurements_start(m, pos, &ima_measurements);
}
+static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos)
+{
+ return _ima_measurements_start(m, pos, &ima_measurements_staged);
+}
+
static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
struct list_head *head)
{
@@ -120,6 +132,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
return _ima_measurements_next(m, v, pos, &ima_measurements);
}
+static void *ima_measurements_staged_next(struct seq_file *m, void *v,
+ loff_t *pos)
+{
+ return _ima_measurements_next(m, v, pos, &ima_measurements_staged);
+}
+
static void ima_measurements_stop(struct seq_file *m, void *v)
{
}
@@ -213,6 +231,13 @@ static const struct seq_operations ima_measurments_seqops = {
.show = ima_measurements_show
};
+static const struct seq_operations ima_measurments_staged_seqops = {
+ .start = ima_measurements_staged_start,
+ .next = ima_measurements_staged_next,
+ .stop = ima_measurements_stop,
+ .show = ima_measurements_show
+};
+
static int ima_measure_lock(bool write)
{
mutex_lock(&ima_measure_mutex);
@@ -307,6 +332,60 @@ static int ima_measurements_release(struct inode *inode, struct file *file)
return ret;
}
+static int ima_measurements_staged_open(struct inode *inode, struct file *file)
+{
+ return _ima_measurements_open(inode, file,
+ &ima_measurments_staged_seqops);
+}
+
+static ssize_t _ima_measurements_write(struct file *file,
+ const char __user *buf, size_t datalen,
+ loff_t *ppos, bool staged_interface)
+{
+ char req[STAGED_REQ_LENGTH];
+ int ret;
+
+ if (datalen < 2 || datalen > STAGED_REQ_LENGTH)
+ return -EINVAL;
+
+ if (copy_from_user(req, buf, datalen) != 0)
+ return -EFAULT;
+
+ if (req[datalen - 1] != '\n')
+ return -EINVAL;
+
+ req[datalen - 1] = '\0';
+
+ switch (req[0]) {
+ case 'A':
+ if (datalen != 2 || !staged_interface)
+ return -EINVAL;
+
+ ret = ima_queue_stage();
+ break;
+ case 'D':
+ if (datalen != 2 || !staged_interface)
+ return -EINVAL;
+
+ ret = ima_queue_staged_delete_all();
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return datalen;
+}
+
+static ssize_t ima_measurements_staged_write(struct file *file,
+ const char __user *buf,
+ size_t datalen, loff_t *ppos)
+{
+ return _ima_measurements_write(file, buf, datalen, ppos, true);
+}
+
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
@@ -314,6 +393,14 @@ static const struct file_operations ima_measurements_ops = {
.release = ima_measurements_release,
};
+static const struct file_operations ima_measurements_staged_ops = {
+ .open = ima_measurements_staged_open,
+ .read = seq_read,
+ .write = ima_measurements_staged_write,
+ .llseek = seq_lseek,
+ .release = ima_measurements_release,
+};
+
void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
{
u32 i;
@@ -387,6 +474,28 @@ static const struct file_operations ima_ascii_measurements_ops = {
.release = ima_measurements_release,
};
+static const struct seq_operations ima_ascii_measurements_staged_seqops = {
+ .start = ima_measurements_staged_start,
+ .next = ima_measurements_staged_next,
+ .stop = ima_measurements_stop,
+ .show = ima_ascii_measurements_show
+};
+
+static int ima_ascii_measurements_staged_open(struct inode *inode,
+ struct file *file)
+{
+ return _ima_measurements_open(inode, file,
+ &ima_ascii_measurements_staged_seqops);
+}
+
+static const struct file_operations ima_ascii_measurements_staged_ops = {
+ .open = ima_ascii_measurements_staged_open,
+ .read = seq_read,
+ .write = ima_measurements_staged_write,
+ .llseek = seq_lseek,
+ .release = ima_measurements_release,
+};
+
static ssize_t ima_read_policy(char *path)
{
void *data = NULL;
@@ -490,10 +599,21 @@ static const struct seq_operations ima_policy_seqops = {
};
#endif
-static int __init create_securityfs_measurement_lists(void)
+static int __init create_securityfs_measurement_lists(bool staging)
{
+ const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
+ const struct file_operations *binary_ops = &ima_measurements_ops;
+ umode_t permissions = (S_IRUSR | S_IRGRP);
+ const char *file_suffix = "";
int count = NR_BANKS(ima_tpm_chip);
+ if (staging) {
+ ascii_ops = &ima_ascii_measurements_staged_ops;
+ binary_ops = &ima_measurements_staged_ops;
+ permissions |= (S_IWUSR | S_IWGRP);
+ file_suffix = "_staged";
+ }
+
if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip))
count++;
@@ -504,29 +624,32 @@ static int __init create_securityfs_measurement_lists(void)
if (algo == HASH_ALGO__LAST)
snprintf(file_name, sizeof(file_name),
- "ascii_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ "ascii_runtime_measurements_tpm_alg_%x%s",
+ ima_tpm_chip->allocated_banks[i].alg_id,
+ file_suffix);
else
snprintf(file_name, sizeof(file_name),
- "ascii_runtime_measurements_%s",
- hash_algo_name[algo]);
- dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
+ "ascii_runtime_measurements_%s%s",
+ hash_algo_name[algo], file_suffix);
+ dentry = securityfs_create_file(file_name, permissions,
ima_dir, (void *)(uintptr_t)i,
- &ima_ascii_measurements_ops);
+ ascii_ops);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
if (algo == HASH_ALGO__LAST)
snprintf(file_name, sizeof(file_name),
- "binary_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ "binary_runtime_measurements_tpm_alg_%x%s",
+ ima_tpm_chip->allocated_banks[i].alg_id,
+ file_suffix);
else
snprintf(file_name, sizeof(file_name),
- "binary_runtime_measurements_%s",
- hash_algo_name[algo]);
- dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
+ "binary_runtime_measurements_%s%s",
+ hash_algo_name[algo], file_suffix);
+
+ dentry = securityfs_create_file(file_name, permissions,
ima_dir, (void *)(uintptr_t)i,
- &ima_measurements_ops);
+ binary_ops);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
}
@@ -534,6 +657,23 @@ static int __init create_securityfs_measurement_lists(void)
return 0;
}
+static int __init create_securityfs_staging_links(void)
+{
+ struct dentry *dentry;
+
+ dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
+ ima_dir, "binary_runtime_measurements_sha1_staged", NULL);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
+ ima_dir, "ascii_runtime_measurements_sha1_staged", NULL);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ return 0;
+}
+
/*
* ima_open_policy: sequentialize access to the policy file
*/
@@ -626,7 +766,13 @@ int __init ima_fs_init(void)
goto out;
}
- ret = create_securityfs_measurement_lists();
+ ret = create_securityfs_measurement_lists(false);
+ if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) {
+ ret = create_securityfs_measurement_lists(true);
+ if (ret == 0)
+ ret = create_securityfs_staging_links();
+ }
+
if (ret != 0)
goto out;
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 26d41974429e..0d845693a1f7 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name)
long len;
int n;
- buf_size = ima_get_binary_runtime_size(BINARY);
- len = atomic_long_read(&ima_num_records[BINARY]);
+ buf_size = ima_get_binary_runtime_size(BINARY_FULL);
+ len = atomic_long_read(&ima_num_records[BINARY_FULL]);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
@@ -106,13 +106,24 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
memset(&khdr, 0, sizeof(khdr));
khdr.version = 1;
- /* This is an append-only list, no need to hold the RCU read lock */
- list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
+ /*
+ * Lockless walks possible due to strict ordering of the reboot
+ * notifiers, suspending measurement before dump, and forbidding
+ * staging/deleting (list mutations) after suspend.
+ */
+ list_for_each_entry(qe, &ima_measurements_staged, later) {
ret = ima_dump_measurement(&khdr, qe);
if (ret < 0)
break;
}
+ list_for_each_entry(qe, &ima_measurements, later) {
+ if (!ret)
+ ret = ima_dump_measurement(&khdr, qe);
+ if (ret < 0)
+ break;
+ }
+
/*
* fill in reserved space with some buffer details
* (eg. version, buffer size, number of measurements)
@@ -167,6 +178,7 @@ void ima_add_kexec_buffer(struct kimage *image)
extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
+ ima_get_binary_runtime_size(BINARY_STAGED) +
extra_memory;
if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 618694d5c082..a1aa141756e1 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -26,6 +26,7 @@
static struct tpm_digest *digests;
LIST_HEAD(ima_measurements); /* list of all measurements */
+LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
#ifdef CONFIG_IMA_KEXEC
static unsigned long binary_runtime_size[BINARY__LAST];
#else
@@ -42,11 +43,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
/* key: inode (before secure-hashing a file) */
struct hlist_head __rcu *ima_htable;
-/* mutex protects atomicity of extending measurement list
+/* mutex protects atomicity of extending and staging measurement list
* and extending the TPM PCR aggregate. Since tpm_extend can take
* long (and the tpm driver uses a mutex), we can't use the spinlock.
*/
-static DEFINE_MUTEX(ima_extend_list_mutex);
+DEFINE_MUTEX(ima_extend_list_mutex);
/*
* Used internally by the kernel to suspend measurements.
@@ -171,12 +172,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
lockdep_is_held(&ima_extend_list_mutex));
atomic_long_inc(&ima_num_records[BINARY]);
+ atomic_long_inc(&ima_num_records[BINARY_FULL]);
+
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
hlist_add_head_rcu(&qe->hnext, &htable[key]);
}
ima_update_binary_runtime_size(entry, BINARY);
+ ima_update_binary_runtime_size(entry, BINARY_FULL);
+
return 0;
}
@@ -277,6 +282,139 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
return result;
}
+/**
+ * ima_queue_stage - Stage all measurements
+ *
+ * If the staged measurements list is empty, the current measurements list is
+ * not empty, and measurement is not suspended, move the measurements from the
+ * current list to the staged one, and update the number of records and binary
+ * run-time size accordingly.
+ *
+ * Do not allow staging after measurement is suspended, so that dumping
+ * measurements can be done in a lockless way.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_stage(void)
+{
+ int ret = 0;
+
+ mutex_lock(&ima_extend_list_mutex);
+ if (!list_empty(&ima_measurements_staged)) {
+ ret = -EEXIST;
+ goto out_unlock;
+ }
+
+ if (list_empty(&ima_measurements)) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ if (ima_measurements_suspended) {
+ ret = -EACCES;
+ goto out_unlock;
+ }
+
+ list_replace(&ima_measurements, &ima_measurements_staged);
+ INIT_LIST_HEAD(&ima_measurements);
+
+ atomic_long_set(&ima_num_records[BINARY_STAGED],
+ atomic_long_read(&ima_num_records[BINARY]));
+ atomic_long_set(&ima_num_records[BINARY], 0);
+
+ if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
+ binary_runtime_size[BINARY_STAGED] =
+ binary_runtime_size[BINARY];
+ binary_runtime_size[BINARY] = 0;
+ }
+out_unlock:
+ mutex_unlock(&ima_extend_list_mutex);
+ return ret;
+}
+
+static void ima_queue_delete(struct list_head *head);
+
+/**
+ * ima_queue_staged_delete_all - Delete staged measurements
+ *
+ * Move staged measurements to a temporary list, ima_measurements_trim, update
+ * the number of records and the binary run-time size accordingly. Finally,
+ * delete measurements in the temporary list.
+ *
+ * Refuse to delete staged measurements if measurement is suspended, so that
+ * dump can be done in a lockless way and user space is notified about staged
+ * measurements being carried over to the secondary kernel, so that it does not
+ * save them twice.
+ *
+ * Return: Zero on success, a negative value otherwise.
+ */
+int ima_queue_staged_delete_all(void)
+{
+ LIST_HEAD(ima_measurements_trim);
+
+ mutex_lock(&ima_extend_list_mutex);
+ if (list_empty(&ima_measurements_staged)) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ENOENT;
+ }
+
+ if (ima_measurements_suspended) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ESTALE;
+ }
+
+ list_replace(&ima_measurements_staged, &ima_measurements_trim);
+ INIT_LIST_HEAD(&ima_measurements_staged);
+
+ atomic_long_set(&ima_num_records[BINARY_STAGED], 0);
+
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size[BINARY_STAGED] = 0;
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ ima_queue_delete(&ima_measurements_trim);
+ return 0;
+}
+
+/**
+ * ima_queue_delete - Delete measurements
+ * @head: List head measurements are deleted from
+ *
+ * Delete the measurements from the passed list head completely if the
+ * hash table is not enabled, or partially (only the template data), if the
+ * hash table is used.
+ */
+static void ima_queue_delete(struct list_head *head)
+{
+ struct ima_queue_entry *qe, *qe_tmp;
+ unsigned int i;
+
+ list_for_each_entry_safe(qe, qe_tmp, head, later) {
+ /*
+ * Safe to free template_data here without synchronize_rcu()
+ * because the only htable reader, ima_lookup_digest_entry(),
+ * accesses only entry->digests, not template_data. If new
+ * htable readers are added that access template_data, a
+ * synchronize_rcu() is required here.
+ */
+ for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
+ kfree(qe->entry->template_data[i].data);
+ qe->entry->template_data[i].data = NULL;
+ qe->entry->template_data[i].len = 0;
+ }
+
+ list_del(&qe->later);
+
+ /* No leak if condition is false, referenced by ima_htable. */
+ if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
+ kfree(qe->entry->digests);
+ kfree(qe->entry);
+ kfree(qe);
+ }
+ }
+}
+
int ima_restore_measurement_entry(struct ima_template_entry *entry)
{
int result = 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v6 08/12] ima: Introduce ima_dump_measurement()
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce ima_dump_measurement() to simplify the code of
ima_dump_measurement_list() and to avoid repeating the
ima_dump_measurement() code block if iteration occurs on multiple lists.
No functional change: only code moved to a separate function.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
security/integrity/ima/ima_kexec.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 8dc9459622b3..26d41974429e 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -80,6 +80,17 @@ static int ima_alloc_kexec_file_buf(size_t segment_size)
return 0;
}
+static int ima_dump_measurement(struct ima_kexec_hdr *khdr,
+ struct ima_queue_entry *qe)
+{
+ if (ima_kexec_file.count >= ima_kexec_file.size)
+ return -EINVAL;
+
+ khdr->count++;
+ ima_measurements_show(&ima_kexec_file, qe);
+ return 0;
+}
+
static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
unsigned long segment_size)
{
@@ -97,13 +108,9 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
khdr.version = 1;
/* This is an append-only list, no need to hold the RCU read lock */
list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
- if (ima_kexec_file.count < ima_kexec_file.size) {
- khdr.count++;
- ima_measurements_show(&ima_kexec_file, qe);
- } else {
- ret = -EINVAL;
+ ret = ima_dump_measurement(&khdr, qe);
+ if (ret < 0)
break;
- }
}
/*
--
2.43.0
^ permalink raw reply related
* [PATCH v6 07/12] ima: Use snprintf() in create_securityfs_measurement_lists
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Use the more secure snprintf() function (accepting the buffer size) in
create_securityfs_measurement_lists().
No functional change: sprintf() and snprintf() have the same behavior.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
security/integrity/ima/ima_fs.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 91bd831d070f..f6ecee2d7699 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -503,11 +503,13 @@ static int __init create_securityfs_measurement_lists(void)
struct dentry *dentry;
if (algo == HASH_ALGO__LAST)
- sprintf(file_name, "ascii_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ snprintf(file_name, sizeof(file_name),
+ "ascii_runtime_measurements_tpm_alg_%x",
+ ima_tpm_chip->allocated_banks[i].alg_id);
else
- sprintf(file_name, "ascii_runtime_measurements_%s",
- hash_algo_name[algo]);
+ snprintf(file_name, sizeof(file_name),
+ "ascii_runtime_measurements_%s",
+ hash_algo_name[algo]);
dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
ima_dir, (void *)(uintptr_t)i,
&ima_ascii_measurements_ops);
@@ -515,11 +517,13 @@ static int __init create_securityfs_measurement_lists(void)
return PTR_ERR(dentry);
if (algo == HASH_ALGO__LAST)
- sprintf(file_name, "binary_runtime_measurements_tpm_alg_%x",
- ima_tpm_chip->allocated_banks[i].alg_id);
+ snprintf(file_name, sizeof(file_name),
+ "binary_runtime_measurements_tpm_alg_%x",
+ ima_tpm_chip->allocated_banks[i].alg_id);
else
- sprintf(file_name, "binary_runtime_measurements_%s",
- hash_algo_name[algo]);
+ snprintf(file_name, sizeof(file_name),
+ "binary_runtime_measurements_%s",
+ hash_algo_name[algo]);
dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
ima_dir, (void *)(uintptr_t)i,
&ima_measurements_ops);
--
2.43.0
^ permalink raw reply related
* [PATCH v6 06/12] ima: Mediate open/release method of the measurements list
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce the ima_measure_users counter, to implement a semaphore-like
locking scheme where the binary and ASCII measurements list interfaces can
be concurrently opened by multiple readers, or alternatively by a single
writer. In addition, allow the same writer to open the other interfaces for
write or read/write, so that it can see the same measurement state across
all the interfaces.
A semaphore cannot be used because the kernel cannot return to user space
with a lock held.
Introduce the ima_measure_lock() and ima_measure_unlock() primitives, to
respectively lock/unlock the interfaces (safely with the ima_measure_users
counter, without holding a lock).
Finally, introduce _ima_measurements_open() to lock the interface before
seq_open(), and call it from ima_measurements_open() and
ima_ascii_measurements_open(). And, introduce ima_measurements_release(),
to unlock the interface.
Require CAP_SYS_ADMIN if the interface is opened for write (not possible
for the current measurements interfaces, since they only have read
permission).
No functional changes: multiple readers are allowed as before.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
security/integrity/ima/ima_fs.c | 102 ++++++++++++++++++++++++++++++--
1 file changed, 98 insertions(+), 4 deletions(-)
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index dcdc4cb8fa0f..91bd831d070f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -25,6 +25,10 @@
#include "ima.h"
static DEFINE_MUTEX(ima_write_mutex);
+static DEFINE_MUTEX(ima_measure_mutex);
+static long ima_measure_users;
+static struct task_struct *measure_writer;
+static long measure_writer_extra_writes;
bool ima_canonical_fmt;
static int __init default_canonical_fmt_setup(char *str)
@@ -209,16 +213,105 @@ static const struct seq_operations ima_measurments_seqops = {
.show = ima_measurements_show
};
+static int ima_measure_lock(bool write)
+{
+ mutex_lock(&ima_measure_mutex);
+ /* Overflow check. */
+ if (!write && ima_measure_users == LONG_MAX) {
+ mutex_unlock(&ima_measure_mutex);
+ return -ENFILE;
+ }
+
+ /* Same writer can do additional writes or read/writes. */
+ if (write && current == measure_writer) {
+ measure_writer_extra_writes++;
+ mutex_unlock(&ima_measure_mutex);
+ return 0;
+ }
+
+ /*
+ * ima_measure_users: > 0 open readers
+ * ima_measure_users: == -1 open writer
+ */
+ if ((write && ima_measure_users != 0) ||
+ (!write && ima_measure_users < 0)) {
+ mutex_unlock(&ima_measure_mutex);
+ return -EBUSY;
+ }
+
+ if (write) {
+ ima_measure_users--;
+ /* Pointer valid, no reuse while the file descriptor is open. */
+ measure_writer = current;
+ } else {
+ ima_measure_users++;
+ }
+ mutex_unlock(&ima_measure_mutex);
+ return 0;
+}
+
+static void ima_measure_unlock(bool write)
+{
+ mutex_lock(&ima_measure_mutex);
+ /* Decrement additional writes or read/writes. */
+ if (write && current == measure_writer &&
+ measure_writer_extra_writes != 0) {
+ measure_writer_extra_writes--;
+ mutex_unlock(&ima_measure_mutex);
+ return;
+ }
+ if (write) {
+ ima_measure_users++;
+ measure_writer = NULL;
+ } else {
+ ima_measure_users--;
+ }
+ mutex_unlock(&ima_measure_mutex);
+}
+
+static int _ima_measurements_open(struct inode *inode, struct file *file,
+ const struct seq_operations *seq_ops)
+{
+ bool write = (file->f_mode & FMODE_WRITE);
+ int ret;
+
+ if (write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = ima_measure_lock(write);
+ if (ret < 0)
+ return ret;
+
+ ret = seq_open(file, seq_ops);
+ if (ret < 0)
+ ima_measure_unlock(write);
+
+ return ret;
+}
+
static int ima_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_measurments_seqops);
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static int ima_measurements_release(struct inode *inode, struct file *file)
+{
+ bool write = (file->f_mode & FMODE_WRITE);
+ int ret;
+
+ /* seq_release() always returns zero. */
+ ret = seq_release(inode, file);
+
+ ima_measure_unlock(write);
+
+ return ret;
}
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
};
void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
@@ -283,14 +376,15 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_ascii_measurements_seqops);
+ return _ima_measurements_open(inode, file,
+ &ima_ascii_measurements_seqops);
}
static const struct file_operations ima_ascii_measurements_ops = {
.open = ima_ascii_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
};
static ssize_t ima_read_policy(char *path)
--
2.43.0
^ permalink raw reply related
* [PATCH v6 05/12] ima: Introduce _ima_measurements_start() and _ima_measurements_next()
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce _ima_measurements_start() and _ima_measurements_next(), renamed
from ima_measurements_start() and ima_measurements_next(), to include the
list head as an additional parameter, so that iteration on different lists
can be implemented by calling those functions.
No functional change: ima_measurements_start() and ima_measurements_next()
pass the ima_measurements list head, used before. They become wrappers for
the new functions.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
security/integrity/ima/ima_fs.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index fcfcf7b6eae2..dcdc4cb8fa0f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -72,14 +72,15 @@ static const struct file_operations ima_measurements_count_ops = {
};
/* returns pointer to hlist_node */
-static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
+static void *_ima_measurements_start(struct seq_file *m, loff_t *pos,
+ struct list_head *head)
{
loff_t l = *pos;
struct ima_queue_entry *qe;
/* we need a lock since pos could point beyond last element */
rcu_read_lock();
- list_for_each_entry_rcu(qe, &ima_measurements, later) {
+ list_for_each_entry_rcu(qe, head, later) {
if (!l--) {
rcu_read_unlock();
return qe;
@@ -89,7 +90,13 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
return NULL;
}
-static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
+static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
+{
+ return _ima_measurements_start(m, pos, &ima_measurements);
+}
+
+static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
+ struct list_head *head)
{
struct ima_queue_entry *qe = v;
@@ -101,7 +108,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
rcu_read_unlock();
(*pos)++;
- return (&qe->later == &ima_measurements) ? NULL : qe;
+ return (&qe->later == head) ? NULL : qe;
+}
+
+static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ return _ima_measurements_next(m, v, pos, &ima_measurements);
}
static void ima_measurements_stop(struct seq_file *m, void *v)
--
2.43.0
^ permalink raw reply related
* [PATCH v6 04/12] ima: Introduce per binary measurements list type binary_runtime_size value
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Make binary_runtime_size as an array, to have separate counters per binary
measurements list type. Currently, define the BINARY type for the existing
binary measurements list.
Introduce ima_update_binary_runtime_size() to facilitate updating a
binary_runtime_size value with a given binary measurement list type.
Also add the binary measurements list type parameter to
ima_get_binary_runtime_size(), to retrieve the desired value. Retrieving
the value is now done under the ima_extend_list_mutex, since there can be
concurrent updates.
No functional change (except for the mutex usage, that fixes the
concurrency issue): the BINARY array element is equivalent to the old
binary_runtime_size.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
security/integrity/ima/ima.h | 2 +-
security/integrity/ima/ima_kexec.c | 5 ++--
security/integrity/ima/ima_queue.c | 40 +++++++++++++++++++++---------
3 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 8f457f2c7b79..c00c133a140f 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -319,7 +319,7 @@ int ima_restore_measurement_entry(struct ima_template_entry *entry);
int ima_restore_measurement_list(loff_t bufsize, void *buf);
int ima_measurements_show(struct seq_file *m, void *v);
int __init ima_init_htable(void);
-unsigned long ima_get_binary_runtime_size(void);
+unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list);
int ima_init_template(void);
void ima_init_template_list(void);
int __init ima_init_digests(void);
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 1a0211a12ea4..8dc9459622b3 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -42,7 +42,7 @@ void ima_measure_kexec_event(const char *event_name)
long len;
int n;
- buf_size = ima_get_binary_runtime_size();
+ buf_size = ima_get_binary_runtime_size(BINARY);
len = atomic_long_read(&ima_num_records[BINARY]);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
@@ -159,7 +159,8 @@ void ima_add_kexec_buffer(struct kimage *image)
else
extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
- binary_runtime_size = ima_get_binary_runtime_size() + extra_memory;
+ binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
+ extra_memory;
if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
kexec_segment_size = ULONG_MAX;
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 012e725ed4fc..618694d5c082 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -27,9 +27,11 @@ static struct tpm_digest *digests;
LIST_HEAD(ima_measurements); /* list of all measurements */
#ifdef CONFIG_IMA_KEXEC
-static unsigned long binary_runtime_size;
+static unsigned long binary_runtime_size[BINARY__LAST];
#else
-static unsigned long binary_runtime_size = ULONG_MAX;
+static unsigned long binary_runtime_size[BINARY__LAST] = {
+ [0 ... BINARY__LAST - 1] = ULONG_MAX
+};
#endif
atomic_long_t ima_num_records[BINARY__LAST] = {
@@ -128,6 +130,20 @@ static int get_binary_runtime_size(struct ima_template_entry *entry)
return size;
}
+static void ima_update_binary_runtime_size(struct ima_template_entry *entry,
+ enum binary_lists binary_list)
+{
+ int size;
+
+ if (binary_runtime_size[binary_list] == ULONG_MAX)
+ return;
+
+ size = get_binary_runtime_size(entry);
+ binary_runtime_size[binary_list] =
+ (binary_runtime_size[binary_list] < ULONG_MAX - size) ?
+ binary_runtime_size[binary_list] + size : ULONG_MAX;
+}
+
/* ima_add_template_entry helper function:
* - Add template entry to the measurement list and hash table, for
* all entries except those carried across kexec.
@@ -160,13 +176,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
hlist_add_head_rcu(&qe->hnext, &htable[key]);
}
- if (binary_runtime_size != ULONG_MAX) {
- int size;
-
- size = get_binary_runtime_size(entry);
- binary_runtime_size = (binary_runtime_size < ULONG_MAX - size) ?
- binary_runtime_size + size : ULONG_MAX;
- }
+ ima_update_binary_runtime_size(entry, BINARY);
return 0;
}
@@ -175,12 +185,18 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
* entire binary_runtime_measurement list, including the ima_kexec_hdr
* structure.
*/
-unsigned long ima_get_binary_runtime_size(void)
+unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list)
{
- if (binary_runtime_size >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
+ unsigned long val;
+
+ mutex_lock(&ima_extend_list_mutex);
+ val = binary_runtime_size[binary_list];
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (val >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
return ULONG_MAX;
else
- return binary_runtime_size + sizeof(struct ima_kexec_hdr);
+ return val + sizeof(struct ima_kexec_hdr);
}
static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
--
2.43.0
^ permalink raw reply related
* [PATCH v6 03/12] ima: Introduce per binary measurements list type ima_num_records counter
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
Make ima_num_records as an array, to have separate counters per binary
measurements list type. Currently, define the BINARY type for the existing
binary measurements list.
No functional change: the BINARY type is equivalent to the value without
the array.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
security/integrity/ima/ima.h | 9 ++++++++-
security/integrity/ima/ima_fs.c | 2 +-
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 6 ++++--
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 0e41c2113efd..8f457f2c7b79 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -28,6 +28,13 @@ enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN,
IMA_SHOW_BINARY_OLD_STRING_FMT, IMA_SHOW_ASCII };
enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
+/*
+ * BINARY: current binary measurements list
+ */
+enum binary_lists {
+ BINARY, BINARY__LAST
+};
+
/* digest size for IMA, fits SHA1 or MD5 */
#define IMA_DIGEST_SIZE SHA1_DIGEST_SIZE
#define IMA_EVENT_NAME_LEN_MAX 255
@@ -326,7 +333,7 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
extern spinlock_t ima_queue_lock;
/* Total number of measurement list records since hard boot. */
-extern atomic_long_t ima_num_records;
+extern atomic_long_t ima_num_records[BINARY__LAST];
/* Total number of violations since hard boot. */
extern atomic_long_t ima_num_violations;
extern struct hlist_head __rcu *ima_htable;
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 523d3e81f631..fcfcf7b6eae2 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -63,7 +63,7 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_counter(buf, count, ppos, &ima_num_records);
+ return ima_show_counter(buf, count, ppos, &ima_num_records[BINARY]);
}
static const struct file_operations ima_measurements_count_ops = {
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 77ad370dbc37..1a0211a12ea4 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -43,7 +43,7 @@ void ima_measure_kexec_event(const char *event_name)
int n;
buf_size = ima_get_binary_runtime_size();
- len = atomic_long_read(&ima_num_records);
+ len = atomic_long_read(&ima_num_records[BINARY]);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index a31b75d9302b..012e725ed4fc 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -32,7 +32,9 @@ static unsigned long binary_runtime_size;
static unsigned long binary_runtime_size = ULONG_MAX;
#endif
-atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0);
+atomic_long_t ima_num_records[BINARY__LAST] = {
+ [0 ... BINARY__LAST - 1] = ATOMIC_LONG_INIT(0)
+};
atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
/* key: inode (before secure-hashing a file) */
@@ -152,7 +154,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
htable = rcu_dereference_protected(ima_htable,
lockdep_is_held(&ima_extend_list_mutex));
- atomic_long_inc(&ima_num_records);
+ atomic_long_inc(&ima_num_records[BINARY]);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
hlist_add_head_rcu(&qe->hnext, &htable[key]);
--
2.43.0
^ permalink raw reply related
* [PATCH v6 02/12] ima: Replace static htable queue with dynamically allocated array
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
The IMA hash table is a fixed-size array of hlist_head buckets:
struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE];
IMA_MEASURE_HTABLE_SIZE is (1 << IMA_HASH_BITS) = 1024 buckets, each a
struct hlist_head (one pointer, 8 bytes on 64-bit). That is 8 KiB allocated
in BSS for every kernel, regardless of whether IMA is ever used, and
regardless of how many measurements are actually made.
Replace the fixed-size array with a RCU-protected pointer to a dynamically
allocated array that is initialized in ima_init_htable(), which is called
from ima_init() during early boot. ima_init_htable() calls the static
function ima_alloc_replace_htable() which, other than initializing the hash
table the first time, can also hot-swap the existing hash table with a
blank one.
The allocation in ima_alloc_replace_htable() uses kcalloc() so the buckets
are zero-initialised (equivalent to HLIST_HEAD_INIT { .first = NULL }).
Callers of ima_alloc_replace_htable() must call synchronize_rcu() and free
the returned hash table.
Finally, access the hash table with rcu_dereference() in
ima_lookup_digest_entry() (reader side) and with
rcu_dereference_protected() in ima_add_digest_entry() (writer side).
No functional change: bucket count, hash function, and all locking remain
identical.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Reviewed-by: Mimi Zohar <zohar@linux.ibm.com>
---
security/integrity/ima/ima.h | 3 +-
security/integrity/ima/ima_init.c | 5 ++++
security/integrity/ima/ima_queue.c | 48 ++++++++++++++++++++++++++----
3 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index b3ad7eac6a1e..0e41c2113efd 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -311,6 +311,7 @@ bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
int ima_restore_measurement_entry(struct ima_template_entry *entry);
int ima_restore_measurement_list(loff_t bufsize, void *buf);
int ima_measurements_show(struct seq_file *m, void *v);
+int __init ima_init_htable(void);
unsigned long ima_get_binary_runtime_size(void);
int ima_init_template(void);
void ima_init_template_list(void);
@@ -328,7 +329,7 @@ extern spinlock_t ima_queue_lock;
extern atomic_long_t ima_num_records;
/* Total number of violations since hard boot. */
extern atomic_long_t ima_num_violations;
-extern struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE];
+extern struct hlist_head __rcu *ima_htable;
static inline unsigned int ima_hash_key(u8 *digest)
{
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index a2f34f2d8ad7..7e0aa09a12e6 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -140,6 +140,11 @@ int __init ima_init(void)
rc = ima_init_digests();
if (rc != 0)
return rc;
+
+ rc = ima_init_htable();
+ if (rc != 0)
+ return rc;
+
rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */
if (rc != 0)
return rc;
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 6bdaefc790c3..a31b75d9302b 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -36,9 +36,7 @@ atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0);
atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
/* key: inode (before secure-hashing a file) */
-struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE] = {
- [0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
-};
+struct hlist_head __rcu *ima_htable;
/* mutex protects atomicity of extending measurement list
* and extending the TPM PCR aggregate. Since tpm_extend can take
@@ -52,17 +50,53 @@ static DEFINE_MUTEX(ima_extend_list_mutex);
*/
static bool ima_measurements_suspended;
+/* Callers must call synchronize_rcu() and free the hash table. */
+static struct hlist_head *ima_alloc_replace_htable(void)
+{
+ struct hlist_head *old_htable, *new_htable;
+
+ /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */
+ new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head),
+ GFP_KERNEL);
+ if (!new_htable)
+ return ERR_PTR(-ENOMEM);
+
+ old_htable = rcu_replace_pointer(ima_htable, new_htable,
+ lockdep_is_held(&ima_extend_list_mutex));
+
+ return old_htable;
+}
+
+int __init ima_init_htable(void)
+{
+ struct hlist_head *old_htable;
+
+ mutex_lock(&ima_extend_list_mutex);
+ old_htable = ima_alloc_replace_htable();
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (IS_ERR(old_htable))
+ return PTR_ERR(old_htable);
+
+ /* Synchronize_rcu() and kfree() not necessary, only for robustness. */
+ synchronize_rcu();
+ kfree(old_htable);
+ return 0;
+}
+
/* lookup up the digest value in the hash table, and return the entry */
static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
int pcr)
{
struct ima_queue_entry *qe, *ret = NULL;
+ struct hlist_head *htable;
unsigned int key;
int rc;
key = ima_hash_key(digest_value);
rcu_read_lock();
- hlist_for_each_entry_rcu(qe, &ima_htable[key], hnext) {
+ htable = rcu_dereference(ima_htable);
+ hlist_for_each_entry_rcu(qe, &htable[key], hnext) {
rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest,
digest_value, hash_digest_size[ima_hash_algo]);
if ((rc == 0) && (qe->entry->pcr == pcr)) {
@@ -102,6 +136,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
bool update_htable)
{
struct ima_queue_entry *qe;
+ struct hlist_head *htable;
unsigned int key;
qe = kmalloc_obj(*qe);
@@ -114,10 +149,13 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
INIT_LIST_HEAD(&qe->later);
list_add_tail_rcu(&qe->later, &ima_measurements);
+ htable = rcu_dereference_protected(ima_htable,
+ lockdep_is_held(&ima_extend_list_mutex));
+
atomic_long_inc(&ima_num_records);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
- hlist_add_head_rcu(&qe->hnext, &ima_htable[key]);
+ hlist_add_head_rcu(&qe->hnext, &htable[key]);
}
if (binary_runtime_size != ULONG_MAX) {
--
2.43.0
^ permalink raw reply related
* [PATCH v6 01/12] ima: Remove ima_h_table structure
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <20260602111401.1706052-1-roberto.sassu@huaweicloud.com>
From: Roberto Sassu <roberto.sassu@huawei.com>
The ima_h_table structure is a collection of IMA measurement list
metadata - number of records in the IMA measurement list, number of
integrity violations, and a hash table containing the IMA template data
hash, needed to prevent measurement list record duplication.
Removing records from the measurement list needs to be reflected in the
hash table. As a pre-req to removing records from the measurement list,
separate those counters from the hash table, remove the ima_h_table
structure, and just replace the hash table pointer.
Finally, rename ima_show_htable_value(), ima_show_htable_violations()
and ima_htable_violations_ops respectively to ima_show_counter(),
ima_show_num_violations() and ima_num_violations_ops.
Link: https://github.com/linux-integrity/linux/issues/1
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
security/integrity/ima/ima.h | 11 +++++------
security/integrity/ima/ima_api.c | 2 +-
security/integrity/ima/ima_fs.c | 20 +++++++++-----------
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 15 ++++++++-------
5 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 69e9bf0b82c6..b3ad7eac6a1e 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -324,12 +324,11 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
*/
extern spinlock_t ima_queue_lock;
-struct ima_h_table {
- atomic_long_t len; /* number of stored measurements in the list */
- atomic_long_t violations;
- struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
-};
-extern struct ima_h_table ima_htable;
+/* Total number of measurement list records since hard boot. */
+extern atomic_long_t ima_num_records;
+/* Total number of violations since hard boot. */
+extern atomic_long_t ima_num_violations;
+extern struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE];
static inline unsigned int ima_hash_key(u8 *digest)
{
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 0916f24f005f..122d127e108d 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -146,7 +146,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename,
int result;
/* can overflow, only indicator */
- atomic_long_inc(&ima_htable.violations);
+ atomic_long_inc(&ima_num_violations);
result = ima_alloc_init_template(&event_data, &entry, NULL);
if (result < 0) {
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index ca4931a95098..523d3e81f631 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -38,8 +38,8 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
static int valid_policy = 1;
-static ssize_t ima_show_htable_value(char __user *buf, size_t count,
- loff_t *ppos, atomic_long_t *val)
+static ssize_t ima_show_counter(char __user *buf, size_t count, loff_t *ppos,
+ atomic_long_t *val)
{
char tmpbuf[32]; /* greater than largest 'long' string value */
ssize_t len;
@@ -48,15 +48,14 @@ static ssize_t ima_show_htable_value(char __user *buf, size_t count,
return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}
-static ssize_t ima_show_htable_violations(struct file *filp,
- char __user *buf,
- size_t count, loff_t *ppos)
+static ssize_t ima_show_num_violations(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.violations);
+ return ima_show_counter(buf, count, ppos, &ima_num_violations);
}
-static const struct file_operations ima_htable_violations_ops = {
- .read = ima_show_htable_violations,
+static const struct file_operations ima_num_violations_ops = {
+ .read = ima_show_num_violations,
.llseek = generic_file_llseek,
};
@@ -64,8 +63,7 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
-
+ return ima_show_counter(buf, count, ppos, &ima_num_records);
}
static const struct file_operations ima_measurements_count_ops = {
@@ -545,7 +543,7 @@ int __init ima_fs_init(void)
}
dentry = securityfs_create_file("violations", S_IRUSR | S_IRGRP,
- ima_dir, NULL, &ima_htable_violations_ops);
+ ima_dir, NULL, &ima_num_violations_ops);
if (IS_ERR(dentry)) {
ret = PTR_ERR(dentry);
goto out;
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 36a34c54de58..77ad370dbc37 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -43,7 +43,7 @@ void ima_measure_kexec_event(const char *event_name)
int n;
buf_size = ima_get_binary_runtime_size();
- len = atomic_long_read(&ima_htable.len);
+ len = atomic_long_read(&ima_num_records);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 319522450854..6bdaefc790c3 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -32,11 +32,12 @@ static unsigned long binary_runtime_size;
static unsigned long binary_runtime_size = ULONG_MAX;
#endif
+atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0);
+atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
+
/* key: inode (before secure-hashing a file) */
-struct ima_h_table ima_htable = {
- .len = ATOMIC_LONG_INIT(0),
- .violations = ATOMIC_LONG_INIT(0),
- .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
+struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE] = {
+ [0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
};
/* mutex protects atomicity of extending measurement list
@@ -61,7 +62,7 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value,
key = ima_hash_key(digest_value);
rcu_read_lock();
- hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) {
+ hlist_for_each_entry_rcu(qe, &ima_htable[key], hnext) {
rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest,
digest_value, hash_digest_size[ima_hash_algo]);
if ((rc == 0) && (qe->entry->pcr == pcr)) {
@@ -113,10 +114,10 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
INIT_LIST_HEAD(&qe->later);
list_add_tail_rcu(&qe->later, &ima_measurements);
- atomic_long_inc(&ima_htable.len);
+ atomic_long_inc(&ima_num_records);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
- hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
+ hlist_add_head_rcu(&qe->hnext, &ima_htable[key]);
}
if (binary_runtime_size != ULONG_MAX) {
--
2.43.0
^ permalink raw reply related
* [PATCH v6 00/12] ima: Exporting and deleting IMA measurement records from kernel memory
From: Roberto Sassu @ 2026-06-02 11:13 UTC (permalink / raw)
To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
From: Roberto Sassu <roberto.sassu@huawei.com>
Introduction
============
The IMA measurements list is currently stored in the kernel memory.
Memory occupation grows linearly with the number of records, and can
become a problem especially in environments with reduced resources.
While there is an advantage in keeping the IMA measurements list in
kernel memory, so that it is always available for reading from the
securityfs interfaces, storing it elsewhere would make it possible to
free precious memory for other kernel usage.
The IMA measurements list needs to be retained and safely stored for new
attestation servers to validate it. Assuming the IMA measurements list
is properly saved, storing it outside the kernel does not introduce
security issues, since its integrity is anyway protected by the TPM.
Hence, the new IMA staging mechanism is introduced to export IMA
measurements to user space and delete them from kernel space.
Staging consists in atomically moving the current measurements list to a
temporary list, so that measurements can be deleted afterwards. The
staging operation locks the hot path (racing with addition of new
measurements) for a very short time, only for swapping the list
pointers. Deletion of the measurements instead is done locklessly, away
from the hot path.
There are two flavors of the staging mechanism. In the staging with
prompt, all current measurements are staged, read and deleted upon
confirmation. In the staging and deleting flavor, N measurements are
staged from the beginning of the current measurements list and
immediately deleted without confirmation.
Usage
=====
The IMA staging mechanism can be enabled from the kernel configuration
with the CONFIG_IMA_STAGING option. This option prevents inadvertently
removing the IMA measurement list on systems which do not properly save
it.
If the option is enabled, IMA duplicates the current securityfs
measurements interfaces (both binary and ASCII), by adding the _staged
file suffix. Both the original and the staging interfaces gain the write
permission for the root user and group, but require the process to have
CAP_SYS_ADMIN set.
The staging mechanism supports two flavors.
Staging with prompt
~~~~~~~~~~~~~~~~~~~
The current measurement list is moved to a temporary staging area,
allowing it to be saved to external storage, before being deleted upon
confirmation.
This staging process is achieved with the following steps.
1. echo A > <_staged interface>: the user requests IMA to stage the
entire measurements list;
2. cat <_staged interface>: the user reads the staged measurements;
3. echo D > <_staged interface>: the user requests IMA to delete
staged measurements.
Staging and deleting
~~~~~~~~~~~~~~~~~~~~
N measurements are staged to a temporary staging area, and immediately
deleted without further confirmation.
This staging process is achieved with the following steps.
1. cat <original interface>: the user reads the current measurements
list and determines what the value N for staging should be;
2. echo N > <original interface>: the user requests IMA to delete N
measurements from the current measurements list.
Management of Staged Measurements
=================================
Since with the staging mechanism measurement records are removed from
the kernel, the staged measurements need to be saved in a storage and
concatenated together, so that they can be presented to remote
attestation agents as if staging was never done. This task can be
accomplished by a system service.
Patch set content
=================
Patches 1-8 are preparatory patches to quickly replace the hash table,
maintain separate counters for the different measurements list types,
mediate access to the measurements list interface, and simplify the staging
patches.
Patch 9 introduces the staging with prompt flavor. Patch 10 makes it
possible to flush the hash table when deleting all the staged measurements.
Patch 11 introduces the staging and deleting flavor. Patch 12 adds the
documentation of the staging mechanism.
Changelog
=========
v5:
- Add motivation for the ima_flush_htable= kernel option (suggested by
Mimi)
- New documentation title and fixes (suggested by Mimi)
- Allow stage all command on the _staged interface instead of the original
- Set CONFIG_IMA_STAGING default to n (suggested by Mimi)
- Rename ima_num_entries to ima_num_records (suggested by Mimi)
- Comment for ima_num_records and ima_num_violations (suggested by Mimi)
- Add overflow check in ima_measure_lock()
- Allow a writer to open for write or read/write the other staging
interfaces
- Ignore ppos in _ima_measurements_write()
- Implement lockless kexec measurement lists dump by denying
staging/delete after measurement suspend (collapse patch 12 into 9 and
11)
- Refuse delete based on measurement suspend instead of using
ima_copied_flags (suggested by Mimi)
- Add staging/deleting functions documentation
v4:
- Add write permission to the original measurement interface, and move
the A and N staging commands to that interface
- Explain better the two staging flavors and highlight that the staging
and delete only stages measurements internally
- Rename ima_queue_staged_delete_partial() to ima_queue_delete_partial()
- Replace ima_staged_measurements_prepended with per measurements list
flag to avoid copying staged and active list measurements twice
- Optimize the staging and deleting flavor by locklessly determining the
cut position in the active list, and immediately deleting entries
without explicit staging and splicing (suggested by Steven Chen)
v3:
- Add Kconfig option to enable the staging mechanism (suggested by Mimi)
- Change the meaning of BINARY_STAGED to be just the staged measurements
- Separate the two staging flavors in two different functions:
ima_queue_staged_delete_all() for staging with prompt,
ima_queue_staged_delete_partial() for staging and deleting
- Delete N entries without staging first (suggested by Mimi)
- Avoid duplicate staged entries if there is contention between the
measurements list interfaces and kexec
v2:
- New patch to move measurements and violation counters outside the
ima_h_table structure
- New patch to quickly replace the hash table
- Forbid partial deletion when flushing hash table (suggested by Mimi)
- Ignore ima_flush_htable if CONFIG_IMA_DISABLE_HTABLE is enabled
- BINARY_SIZE_* renamed to BINARY_* for better clarity
- Removed ima_measurements_staged_exist and testing list empty instead
- ima_queue_stage_trim() and ima_queue_delete_staged_trimmed() renamed to
ima_queue_stage() and ima_queue_delete_staged()
- New delete interval [1, ULONG_MAX - 1]
- Rename ima_measure_lock to ima_measure_mutex
- Move seq_open() and seq_release() outside the ima_measure_mutex lock
- Drop ima_measurements_staged_read() and use seq_read() instead
- Optimize create_securityfs_measurement_lists() changes
- New file name format with _staged suffix at the end of the file name
- Use _rcu list variant in ima_dump_measurement_list()
- Remove support for direct trimming and splice the remaining entries to
the active list (suggested by Mimi)
- Hot swap the hash table if flushing is requested
v1:
- Support for direct trimming without staging
- Support unstaging on kexec (requested by Gregory Lumen)
Roberto Sassu (12):
ima: Remove ima_h_table structure
ima: Replace static htable queue with dynamically allocated array
ima: Introduce per binary measurements list type ima_num_records
counter
ima: Introduce per binary measurements list type binary_runtime_size
value
ima: Introduce _ima_measurements_start() and _ima_measurements_next()
ima: Mediate open/release method of the measurements list
ima: Use snprintf() in create_securityfs_measurement_lists
ima: Introduce ima_dump_measurement()
ima: Add support for staging measurements with prompt
ima: Add support for flushing the hash table when staging measurements
ima: Support staging and deleting N measurements records
doc: security: Add documentation of exporting and deleting IMA
measurements
.../admin-guide/kernel-parameters.txt | 6 +
Documentation/security/IMA-export-delete.rst | 190 ++++++++++
Documentation/security/index.rst | 1 +
MAINTAINERS | 2 +
security/integrity/ima/Kconfig | 15 +
security/integrity/ima/ima.h | 29 +-
security/integrity/ima/ima_api.c | 2 +-
security/integrity/ima/ima_fs.c | 346 ++++++++++++++++--
security/integrity/ima/ima_init.c | 5 +
security/integrity/ima/ima_kexec.c | 42 ++-
security/integrity/ima/ima_queue.c | 329 +++++++++++++++--
11 files changed, 894 insertions(+), 73 deletions(-)
create mode 100644 Documentation/security/IMA-export-delete.rst
--
2.43.0
^ permalink raw reply
* Re: [PATCH v23 06/28] riscv/mm : ensure PROT_WRITE leads to VM_READ | VM_WRITE
From: Eric W. Biederman @ 2026-06-02 10:23 UTC (permalink / raw)
To: Andreas Schwab
Cc: Deepak Gupta, Deepak Gupta via B4 Relay, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Andrew Morton, Liam R. Howlett, Vlastimil Babka, Lorenzo Stoakes,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Conor Dooley,
Rob Herring, Krzysztof Kozlowski, Arnd Bergmann,
Christian Brauner, Peter Zijlstra, Oleg Nesterov, Kees Cook,
Jonathan Corbet, Shuah Khan, Jann Horn, Conor Dooley,
Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Benno Lossin, linux-kernel, linux-fsdevel, linux-mm, linux-riscv,
devicetree, linux-arch, linux-doc, linux-kselftest,
alistair.francis, richard.henderson, jim.shu, andybnac,
kito.cheng, charlie, atishp, evan, cleger, alexghiti,
samitolvanen, broonie, rick.p.edgecombe, rust-for-linux, Zong Li
In-Reply-To: <87wlwif04z.fsf@igel.home>
Andreas Schwab <schwab@linux-m68k.org> writes:
> On Jun 01 2026, Deepak Gupta wrote:
>
>> riscv uses `VM_SHADOW_STACK`. It's just very simple to use `protection_map`
>> with just `VM_WRITE`. On RISC-V, `-W-` is a shadow stack mapping. It's not same
>> on x86 or arm64. So `protection_map[VM_WRITE]` simply picks shadow stack
>> encoding. We just ensure that PROT_WRITE is converted to
>> "VM_READ | VM_WRITE" at vma level.
>
> That does not explain _why_ you need to make that user visible change,
> when others can get away without it.
Especially since as I recall the decision was made was that the user
visible protection bits would match the mmap call. If there is some
combination an architecture does not support that would simply not be
reflected in hardware until some future version of the hardware gets
around to supporting it.
This is what happened with executable page permissions on x86 for
example. It used to be that it was not possible to deny execute
permission on a readable page. Later that proved sufficiently valuable
that support for denying execute permission was added to the hardware.
That all happened quite transparently to userspace, that wasn't trying
to assuming PROT_READ implied PROT_EXEC.
So I am at a complete loss why someone would choose to break userspace
by confusing hardware limitations with what userspace asks for in mmap.
Eric
^ permalink raw reply
* Re: [PATCH v3] cpu/hotplug: Fix NULL kobject warning in cpuhp_smt_enable()
From: Will Deacon @ 2026-06-02 11:09 UTC (permalink / raw)
To: Jinjie Ruan
Cc: catalin.marinas, corbet, skhan, punit.agrawal, jic23,
osama.abdelkader, chenl311, fengchengwen, suzuki.poulose, maz,
lpieralisi, timothy.hayes, sascha.bischoff, arnd,
mrigendra.chaubey, pierre.gondois, dietmar.eggemann, yangyicong,
sudeep.holla, linux-arm-kernel, linux-doc, linux-kernel
In-Reply-To: <20260520022023.126670-1-ruanjinjie@huawei.com>
On Wed, May 20, 2026 at 10:20:23AM +0800, Jinjie Ruan wrote:
> On arm64, when booting with `maxcpus` greater than the number of present
> CPUs (e.g., QEMU -smp cpus=4,maxcpus=8), some CPUs are marked as 'present'
> but have not yet been registered via register_cpu(). Consequently,
> the per-cpu device objects for these CPUs are not yet initialized.
>
> In cpuhp_smt_enable(), the code iterates over all present CPUs. Calling
> _cpu_up() for these unregistered CPUs eventually leads to
> sysfs_create_group() being called with a NULL kobject (or a kobject
> without a directory), triggering the following warning in
> fs/sysfs/group.c:
>
> if (WARN_ON(!kobj || (!update && !kobj->sd)))
> return -EINVAL;
>
> When booting with ACPI, arm64 smp_prepare_cpus() currently sets all
> enumerated CPUs as "present" regardless of their status in the MADT. This
> causes issues with SMT hotplug control. For instance, with QEMU's
> "-smp 4,maxcpus=8" configuration, the MADT GICC entries are populated as
> follows: the first four CPUs are marked Enabled while the remaining four
> are marked Online Capable to support potential hot-plugging.
>
> Fix this by:
>
> 1. When booting with ACPI, checking the ACPI_MADT_ENABLED flag in the GICC
> entry before calling set_cpu_present() during SMP initialization.
>
> 2. Properly managing the present mask in acpi_map_cpu() and
> acpi_unmap_cpu() to support actual CPU hotplug events, This aligns with
> other architectures like x86 and LoongArch.
>
> 3. Update the arm64 CPU hotplug documentation to no longer state that all
> online-capable vCPUs are marked as present by the kernel at boot time.
>
> This ensures that only physically available or explicitly enabled CPUs
> are in the present mask, keeping the SMT control logic consistent with
> the actual hardware state.
Please can you check the Sashiko review comment?
https://sashiko.dev/#/patchset/20260520022023.126670-1-ruanjinjie@huawei.com
Cheers,
Will
^ permalink raw reply
* RE: [External Mail] Re: [PATCH 00/11] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Wu. JackBB (GSM) @ 2026-06-02 10:58 UTC (permalink / raw)
To: Jakub Kicinski, Jack Wu via B4 Relay
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Wen-Zhi Huang,
Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260601173401.2c892526@kernel.org>
Hi Jakub,
> On Fri, 29 May 2026 18:31:39 +0800 Jack Wu via B4 Relay wrote:
> > 43 files changed, 14761 insertions(+)
>
> Please try to cut this down to ~5kLoC for the initial submission.
> Whatever the absolute minimum sensible chunk of code is.
>
> Each patch must build cleanly with W=1
We've already reduced this significantly from the original 41k LoC
down to ~14.7k by stripping out non-essential features such as
exception handling, memory logging, devlink, statistics, debug
tracing, and others.
We even removed some arguably necessary features (PM, mdlog,
throughput optimizations) that we plan to submit as follow-up
series.
Note that the line count may slightly increase in v2, as we plan
to add missing kdoc comments based on review feedback.
For reference, the t7xx driver (two generations older, simpler HW)
had an initial submission of ~11.3k LoC [1]. The t9xx hardware is
more complex, so we believe being in a similar range is reasonable.
We'd like to keep the driver functional and reviewable in its
current scope. Do you have any suggestions on how we could further
reduce the size while maintaining a working initial submission?
[1] https://patchwork.kernel.org/project/netdevbpf/cover/20220506181310.2183829-1-ricardo.martinez@linux.intel.com/
Thanks.
================================================================================================================================================================
This message may contain information which is private, privileged or confidential of Compal Electronics, Inc. If you are not the intended recipient of this message, please notify the sender and destroy/delete the message. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon this information, by persons or entities other than the intended recipient is prohibited.
================================================================================================================================================================
^ permalink raw reply
* Re: [PATCH mm-unstable v18 11/14] mm/khugepaged: Introduce mTHP collapse support
From: Nico Pache @ 2026-06-02 10:58 UTC (permalink / raw)
To: Lance Yang
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
liam, ljs, mathieu.desnoyers, matthew.brost, mhiramat, mhocko,
peterx, pfalcato, rakie.kim, raquini, rdunlap, richard.weiyang,
rientjes, rostedt, rppt, ryan.roberts, shivankg, sunnanyong,
surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <20260531071845.10875-1-lance.yang@linux.dev>
On Sun, May 31, 2026 at 1:19 AM Lance Yang <lance.yang@linux.dev> wrote:
>
>
> On Fri, May 22, 2026 at 09:00:06AM -0600, Nico Pache wrote:
> [...]
> >@@ -1587,10 +1749,11 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> > if (result == SCAN_SUCCEED) {
> > /* collapse_huge_page expects the lock to be dropped before calling */
> > mmap_read_unlock(mm);
> >- result = collapse_huge_page(mm, start_addr, referenced,
> >- unmapped, cc, HPAGE_PMD_ORDER);
> >- /* collapse_huge_page will return with the mmap_lock released */
> >+ nr_collapsed = mthp_collapse(mm, vma, start_addr, referenced,
> >+ unmapped, cc, enabled_orders);
> >+ /* mmap_lock was released above, set lock_dropped */
> > *lock_dropped = true;
> >+ result = nr_collapsed ? SCAN_SUCCEED : SCAN_FAIL;
>
> Hmm ... don't we lose the allocation-failure result here?
>
> Previously collapse_scan_pmd() propagated SCAN_ALLOC_HUGE_PAGE_FAIL from
> collapse_huge_page(), so khugepaged would call khugepaged_alloc_sleep()
> in khugepaged_do_scan().
>
> Now if allocation fails and nr_collapsed stays 0, we just return
> SCAN_FAIL. So we won't back off via khugepaged_alloc_sleep() anymore?
Ok I did the error propagation! I think I handled both of these cases
you brought up pretty easily.
However I don't know what to do in the following case: We successfully
collapsed some portion of the PMD, but during that process, we also
hit an allocation failure. Is it best to back off entirely? or can we
treat some forward progress as a sign we can continue trying collapses
without sleeping.
Basically, do we prioritize SCAN_ALLOC_HUGE_PAGE_FAIL or the
successful collapses as the returned value?
This is what I currently have:
done:
if (collapsed)
return SCAN_SUCCEED;
if (alloc_failed)
return SCAN_ALLOC_HUGE_PAGE_FAIL;
Thanks,
-- Nico
>
> Cheers, Lance
>
^ permalink raw reply
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