diff mbox series

[1/5] KVM: Add helper functions for accessing GDT/LDT

Message ID 20230421145746.5704-1-mdoucha@suse.cz
State Accepted
Headers show
Series [1/5] KVM: Add helper functions for accessing GDT/LDT | expand

Commit Message

Martin Doucha April 21, 2023, 2:57 p.m. UTC
The fragmented structure of GDT and LDT requires helper functions
to work with descriptors. Add the necessary structure definitions,
constants and functions. Also increase GDT size to 32 descriptors
for later use.

Signed-off-by: Martin Doucha <mdoucha@suse.cz>
---
 doc/kvm-test-api.txt                    | 20 ++++++
 testcases/kernel/kvm/bootstrap_x86.S    |  3 +-
 testcases/kernel/kvm/bootstrap_x86_64.S |  3 +-
 testcases/kernel/kvm/include/kvm_x86.h  | 53 ++++++++++++++
 testcases/kernel/kvm/lib_x86.c          | 92 +++++++++++++++++++++++++
 5 files changed, 169 insertions(+), 2 deletions(-)

Comments

Cyril Hrubis April 26, 2023, 8:08 a.m. UTC | #1
Hi!
I did have a look at the patchset and I do not see anything wrong, but I
cannot really review the code without spending a weeks reading the AMD
CPU manuals. What about sending v2 with the runtest file change and
CCing the x86@kernel.org mailing list? Hopefully some of the kernel devs
on that list will have a bit of time to check the actual correctness of
the code...
Martin Doucha April 26, 2023, 9:19 a.m. UTC | #2
On 26. 04. 23 10:08, Cyril Hrubis wrote:
> Hi!
> I did have a look at the patchset and I do not see anything wrong, but I
> cannot really review the code without spending a weeks reading the AMD
> CPU manuals. What about sending v2 with the runtest file change and
> CCing the x86@kernel.org mailing list? Hopefully some of the kernel devs
> on that list will have a bit of time to check the actual correctness of
> the code...

I've CC'd Nicolai who wrote the original KVM tests. If he approves, I'd 
say it'd good enough to merge. I've tested the code on AMD machines and 
it works and successfully reproduces the CVE on affected kernels.
Cyril Hrubis April 26, 2023, 9:20 a.m. UTC | #3
Hi!
> > I did have a look at the patchset and I do not see anything wrong, but I
> > cannot really review the code without spending a weeks reading the AMD
> > CPU manuals. What about sending v2 with the runtest file change and
> > CCing the x86@kernel.org mailing list? Hopefully some of the kernel devs
> > on that list will have a bit of time to check the actual correctness of
> > the code...
> 
> I've CC'd Nicolai who wrote the original KVM tests. If he approves, I'd 
> say it'd good enough to merge. I've tested the code on AMD machines and 
> it works and successfully reproduces the CVE on affected kernels.

Fair enough, Nikoai can you add you Reviewed-by?
Petr Vorel May 2, 2023, 2:24 p.m. UTC | #4
Hi Martin,

Acked-by: Petr Vorel <pvorel@suse.cz>
(LGTM, but my knowledge here is very limited)

Kind regards,
Petr
diff mbox series

Patch

diff --git a/doc/kvm-test-api.txt b/doc/kvm-test-api.txt
index 25b72172d..f62819764 100644
--- a/doc/kvm-test-api.txt
+++ b/doc/kvm-test-api.txt
@@ -343,6 +343,26 @@  Developer's Manual, Volume 3, Chapter 4 for explanation of the fields.
   the known position in page table hierarchy and `entry->page_type`. Returns
   zero if the `entry` does not reference any memory page.
 
+- `void kvm_set_segment_descriptor(struct segment_descriptor *dst, uint64_t baseaddr, uint32_t limit, unsigned int flags)` -
+  Fill the `dst` segment descriptor with given values. The maximum value
+  of `limit` is `0xfffff` (inclusive) regardless of `flags`.
+
+- `void kvm_parse_segment_descriptor(struct segment_descriptor *src, uint64_t *baseaddr, uint32_t *limit, unsigned int *flags)` -
+  Parse data in the `src` segment descriptor and copy them to variables
+  pointed to by the other arguments. Any parameter except the first one can
+  be `NULL`.
+
+- `int kvm_find_free_descriptor(const struct segment_descriptor *table, size_t size)` -
+  Find the first segment descriptor in `table` which does not have
+  the `SEGFLAG_PRESENT` bit set. The function handles double-size descriptors
+  correctly. Returns index of the first available descriptor or -1 if all
+  `size` descriptors are taken.
+
+- `unsigned int kvm_create_stack_descriptor(struct segment_descriptor *table, size_t tabsize, void *stack_base)` -
+  Convenience function for registering a stack segment descriptor. It'll
+  automatically find a free slot in `table` and fill the necessary flags.
+  The `stack_base` pointer must point to the bottom of the stack.
+
 - `void kvm_get_cpuid(unsigned int eax, unsigned int ecx,
   struct kvm_cpuid *buf)` – Executes the CPUID instruction with the given
   `eax` and `ecx` arguments and stores the results in `buf`.
