1DMOP 2==== 3 4Introduction 5------------ 6 7The aim of DMOP is to prevent a compromised device model from compromising 8domains other than the one it is providing emulation for (which is therefore 9likely already compromised). 10 11The problem occurs when you a device model issues an hypercall that 12includes references to user memory other than the operation structure 13itself, such as with Track dirty VRAM (as used in VGA emulation). 14Is this case, the address of this other user memory needs to be vetted, 15to ensure it is not within restricted address ranges, such as kernel 16memory. The real problem comes down to how you would vet this address - 17the idea place to do this is within the privcmd driver, without privcmd 18having to have specific knowledge of the hypercall's semantics. 19 20The Design 21---------- 22 23The privcmd driver implements a new restriction ioctl, which takes a domid 24parameter. After that restriction ioctl is issued, all unaudited operations 25on the privcmd driver will cease to function, including regular hypercalls. 26DMOP hypercalls will continue to function as they can be audited. 27 28A DMOP hypercall consists of a domid (which is audited to verify that it 29matches any restriction in place) and an array of buffers and lengths, 30with the first one containing the specific DMOP parameters. These can 31then reference further buffers from within in the array. Since the only 32user buffers passed are that found with that array, they can all can be 33audited by privcmd. 34 35The following code illustrates this idea: 36 37struct xen_dm_op { 38 uint32_t op; 39}; 40 41struct xen_dm_op_buf { 42 XEN_GUEST_HANDLE(void) h; 43 unsigned long size; 44}; 45typedef struct xen_dm_op_buf xen_dm_op_buf_t; 46 47enum neg_errnoval 48HYPERVISOR_dm_op(domid_t domid, 49 xen_dm_op_buf_t bufs[], 50 unsigned int nr_bufs) 51 52@domid is the domain the hypercall operates on. 53@bufs points to an array of buffers where @bufs[0] contains a struct 54dm_op, describing the specific device model operation and its parameters. 55@bufs[1..] may be referenced in the parameters for the purposes of 56passing extra information to or from the domain. 57@nr_bufs is the number of buffers in the @bufs array. 58 59It is forbidden for the above struct (xen_dm_op) to contain any guest 60handles. If they are needed, they should instead be in 61HYPERVISOR_dm_op->bufs. 62 63Validation by privcmd driver 64---------------------------- 65 66If the privcmd driver has been restricted to specific domain (using a 67 new ioctl), when it received an op, it will: 68 691. Check hypercall is DMOP. 70 712. Check domid == restricted domid. 72 733. For each @nr_bufs in @bufs: Check @h and @size give a buffer 74 wholly in the user space part of the virtual address space. (e.g. 75 Linux will use access_ok()). 76 77 78Xen Implementation 79------------------ 80 81Since a DMOP buffers need to be copied from or to the guest, functions for 82doing this would be written as below. Note that care is taken to prevent 83damage from buffer under- or over-run situations. If the DMOP is called 84with incorrectly sized buffers, zeros will be read, while extra is ignored. 85 86static bool copy_buf_from_guest(xen_dm_op_buf_t bufs[], 87 unsigned int nr_bufs, void *dst, 88 unsigned int idx, size_t dst_size) 89{ 90 size_t size; 91 92 if ( idx >= nr_bufs ) 93 return false; 94 95 memset(dst, 0, dst_size); 96 97 size = min_t(size_t, dst_size, bufs[idx].size); 98 99 return !copy_from_guest(dst, bufs[idx].h, size); 100} 101 102static bool copy_buf_to_guest(xen_dm_op_buf_t bufs[], 103 unsigned int nr_bufs, unsigned int idx, 104 void *src, size_t src_size) 105{ 106 size_t size; 107 108 if ( idx >= nr_bufs ) 109 return false; 110 111 size = min_t(size_t, bufs[idx].size, src_size); 112 113 return !copy_to_guest(bufs[idx].h, src, size); 114} 115 116This leaves do_dm_op easy to implement as below: 117 118static int dm_op(domid_t domid, 119 unsigned int nr_bufs, 120 xen_dm_op_buf_t bufs[]) 121{ 122 struct domain *d; 123 struct xen_dm_op op; 124 bool const_op = true; 125 long rc; 126 127 rc = rcu_lock_remote_domain_by_id(domid, &d); 128 if ( rc ) 129 return rc; 130 131 if ( !is_hvm_domain(d) ) 132 goto out; 133 134 rc = xsm_dm_op(XSM_DM_PRIV, d); 135 if ( rc ) 136 goto out; 137 138 if ( !copy_buf_from_guest(bufs, nr_bufs, &op, 0, sizeof(op)) ) 139 { 140 rc = -EFAULT; 141 goto out; 142 } 143 144 switch ( op.op ) 145 { 146 default: 147 rc = -EOPNOTSUPP; 148 break; 149 } 150 151 if ( !rc && 152 !const_op && 153 !copy_buf_to_guest(bufs, nr_bufs, 0, &op, sizeof(op)) ) 154 rc = -EFAULT; 155 156 out: 157 rcu_unlock_domain(d); 158 159 return rc; 160} 161 162long do_dm_op(domid_t domid, 163 unsigned int nr_bufs, 164 XEN_GUEST_HANDLE_PARAM(xen_dm_op_buf_t) bufs) 165{ 166 struct xen_dm_op_buf nat[MAX_NR_BUFS]; 167 168 if ( nr_bufs > MAX_NR_BUFS ) 169 return -EINVAL; 170 171 if ( copy_from_guest_offset(nat, bufs, 0, nr_bufs) ) 172 return -EFAULT; 173 174 return dm_op(domid, nr_bufs, nat); 175} 176