When shadowing, the guest page tables are write-protected, in order to
trap changes and properly unshadow the shadow mapping for the nested
guest. Already shadowed levels are skipped, so that only the needed
levels are write protected.
Currently the levels that get write protected are exactly one level too
deep: the last level (nested guest memory) gets protected in the wrong
way, and will be protected again correctly a few lines afterwards; most
importantly, the highest non-shadowed level does *not* get write
protected.
Moreover, if the nested guest is running in a real address space, there
are no DAT tables to shadow.
Write protect the correct levels, so that all the levels that need to
be protected are protected, and avoid double protecting the last level;
skip attempting to shadow the DAT tables when the nested guest is
running in a real address space.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
Tested-by: Christian Borntraeger <borntraeger@linux.ibm.com>
Reviewed-by: Janosch Frank <frankja@linux.ibm.com>
---
arch/s390/kvm/gaccess.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/arch/s390/kvm/gaccess.c b/arch/s390/kvm/gaccess.c
index 0ac2d775d4c0..93a757749a6e 100644
--- a/arch/s390/kvm/gaccess.c
+++ b/arch/s390/kvm/gaccess.c
@@ -1518,6 +1518,13 @@ static int _gaccess_do_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *sg,
(!ptep && crste_leaf(*table) && !table->h.i && table->h.p == w->p))
return 0;
+ /* In case of a real address space */
+ if (w->level <= LEVEL_MEM) {
+ l = TABLE_TYPE_PAGE_TABLE;
+ hl = TABLE_TYPE_REGION1;
+ goto real_address_space;
+ }
+
gl = get_level(table, ptep);
/*
@@ -1525,8 +1532,8 @@ static int _gaccess_do_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *sg,
* only the page containing the entry, not the whole table.
*/
for (i = gl ; i >= w->level; i--) {
- rc = gmap_protect_rmap(mc, sg, entries[i - 1].gfn, gpa_to_gfn(saddr),
- entries[i - 1].pfn, i, entries[i - 1].writable);
+ rc = gmap_protect_rmap(mc, sg, entries[i].gfn, gpa_to_gfn(saddr),
+ entries[i].pfn, i + 1, entries[i].writable);
if (rc)
return rc;
if (!sg->parent)
@@ -1542,6 +1549,7 @@ static int _gaccess_do_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *sg,
/* Get the smallest granularity */
l = min3(gl, hl, w->level);
+real_address_space:
flags = DAT_WALK_SPLIT_ALLOC | (uses_skeys(sg->parent) ? DAT_WALK_USES_SKEYS : 0);
/* If necessary, create the shadow mapping */
if (l < gl) {
--
2.53.0
Hi Claudio,
kernel test robot noticed the following build warnings:
[auto build test WARNING on kvm/queue]
[also build test WARNING on kvm/next linus/master v7.0-rc5 next-20260324]
[cannot apply to kvms390/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Claudio-Imbrenda/KVM-s390-vsie-Fix-dat_split_ste/20260325-092851
base: https://git.kernel.org/pub/scm/virt/kvm/kvm.git queue
patch link: https://lore.kernel.org/r/20260324174301.232921-9-imbrenda%40linux.ibm.com
patch subject: [PATCH v3 8/9] KVM: s390: vsie: Fix guest page tables protection
config: s390-defconfig (https://download.01.org/0day-ci/archive/20260325/202603252045.f3Juk9sU-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 054e11d1a17e5ba88bb1a8ef32fad3346e80b186)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603252045.f3Juk9sU-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603252045.f3Juk9sU-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> arch/s390/kvm/gaccess.c:1522:6: warning: variable 'gl' is used uninitialized whenever 'if' condition is true [-Wsometimes-uninitialized]
1522 | if (w->level <= LEVEL_MEM) {
| ^~~~~~~~~~~~~~~~~~~~~
arch/s390/kvm/gaccess.c:1555:10: note: uninitialized use occurs here
1555 | if (l < gl) {
| ^~
arch/s390/kvm/gaccess.c:1522:2: note: remove the 'if' if its condition is always false
1522 | if (w->level <= LEVEL_MEM) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
1523 | l = TABLE_TYPE_PAGE_TABLE;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
1524 | hl = TABLE_TYPE_REGION1;
| ~~~~~~~~~~~~~~~~~~~~~~~~
1525 | goto real_address_space;
| ~~~~~~~~~~~~~~~~~~~~~~~~
1526 | }
| ~
arch/s390/kvm/gaccess.c:1500:22: note: initialize the variable 'gl' to silence this warning
1500 | int flags, i, hl, gl, l, rc;
| ^
| = 0
1 warning generated.
vim +1522 arch/s390/kvm/gaccess.c
1495
1496 static int _gaccess_do_shadow(struct kvm_s390_mmu_cache *mc, struct gmap *sg,
1497 unsigned long saddr, struct pgtwalk *w)
1498 {
1499 struct guest_fault *entries;
1500 int flags, i, hl, gl, l, rc;
1501 union crste *table, *host;
1502 union pte *ptep, *ptep_h;
1503
1504 lockdep_assert_held(&sg->kvm->mmu_lock);
1505 lockdep_assert_held(&sg->parent->children_lock);
1506
1507 entries = get_entries(w);
1508 ptep_h = NULL;
1509 ptep = NULL;
1510
1511 rc = dat_entry_walk(NULL, gpa_to_gfn(saddr), sg->asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE,
1512 &table, &ptep);
1513 if (rc)
1514 return rc;
1515
1516 /* A race occurred. The shadow mapping is already valid, nothing to do */
1517 if ((ptep && !ptep->h.i && ptep->h.p == w->p) ||
1518 (!ptep && crste_leaf(*table) && !table->h.i && table->h.p == w->p))
1519 return 0;
1520
1521 /* In case of a real address space */
> 1522 if (w->level <= LEVEL_MEM) {
1523 l = TABLE_TYPE_PAGE_TABLE;
1524 hl = TABLE_TYPE_REGION1;
1525 goto real_address_space;
1526 }
1527
1528 gl = get_level(table, ptep);
1529
1530 /*
1531 * Skip levels that are already protected. For each level, protect
1532 * only the page containing the entry, not the whole table.
1533 */
1534 for (i = gl ; i >= w->level; i--) {
1535 rc = gmap_protect_rmap(mc, sg, entries[i].gfn, gpa_to_gfn(saddr),
1536 entries[i].pfn, i + 1, entries[i].writable);
1537 if (rc)
1538 return rc;
1539 if (!sg->parent)
1540 return -EAGAIN;
1541 }
1542
1543 rc = dat_entry_walk(NULL, entries[LEVEL_MEM].gfn, sg->parent->asce, DAT_WALK_LEAF,
1544 TABLE_TYPE_PAGE_TABLE, &host, &ptep_h);
1545 if (rc)
1546 return rc;
1547
1548 hl = get_level(host, ptep_h);
1549 /* Get the smallest granularity */
1550 l = min3(gl, hl, w->level);
1551
1552 real_address_space:
1553 flags = DAT_WALK_SPLIT_ALLOC | (uses_skeys(sg->parent) ? DAT_WALK_USES_SKEYS : 0);
1554 /* If necessary, create the shadow mapping */
1555 if (l < gl) {
1556 rc = dat_entry_walk(mc, gpa_to_gfn(saddr), sg->asce, flags, l, &table, &ptep);
1557 if (rc)
1558 return rc;
1559 }
1560 if (l < hl) {
1561 rc = dat_entry_walk(mc, entries[LEVEL_MEM].gfn, sg->parent->asce,
1562 flags, l, &host, &ptep_h);
1563 if (rc)
1564 return rc;
1565 }
1566
1567 if (KVM_BUG_ON(l > TABLE_TYPE_REGION3, sg->kvm))
1568 return -EFAULT;
1569 if (l == TABLE_TYPE_PAGE_TABLE)
1570 return _do_shadow_pte(sg, saddr, ptep_h, ptep, entries + LEVEL_MEM, w->p);
1571 return _do_shadow_crste(sg, saddr, host, table, entries + LEVEL_MEM, w->p);
1572 }
1573
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2026 Red Hat, Inc.