diff --git a/testcases/kernel/kvm/bootstrap_x86.S b/testcases/kernel/kvm/bootstrap_x86.S
index 1aaf0a4d1..3c17a2b47 100644
--- a/testcases/kernel/kvm/bootstrap_x86.S
+++ b/testcases/kernel/kvm/bootstrap_x86.S
@@ -7,6 +7,7 @@ 
 
 .set KVM_TEXIT, 0xff
 .set RESULT_ADDRESS, 0xfffff000
+.set KVM_GDT_SIZE, 32
 
 /*
  * This section will be allocated at address 0x1000 and
@@ -44,7 +45,7 @@  kvm_gdt:
 	.8byte 0
 	gdt32_entry type=0x1a l=0 d=1 /* Code segment protected_mode, 32bits */
 	gdt32_entry type=0x12 /* Data segment, writable */
-	.skip 16 /* Stack and TSS segment descriptors */
+	.skip (KVM_GDT_SIZE-3)*8 /* Stack, TSS and other segment descriptors */
 
 .Lgdt_end:
 .global kvm_gdt_desc
diff --git a/testcases/kernel/kvm/bootstrap_x86_64.S b/testcases/kernel/kvm/bootstrap_x86_64.S
index 0cffd5a12..3d8c49b10 100644
--- a/testcases/kernel/kvm/bootstrap_x86_64.S
+++ b/testcases/kernel/kvm/bootstrap_x86_64.S
@@ -8,6 +8,7 @@ 
 .set KVM_TCONF, 32
 .set KVM_TEXIT, 0xff
 .set RESULT_ADDRESS, 0xfffff000
+.set KVM_GDT_SIZE, 32
 
 /*
  * This section will be allocated at address 0x1000 and
@@ -478,7 +479,7 @@  kvm_pgtable_l4:
 kvm_gdt:
 	.8byte 0
 	gdt32_entry type=0x1a l=1 limit=0 g=0 /* Code segment long mode */
-	.skip 16 /* TSS segment descriptor */
+	.skip (KVM_GDT_SIZE-2)*8 /* TSS and other segment descriptors */
 
 .Lgdt_end:
 .global kvm_gdt_desc
diff --git a/testcases/kernel/kvm/include/kvm_x86.h b/testcases/kernel/kvm/include/kvm_x86.h
index 4f3671135..a655c9834 100644
--- a/testcases/kernel/kvm/include/kvm_x86.h
+++ b/testcases/kernel/kvm/include/kvm_x86.h
@@ -10,6 +10,9 @@ 
 
 #include "kvm_test.h"
 
+#define PAGESIZE 0x1000
+#define KVM_GDT_SIZE 32
+
 /* Interrupts */
 #define X86_INTR_COUNT 256
 
@@ -38,6 +41,26 @@ 
 #define INTR_SECURITY_ERROR 30
 
 
+/* Segment descriptor flags */
+#define SEGTYPE_LDT 0x02
+#define SEGTYPE_TSS 0x09
+#define SEGTYPE_TSS_BUSY 0x0b
+#define SEGTYPE_CALL_GATE 0x0c
+#define SEGTYPE_INTR_GATE 0x0e
+#define SEGTYPE_TRAP_GATE 0x0f
+#define SEGTYPE_RODATA 0x10
+#define SEGTYPE_RWDATA 0x12
+#define SEGTYPE_STACK 0x16
+#define SEGTYPE_CODE 0x1a
+#define SEGTYPE_MASK 0x1f
+
+#define SEGFLAG_NSYSTEM 0x10
+#define SEGFLAG_PRESENT 0x80
+#define SEGFLAG_CODE64 0x200
+#define SEGFLAG_32BIT 0x400
+#define SEGFLAG_PAGE_LIMIT 0x800
+
+
 /* CPUID constants */
 #define CPUID_GET_INPUT_RANGE 0x80000000
 #define CPUID_GET_EXT_FEATURES 0x80000001
@@ -91,6 +114,25 @@  struct intr_descriptor {
 #endif /* defined(__x86_64__) */
 } __attribute__((__packed__));
 
+struct segment_descriptor {
+	unsigned int limit_lo : 16;
+	unsigned int baseaddr_lo : 24;
+	unsigned int flags_lo : 8;
+	unsigned int limit_hi : 4;
+	unsigned int flags_hi : 4;
+	unsigned int baseaddr_hi : 8;
+} __attribute__((__packed__));
+
+struct segment_descriptor64 {
+	unsigned int limit_lo : 16;
+	unsigned int baseaddr_lo : 24;
+	unsigned int flags_lo : 8;
+	unsigned int limit_hi : 4;
+	unsigned int flags_hi : 4;
+	uint64_t baseaddr_hi : 40;
+	uint32_t reserved;
+} __attribute__((__packed__));
+
 struct page_table_entry_pae {
 	unsigned int present: 1;
 	unsigned int writable: 1;
@@ -118,10 +160,21 @@  struct kvm_cregs {
 
 extern struct page_table_entry_pae kvm_pagetable[];
 extern struct intr_descriptor kvm_idt[X86_INTR_COUNT];
+extern struct segment_descriptor kvm_gdt[KVM_GDT_SIZE];
 
 /* Page table helper functions */
 uintptr_t kvm_get_page_address_pae(const struct page_table_entry_pae *entry);
 
+/* Segment descriptor table functions */
+void kvm_set_segment_descriptor(struct segment_descriptor *dst,
+	uint64_t baseaddr, uint32_t limit, unsigned int flags);
+void kvm_parse_segment_descriptor(struct segment_descriptor *src,
+	uint64_t *baseaddr, uint32_t *limit, unsigned int *flags);
+int kvm_find_free_descriptor(const struct segment_descriptor *table,
+	size_t size);
+unsigned int kvm_create_stack_descriptor(struct segment_descriptor *table,
+	size_t tabsize, void *stack_base);
+
 /* Functions for querying CPU info and status */
 void kvm_get_cpuid(unsigned int eax, unsigned int ecx, struct kvm_cpuid *buf);
 void kvm_read_cregs(struct kvm_cregs *buf);
diff --git a/testcases/kernel/kvm/lib_x86.c b/testcases/kernel/kvm/lib_x86.c
index dc2354b10..d206072ee 100644
--- a/testcases/kernel/kvm/lib_x86.c
+++ b/testcases/kernel/kvm/lib_x86.c
@@ -110,6 +110,98 @@  uintptr_t kvm_get_page_address_pae(const struct page_table_entry_pae *entry)
 	return entry->address << 12;
 }
 
+#ifdef __x86_64__
+static void kvm_set_segment_descriptor64(struct segment_descriptor64 *dst,
+	uint64_t baseaddr, uint32_t limit, unsigned int flags)
+{
+
+	dst->baseaddr_lo = baseaddr & 0xffffff;
+	dst->baseaddr_hi = baseaddr >> 24;
+	dst->limit_lo = limit & 0xffff;
+	dst->limit_hi = limit >> 16;
+	dst->flags_lo = flags & 0xff;
+	dst->flags_hi = (flags >> 8) & 0xf;
+	dst->reserved = 0;
+}
+#endif
+
+void kvm_set_segment_descriptor(struct segment_descriptor *dst,
+	uint64_t baseaddr, uint32_t limit, unsigned int flags)
+{
+	if (limit >> 20)
+		tst_brk(TBROK, "Segment limit out of range");
+
+#ifdef __x86_64__
+	/* System descriptors have double size in 64bit mode */
+	if (!(flags & SEGFLAG_NSYSTEM)) {
+		kvm_set_segment_descriptor64((struct segment_descriptor64 *)dst,
+			baseaddr, limit, flags);
+		return;
+	}
+#endif
+
+	if (baseaddr >> 32)
+		tst_brk(TBROK, "Segment base address out of range");
+
+	dst->baseaddr_lo = baseaddr & 0xffffff;
+	dst->baseaddr_hi = baseaddr >> 24;
+	dst->limit_lo = limit & 0xffff;
+	dst->limit_hi = limit >> 16;
+	dst->flags_lo = flags & 0xff;
+	dst->flags_hi = (flags >> 8) & 0xf;
+}
+
+void kvm_parse_segment_descriptor(struct segment_descriptor *src,
+	uint64_t *baseaddr, uint32_t *limit, unsigned int *flags)
+{
+	if (baseaddr) {
+		*baseaddr = (((uint64_t)src->baseaddr_hi) << 24) |
+			src->baseaddr_lo;
+	}
+
+	if (limit)
+		*limit = (((uint32_t)src->limit_hi) << 16) | src->limit_lo;
+
+	if (flags)
+		*flags = (((uint32_t)src->flags_hi) << 8) | src->flags_lo;
+}
+
+int kvm_find_free_descriptor(const struct segment_descriptor *table,
+	size_t size)
+{
+	const struct segment_descriptor *ptr;
+	size_t i;
+
+	for (i = 0, ptr = table; i < size; i++, ptr++) {
+		if (!(ptr->flags_lo & SEGFLAG_PRESENT))
+			return i;
+
+#ifdef __x86_64__
+		/* System descriptors have double size in 64bit mode */
+		if (!(ptr->flags_lo & SEGFLAG_NSYSTEM)) {
+			ptr++;
+			i++;
+		}
+#endif
+	}
+
+	return -1;
+}
+
+unsigned int kvm_create_stack_descriptor(struct segment_descriptor *table,
+	size_t tabsize, void *stack_base)
+{
+	int ret = kvm_find_free_descriptor(table, tabsize);
+
+	if (ret < 0)
+		tst_brk(TBROK, "Descriptor table is full");
+
+	kvm_set_segment_descriptor(table + ret, 0,
+		(((uintptr_t)stack_base) - 1) >> 12, SEGTYPE_STACK |
+		SEGFLAG_PRESENT | SEGFLAG_32BIT | SEGFLAG_PAGE_LIMIT);
+	return ret;
+}
+
 void kvm_get_cpuid(unsigned int eax, unsigned int ecx, struct kvm_cpuid *buf)
 {
 	asm (