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