This patch series adds a some not yet picked selftests to the kvm s390x selftest suite.
The additional test cases are covering: * Assert KVM_EXIT_S390_UCONTROL exit on not mapped memory access * Assert functionality of storage keys in ucontrol VM * Assert that memory region operations are rejected for ucontrol VMs
Running the test cases requires sys_admin capabilities to start the ucontrol VM. This can be achieved by running as root or with a command like:
sudo setpriv --reuid nobody --inh-caps -all,+sys_admin \ --ambient-caps -all,+sys_admin --bounding-set -all,+sys_admin \ ./ucontrol_test
---
The patches in this series have been part of the previous patch series. The test cases added here do depend on the fixture added in the earlier patches. From v5 PATCH 7-9 the segment and page table generation has been removed and DAT has been disabled. Since DAT is not necessary to validate the KVM code.
Previeous series: https://lore.kernel.org/kvm/20240807154512.316936-1-schlameuss@linux.ibm.com...
Also see: https://lore.kernel.org/kvm/d97f4dec-31c3-45c0-ac33-90e665eb6e99@linux.ibm.c...
Christoph Schlameuss (3): selftests: kvm: s390: Add uc_map_unmap VM test case selftests: kvm: s390: Add uc_skey VM test case selftests: kvm: s390: Verify reject memory region operations for ucontrol VMs
.../selftests/kvm/s390x/ucontrol_test.c | 218 +++++++++++++++++- 1 file changed, 217 insertions(+), 1 deletion(-)
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
* uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com --- .../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c index 030c59010fe1..41306bb52f29 100644 --- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c +++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c @@ -16,7 +16,11 @@ #include <linux/capability.h> #include <linux/sizes.h>
+#define PGM_SEGMENT_TRANSLATION 0x10 + #define VM_MEM_SIZE (4 * SZ_1M) +#define VM_MEM_EXT_SIZE (2 * SZ_1M) +#define VM_MEM_MAX_M ((VM_MEM_SIZE + VM_MEM_EXT_SIZE) / SZ_1M)
/* so directly declare capget to check caps without libcap */ int capget(cap_user_header_t header, cap_user_data_t data); @@ -58,6 +62,23 @@ asm("test_gprs_asm:\n" " j 0b\n" );
+/* Test program manipulating memory */ +extern char test_mem_asm[]; +asm("test_mem_asm:\n" + "xgr %r0, %r0\n" + + "0:\n" + " ahi %r0,1\n" + " st %r1,0(%r5,%r6)\n" + + " xgr %r1, %r1\n" + " l %r1,0(%r5,%r6)\n" + " ahi %r0,1\n" + " diag 0,0,0x44\n" + + " j 0b\n" +); + FIXTURE(uc_kvm) { struct kvm_s390_sie_block *sie_block; @@ -67,6 +88,7 @@ FIXTURE(uc_kvm) uintptr_t base_hva; uintptr_t code_hva; int kvm_run_size; + vm_paddr_t pgd; void *vm_mem; int vcpu_fd; int kvm_fd; @@ -116,7 +138,7 @@ FIXTURE_SETUP(uc_kvm) self->base_gpa = 0; self->code_gpa = self->base_gpa + (3 * SZ_1M);
- self->vm_mem = aligned_alloc(SZ_1M, VM_MEM_SIZE); + self->vm_mem = aligned_alloc(SZ_1M, VM_MEM_MAX_M * SZ_1M); ASSERT_NE(NULL, self->vm_mem) TH_LOG("malloc failed %u", errno); self->base_hva = (uintptr_t)self->vm_mem; self->code_hva = self->base_hva - self->base_gpa + self->code_gpa; @@ -222,6 +244,36 @@ TEST(uc_cap_hpage) close(kvm_fd); }
+/* calculate host virtual addr from guest physical addr */ +static void *gpa2hva(FIXTURE_DATA(uc_kvm) * self, u64 gpa) +{ + return (void *)(self->base_hva - self->base_gpa + gpa); +} + +static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{ + struct kvm_run *run = self->run; + + TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason); + switch (run->s390_ucontrol.pgm_code) { + case PGM_SEGMENT_TRANSLATION: + pr_info("ucontrol pic segment translation 0x%llx\n", + run->s390_ucontrol.trans_exc_code); + /* map / make additional memory available */ + struct kvm_s390_ucas_mapping map2 = { + .user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code), + .vcpu_addr = run->s390_ucontrol.trans_exc_code, + .length = VM_MEM_EXT_SIZE, + }; + pr_info("ucas map %p %p 0x%llx\n", + (void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length); + TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2)); + break; + default: + TEST_FAIL("UNEXPECTED PGM CODE %d", run->s390_ucontrol.pgm_code); + } +} + /* verify SIEIC exit * * reset stop requests * * fail on codes not expected in the test cases @@ -256,6 +308,12 @@ static bool uc_handle_exit(FIXTURE_DATA(uc_kvm) * self) struct kvm_run *run = self->run;
switch (run->exit_reason) { + case KVM_EXIT_S390_UCONTROL: + /** check program interruption code + * handle page fault --> ucas map + */ + uc_handle_exit_ucontrol(self); + break; case KVM_EXIT_S390_SIEIC: return uc_handle_sieic(self); default: @@ -287,6 +345,66 @@ static void uc_assert_diag44(FIXTURE_DATA(uc_kvm) * self) TEST_ASSERT_EQ(0x440000, sie_block->ipb); }
+TEST_F(uc_kvm, uc_map_unmap) +{ + struct kvm_sync_regs *sync_regs = &self->run->s.regs; + struct kvm_run *run = self->run; + int rc; + + /* copy test_mem_asm to code_hva / code_gpa */ + TH_LOG("copy code %p to vm mapped memory %p / %p", + &test_mem_asm, (void *)self->code_hva, (void *)self->code_gpa); + memcpy((void *)self->code_hva, &test_mem_asm, PAGE_SIZE); + + /* DAT disabled + 64 bit mode */ + run->psw_mask = 0x0000000180000000ULL; + run->psw_addr = self->code_gpa; + + /* set register content for test_mem_asm to access not mapped memory*/ + sync_regs->gprs[1] = 0x55; + sync_regs->gprs[5] = self->base_gpa; + sync_regs->gprs[6] = VM_MEM_SIZE; + run->kvm_dirty_regs |= KVM_SYNC_GPRS; + + /* run and expect to fail witch ucontrol pic segment translation */ + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(1, sync_regs->gprs[0]); + ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason); + + ASSERT_EQ(PGM_SEGMENT_TRANSLATION, run->s390_ucontrol.pgm_code); + ASSERT_EQ(self->base_gpa + VM_MEM_SIZE, run->s390_ucontrol.trans_exc_code); + /* map / make additional memory available */ + struct kvm_s390_ucas_mapping map2 = { + .user_addr = (u64)gpa2hva(self, self->base_gpa + VM_MEM_SIZE), + .vcpu_addr = self->base_gpa + VM_MEM_SIZE, + .length = VM_MEM_EXT_SIZE, + }; + TH_LOG("ucas map %p %p 0x%llx", + (void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length); + rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2); + ASSERT_EQ(0, rc) + TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno)); + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(false, uc_handle_exit(self)); + uc_assert_diag44(self); + + /* assert registers and memory are in expected state */ + ASSERT_EQ(2, sync_regs->gprs[0]); + ASSERT_EQ(0x55, sync_regs->gprs[1]); + ASSERT_EQ(0x55, *(u32 *)gpa2hva(self, self->base_gpa + VM_MEM_SIZE)); + + /* unmap and run loop again */ + TH_LOG("ucas unmap %p %p 0x%llx", + (void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length); + rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_UNMAP, &map2); + ASSERT_EQ(0, rc) + TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno)); + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(3, sync_regs->gprs[0]); + ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason); + ASSERT_EQ(true, uc_handle_exit(self)); +} + TEST_F(uc_kvm, uc_gprs) { struct kvm_sync_regs *sync_regs = &self->run->s.regs;
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
- uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
+static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{
- struct kvm_run *run = self->run;
- TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- switch (run->s390_ucontrol.pgm_code) {
- case PGM_SEGMENT_TRANSLATION:
pr_info("ucontrol pic segment translation 0x%llx\n",
run->s390_ucontrol.trans_exc_code);
/* map / make additional memory available */
struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code),
.vcpu_addr = run->s390_ucontrol.trans_exc_code,
.length = VM_MEM_EXT_SIZE,
};
pr_info("ucas map %p %p 0x%llx\n",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2));
break;
Why is this necessary if you fix up the mapping in the test?
[...]
+TEST_F(uc_kvm, uc_map_unmap) +{
- struct kvm_sync_regs *sync_regs = &self->run->s.regs;
- struct kvm_run *run = self->run;
- int rc;
- /* copy test_mem_asm to code_hva / code_gpa */
- TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_mem_asm, (void *)self->code_hva, (void *)self->code_gpa);
- memcpy((void *)self->code_hva, &test_mem_asm, PAGE_SIZE);
- /* DAT disabled + 64 bit mode */
- run->psw_mask = 0x0000000180000000ULL;
- run->psw_addr = self->code_gpa;
- /* set register content for test_mem_asm to access not mapped memory*/
- sync_regs->gprs[1] = 0x55;
- sync_regs->gprs[5] = self->base_gpa;
- sync_regs->gprs[6] = VM_MEM_SIZE;
- run->kvm_dirty_regs |= KVM_SYNC_GPRS;
- /* run and expect to fail witch ucontrol pic segment translation */
s/witch/with/
- ASSERT_EQ(0, uc_run_once(self));
- ASSERT_EQ(1, sync_regs->gprs[0]);
- ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- ASSERT_EQ(PGM_SEGMENT_TRANSLATION, run->s390_ucontrol.pgm_code);
- ASSERT_EQ(self->base_gpa + VM_MEM_SIZE, run->s390_ucontrol.trans_exc_code);
- /* map / make additional memory available */
- struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, self->base_gpa + VM_MEM_SIZE),
.vcpu_addr = self->base_gpa + VM_MEM_SIZE,
.length = VM_MEM_EXT_SIZE,
- };
- TH_LOG("ucas map %p %p 0x%llx",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
- rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2);
- ASSERT_EQ(0, rc)
TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno));
- ASSERT_EQ(0, uc_run_once(self));
- ASSERT_EQ(false, uc_handle_exit(self));
- uc_assert_diag44(self);
- /* assert registers and memory are in expected state */
- ASSERT_EQ(2, sync_regs->gprs[0]);
- ASSERT_EQ(0x55, sync_regs->gprs[1]);
- ASSERT_EQ(0x55, *(u32 *)gpa2hva(self, self->base_gpa + VM_MEM_SIZE));
- /* unmap and run loop again */
- TH_LOG("ucas unmap %p %p 0x%llx",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
- rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_UNMAP, &map2);
- ASSERT_EQ(0, rc)
TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno));
s/map/unmap/
- ASSERT_EQ(0, uc_run_once(self));
- ASSERT_EQ(3, sync_regs->gprs[0]);
- ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- ASSERT_EQ(true, uc_handle_exit(self));
+}
- TEST_F(uc_kvm, uc_gprs) { struct kvm_sync_regs *sync_regs = &self->run->s.regs;
On Fri Aug 16, 2024 at 4:29 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
- uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
+static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{
- struct kvm_run *run = self->run;
- TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- switch (run->s390_ucontrol.pgm_code) {
- case PGM_SEGMENT_TRANSLATION:
pr_info("ucontrol pic segment translation 0x%llx\n",
run->s390_ucontrol.trans_exc_code);
/* map / make additional memory available */
struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code),
.vcpu_addr = run->s390_ucontrol.trans_exc_code,
.length = VM_MEM_EXT_SIZE,
};
pr_info("ucas map %p %p 0x%llx\n",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2));
break;
Why is this necessary if you fix up the mapping in the test?
I did split this out here to have an automatic and clean way to do the map for other test cases as well. Other test cases would likely not bother with the unmap. This is also used within the uc_skey test to make sure the remap does work after the unmap.
I could change this to use uc_handle_exit_ucontrol for both, the map and the remap. But looking at the test code I felt that was more confusing than this within the uc_skey test.
[...]
Thanks for finding the typos.
On 8/19/24 6:03 PM, Christoph Schlameuss wrote:
On Fri Aug 16, 2024 at 4:29 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
- uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
+static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{
- struct kvm_run *run = self->run;
- TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- switch (run->s390_ucontrol.pgm_code) {
- case PGM_SEGMENT_TRANSLATION:
pr_info("ucontrol pic segment translation 0x%llx\n",
run->s390_ucontrol.trans_exc_code);
/* map / make additional memory available */
struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code),
.vcpu_addr = run->s390_ucontrol.trans_exc_code,
.length = VM_MEM_EXT_SIZE,
};
pr_info("ucas map %p %p 0x%llx\n",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2));
break;
Why is this necessary if you fix up the mapping in the test?
This is also used within the uc_skey test to make sure the remap does work after the unmap.
Maybe I'm blind because I'm still recovering but where exactly?
On Fri Aug 23, 2024 at 10:02 AM CEST, Janosch Frank wrote:
On 8/19/24 6:03 PM, Christoph Schlameuss wrote:
On Fri Aug 16, 2024 at 4:29 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
- uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
+static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{
- struct kvm_run *run = self->run;
- TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- switch (run->s390_ucontrol.pgm_code) {
- case PGM_SEGMENT_TRANSLATION:
pr_info("ucontrol pic segment translation 0x%llx\n",
run->s390_ucontrol.trans_exc_code);
/* map / make additional memory available */
struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code),
.vcpu_addr = run->s390_ucontrol.trans_exc_code,
.length = VM_MEM_EXT_SIZE,
};
pr_info("ucas map %p %p 0x%llx\n",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2));
break;
Why is this necessary if you fix up the mapping in the test?
This is also used within the uc_skey test to make sure the remap does work after the unmap.
Maybe I'm blind because I'm still recovering but where exactly?
It is literally used in the last line of the test case. Calling uc_handle_exit() again re-maps previously unmapped memory.
I can try to make that a little bit more obvious.
+ /* unmap and run loop again */ + TH_LOG("ucas unmap %p %p 0x%llx", + (void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length); + rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_UNMAP, &map2); + ASSERT_EQ(0, rc) + TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno)); + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(3, sync_regs->gprs[0]); + ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason); + ASSERT_EQ(true, uc_handle_exit(self)); // <--- HERE +}
On 8/23/24 3:03 PM, Christoph Schlameuss wrote:
On Fri Aug 23, 2024 at 10:02 AM CEST, Janosch Frank wrote:
On 8/19/24 6:03 PM, Christoph Schlameuss wrote:
On Fri Aug 16, 2024 at 4:29 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case verifying basic running and interaction of ucontrol VMs. Fill the segment and page tables for allocated memory and map memory on first access.
- uc_map_unmap Store and load data to mapped and unmapped memory and use pic segment translation handling to map memory on access.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-)
+static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) * self) +{
- struct kvm_run *run = self->run;
- TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- switch (run->s390_ucontrol.pgm_code) {
- case PGM_SEGMENT_TRANSLATION:
pr_info("ucontrol pic segment translation 0x%llx\n",
run->s390_ucontrol.trans_exc_code);
/* map / make additional memory available */
struct kvm_s390_ucas_mapping map2 = {
.user_addr = (u64)gpa2hva(self, run->s390_ucontrol.trans_exc_code),
.vcpu_addr = run->s390_ucontrol.trans_exc_code,
.length = VM_MEM_EXT_SIZE,
};
pr_info("ucas map %p %p 0x%llx\n",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
TEST_ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map2));
break;
Why is this necessary if you fix up the mapping in the test?
This is also used within the uc_skey test to make sure the remap does work after the unmap.
Maybe I'm blind because I'm still recovering but where exactly?
It is literally used in the last line of the test case. Calling uc_handle_exit() again re-maps previously unmapped memory.
I can try to make that a little bit more obvious.
- /* unmap and run loop again */
- TH_LOG("ucas unmap %p %p 0x%llx",
(void *)map2.user_addr, (void *)map2.vcpu_addr, map2.length);
- rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_UNMAP, &map2);
- ASSERT_EQ(0, rc)
TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno));
- ASSERT_EQ(0, uc_run_once(self));
- ASSERT_EQ(3, sync_regs->gprs[0]);
- ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
- ASSERT_EQ(true, uc_handle_exit(self)); // <--- HERE
+}
Seems like I'm blind then :)
Add a test case manipulating s390 storage keys from within the ucontrol VM.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com --- .../selftests/kvm/s390x/ucontrol_test.c | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c index 41306bb52f29..5f8815a80544 100644 --- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c +++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c @@ -79,6 +79,32 @@ asm("test_mem_asm:\n" " j 0b\n" );
+/* Test program manipulating storage keys */ +extern char test_skey_asm[]; +asm("test_skey_asm:\n" + "xgr %r0, %r0\n" + + "0:\n" + " ahi %r0,1\n" + " st %r1,0(%r5,%r6)\n" + + " iske %r1,%r6\n" + " ahi %r0,1\n" + " diag 0,0,0x44\n" + + " sske %r1,%r6\n" + " iske %r1,%r6\n" + " ahi %r0,1\n" + " diag 0,0,0x44\n" + + " rrbe %r1,%r6\n" + " iske %r1,%r6\n" + " ahi %r0,1\n" + " diag 0,0,0x44\n" + + " j 0b\n" +); + FIXTURE(uc_kvm) { struct kvm_s390_sie_block *sie_block; @@ -345,6 +371,56 @@ static void uc_assert_diag44(FIXTURE_DATA(uc_kvm) * self) TEST_ASSERT_EQ(0x440000, sie_block->ipb); }
+TEST_F(uc_kvm, uc_skey) +{ + u64 test_vaddr = self->base_gpa + VM_MEM_SIZE - (SZ_1M / 2); + struct kvm_sync_regs *sync_regs = &self->run->s.regs; + struct kvm_run *run = self->run; + u8 skeyvalue = 0x34; + + /* copy test_skey_asm to code_hva / code_gpa */ + TH_LOG("copy code %p to vm mapped memory %p / %p", + &test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa); + memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE); + + /* set register content for test_skey_asm to access not mapped memory */ + sync_regs->gprs[1] = skeyvalue; + sync_regs->gprs[5] = self->base_gpa; + sync_regs->gprs[6] = test_vaddr; + run->kvm_dirty_regs |= KVM_SYNC_GPRS; + + self->sie_block->ictl |= ICTL_OPEREXC | ICTL_PINT; + self->sie_block->cpuflags &= ~CPUSTAT_KSS; + /* DAT disabled + 64 bit mode */ + run->psw_mask = 0x0000000180000000ULL; + run->psw_addr = self->code_gpa; + + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(false, uc_handle_exit(self)); + ASSERT_EQ(2, sync_regs->gprs[0]); + /* assert initial skey (ACC = 0, R & C = 1) */ + ASSERT_EQ(0x06, sync_regs->gprs[1]); + uc_assert_diag44(self); + + sync_regs->gprs[1] = skeyvalue; + run->kvm_dirty_regs |= KVM_SYNC_GPRS; + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(false, uc_handle_exit(self)); + ASSERT_EQ(3, sync_regs->gprs[0]); + ASSERT_EQ(skeyvalue, sync_regs->gprs[1]); + uc_assert_diag44(self); + + sync_regs->gprs[1] = skeyvalue; + run->kvm_dirty_regs |= KVM_SYNC_GPRS; + ASSERT_EQ(0, uc_run_once(self)); + ASSERT_EQ(false, uc_handle_exit(self)); + ASSERT_EQ(4, sync_regs->gprs[0]); + /* assert R reset but rest of skey unchanged*/ + ASSERT_EQ(skeyvalue & 0xfa, sync_regs->gprs[1]); + ASSERT_EQ(0x00, sync_regs->gprs[1] & 0x04); + uc_assert_diag44(self); +} + TEST_F(uc_kvm, uc_map_unmap) { struct kvm_sync_regs *sync_regs = &self->run->s.regs;
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case manipulating s390 storage keys from within the ucontrol VM.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c index 41306bb52f29..5f8815a80544 100644 --- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c +++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c @@ -79,6 +79,32 @@ asm("test_mem_asm:\n" " j 0b\n" ); +/* Test program manipulating storage keys */ +extern char test_skey_asm[]; +asm("test_skey_asm:\n"
- "xgr %r0, %r0\n"
- "0:\n"
- " ahi %r0,1\n"
- " st %r1,0(%r5,%r6)\n"
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " sske %r1,%r6\n"
Might want to add a xgr here so you're sure that you're not reading your own values if iske fails.
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " rrbe %r1,%r6\n"
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " j 0b\n"
+);
- FIXTURE(uc_kvm) { struct kvm_s390_sie_block *sie_block;
@@ -345,6 +371,56 @@ static void uc_assert_diag44(FIXTURE_DATA(uc_kvm) * self) TEST_ASSERT_EQ(0x440000, sie_block->ipb); } +TEST_F(uc_kvm, uc_skey) +{
- u64 test_vaddr = self->base_gpa + VM_MEM_SIZE - (SZ_1M / 2);
- struct kvm_sync_regs *sync_regs = &self->run->s.regs;
- struct kvm_run *run = self->run;
- u8 skeyvalue = 0x34;
- /* copy test_skey_asm to code_hva / code_gpa */
- TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
- memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
- /* set register content for test_skey_asm to access not mapped memory */
- sync_regs->gprs[1] = skeyvalue;
- sync_regs->gprs[5] = self->base_gpa;
- sync_regs->gprs[6] = test_vaddr;
- run->kvm_dirty_regs |= KVM_SYNC_GPRS;
- self->sie_block->ictl |= ICTL_OPEREXC | ICTL_PINT;
- self->sie_block->cpuflags &= ~CPUSTAT_KSS;
So you don't want KVM to initialize skeys? Or am I missing a ucontrol skey interaction?
What about the ICTLs if KSS is not available on the machine?
On Fri Aug 16, 2024 at 4:36 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
Add a test case manipulating s390 storage keys from within the ucontrol VM.
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com
.../selftests/kvm/s390x/ucontrol_test.c | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c index 41306bb52f29..5f8815a80544 100644 --- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c +++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c @@ -79,6 +79,32 @@ asm("test_mem_asm:\n" " j 0b\n" ); +/* Test program manipulating storage keys */ +extern char test_skey_asm[]; +asm("test_skey_asm:\n"
- "xgr %r0, %r0\n"
- "0:\n"
- " ahi %r0,1\n"
- " st %r1,0(%r5,%r6)\n"
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " sske %r1,%r6\n"
Might want to add a xgr here so you're sure that you're not reading your own values if iske fails.
Good point. Will change the r1 value here.
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " rrbe %r1,%r6\n"
- " iske %r1,%r6\n"
- " ahi %r0,1\n"
- " diag 0,0,0x44\n"
- " j 0b\n"
+);
- FIXTURE(uc_kvm) { struct kvm_s390_sie_block *sie_block;
@@ -345,6 +371,56 @@ static void uc_assert_diag44(FIXTURE_DATA(uc_kvm) * self) TEST_ASSERT_EQ(0x440000, sie_block->ipb); } +TEST_F(uc_kvm, uc_skey) +{
- u64 test_vaddr = self->base_gpa + VM_MEM_SIZE - (SZ_1M / 2);
- struct kvm_sync_regs *sync_regs = &self->run->s.regs;
- struct kvm_run *run = self->run;
- u8 skeyvalue = 0x34;
- /* copy test_skey_asm to code_hva / code_gpa */
- TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
- memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
- /* set register content for test_skey_asm to access not mapped memory */
- sync_regs->gprs[1] = skeyvalue;
- sync_regs->gprs[5] = self->base_gpa;
- sync_regs->gprs[6] = test_vaddr;
- run->kvm_dirty_regs |= KVM_SYNC_GPRS;
- self->sie_block->ictl |= ICTL_OPEREXC | ICTL_PINT;
- self->sie_block->cpuflags &= ~CPUSTAT_KSS;
So you don't want KVM to initialize skeys? Or am I missing a ucontrol skey interaction?
What about the ICTLs if KSS is not available on the machine?
This is explicitly disabling KSS, not enabling it. Doing that explicitly might not strictly be necessary but I thought this does provide some clarity about the state.
On 8/19/24 6:00 PM, Christoph Schlameuss wrote:
On Fri Aug 16, 2024 at 4:36 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
[...]
+TEST_F(uc_kvm, uc_skey) +{
- u64 test_vaddr = self->base_gpa + VM_MEM_SIZE - (SZ_1M / 2);
- struct kvm_sync_regs *sync_regs = &self->run->s.regs;
- struct kvm_run *run = self->run;
- u8 skeyvalue = 0x34;
- /* copy test_skey_asm to code_hva / code_gpa */
- TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
- memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
- /* set register content for test_skey_asm to access not mapped memory */
- sync_regs->gprs[1] = skeyvalue;
- sync_regs->gprs[5] = self->base_gpa;
- sync_regs->gprs[6] = test_vaddr;
- run->kvm_dirty_regs |= KVM_SYNC_GPRS;
- self->sie_block->ictl |= ICTL_OPEREXC | ICTL_PINT;
- self->sie_block->cpuflags &= ~CPUSTAT_KSS;
So you don't want KVM to initialize skeys? Or am I missing a ucontrol skey interaction?
What about the ICTLs if KSS is not available on the machine?
This is explicitly disabling KSS, not enabling it. Doing that explicitly might not strictly be necessary but I thought this does provide some clarity about the state.
The 3 skey ICTLs and KSS are used by KVM to get an intercept on the first skey instruction that the guest issues. KVM uses that intercept to initialize the keys and setup skey handling since it's an edge case because Linux doesn't use skeys.
If KSS is available KVM will not set the skey ICTLs but KSS is a "recent" addition (my guess would be ~z13). So if you want to disable skey intercepts regardless of the machine you need to clear all 4 bits.
Are you sure that disabling KSS makes sense and does what you think it does?
On Fri Aug 23, 2024 at 9:59 AM CEST, Janosch Frank wrote:
On 8/19/24 6:00 PM, Christoph Schlameuss wrote:
On Fri Aug 16, 2024 at 4:36 PM CEST, Janosch Frank wrote:
On 8/15/24 5:45 PM, Christoph Schlameuss wrote:
[...]
+TEST_F(uc_kvm, uc_skey) +{
- u64 test_vaddr = self->base_gpa + VM_MEM_SIZE - (SZ_1M / 2);
- struct kvm_sync_regs *sync_regs = &self->run->s.regs;
- struct kvm_run *run = self->run;
- u8 skeyvalue = 0x34;
- /* copy test_skey_asm to code_hva / code_gpa */
- TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
- memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
- /* set register content for test_skey_asm to access not mapped memory */
- sync_regs->gprs[1] = skeyvalue;
- sync_regs->gprs[5] = self->base_gpa;
- sync_regs->gprs[6] = test_vaddr;
- run->kvm_dirty_regs |= KVM_SYNC_GPRS;
- self->sie_block->ictl |= ICTL_OPEREXC | ICTL_PINT;
- self->sie_block->cpuflags &= ~CPUSTAT_KSS;
So you don't want KVM to initialize skeys? Or am I missing a ucontrol skey interaction?
What about the ICTLs if KSS is not available on the machine?
This is explicitly disabling KSS, not enabling it. Doing that explicitly might not strictly be necessary but I thought this does provide some clarity about the state.
The 3 skey ICTLs and KSS are used by KVM to get an intercept on the first skey instruction that the guest issues. KVM uses that intercept to initialize the keys and setup skey handling since it's an edge case because Linux doesn't use skeys.
If KSS is available KVM will not set the skey ICTLs but KSS is a "recent" addition (my guess would be ~z13). So if you want to disable skey intercepts regardless of the machine you need to clear all 4 bits.
Are you sure that disabling KSS makes sense and does what you think it does?
I did revisit the normal skey initialization. It is as you say triggered by the first KSS intercept. But this is where it actually differs in uncontrol VMs. kvm_handle_sie_intercept() would normally call kvm_s390_skey_check_enable(). But in the ucontrol case it exists early and sets KVM_EXIT_S390_SIEIC with the KSS intercept code.
So I think the best coverage we can produce here is to mimic that within the tests userspace code. I will restore the KSS interception and handle it in the next patch version.
Add a test case verifying KVM_SET_USER_MEMORY_REGION and KVM_SET_USER_MEMORY_REGION2 cannot be executed on ucontrol VMs.
Executing this test case on not patched kernels will cause a null pointer dereference in the host kernel. This is fixed with commit: commit 7816e58967d0 ("kvm: s390: Reject memory region operations for ucontrol VMs")
Signed-off-by: Christoph Schlameuss schlameuss@linux.ibm.com Reviewed-by: Janosch Frank frankja@linux.ibm.com --- .../selftests/kvm/s390x/ucontrol_test.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+)
diff --git a/tools/testing/selftests/kvm/s390x/ucontrol_test.c b/tools/testing/selftests/kvm/s390x/ucontrol_test.c index 5f8815a80544..ed3c441522c8 100644 --- a/tools/testing/selftests/kvm/s390x/ucontrol_test.c +++ b/tools/testing/selftests/kvm/s390x/ucontrol_test.c @@ -524,4 +524,26 @@ TEST_F(uc_kvm, uc_gprs) ASSERT_EQ(1, sync_regs->gprs[0]); }
+TEST_F(uc_kvm, uc_no_user_region) +{ + struct kvm_userspace_memory_region region = { + .slot = 1, + .guest_phys_addr = self->code_gpa, + .memory_size = VM_MEM_EXT_SIZE, + .userspace_addr = (uintptr_t)self->code_hva, + }; + struct kvm_userspace_memory_region2 region2 = { + .slot = 1, + .guest_phys_addr = self->code_gpa, + .memory_size = VM_MEM_EXT_SIZE, + .userspace_addr = (uintptr_t)self->code_hva, + }; + + ASSERT_EQ(-1, ioctl(self->vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(-1, ioctl(self->vm_fd, KVM_SET_USER_MEMORY_REGION2, ®ion2)); + ASSERT_EQ(EINVAL, errno); +} + TEST_HARNESS_MAIN
linux-kselftest-mirror@lists.linaro.org