Message ID | 20240129083302.26044-7-yi-de.wu@mediatek.com (mailing list archive) |
---|---|
State | Not Applicable |
Headers | show |
Series | GenieZone hypervisor drivers | expand |
Context | Check | Description |
---|---|---|
netdev/tree_selection | success | Not a local patch |
Il 29/01/24 09:32, Yi-De Wu ha scritto: > From: "Yingshiuan Pan" <yingshiuan.pan@mediatek.com> > > Direct use of physical memory from VMs is forbidden and designed to be > dictated to the privilege models managed by GenieZone hypervisor for > security reason. With the help of gzvm-ko, the hypervisor would be able > to manipulate memory as objects. And the memory management is highly > integrated with ARM 2-stage translation tables to convert VA to IPA to > PA under proper security measures required by protected VMs. > > Signed-off-by: Yingshiuan Pan <yingshiuan.pan@mediatek.com> > Signed-off-by: Jerry Wang <ze-yu.wang@mediatek.com> > Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com> > Signed-off-by: Yi-De Wu <yi-de.wu@mediatek.com> > --- > arch/arm64/geniezone/gzvm_arch_common.h | 2 + > arch/arm64/geniezone/vm.c | 9 ++ > drivers/virt/geniezone/Makefile | 1 - > drivers/virt/geniezone/gzvm_vm.c | 110 ++++++++++++++++++++++++ > include/linux/gzvm_drv.h | 40 +++++++++ > include/uapi/linux/gzvm.h | 26 ++++++ > 6 files changed, 187 insertions(+), 1 deletion(-) > > diff --git a/arch/arm64/geniezone/gzvm_arch_common.h b/arch/arm64/geniezone/gzvm_arch_common.h > index fdaa7849353d..2f66e496dfae 100644 > --- a/arch/arm64/geniezone/gzvm_arch_common.h > +++ b/arch/arm64/geniezone/gzvm_arch_common.h > @@ -11,6 +11,7 @@ > enum { > GZVM_FUNC_CREATE_VM = 0, > GZVM_FUNC_DESTROY_VM = 1, > + GZVM_FUNC_SET_MEMREGION = 4, > GZVM_FUNC_PROBE = 12, > NR_GZVM_FUNC, > }; > @@ -23,6 +24,7 @@ enum { > > #define MT_HVC_GZVM_CREATE_VM GZVM_HCALL_ID(GZVM_FUNC_CREATE_VM) > #define MT_HVC_GZVM_DESTROY_VM GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VM) > +#define MT_HVC_GZVM_SET_MEMREGION GZVM_HCALL_ID(GZVM_FUNC_SET_MEMREGION) > #define MT_HVC_GZVM_PROBE GZVM_HCALL_ID(GZVM_FUNC_PROBE) > > /** > diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c > index a15bad13c2ee..998d6498ac5e 100644 > --- a/arch/arm64/geniezone/vm.c > +++ b/arch/arm64/geniezone/vm.c > @@ -49,6 +49,15 @@ int gzvm_arch_probe(void) > return 0; > } > > +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size, > + phys_addr_t region) > +{ > + struct arm_smccc_res res; > + > + return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id, > + buf_size, region, 0, 0, 0, 0, &res); > +} > + > /** > * gzvm_arch_create_vm() - create vm > * @vm_type: VM type. Only supports Linux VM now. > diff --git a/drivers/virt/geniezone/Makefile b/drivers/virt/geniezone/Makefile > index 066efddc0b9c..25614ea3dea2 100644 > --- a/drivers/virt/geniezone/Makefile > +++ b/drivers/virt/geniezone/Makefile > @@ -7,4 +7,3 @@ > GZVM_DIR ?= ../../../drivers/virt/geniezone > > gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o > - Don't remove this line here - actually, don't introduce it in the first place... > diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c > index d5e850af924a..326cc9e93d92 100644 > --- a/drivers/virt/geniezone/gzvm_vm.c > +++ b/drivers/virt/geniezone/gzvm_vm.c > @@ -15,6 +15,115 @@ > static DEFINE_MUTEX(gzvm_list_lock); > static LIST_HEAD(gzvm_list); > > +u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn) > +{ > + u64 offset = gfn - memslot->base_gfn; I'd check if `gfn` is less than `memslot->base_gfn` - that's a potential security issue. This means that this function should be int gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn, u64 *hva_memslot) if (gfn < memslot->base_gfn) return -EINVAL offset = gfn - memslot->base_gfn; *hva_memslot = memslot->userspace_addr + offset * PAGE_SIZE: return 0; > + > + return memslot->userspace_addr + offset * PAGE_SIZE; > +} > + > +/** > + * register_memslot_addr_range() - Register memory region to GenieZone > + * @gzvm: Pointer to struct gzvm > + * @memslot: Pointer to struct gzvm_memslot > + * > + * Return: 0 for success, negative number for error > + */ > +static int > +register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot *memslot) > +{ > + struct gzvm_memory_region_ranges *region; > + u32 buf_size = PAGE_SIZE * 2; > + u64 gfn; > + > + region = alloc_pages_exact(buf_size, GFP_KERNEL); > + if (!region) > + return -ENOMEM; > + > + region->slot = memslot->slot_id; > + region->total_pages = memslot->npages; > + gfn = memslot->base_gfn; > + region->gpa = PFN_PHYS(gfn); > + > + if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size, > + virt_to_phys(region))) { > + pr_err("Failed to register memregion to hypervisor\n"); > + free_pages_exact(region, buf_size); > + return -EFAULT; > + } > + > + free_pages_exact(region, buf_size); > + return 0; > +} > + > +/** > + * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest > + * @gzvm: Pointer to struct gzvm. > + * @mem: Input memory region from user. > + * > + * Return: 0 for success, negative number for error > + * > + * -EXIO - The memslot is out-of-range > + * -EFAULT - Cannot find corresponding vma > + * -EINVAL - Region size and VMA size mismatch > + */ > +static int > +gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm, > + struct gzvm_userspace_memory_region *mem) > +{ > + struct vm_area_struct *vma; > + struct gzvm_memslot *memslot; > + unsigned long size; > + __u32 slot; > + Remove __u32 slot..... if (mem->slot >= GZVM_MAX_MEM_REGION) return -ENXIO; memslot = &gzvm->memslot[mem->slot]; > + slot = mem->slot; > + if (slot >= GZVM_MAX_MEM_REGION) > + return -ENXIO; > + memslot = &gzvm->memslot[slot]; > + > + vma = vma_lookup(gzvm->mm, mem->userspace_addr); > + if (!vma) > + return -EFAULT; > + > + size = vma->vm_end - vma->vm_start; > + if (size != mem->memory_size) > + return -EINVAL; > + > + memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr); > + memslot->npages = size >> PAGE_SHIFT; > + memslot->userspace_addr = mem->userspace_addr; > + memslot->vma = vma; > + memslot->flags = mem->flags; > + memslot->slot_id = mem->slot; > + return register_memslot_addr_range(gzvm, memslot); > +} > + > +/* gzvm_vm_ioctl() - Ioctl handler of VM FD */ > +static long gzvm_vm_ioctl(struct file *filp, unsigned int ioctl, > + unsigned long arg) > +{ > + long ret; > + void __user *argp = (void __user *)arg; > + struct gzvm *gzvm = filp->private_data; > + > + switch (ioctl) { > + case GZVM_SET_USER_MEMORY_REGION: { > + struct gzvm_userspace_memory_region userspace_mem; > + > + if (copy_from_user(&userspace_mem, argp, sizeof(userspace_mem))) { return -EFAULT; > + ret = -EFAULT; > + goto out; > + } > + ret = gzvm_vm_ioctl_set_memory_region(gzvm, &userspace_mem); > + break; > + } > + default: > + ret = -ENOTTY; > + } > +out: > + return ret; > +} > + > static void gzvm_destroy_vm(struct gzvm *gzvm) > { > pr_debug("VM-%u is going to be destroyed\n", gzvm->vm_id); > @@ -42,6 +151,7 @@ static int gzvm_vm_release(struct inode *inode, struct file *filp) > > static const struct file_operations gzvm_vm_fops = { > .release = gzvm_vm_release, > + .unlocked_ioctl = gzvm_vm_ioctl, > .llseek = noop_llseek, > }; > > diff --git a/include/linux/gzvm_drv.h b/include/linux/gzvm_drv.h > index f1dce23838e4..81696b7b67cc 100644 > --- a/include/linux/gzvm_drv.h > +++ b/include/linux/gzvm_drv.h > @@ -7,9 +7,16 @@ > #define __GZVM_DRV_H__ > > #include <linux/list.h> > +#include <linux/mm.h> > #include <linux/mutex.h> > #include <linux/gzvm.h> > > +/* > + * For the normal physical address, the highest 12 bits should be zero, so we > + * can mask bit 62 ~ bit 52 to indicate the error physical address > + */ > +#define GZVM_PA_ERR_BAD (0x7ffULL << 52) > + > #define INVALID_VM_ID 0xffff > > /* > @@ -27,10 +34,39 @@ > * The following data structures are for data transferring between driver and > * hypervisor, and they're aligned with hypervisor definitions > */ > +#define GZVM_MAX_MEM_REGION 10 > + > +/* struct mem_region_addr_range - Identical to ffa memory constituent */ > +struct mem_region_addr_range { > + /* the base IPA of the constituent memory region, aligned to 4 kiB */ > + __u64 address; > + /* the number of 4 kiB pages in the constituent memory region. */ > + __u32 pg_cnt; > + __u32 reserved; > +}; > + > +struct gzvm_memory_region_ranges { > + __u32 slot; > + __u32 constituent_cnt; > + __u64 total_pages; > + __u64 gpa; > + struct mem_region_addr_range constituents[]; > +}; > + > +/* struct gzvm_memslot - VM's memory slot descriptor */ > +struct gzvm_memslot { > + u64 base_gfn; /* begin of guest page frame */ > + unsigned long npages; /* number of pages this slot covers */ > + unsigned long userspace_addr; /* corresponding userspace va */ > + struct vm_area_struct *vma; /* vma related to this userspace addr */ kerneldoc please > + u32 flags; > + u32 slot_id; > +}; > Regards, Angelo
diff --git a/arch/arm64/geniezone/gzvm_arch_common.h b/arch/arm64/geniezone/gzvm_arch_common.h index fdaa7849353d..2f66e496dfae 100644 --- a/arch/arm64/geniezone/gzvm_arch_common.h +++ b/arch/arm64/geniezone/gzvm_arch_common.h @@ -11,6 +11,7 @@ enum { GZVM_FUNC_CREATE_VM = 0, GZVM_FUNC_DESTROY_VM = 1, + GZVM_FUNC_SET_MEMREGION = 4, GZVM_FUNC_PROBE = 12, NR_GZVM_FUNC, }; @@ -23,6 +24,7 @@ enum { #define MT_HVC_GZVM_CREATE_VM GZVM_HCALL_ID(GZVM_FUNC_CREATE_VM) #define MT_HVC_GZVM_DESTROY_VM GZVM_HCALL_ID(GZVM_FUNC_DESTROY_VM) +#define MT_HVC_GZVM_SET_MEMREGION GZVM_HCALL_ID(GZVM_FUNC_SET_MEMREGION) #define MT_HVC_GZVM_PROBE GZVM_HCALL_ID(GZVM_FUNC_PROBE) /** diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c index a15bad13c2ee..998d6498ac5e 100644 --- a/arch/arm64/geniezone/vm.c +++ b/arch/arm64/geniezone/vm.c @@ -49,6 +49,15 @@ int gzvm_arch_probe(void) return 0; } +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size, + phys_addr_t region) +{ + struct arm_smccc_res res; + + return gzvm_hypcall_wrapper(MT_HVC_GZVM_SET_MEMREGION, vm_id, + buf_size, region, 0, 0, 0, 0, &res); +} + /** * gzvm_arch_create_vm() - create vm * @vm_type: VM type. Only supports Linux VM now. diff --git a/drivers/virt/geniezone/Makefile b/drivers/virt/geniezone/Makefile index 066efddc0b9c..25614ea3dea2 100644 --- a/drivers/virt/geniezone/Makefile +++ b/drivers/virt/geniezone/Makefile @@ -7,4 +7,3 @@ GZVM_DIR ?= ../../../drivers/virt/geniezone gzvm-y := $(GZVM_DIR)/gzvm_main.o $(GZVM_DIR)/gzvm_vm.o - diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c index d5e850af924a..326cc9e93d92 100644 --- a/drivers/virt/geniezone/gzvm_vm.c +++ b/drivers/virt/geniezone/gzvm_vm.c @@ -15,6 +15,115 @@ static DEFINE_MUTEX(gzvm_list_lock); static LIST_HEAD(gzvm_list); +u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn) +{ + u64 offset = gfn - memslot->base_gfn; + + return memslot->userspace_addr + offset * PAGE_SIZE; +} + +/** + * register_memslot_addr_range() - Register memory region to GenieZone + * @gzvm: Pointer to struct gzvm + * @memslot: Pointer to struct gzvm_memslot + * + * Return: 0 for success, negative number for error + */ +static int +register_memslot_addr_range(struct gzvm *gzvm, struct gzvm_memslot *memslot) +{ + struct gzvm_memory_region_ranges *region; + u32 buf_size = PAGE_SIZE * 2; + u64 gfn; + + region = alloc_pages_exact(buf_size, GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->slot = memslot->slot_id; + region->total_pages = memslot->npages; + gfn = memslot->base_gfn; + region->gpa = PFN_PHYS(gfn); + + if (gzvm_arch_set_memregion(gzvm->vm_id, buf_size, + virt_to_phys(region))) { + pr_err("Failed to register memregion to hypervisor\n"); + free_pages_exact(region, buf_size); + return -EFAULT; + } + + free_pages_exact(region, buf_size); + return 0; +} + +/** + * gzvm_vm_ioctl_set_memory_region() - Set memory region of guest + * @gzvm: Pointer to struct gzvm. + * @mem: Input memory region from user. + * + * Return: 0 for success, negative number for error + * + * -EXIO - The memslot is out-of-range + * -EFAULT - Cannot find corresponding vma + * -EINVAL - Region size and VMA size mismatch + */ +static int +gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm, + struct gzvm_userspace_memory_region *mem) +{ + struct vm_area_struct *vma; + struct gzvm_memslot *memslot; + unsigned long size; + __u32 slot; + + slot = mem->slot; + if (slot >= GZVM_MAX_MEM_REGION) + return -ENXIO; + memslot = &gzvm->memslot[slot]; + + vma = vma_lookup(gzvm->mm, mem->userspace_addr); + if (!vma) + return -EFAULT; + + size = vma->vm_end - vma->vm_start; + if (size != mem->memory_size) + return -EINVAL; + + memslot->base_gfn = __phys_to_pfn(mem->guest_phys_addr); + memslot->npages = size >> PAGE_SHIFT; + memslot->userspace_addr = mem->userspace_addr; + memslot->vma = vma; + memslot->flags = mem->flags; + memslot->slot_id = mem->slot; + return register_memslot_addr_range(gzvm, memslot); +} + +/* gzvm_vm_ioctl() - Ioctl handler of VM FD */ +static long gzvm_vm_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + long ret; + void __user *argp = (void __user *)arg; + struct gzvm *gzvm = filp->private_data; + + switch (ioctl) { + case GZVM_SET_USER_MEMORY_REGION: { + struct gzvm_userspace_memory_region userspace_mem; + + if (copy_from_user(&userspace_mem, argp, sizeof(userspace_mem))) { + ret = -EFAULT; + goto out; + } + ret = gzvm_vm_ioctl_set_memory_region(gzvm, &userspace_mem); + break; + } + default: + ret = -ENOTTY; + } +out: + return ret; +} + static void gzvm_destroy_vm(struct gzvm *gzvm) { pr_debug("VM-%u is going to be destroyed\n", gzvm->vm_id); @@ -42,6 +151,7 @@ static int gzvm_vm_release(struct inode *inode, struct file *filp) static const struct file_operations gzvm_vm_fops = { .release = gzvm_vm_release, + .unlocked_ioctl = gzvm_vm_ioctl, .llseek = noop_llseek, }; diff --git a/include/linux/gzvm_drv.h b/include/linux/gzvm_drv.h index f1dce23838e4..81696b7b67cc 100644 --- a/include/linux/gzvm_drv.h +++ b/include/linux/gzvm_drv.h @@ -7,9 +7,16 @@ #define __GZVM_DRV_H__ #include <linux/list.h> +#include <linux/mm.h> #include <linux/mutex.h> #include <linux/gzvm.h> +/* + * For the normal physical address, the highest 12 bits should be zero, so we + * can mask bit 62 ~ bit 52 to indicate the error physical address + */ +#define GZVM_PA_ERR_BAD (0x7ffULL << 52) + #define INVALID_VM_ID 0xffff /* @@ -27,10 +34,39 @@ * The following data structures are for data transferring between driver and * hypervisor, and they're aligned with hypervisor definitions */ +#define GZVM_MAX_MEM_REGION 10 + +/* struct mem_region_addr_range - Identical to ffa memory constituent */ +struct mem_region_addr_range { + /* the base IPA of the constituent memory region, aligned to 4 kiB */ + __u64 address; + /* the number of 4 kiB pages in the constituent memory region. */ + __u32 pg_cnt; + __u32 reserved; +}; + +struct gzvm_memory_region_ranges { + __u32 slot; + __u32 constituent_cnt; + __u64 total_pages; + __u64 gpa; + struct mem_region_addr_range constituents[]; +}; + +/* struct gzvm_memslot - VM's memory slot descriptor */ +struct gzvm_memslot { + u64 base_gfn; /* begin of guest page frame */ + unsigned long npages; /* number of pages this slot covers */ + unsigned long userspace_addr; /* corresponding userspace va */ + struct vm_area_struct *vma; /* vma related to this userspace addr */ + u32 flags; + u32 slot_id; +}; struct gzvm { /* userspace tied to this vm */ struct mm_struct *mm; + struct gzvm_memslot memslot[GZVM_MAX_MEM_REGION]; /* lock for list_add*/ struct mutex lock; struct list_head vm_list; @@ -45,7 +81,11 @@ void gzvm_destroy_all_vms(void); /* arch-dependant functions */ int gzvm_arch_probe(void); +int gzvm_arch_set_memregion(u16 vm_id, size_t buf_size, + phys_addr_t region); int gzvm_arch_create_vm(unsigned long vm_type); int gzvm_arch_destroy_vm(u16 vm_id); +u64 gzvm_gfn_to_hva_memslot(struct gzvm_memslot *memslot, u64 gfn); + #endif /* __GZVM_DRV_H__ */ diff --git a/include/uapi/linux/gzvm.h b/include/uapi/linux/gzvm.h index c26c7720fab7..d2d5e6cfc2c9 100644 --- a/include/uapi/linux/gzvm.h +++ b/include/uapi/linux/gzvm.h @@ -22,4 +22,30 @@ /* ioctls for /dev/gzvm fds */ #define GZVM_CREATE_VM _IO(GZVM_IOC_MAGIC, 0x01) /* Returns a Geniezone VM fd */ +/* ioctls for VM fds */ +/* for GZVM_SET_MEMORY_REGION */ +struct gzvm_memory_region { + __u32 slot; + __u32 flags; + __u64 guest_phys_addr; + __u64 memory_size; /* bytes */ +}; + +#define GZVM_SET_MEMORY_REGION _IOW(GZVM_IOC_MAGIC, 0x40, \ + struct gzvm_memory_region) + +/* for GZVM_SET_USER_MEMORY_REGION */ +struct gzvm_userspace_memory_region { + __u32 slot; + __u32 flags; + __u64 guest_phys_addr; + /* bytes */ + __u64 memory_size; + /* start of the userspace allocated memory */ + __u64 userspace_addr; +}; + +#define GZVM_SET_USER_MEMORY_REGION _IOW(GZVM_IOC_MAGIC, 0x46, \ + struct gzvm_userspace_memory_region) + #endif /* __GZVM_H__ */