* [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area
2026-07-16 14:14 [PATCH 0/4] s390: Reintroduce support for DCACHE_WORD_ACCESS Heiko Carstens
@ 2026-07-16 14:14 ` Heiko Carstens
2026-07-16 14:33 ` sashiko-bot
2026-07-16 14:14 ` [PATCH 2/4] s390/mm: Fix handling of secure storage access exceptions in vmalloc area Heiko Carstens
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Heiko Carstens @ 2026-07-16 14:14 UTC (permalink / raw)
To: Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
Christian Borntraeger, Janosch Frank, Claudio Imbrenda,
David Hildenbrand
Cc: linux-s390, kvm, linux-kernel
The guest variable storage area is allocated with vmalloc and then
donated to the ultravisor. Any kernel access to that area will result
in a secure storage access exception (aka fault).
This is a problem if such a memory area is read via /proc/kcore. This
causes an exception via vread_iter() and results in an unexpected short
read. Avoid this by allocating a custom VM_SPARSE area. If such an area
is read, vread_iter() returns zeroes for the entire area.
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/include/asm/uv.h | 2 ++
arch/s390/kernel/uv.c | 59 ++++++++++++++++++++++++++++++++++++++
arch/s390/kvm/pv.c | 6 ++--
3 files changed, 64 insertions(+), 3 deletions(-)
diff --git a/arch/s390/include/asm/uv.h b/arch/s390/include/asm/uv.h
index d919e69662f5..153fed63adda 100644
--- a/arch/s390/include/asm/uv.h
+++ b/arch/s390/include/asm/uv.h
@@ -635,6 +635,8 @@ int s390_wiggle_split_folio(struct mm_struct *mm, struct folio *folio);
int __make_folio_secure(struct folio *folio, struct uv_cb_header *uvcb);
int uv_convert_from_secure(unsigned long paddr);
int uv_convert_from_secure_folio(struct folio *folio);
+void *uv_alloc_stor_var(unsigned long size);
+void uv_free_stor_var(void *stor_var);
void setup_uv(void);
diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c
index a284f98d9716..98a7f988fc69 100644
--- a/arch/s390/kernel/uv.c
+++ b/arch/s390/kernel/uv.c
@@ -16,6 +16,7 @@
#include <linux/swap.h>
#include <linux/pagewalk.h>
#include <linux/backing-dev.h>
+#include <linux/vmalloc.h>
#include <asm/facility.h>
#include <asm/sections.h>
#include <asm/uv.h>
@@ -209,6 +210,64 @@ int uv_convert_from_secure_pte(pte_t pte)
return uv_convert_from_secure_folio(pfn_folio(pte_pfn(pte)));
}
+static int uv_free_range_cb(pte_t *ptep, unsigned long addr, void *data)
+{
+ pte_t pte = ptep_get(ptep);
+
+ if (!pte_present(pte))
+ return 0;
+ __free_page(pte_page(pte));
+ return 0;
+}
+
+void uv_free_stor_var(void *stor_var)
+{
+ unsigned long addr, size;
+ struct vm_struct *area;
+
+ if (!stor_var)
+ return;
+ area = find_vm_area(stor_var);
+ if (WARN_ON_ONCE(!area || !(area->flags & VM_SPARSE)))
+ return;
+ size = get_vm_area_size(area);
+ addr = (unsigned long)area->addr;
+ apply_to_existing_page_range(&init_mm, addr, size, uv_free_range_cb, NULL);
+ free_vm_area(area);
+}
+EXPORT_SYMBOL_FOR_MODULES(uv_free_stor_var, "kvm");
+
+static int uv_alloc_range_cb(pte_t *ptep, unsigned long addr, void *data)
+{
+ struct page *page;
+ pte_t pte;
+
+ page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ if (!page)
+ return -ENOMEM;
+ pte = __pte(page_to_phys(page) | pgprot_val(PAGE_KERNEL));
+ set_pte(ptep, pte);
+ return 0;
+}
+
+void *uv_alloc_stor_var(unsigned long size)
+{
+ struct vm_struct *area;
+ unsigned long addr;
+
+ area = get_vm_area(size, VM_SPARSE);
+ if (!area)
+ return NULL;
+ addr = (unsigned long)area->addr;
+ if (apply_to_page_range(&init_mm, addr, size, uv_alloc_range_cb, NULL))
+ goto out;
+ return area->addr;
+out:
+ uv_free_stor_var(area->addr);
+ return NULL;
+}
+EXPORT_SYMBOL_FOR_MODULES(uv_alloc_stor_var, "kvm");
+
/*
* Calculate the expected ref_count for a folio that would otherwise have no
* further pins. This was cribbed from similar functions in other places in
diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c
index 1beacc841ca8..dc5ac29b4c31 100644
--- a/arch/s390/kvm/pv.c
+++ b/arch/s390/kvm/pv.c
@@ -337,7 +337,7 @@ int kvm_s390_pv_create_cpu(struct kvm_vcpu *vcpu, u16 *rc, u16 *rrc)
/* only free resources when the destroy was successful */
static void kvm_s390_pv_dealloc_vm(struct kvm *kvm)
{
- vfree(kvm->arch.pv.stor_var);
+ uv_free_stor_var(kvm->arch.pv.stor_var);
free_pages(kvm->arch.pv.stor_base,
get_order(uv_info.guest_base_stor_len));
kvm_s390_clear_pv_state(kvm);
@@ -369,7 +369,7 @@ static int kvm_s390_pv_alloc_vm(struct kvm *kvm)
/* Allocate variable storage */
vlen = ALIGN(virt * ((npages * PAGE_SIZE) / HPAGE_SIZE), PAGE_SIZE);
vlen += uv_info.guest_virt_base_stor_len;
- kvm->arch.pv.stor_var = vzalloc(vlen);
+ kvm->arch.pv.stor_var = uv_alloc_stor_var(vlen);
if (!kvm->arch.pv.stor_var)
goto out_err;
return 0;
@@ -414,7 +414,7 @@ static int kvm_s390_pv_dispose_one_leftover(struct kvm *kvm,
*/
free_pages(leftover->stor_base, get_order(uv_info.guest_base_stor_len));
free_pages(leftover->old_gmap_table, CRST_ALLOC_ORDER);
- vfree(leftover->stor_var);
+ uv_free_stor_var(leftover->stor_var);
done_fast:
atomic_dec(&kvm->mm->context.protected_count);
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area
2026-07-16 14:14 ` [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area Heiko Carstens
@ 2026-07-16 14:33 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-07-16 14:33 UTC (permalink / raw)
To: Heiko Carstens
Cc: linux-s390, Heiko Carstens, Alexander Gordeev, Vasily Gorbik, kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Physical pages are freed to the buddy allocator before their corresponding page table entries are cleared and the TLB is flushed, creating a Use-After-Free vulnerability window.
- [High] Passing an unaligned `size` to `uv_alloc_stor_var()` causes an infinite loop in the kernel page table walker.
--
--- Patch [1]: [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area ---
commit fd3cc6954f3085a22cae9f0932705a2913ea0050
Author: Heiko Carstens <hca@linux.ibm.com>
KVM: s390: pv: Use VM_SPARSE area for guest variable storage area
The guest variable storage area is allocated with vmalloc and then
donated to the ultravisor. This patch allocates it via a custom
VM_SPARSE area to avoid unexpected short reads via /proc/kcore.
> diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c
> index a284f98d97168..98a7f988fc691 100644
> --- a/arch/s390/kernel/uv.c
> +++ b/arch/s390/kernel/uv.c
[ ... ]
> @@ -209,6 +210,64 @@ int uv_convert_from_secure_pte(pte_t pte)
> return uv_convert_from_secure_folio(pfn_folio(pte_pfn(pte)));
> }
>
> +static int uv_free_range_cb(pte_t *ptep, unsigned long addr, void *data)
> +{
> + pte_t pte = ptep_get(ptep);
> +
> + if (!pte_present(pte))
> + return 0;
> + __free_page(pte_page(pte));
> + return 0;
> +}
> +
> +void uv_free_stor_var(void *stor_var)
> +{
> + unsigned long addr, size;
> + struct vm_struct *area;
> +
> + if (!stor_var)
> + return;
> + area = find_vm_area(stor_var);
> + if (WARN_ON_ONCE(!area || !(area->flags & VM_SPARSE)))
> + return;
> + size = get_vm_area_size(area);
> + addr = (unsigned long)area->addr;
> + apply_to_existing_page_range(&init_mm, addr, size, uv_free_range_cb, NULL);
> + free_vm_area(area);
[Severity: High]
Does this create a Use-After-Free window during VM teardown?
In uv_free_stor_var(), we call apply_to_existing_page_range() which invokes
uv_free_range_cb() for each page. The callback frees the physical page
directly to the buddy allocator via __free_page() while the PTE remains
fully present and writable in the init_mm page tables.
After all pages are freed, free_vm_area() is called to clear the PTEs and
flush the TLB. If a freed page is immediately reallocated to another
subsystem, can concurrent accesses via the stale vmalloc alias read or
overwrite the newly reallocated page before free_vm_area() completes?
> +}
> +EXPORT_SYMBOL_FOR_MODULES(uv_free_stor_var, "kvm");
[ ... ]
> +void *uv_alloc_stor_var(unsigned long size)
> +{
> + struct vm_struct *area;
> + unsigned long addr;
> +
> + area = get_vm_area(size, VM_SPARSE);
> + if (!area)
> + return NULL;
> + addr = (unsigned long)area->addr;
> + if (apply_to_page_range(&init_mm, addr, size, uv_alloc_range_cb, NULL))
[Severity: High]
If an unaligned size is passed to uv_alloc_stor_var(), could this cause an
infinite loop in the page table walker?
The size parameter is passed directly to apply_to_page_range(). The internal
page table walker loop relies on the end boundary being exactly page-aligned
to terminate:
do {
...
} while (pte++, addr += PAGE_SIZE, addr != end);
If size is not a multiple of PAGE_SIZE, addr will increment past end without
matching it, looping indefinitely over the page tables.
Should we pass the page-aligned size of the allocated VM area instead, perhaps
by using get_vm_area_size(area) or explicitly aligning size beforehand?
> + goto out;
> + return area->addr;
> +out:
> + uv_free_stor_var(area->addr);
> + return NULL;
> +}
> +EXPORT_SYMBOL_FOR_MODULES(uv_alloc_stor_var, "kvm");
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260716141436.2125703-1-hca@linux.ibm.com?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/4] s390/mm: Fix handling of secure storage access exceptions in vmalloc area
2026-07-16 14:14 [PATCH 0/4] s390: Reintroduce support for DCACHE_WORD_ACCESS Heiko Carstens
2026-07-16 14:14 ` [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area Heiko Carstens
@ 2026-07-16 14:14 ` Heiko Carstens
2026-07-16 14:14 ` [PATCH 3/4] s390/mm: Remove folio handling for secure storage access exceptions Heiko Carstens
2026-07-16 14:14 ` [PATCH 4/4] s390: Add support for DCACHE_WORD_ACCESS (again) Heiko Carstens
3 siblings, 0 replies; 7+ messages in thread
From: Heiko Carstens @ 2026-07-16 14:14 UTC (permalink / raw)
To: Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
Christian Borntraeger, Janosch Frank, Claudio Imbrenda,
David Hildenbrand
Cc: linux-s390, kvm, linux-kernel
Secure storage access exceptions in vmalloc area need to be handled
separately since folio_try_get() or virt_to_phys() do not work on such
addresses. Given that there cannot be a valid access let such exceptions
fail immediately. The kernel will either fixup or crash.
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/mm/fault.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c
index 028aeb9c48d6..392e46742039 100644
--- a/arch/s390/mm/fault.c
+++ b/arch/s390/mm/fault.c
@@ -438,6 +438,8 @@ void do_secure_storage_access(struct pt_regs *regs)
panic("Unexpected PGM 0x3d with TEID bit 61=0");
}
if (is_kernel_fault(regs)) {
+ if (is_vmalloc_addr((void *)addr))
+ return handle_fault_error_nolock(regs, 0);
folio = virt_to_folio((void *)addr);
if (unlikely(!folio_try_get(folio)))
return;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 3/4] s390/mm: Remove folio handling for secure storage access exceptions
2026-07-16 14:14 [PATCH 0/4] s390: Reintroduce support for DCACHE_WORD_ACCESS Heiko Carstens
2026-07-16 14:14 ` [PATCH 1/4] KVM: s390: pv: Use VM_SPARSE area for guest variable storage area Heiko Carstens
2026-07-16 14:14 ` [PATCH 2/4] s390/mm: Fix handling of secure storage access exceptions in vmalloc area Heiko Carstens
@ 2026-07-16 14:14 ` Heiko Carstens
2026-07-16 14:19 ` David Hildenbrand (Arm)
2026-07-16 14:14 ` [PATCH 4/4] s390: Add support for DCACHE_WORD_ACCESS (again) Heiko Carstens
3 siblings, 1 reply; 7+ messages in thread
From: Heiko Carstens @ 2026-07-16 14:14 UTC (permalink / raw)
To: Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
Christian Borntraeger, Janosch Frank, Claudio Imbrenda,
David Hildenbrand
Cc: linux-s390, kvm, linux-kernel
Remove folio handling for secure storage access exceptions that happen
via the kernel mapping. Using folio_try_get() for exceptions via the
kernel mapping is racy: for example such an exception may happen on one
CPU, while a different CPU frees the corresponding page. Subsequently the
page can be part of a folio of different size, before the faulting CPU
executes folio_try_get(). This may lead to data corruption.
As of now this scenario does not seem to be possible, however with the
upcoming load_unaligned_zeropad() this is possible.
As a side effect PG_arch_1 is not cleared anymore for such faults via the
kernel mapping. However given that this bit is over-indicating it
shouldn't matter.
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/mm/fault.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c
index 392e46742039..db77c8c0023e 100644
--- a/arch/s390/mm/fault.c
+++ b/arch/s390/mm/fault.c
@@ -440,13 +440,7 @@ void do_secure_storage_access(struct pt_regs *regs)
if (is_kernel_fault(regs)) {
if (is_vmalloc_addr((void *)addr))
return handle_fault_error_nolock(regs, 0);
- folio = virt_to_folio((void *)addr);
- if (unlikely(!folio_try_get(folio)))
- return;
- rc = uv_convert_from_secure(folio_to_phys(folio));
- if (!rc)
- clear_bit(PG_arch_1, &folio->flags.f);
- folio_put(folio);
+ rc = uv_convert_from_secure(__pa(addr));
/*
* There are some valid fixup types for kernel
* accesses to donated secure memory. zeropad is one
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH 3/4] s390/mm: Remove folio handling for secure storage access exceptions
2026-07-16 14:14 ` [PATCH 3/4] s390/mm: Remove folio handling for secure storage access exceptions Heiko Carstens
@ 2026-07-16 14:19 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 7+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-16 14:19 UTC (permalink / raw)
To: Heiko Carstens, Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
Christian Borntraeger, Janosch Frank, Claudio Imbrenda
Cc: linux-s390, kvm, linux-kernel
On 7/16/26 16:14, Heiko Carstens wrote:
> Remove folio handling for secure storage access exceptions that happen
> via the kernel mapping. Using folio_try_get() for exceptions via the
> kernel mapping is racy: for example such an exception may happen on one
> CPU, while a different CPU frees the corresponding page. Subsequently the
> page can be part of a folio of different size, before the faulting CPU
> executes folio_try_get(). This may lead to data corruption.
Right, that's why one would have to re-verify whether the page is still part of
the folio afterwards.
--
Cheers,
David
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 4/4] s390: Add support for DCACHE_WORD_ACCESS (again)
2026-07-16 14:14 [PATCH 0/4] s390: Reintroduce support for DCACHE_WORD_ACCESS Heiko Carstens
` (2 preceding siblings ...)
2026-07-16 14:14 ` [PATCH 3/4] s390/mm: Remove folio handling for secure storage access exceptions Heiko Carstens
@ 2026-07-16 14:14 ` Heiko Carstens
3 siblings, 0 replies; 7+ messages in thread
From: Heiko Carstens @ 2026-07-16 14:14 UTC (permalink / raw)
To: Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
Christian Borntraeger, Janosch Frank, Claudio Imbrenda,
David Hildenbrand
Cc: linux-s390, kvm, linux-kernel
Implement load_unaligned_zeropad() and enable DCACHE_WORD_ACCESS to
speed up string operations in fs/dcache.c and fs/namei.c.
With the secure storage access exception cases addressed, add support
for DCACHE_WORD_ACCESS again.
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/Kconfig | 1 +
arch/s390/include/asm/asm-extable.h | 4 ++++
arch/s390/include/asm/word-at-a-time.h | 22 ++++++++++++++++++++++
arch/s390/mm/extable.c | 18 ++++++++++++++++++
4 files changed, 45 insertions(+)
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig
index 84404e6778d5..fe81c539ef2f 100644
--- a/arch/s390/Kconfig
+++ b/arch/s390/Kconfig
@@ -163,6 +163,7 @@ config S390
select ARCH_WANTS_THP_SWAP
select BUILDTIME_TABLE_SORT
select CLONE_BACKWARDS2
+ select DCACHE_WORD_ACCESS if !KMSAN
select DYNAMIC_FTRACE if FUNCTION_TRACER
select FUNCTION_ALIGNMENT_8B if CC_IS_GCC
select FUNCTION_ALIGNMENT_16B if !CC_IS_GCC
diff --git a/arch/s390/include/asm/asm-extable.h b/arch/s390/include/asm/asm-extable.h
index 99748c20e767..d23ea0c94e4e 100644
--- a/arch/s390/include/asm/asm-extable.h
+++ b/arch/s390/include/asm/asm-extable.h
@@ -12,6 +12,7 @@
#define EX_TYPE_UA_FAULT 3
#define EX_TYPE_UA_LOAD_REG 5
#define EX_TYPE_UA_LOAD_REGPAIR 6
+#define EX_TYPE_ZEROPAD 7
#define EX_TYPE_FPC 8
#define EX_TYPE_UA_MVCOS_TO 9
#define EX_TYPE_UA_MVCOS_FROM 10
@@ -79,6 +80,9 @@
#define EX_TABLE_UA_LOAD_REGPAIR(_fault, _target, _regerr, _regzero) \
__EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REGPAIR, _regerr, _regzero, 0)
+#define EX_TABLE_ZEROPAD(_fault, _target, _regdata, _regaddr) \
+ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_ZEROPAD, _regdata, _regaddr, 0)
+
#define EX_TABLE_FPC(_fault, _target) \
__EX_TABLE(__ex_table, _fault, _target, EX_TYPE_FPC, __stringify(%%r0), __stringify(%%r0), 0)
diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h
index e9287036392d..eaa19dee7699 100644
--- a/arch/s390/include/asm/word-at-a-time.h
+++ b/arch/s390/include/asm/word-at-a-time.h
@@ -4,6 +4,7 @@
#include <linux/bitops.h>
#include <linux/wordpart.h>
+#include <asm/asm-extable.h>
#include <asm/bitsperlong.h>
struct word_at_a_time {
@@ -40,4 +41,25 @@ static inline unsigned long zero_bytemask(unsigned long data)
return ~1UL << data;
}
+/*
+ * Load an unaligned word from kernel space.
+ *
+ * In the (very unlikely) case of the word being a page-crosser
+ * and the next page not being mapped, take the exception and
+ * return zeroes in the non-existing part.
+ */
+static inline unsigned long load_unaligned_zeropad(const void *addr)
+{
+ unsigned long data;
+
+ asm_inline volatile(
+ "0: lg %[data],0(%[addr])\n"
+ "1: nopr %%r7\n"
+ EX_TABLE_ZEROPAD(0b, 1b, %[data], %[addr])
+ EX_TABLE_ZEROPAD(1b, 1b, %[data], %[addr])
+ : [data] "=d" (data)
+ : [addr] "a" (addr), "m" (*(unsigned long *)addr));
+ return data;
+}
+
#endif /* _ASM_WORD_AT_A_TIME_H */
diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c
index 063b4346742d..7498e858c401 100644
--- a/arch/s390/mm/extable.c
+++ b/arch/s390/mm/extable.c
@@ -50,6 +50,22 @@ static bool ex_handler_ua_load_reg(const struct exception_table_entry *ex,
return true;
}
+static bool ex_handler_zeropad(const struct exception_table_entry *ex, struct pt_regs *regs)
+{
+ unsigned int reg_addr = FIELD_GET(EX_DATA_REG_ADDR, ex->data);
+ unsigned int reg_data = FIELD_GET(EX_DATA_REG_ERR, ex->data);
+ unsigned long data, addr, offset;
+
+ addr = regs->gprs[reg_addr];
+ offset = addr & (sizeof(unsigned long) - 1);
+ addr &= ~(sizeof(unsigned long) - 1);
+ data = *(unsigned long *)addr;
+ data <<= BITS_PER_BYTE * offset;
+ regs->gprs[reg_data] = data;
+ regs->psw.addr = extable_fixup(ex);
+ return true;
+}
+
static bool ex_handler_fpc(const struct exception_table_entry *ex, struct pt_regs *regs)
{
fpu_sfpc(0);
@@ -118,6 +134,8 @@ bool fixup_exception(struct pt_regs *regs)
return ex_handler_ua_load_reg(ex, false, regs);
case EX_TYPE_UA_LOAD_REGPAIR:
return ex_handler_ua_load_reg(ex, true, regs);
+ case EX_TYPE_ZEROPAD:
+ return ex_handler_zeropad(ex, regs);
case EX_TYPE_FPC:
return ex_handler_fpc(ex, regs);
case EX_TYPE_UA_MVCOS_TO:
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread