Skip to content

Commit 476878e

Browse files
marmarekbostrovs
authored andcommitted
xen-pciback: optionally allow interrupt enable flag writes
QEMU running in a stubdom needs to be able to set INTX_DISABLE, and the MSI(-X) enable flags in the PCI config space. This adds an attribute 'allow_interrupt_control' which when set for a PCI device allows writes to this flag(s). The toolstack will need to set this for stubdoms. When enabled, guest (stubdomain) will be allowed to set relevant enable flags, but only one at a time - i.e. it refuses to enable more than one of INTx, MSI, MSI-X at a time. This functionality is needed only for config space access done by device model (stubdomain) serving a HVM with the actual PCI device. It is not necessary and unsafe to enable direct access to those bits for PV domain with the device attached. For PV domains, there are separate protocol messages (XEN_PCI_OP_{enable,disable}_{msi,msix}) for this purpose. Those ops in addition to setting enable bits, also configure MSI(-X) in dom0 kernel - which is undesirable for PCI passthrough to HVM guests. This should not introduce any new security issues since a malicious guest (or stubdom) can already generate MSIs through other ways, see [1] page 8. Additionally, when qemu runs in dom0, it already have direct access to those bits. This is the second iteration of this feature. First was proposed as a direct Xen interface through a new hypercall, but ultimately it was rejected by the maintainer, because of mixing pciback and hypercalls for PCI config space access isn't a good design. Full discussion at [2]. [1]: https://invisiblethingslab.com/resources/2011/Software%20Attacks%20on%20Intel%20VT-d.pdf [2]: https://xen.markmail.org/thread/smpgpws4umdzizze [part of the commit message and sysfs handling] Signed-off-by: Simon Gaiser <[email protected]> [the rest] Signed-off-by: Marek Marczykowski-Górecki <[email protected]> Reviewed-by: Roger Pau Monné <[email protected]> [boris: A few small changes suggested by Roger, some formatting changes] Signed-off-by: Boris Ostrovsky <[email protected]>
1 parent b3a987b commit 476878e

File tree

7 files changed

+232
-0
lines changed

7 files changed

+232
-0
lines changed

Documentation/ABI/testing/sysfs-driver-pciback

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,16 @@ Description:
1111
#echo 00:19.0-E0:2:FF > /sys/bus/pci/drivers/pciback/quirks
1212
will allow the guest to read and write to the configuration
1313
register 0x0E.
14+
15+
What: /sys/bus/pci/drivers/pciback/allow_interrupt_control
16+
Date: Jan 2020
17+
KernelVersion: 5.6
18+
19+
Description:
20+
List of devices which can have interrupt control flag (INTx,
21+
MSI, MSI-X) set by a connected guest. It is meant to be set
22+
only when the guest is a stubdomain hosting device model (qemu)
23+
and the actual device is assigned to a HVM. It is not safe
24+
(similar to permissive attribute) to set for a devices assigned
25+
to a PV guest. The device is automatically removed from this
26+
list when the connected pcifront terminates.

drivers/xen/xen-pciback/conf_space.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,43 @@ int xen_pcibk_config_write(struct pci_dev *dev, int offset, int size, u32 value)
286286
return xen_pcibios_err_to_errno(err);
287287
}
288288

289+
int xen_pcibk_get_interrupt_type(struct pci_dev *dev)
290+
{
291+
int err;
292+
u16 val;
293+
int ret = 0;
294+
295+
err = pci_read_config_word(dev, PCI_COMMAND, &val);
296+
if (err)
297+
return err;
298+
if (!(val & PCI_COMMAND_INTX_DISABLE))
299+
ret |= INTERRUPT_TYPE_INTX;
300+
301+
/*
302+
* Do not trust dev->msi(x)_enabled here, as enabling could be done
303+
* bypassing the pci_*msi* functions, by the qemu.
304+
*/
305+
if (dev->msi_cap) {
306+
err = pci_read_config_word(dev,
307+
dev->msi_cap + PCI_MSI_FLAGS,
308+
&val);
309+
if (err)
310+
return err;
311+
if (val & PCI_MSI_FLAGS_ENABLE)
312+
ret |= INTERRUPT_TYPE_MSI;
313+
}
314+
if (dev->msix_cap) {
315+
err = pci_read_config_word(dev,
316+
dev->msix_cap + PCI_MSIX_FLAGS,
317+
&val);
318+
if (err)
319+
return err;
320+
if (val & PCI_MSIX_FLAGS_ENABLE)
321+
ret |= INTERRUPT_TYPE_MSIX;
322+
}
323+
return ret;
324+
}
325+
289326
void xen_pcibk_config_free_dyn_fields(struct pci_dev *dev)
290327
{
291328
struct xen_pcibk_dev_data *dev_data = pci_get_drvdata(dev);

drivers/xen/xen-pciback/conf_space.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ struct config_field_entry {
6565
void *data;
6666
};
6767

68+
#define INTERRUPT_TYPE_NONE (1<<0)
69+
#define INTERRUPT_TYPE_INTX (1<<1)
70+
#define INTERRUPT_TYPE_MSI (1<<2)
71+
#define INTERRUPT_TYPE_MSIX (1<<3)
72+
6873
extern bool xen_pcibk_permissive;
6974

7075
#define OFFSET(cfg_entry) ((cfg_entry)->base_offset+(cfg_entry)->field->offset)
@@ -126,4 +131,6 @@ int xen_pcibk_config_capability_init(void);
126131
int xen_pcibk_config_header_add_fields(struct pci_dev *dev);
127132
int xen_pcibk_config_capability_add_fields(struct pci_dev *dev);
128133

134+
int xen_pcibk_get_interrupt_type(struct pci_dev *dev);
135+
129136
#endif /* __XEN_PCIBACK_CONF_SPACE_H__ */

drivers/xen/xen-pciback/conf_space_capability.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,85 @@ static const struct config_field caplist_pm[] = {
189189
{}
190190
};
191191

192+
static struct msi_msix_field_config {
193+
u16 enable_bit; /* bit for enabling MSI/MSI-X */
194+
unsigned int int_type; /* interrupt type for exclusiveness check */
195+
} msi_field_config = {
196+
.enable_bit = PCI_MSI_FLAGS_ENABLE,
197+
.int_type = INTERRUPT_TYPE_MSI,
198+
}, msix_field_config = {
199+
.enable_bit = PCI_MSIX_FLAGS_ENABLE,
200+
.int_type = INTERRUPT_TYPE_MSIX,
201+
};
202+
203+
static void *msi_field_init(struct pci_dev *dev, int offset)
204+
{
205+
return &msi_field_config;
206+
}
207+
208+
static void *msix_field_init(struct pci_dev *dev, int offset)
209+
{
210+
return &msix_field_config;
211+
}
212+
213+
static int msi_msix_flags_write(struct pci_dev *dev, int offset, u16 new_value,
214+
void *data)
215+
{
216+
int err;
217+
u16 old_value;
218+
const struct msi_msix_field_config *field_config = data;
219+
const struct xen_pcibk_dev_data *dev_data = pci_get_drvdata(dev);
220+
221+
if (xen_pcibk_permissive || dev_data->permissive)
222+
goto write;
223+
224+
err = pci_read_config_word(dev, offset, &old_value);
225+
if (err)
226+
return err;
227+
228+
if (new_value == old_value)
229+
return 0;
230+
231+
if (!dev_data->allow_interrupt_control ||
232+
(new_value ^ old_value) & ~field_config->enable_bit)
233+
return PCIBIOS_SET_FAILED;
234+
235+
if (new_value & field_config->enable_bit) {
236+
/* don't allow enabling together with other interrupt types */
237+
int int_type = xen_pcibk_get_interrupt_type(dev);
238+
239+
if (int_type == INTERRUPT_TYPE_NONE ||
240+
int_type == field_config->int_type)
241+
goto write;
242+
return PCIBIOS_SET_FAILED;
243+
}
244+
245+
write:
246+
return pci_write_config_word(dev, offset, new_value);
247+
}
248+
249+
static const struct config_field caplist_msix[] = {
250+
{
251+
.offset = PCI_MSIX_FLAGS,
252+
.size = 2,
253+
.init = msix_field_init,
254+
.u.w.read = xen_pcibk_read_config_word,
255+
.u.w.write = msi_msix_flags_write,
256+
},
257+
{}
258+
};
259+
260+
static const struct config_field caplist_msi[] = {
261+
{
262+
.offset = PCI_MSI_FLAGS,
263+
.size = 2,
264+
.init = msi_field_init,
265+
.u.w.read = xen_pcibk_read_config_word,
266+
.u.w.write = msi_msix_flags_write,
267+
},
268+
{}
269+
};
270+
192271
static struct xen_pcibk_config_capability xen_pcibk_config_capability_pm = {
193272
.capability = PCI_CAP_ID_PM,
194273
.fields = caplist_pm,
@@ -197,11 +276,21 @@ static struct xen_pcibk_config_capability xen_pcibk_config_capability_vpd = {
197276
.capability = PCI_CAP_ID_VPD,
198277
.fields = caplist_vpd,
199278
};
279+
static struct xen_pcibk_config_capability xen_pcibk_config_capability_msi = {
280+
.capability = PCI_CAP_ID_MSI,
281+
.fields = caplist_msi,
282+
};
283+
static struct xen_pcibk_config_capability xen_pcibk_config_capability_msix = {
284+
.capability = PCI_CAP_ID_MSIX,
285+
.fields = caplist_msix,
286+
};
200287

201288
int xen_pcibk_config_capability_init(void)
202289
{
203290
register_capability(&xen_pcibk_config_capability_vpd);
204291
register_capability(&xen_pcibk_config_capability_pm);
292+
register_capability(&xen_pcibk_config_capability_msi);
293+
register_capability(&xen_pcibk_config_capability_msix);
205294

206295
return 0;
207296
}

drivers/xen/xen-pciback/conf_space_header.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ static int command_write(struct pci_dev *dev, int offset, u16 value, void *data)
117117
pci_clear_mwi(dev);
118118
}
119119

120+
if (dev_data && dev_data->allow_interrupt_control) {
121+
if ((cmd->val ^ value) & PCI_COMMAND_INTX_DISABLE) {
122+
if (value & PCI_COMMAND_INTX_DISABLE) {
123+
pci_intx(dev, 0);
124+
} else {
125+
/* Do not allow enabling INTx together with MSI or MSI-X. */
126+
switch (xen_pcibk_get_interrupt_type(dev)) {
127+
case INTERRUPT_TYPE_NONE:
128+
pci_intx(dev, 1);
129+
break;
130+
case INTERRUPT_TYPE_INTX:
131+
break;
132+
default:
133+
return PCIBIOS_SET_FAILED;
134+
}
135+
}
136+
}
137+
}
138+
120139
cmd->val = value;
121140

122141
if (!xen_pcibk_permissive && (!dev_data || !dev_data->permissive))

drivers/xen/xen-pciback/pci_stub.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ void pcistub_put_pci_dev(struct pci_dev *dev)
304304
xen_pcibk_config_reset_dev(dev);
305305
xen_pcibk_config_free_dyn_fields(dev);
306306

307+
dev_data->allow_interrupt_control = 0;
308+
307309
xen_unregister_device_domain_owner(dev);
308310

309311
spin_lock_irqsave(&found_psdev->lock, flags);
@@ -1431,6 +1433,65 @@ static ssize_t permissive_show(struct device_driver *drv, char *buf)
14311433
}
14321434
static DRIVER_ATTR_RW(permissive);
14331435

1436+
static ssize_t allow_interrupt_control_store(struct device_driver *drv,
1437+
const char *buf, size_t count)
1438+
{
1439+
int domain, bus, slot, func;
1440+
int err;
1441+
struct pcistub_device *psdev;
1442+
struct xen_pcibk_dev_data *dev_data;
1443+
1444+
err = str_to_slot(buf, &domain, &bus, &slot, &func);
1445+
if (err)
1446+
goto out;
1447+
1448+
psdev = pcistub_device_find(domain, bus, slot, func);
1449+
if (!psdev) {
1450+
err = -ENODEV;
1451+
goto out;
1452+
}
1453+
1454+
dev_data = pci_get_drvdata(psdev->dev);
1455+
/* the driver data for a device should never be null at this point */
1456+
if (!dev_data) {
1457+
err = -ENXIO;
1458+
goto release;
1459+
}
1460+
dev_data->allow_interrupt_control = 1;
1461+
release:
1462+
pcistub_device_put(psdev);
1463+
out:
1464+
if (!err)
1465+
err = count;
1466+
return err;
1467+
}
1468+
1469+
static ssize_t allow_interrupt_control_show(struct device_driver *drv,
1470+
char *buf)
1471+
{
1472+
struct pcistub_device *psdev;
1473+
struct xen_pcibk_dev_data *dev_data;
1474+
size_t count = 0;
1475+
unsigned long flags;
1476+
1477+
spin_lock_irqsave(&pcistub_devices_lock, flags);
1478+
list_for_each_entry(psdev, &pcistub_devices, dev_list) {
1479+
if (count >= PAGE_SIZE)
1480+
break;
1481+
if (!psdev->dev)
1482+
continue;
1483+
dev_data = pci_get_drvdata(psdev->dev);
1484+
if (!dev_data || !dev_data->allow_interrupt_control)
1485+
continue;
1486+
count +=
1487+
scnprintf(buf + count, PAGE_SIZE - count, "%s\n",
1488+
pci_name(psdev->dev));
1489+
}
1490+
spin_unlock_irqrestore(&pcistub_devices_lock, flags);
1491+
return count;
1492+
}
1493+
static DRIVER_ATTR_RW(allow_interrupt_control);
1494+
14341495
static void pcistub_exit(void)
14351496
{
14361497
driver_remove_file(&xen_pcibk_pci_driver.driver, &driver_attr_new_slot);
@@ -1440,6 +1501,8 @@ static void pcistub_exit(void)
14401501
driver_remove_file(&xen_pcibk_pci_driver.driver, &driver_attr_quirks);
14411502
driver_remove_file(&xen_pcibk_pci_driver.driver,
14421503
&driver_attr_permissive);
1504+
driver_remove_file(&xen_pcibk_pci_driver.driver,
1505+
&driver_attr_allow_interrupt_control);
14431506
driver_remove_file(&xen_pcibk_pci_driver.driver,
14441507
&driver_attr_irq_handlers);
14451508
driver_remove_file(&xen_pcibk_pci_driver.driver,
@@ -1530,6 +1593,9 @@ static int __init pcistub_init(void)
15301593
if (!err)
15311594
err = driver_create_file(&xen_pcibk_pci_driver.driver,
15321595
&driver_attr_permissive);
1596+
if (!err)
1597+
err = driver_create_file(&xen_pcibk_pci_driver.driver,
1598+
&driver_attr_allow_interrupt_control);
15331599

15341600
if (!err)
15351601
err = driver_create_file(&xen_pcibk_pci_driver.driver,

drivers/xen/xen-pciback/pciback.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct xen_pcibk_dev_data {
4545
struct list_head config_fields;
4646
struct pci_saved_state *pci_saved_state;
4747
unsigned int permissive:1;
48+
unsigned int allow_interrupt_control:1;
4849
unsigned int warned_on_write:1;
4950
unsigned int enable_intx:1;
5051
unsigned int isr_on:1; /* Whether the IRQ handler is installed. */

0 commit comments

Comments
 (0)