diff options
author | Paul Durrant <paul.durrant@citrix.com> | 2017-02-13 12:03:23 -0500 |
---|---|---|
committer | Boris Ostrovsky <boris.ostrovsky@oracle.com> | 2017-02-14 15:13:43 -0500 |
commit | ab520be8cd5d56867fc95cfbc34b90880faf1f9d (patch) | |
tree | 4c432a918ffbd6308f752eaac36b322b811582b1 | |
parent | dc9eab6fd94dd26340749321bba2c58634761516 (diff) |
xen/privcmd: Add IOCTL_PRIVCMD_DM_OP
Recently a new dm_op[1] hypercall was added to Xen to provide a mechanism
for restricting device emulators (such as QEMU) to a limited set of
hypervisor operations, and being able to audit those operations in the
kernel of the domain in which they run.
This patch adds IOCTL_PRIVCMD_DM_OP as gateway for __HYPERVISOR_dm_op.
NOTE: There is no requirement for user-space code to bounce data through
locked memory buffers (as with IOCTL_PRIVCMD_HYPERCALL) since
privcmd has enough information to lock the original buffers
directly.
[1] http://xenbits.xen.org/gitweb/?p=xen.git;a=commit;h=524a98c2
Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
Acked-by: Stefano Stabellini <sstabellini@kernel.org>
Signed-off-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
-rw-r--r-- | arch/arm/xen/enlighten.c | 1 | ||||
-rw-r--r-- | arch/arm/xen/hypercall.S | 1 | ||||
-rw-r--r-- | arch/arm64/xen/hypercall.S | 1 | ||||
-rw-r--r-- | arch/x86/include/asm/xen/hypercall.h | 7 | ||||
-rw-r--r-- | drivers/xen/privcmd.c | 139 | ||||
-rw-r--r-- | include/uapi/xen/privcmd.h | 13 | ||||
-rw-r--r-- | include/xen/arm/hypercall.h | 1 | ||||
-rw-r--r-- | include/xen/interface/hvm/dm_op.h | 32 | ||||
-rw-r--r-- | include/xen/interface/xen.h | 1 |
9 files changed, 196 insertions, 0 deletions
diff --git a/arch/arm/xen/enlighten.c b/arch/arm/xen/enlighten.c index 11d9f2898b16..81e3217b12d3 100644 --- a/arch/arm/xen/enlighten.c +++ b/arch/arm/xen/enlighten.c | |||
@@ -457,4 +457,5 @@ EXPORT_SYMBOL_GPL(HYPERVISOR_tmem_op); | |||
457 | EXPORT_SYMBOL_GPL(HYPERVISOR_platform_op); | 457 | EXPORT_SYMBOL_GPL(HYPERVISOR_platform_op); |
458 | EXPORT_SYMBOL_GPL(HYPERVISOR_multicall); | 458 | EXPORT_SYMBOL_GPL(HYPERVISOR_multicall); |
459 | EXPORT_SYMBOL_GPL(HYPERVISOR_vm_assist); | 459 | EXPORT_SYMBOL_GPL(HYPERVISOR_vm_assist); |
460 | EXPORT_SYMBOL_GPL(HYPERVISOR_dm_op); | ||
460 | EXPORT_SYMBOL_GPL(privcmd_call); | 461 | EXPORT_SYMBOL_GPL(privcmd_call); |
diff --git a/arch/arm/xen/hypercall.S b/arch/arm/xen/hypercall.S index a648dfc3be30..b0b80c0f09f3 100644 --- a/arch/arm/xen/hypercall.S +++ b/arch/arm/xen/hypercall.S | |||
@@ -92,6 +92,7 @@ HYPERCALL1(tmem_op); | |||
92 | HYPERCALL1(platform_op_raw); | 92 | HYPERCALL1(platform_op_raw); |
93 | HYPERCALL2(multicall); | 93 | HYPERCALL2(multicall); |
94 | HYPERCALL2(vm_assist); | 94 | HYPERCALL2(vm_assist); |
95 | HYPERCALL3(dm_op); | ||
95 | 96 | ||
96 | ENTRY(privcmd_call) | 97 | ENTRY(privcmd_call) |
97 | stmdb sp!, {r4} | 98 | stmdb sp!, {r4} |
diff --git a/arch/arm64/xen/hypercall.S b/arch/arm64/xen/hypercall.S index 947830a459d2..401ceb71540c 100644 --- a/arch/arm64/xen/hypercall.S +++ b/arch/arm64/xen/hypercall.S | |||
@@ -84,6 +84,7 @@ HYPERCALL1(tmem_op); | |||
84 | HYPERCALL1(platform_op_raw); | 84 | HYPERCALL1(platform_op_raw); |
85 | HYPERCALL2(multicall); | 85 | HYPERCALL2(multicall); |
86 | HYPERCALL2(vm_assist); | 86 | HYPERCALL2(vm_assist); |
87 | HYPERCALL3(dm_op); | ||
87 | 88 | ||
88 | ENTRY(privcmd_call) | 89 | ENTRY(privcmd_call) |
89 | mov x16, x0 | 90 | mov x16, x0 |
diff --git a/arch/x86/include/asm/xen/hypercall.h b/arch/x86/include/asm/xen/hypercall.h index a12a047184ee..f6d20f6cca12 100644 --- a/arch/x86/include/asm/xen/hypercall.h +++ b/arch/x86/include/asm/xen/hypercall.h | |||
@@ -472,6 +472,13 @@ HYPERVISOR_xenpmu_op(unsigned int op, void *arg) | |||
472 | return _hypercall2(int, xenpmu_op, op, arg); | 472 | return _hypercall2(int, xenpmu_op, op, arg); |
473 | } | 473 | } |
474 | 474 | ||
475 | static inline int | ||
476 | HYPERVISOR_dm_op( | ||
477 | domid_t dom, unsigned int nr_bufs, void *bufs) | ||
478 | { | ||
479 | return _hypercall3(int, dm_op, dom, nr_bufs, bufs); | ||
480 | } | ||
481 | |||
475 | static inline void | 482 | static inline void |
476 | MULTI_fpu_taskswitch(struct multicall_entry *mcl, int set) | 483 | MULTI_fpu_taskswitch(struct multicall_entry *mcl, int set) |
477 | { | 484 | { |
diff --git a/drivers/xen/privcmd.c b/drivers/xen/privcmd.c index 5e5c7aef0c9f..1a6f1860e008 100644 --- a/drivers/xen/privcmd.c +++ b/drivers/xen/privcmd.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/pagemap.h> | 22 | #include <linux/pagemap.h> |
23 | #include <linux/seq_file.h> | 23 | #include <linux/seq_file.h> |
24 | #include <linux/miscdevice.h> | 24 | #include <linux/miscdevice.h> |
25 | #include <linux/moduleparam.h> | ||
25 | 26 | ||
26 | #include <asm/pgalloc.h> | 27 | #include <asm/pgalloc.h> |
27 | #include <asm/pgtable.h> | 28 | #include <asm/pgtable.h> |
@@ -32,6 +33,7 @@ | |||
32 | #include <xen/xen.h> | 33 | #include <xen/xen.h> |
33 | #include <xen/privcmd.h> | 34 | #include <xen/privcmd.h> |
34 | #include <xen/interface/xen.h> | 35 | #include <xen/interface/xen.h> |
36 | #include <xen/interface/hvm/dm_op.h> | ||
35 | #include <xen/features.h> | 37 | #include <xen/features.h> |
36 | #include <xen/page.h> | 38 | #include <xen/page.h> |
37 | #include <xen/xen-ops.h> | 39 | #include <xen/xen-ops.h> |
@@ -43,6 +45,17 @@ MODULE_LICENSE("GPL"); | |||
43 | 45 | ||
44 | #define PRIV_VMA_LOCKED ((void *)1) | 46 | #define PRIV_VMA_LOCKED ((void *)1) |
45 | 47 | ||
48 | static unsigned int privcmd_dm_op_max_num = 16; | ||
49 | module_param_named(dm_op_max_nr_bufs, privcmd_dm_op_max_num, uint, 0644); | ||
50 | MODULE_PARM_DESC(dm_op_max_nr_bufs, | ||
51 | "Maximum number of buffers per dm_op hypercall"); | ||
52 | |||
53 | static unsigned int privcmd_dm_op_buf_max_size = 4096; | ||
54 | module_param_named(dm_op_buf_max_size, privcmd_dm_op_buf_max_size, uint, | ||
55 | 0644); | ||
56 | MODULE_PARM_DESC(dm_op_buf_max_size, | ||
57 | "Maximum size of a dm_op hypercall buffer"); | ||
58 | |||
46 | static int privcmd_vma_range_is_mapped( | 59 | static int privcmd_vma_range_is_mapped( |
47 | struct vm_area_struct *vma, | 60 | struct vm_area_struct *vma, |
48 | unsigned long addr, | 61 | unsigned long addr, |
@@ -548,6 +561,128 @@ out_unlock: | |||
548 | goto out; | 561 | goto out; |
549 | } | 562 | } |
550 | 563 | ||
564 | static int lock_pages( | ||
565 | struct privcmd_dm_op_buf kbufs[], unsigned int num, | ||
566 | struct page *pages[], unsigned int nr_pages) | ||
567 | { | ||
568 | unsigned int i; | ||
569 | |||
570 | for (i = 0; i < num; i++) { | ||
571 | unsigned int requested; | ||
572 | int pinned; | ||
573 | |||
574 | requested = DIV_ROUND_UP( | ||
575 | offset_in_page(kbufs[i].uptr) + kbufs[i].size, | ||
576 | PAGE_SIZE); | ||
577 | if (requested > nr_pages) | ||
578 | return -ENOSPC; | ||
579 | |||
580 | pinned = get_user_pages_fast( | ||
581 | (unsigned long) kbufs[i].uptr, | ||
582 | requested, FOLL_WRITE, pages); | ||
583 | if (pinned < 0) | ||
584 | return pinned; | ||
585 | |||
586 | nr_pages -= pinned; | ||
587 | pages += pinned; | ||
588 | } | ||
589 | |||
590 | return 0; | ||
591 | } | ||
592 | |||
593 | static void unlock_pages(struct page *pages[], unsigned int nr_pages) | ||
594 | { | ||
595 | unsigned int i; | ||
596 | |||
597 | if (!pages) | ||
598 | return; | ||
599 | |||
600 | for (i = 0; i < nr_pages; i++) { | ||
601 | if (pages[i]) | ||
602 | put_page(pages[i]); | ||
603 | } | ||
604 | } | ||
605 | |||
606 | static long privcmd_ioctl_dm_op(void __user *udata) | ||
607 | { | ||
608 | struct privcmd_dm_op kdata; | ||
609 | struct privcmd_dm_op_buf *kbufs; | ||
610 | unsigned int nr_pages = 0; | ||
611 | struct page **pages = NULL; | ||
612 | struct xen_dm_op_buf *xbufs = NULL; | ||
613 | unsigned int i; | ||
614 | long rc; | ||
615 | |||
616 | if (copy_from_user(&kdata, udata, sizeof(kdata))) | ||
617 | return -EFAULT; | ||
618 | |||
619 | if (kdata.num == 0) | ||
620 | return 0; | ||
621 | |||
622 | if (kdata.num > privcmd_dm_op_max_num) | ||
623 | return -E2BIG; | ||
624 | |||
625 | kbufs = kcalloc(kdata.num, sizeof(*kbufs), GFP_KERNEL); | ||
626 | if (!kbufs) | ||
627 | return -ENOMEM; | ||
628 | |||
629 | if (copy_from_user(kbufs, kdata.ubufs, | ||
630 | sizeof(*kbufs) * kdata.num)) { | ||
631 | rc = -EFAULT; | ||
632 | goto out; | ||
633 | } | ||
634 | |||
635 | for (i = 0; i < kdata.num; i++) { | ||
636 | if (kbufs[i].size > privcmd_dm_op_buf_max_size) { | ||
637 | rc = -E2BIG; | ||
638 | goto out; | ||
639 | } | ||
640 | |||
641 | if (!access_ok(VERIFY_WRITE, kbufs[i].uptr, | ||
642 | kbufs[i].size)) { | ||
643 | rc = -EFAULT; | ||
644 | goto out; | ||
645 | } | ||
646 | |||
647 | nr_pages += DIV_ROUND_UP( | ||
648 | offset_in_page(kbufs[i].uptr) + kbufs[i].size, | ||
649 | PAGE_SIZE); | ||
650 | } | ||
651 | |||
652 | pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL); | ||
653 | if (!pages) { | ||
654 | rc = -ENOMEM; | ||
655 | goto out; | ||
656 | } | ||
657 | |||
658 | xbufs = kcalloc(kdata.num, sizeof(*xbufs), GFP_KERNEL); | ||
659 | if (!xbufs) { | ||
660 | rc = -ENOMEM; | ||
661 | goto out; | ||
662 | } | ||
663 | |||
664 | rc = lock_pages(kbufs, kdata.num, pages, nr_pages); | ||
665 | if (rc) | ||
666 | goto out; | ||
667 | |||
668 | for (i = 0; i < kdata.num; i++) { | ||
669 | set_xen_guest_handle(xbufs[i].h, kbufs[i].uptr); | ||
670 | xbufs[i].size = kbufs[i].size; | ||
671 | } | ||
672 | |||
673 | xen_preemptible_hcall_begin(); | ||
674 | rc = HYPERVISOR_dm_op(kdata.dom, kdata.num, xbufs); | ||
675 | xen_preemptible_hcall_end(); | ||
676 | |||
677 | out: | ||
678 | unlock_pages(pages, nr_pages); | ||
679 | kfree(xbufs); | ||
680 | kfree(pages); | ||
681 | kfree(kbufs); | ||
682 | |||
683 | return rc; | ||
684 | } | ||
685 | |||
551 | static long privcmd_ioctl(struct file *file, | 686 | static long privcmd_ioctl(struct file *file, |
552 | unsigned int cmd, unsigned long data) | 687 | unsigned int cmd, unsigned long data) |
553 | { | 688 | { |
@@ -571,6 +706,10 @@ static long privcmd_ioctl(struct file *file, | |||
571 | ret = privcmd_ioctl_mmap_batch(udata, 2); | 706 | ret = privcmd_ioctl_mmap_batch(udata, 2); |
572 | break; | 707 | break; |
573 | 708 | ||
709 | case IOCTL_PRIVCMD_DM_OP: | ||
710 | ret = privcmd_ioctl_dm_op(udata); | ||
711 | break; | ||
712 | |||
574 | default: | 713 | default: |
575 | break; | 714 | break; |
576 | } | 715 | } |
diff --git a/include/uapi/xen/privcmd.h b/include/uapi/xen/privcmd.h index 7ddeeda93809..f8c5d75b99e1 100644 --- a/include/uapi/xen/privcmd.h +++ b/include/uapi/xen/privcmd.h | |||
@@ -77,6 +77,17 @@ struct privcmd_mmapbatch_v2 { | |||
77 | int __user *err; /* array of error codes */ | 77 | int __user *err; /* array of error codes */ |
78 | }; | 78 | }; |
79 | 79 | ||
80 | struct privcmd_dm_op_buf { | ||
81 | void __user *uptr; | ||
82 | size_t size; | ||
83 | }; | ||
84 | |||
85 | struct privcmd_dm_op { | ||
86 | domid_t dom; | ||
87 | __u16 num; | ||
88 | const struct privcmd_dm_op_buf __user *ubufs; | ||
89 | }; | ||
90 | |||
80 | /* | 91 | /* |
81 | * @cmd: IOCTL_PRIVCMD_HYPERCALL | 92 | * @cmd: IOCTL_PRIVCMD_HYPERCALL |
82 | * @arg: &privcmd_hypercall_t | 93 | * @arg: &privcmd_hypercall_t |
@@ -98,5 +109,7 @@ struct privcmd_mmapbatch_v2 { | |||
98 | _IOC(_IOC_NONE, 'P', 3, sizeof(struct privcmd_mmapbatch)) | 109 | _IOC(_IOC_NONE, 'P', 3, sizeof(struct privcmd_mmapbatch)) |
99 | #define IOCTL_PRIVCMD_MMAPBATCH_V2 \ | 110 | #define IOCTL_PRIVCMD_MMAPBATCH_V2 \ |
100 | _IOC(_IOC_NONE, 'P', 4, sizeof(struct privcmd_mmapbatch_v2)) | 111 | _IOC(_IOC_NONE, 'P', 4, sizeof(struct privcmd_mmapbatch_v2)) |
112 | #define IOCTL_PRIVCMD_DM_OP \ | ||
113 | _IOC(_IOC_NONE, 'P', 5, sizeof(struct privcmd_dm_op)) | ||
101 | 114 | ||
102 | #endif /* __LINUX_PUBLIC_PRIVCMD_H__ */ | 115 | #endif /* __LINUX_PUBLIC_PRIVCMD_H__ */ |
diff --git a/include/xen/arm/hypercall.h b/include/xen/arm/hypercall.h index 9d874db13c0e..73db4b2eeb89 100644 --- a/include/xen/arm/hypercall.h +++ b/include/xen/arm/hypercall.h | |||
@@ -53,6 +53,7 @@ int HYPERVISOR_physdev_op(int cmd, void *arg); | |||
53 | int HYPERVISOR_vcpu_op(int cmd, int vcpuid, void *extra_args); | 53 | int HYPERVISOR_vcpu_op(int cmd, int vcpuid, void *extra_args); |
54 | int HYPERVISOR_tmem_op(void *arg); | 54 | int HYPERVISOR_tmem_op(void *arg); |
55 | int HYPERVISOR_vm_assist(unsigned int cmd, unsigned int type); | 55 | int HYPERVISOR_vm_assist(unsigned int cmd, unsigned int type); |
56 | int HYPERVISOR_dm_op(domid_t domid, unsigned int nr_bufs, void *bufs); | ||
56 | int HYPERVISOR_platform_op_raw(void *arg); | 57 | int HYPERVISOR_platform_op_raw(void *arg); |
57 | static inline int HYPERVISOR_platform_op(struct xen_platform_op *op) | 58 | static inline int HYPERVISOR_platform_op(struct xen_platform_op *op) |
58 | { | 59 | { |
diff --git a/include/xen/interface/hvm/dm_op.h b/include/xen/interface/hvm/dm_op.h new file mode 100644 index 000000000000..ee9e480bc559 --- /dev/null +++ b/include/xen/interface/hvm/dm_op.h | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016, Citrix Systems Inc | ||
3 | * | ||
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
5 | * of this software and associated documentation files (the "Software"), to | ||
6 | * deal in the Software without restriction, including without limitation the | ||
7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||
8 | * sell copies of the Software, and to permit persons to whom the Software is | ||
9 | * furnished to do so, subject to the following conditions: | ||
10 | * | ||
11 | * The above copyright notice and this permission notice shall be included in | ||
12 | * all copies or substantial portions of the Software. | ||
13 | * | ||
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
20 | * DEALINGS IN THE SOFTWARE. | ||
21 | */ | ||
22 | |||
23 | #ifndef __XEN_PUBLIC_HVM_DM_OP_H__ | ||
24 | #define __XEN_PUBLIC_HVM_DM_OP_H__ | ||
25 | |||
26 | struct xen_dm_op_buf { | ||
27 | GUEST_HANDLE(void) h; | ||
28 | xen_ulong_t size; | ||
29 | }; | ||
30 | DEFINE_GUEST_HANDLE_STRUCT(xen_dm_op_buf); | ||
31 | |||
32 | #endif /* __XEN_PUBLIC_HVM_DM_OP_H__ */ | ||
diff --git a/include/xen/interface/xen.h b/include/xen/interface/xen.h index 1b0d189cd3d3..4f4830ef8f93 100644 --- a/include/xen/interface/xen.h +++ b/include/xen/interface/xen.h | |||
@@ -81,6 +81,7 @@ | |||
81 | #define __HYPERVISOR_tmem_op 38 | 81 | #define __HYPERVISOR_tmem_op 38 |
82 | #define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */ | 82 | #define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */ |
83 | #define __HYPERVISOR_xenpmu_op 40 | 83 | #define __HYPERVISOR_xenpmu_op 40 |
84 | #define __HYPERVISOR_dm_op 41 | ||
84 | 85 | ||
85 | /* Architecture-specific hypercall definitions. */ | 86 | /* Architecture-specific hypercall definitions. */ |
86 | #define __HYPERVISOR_arch_0 48 | 87 | #define __HYPERVISOR_arch_0 48 |