xen-pciback: limit guest control of command register

Otherwise the guest can abuse that control to cause e.g. PCIe
Unsupported Request responses by disabling memory and/or I/O decoding
and subsequently causing (CPU side) accesses to the respective address
ranges, which (depending on system configuration) may be fatal to the
host.

Note that to alter any of the bits collected together as
PCI_COMMAND_GUEST permissive mode is now required to be enabled
globally or on the specific device.

This is CVE-2015-2150 / XSA-120.

Signed-off-by: Jan Beulich <jbeulich@suse.com>
Reviewed-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: David Vrabel <david.vrabel@citrix.com>
This commit is contained in:
Jan Beulich 2015-03-11 13:51:17 +00:00 committed by David Vrabel
parent 85e40b0539
commit af6fc858a3
3 changed files with 50 additions and 13 deletions

View file

@ -16,7 +16,7 @@
#include "conf_space.h" #include "conf_space.h"
#include "conf_space_quirks.h" #include "conf_space_quirks.h"
static bool permissive; bool permissive;
module_param(permissive, bool, 0644); module_param(permissive, bool, 0644);
/* This is where xen_pcibk_read_config_byte, xen_pcibk_read_config_word, /* This is where xen_pcibk_read_config_byte, xen_pcibk_read_config_word,

View file

@ -64,6 +64,8 @@ struct config_field_entry {
void *data; void *data;
}; };
extern bool permissive;
#define OFFSET(cfg_entry) ((cfg_entry)->base_offset+(cfg_entry)->field->offset) #define OFFSET(cfg_entry) ((cfg_entry)->base_offset+(cfg_entry)->field->offset)
/* Add fields to a device - the add_fields macro expects to get a pointer to /* Add fields to a device - the add_fields macro expects to get a pointer to

View file

@ -11,6 +11,10 @@
#include "pciback.h" #include "pciback.h"
#include "conf_space.h" #include "conf_space.h"
struct pci_cmd_info {
u16 val;
};
struct pci_bar_info { struct pci_bar_info {
u32 val; u32 val;
u32 len_val; u32 len_val;
@ -20,21 +24,35 @@ struct pci_bar_info {
#define is_enable_cmd(value) ((value)&(PCI_COMMAND_MEMORY|PCI_COMMAND_IO)) #define is_enable_cmd(value) ((value)&(PCI_COMMAND_MEMORY|PCI_COMMAND_IO))
#define is_master_cmd(value) ((value)&PCI_COMMAND_MASTER) #define is_master_cmd(value) ((value)&PCI_COMMAND_MASTER)
/* Bits guests are allowed to control in permissive mode. */
#define PCI_COMMAND_GUEST (PCI_COMMAND_MASTER|PCI_COMMAND_SPECIAL| \
PCI_COMMAND_INVALIDATE|PCI_COMMAND_VGA_PALETTE| \
PCI_COMMAND_WAIT|PCI_COMMAND_FAST_BACK)
static void *command_init(struct pci_dev *dev, int offset)
{
struct pci_cmd_info *cmd = kmalloc(sizeof(*cmd), GFP_KERNEL);
int err;
if (!cmd)
return ERR_PTR(-ENOMEM);
err = pci_read_config_word(dev, PCI_COMMAND, &cmd->val);
if (err) {
kfree(cmd);
return ERR_PTR(err);
}
return cmd;
}
static int command_read(struct pci_dev *dev, int offset, u16 *value, void *data) static int command_read(struct pci_dev *dev, int offset, u16 *value, void *data)
{ {
int i; int ret = pci_read_config_word(dev, offset, value);
int ret; const struct pci_cmd_info *cmd = data;
ret = xen_pcibk_read_config_word(dev, offset, value, data); *value &= PCI_COMMAND_GUEST;
if (!pci_is_enabled(dev)) *value |= cmd->val & ~PCI_COMMAND_GUEST;
return ret;
for (i = 0; i < PCI_ROM_RESOURCE; i++) {
if (dev->resource[i].flags & IORESOURCE_IO)
*value |= PCI_COMMAND_IO;
if (dev->resource[i].flags & IORESOURCE_MEM)
*value |= PCI_COMMAND_MEMORY;
}
return ret; return ret;
} }
@ -43,6 +61,8 @@ static int command_write(struct pci_dev *dev, int offset, u16 value, void *data)
{ {
struct xen_pcibk_dev_data *dev_data; struct xen_pcibk_dev_data *dev_data;
int err; int err;
u16 val;
struct pci_cmd_info *cmd = data;
dev_data = pci_get_drvdata(dev); dev_data = pci_get_drvdata(dev);
if (!pci_is_enabled(dev) && is_enable_cmd(value)) { if (!pci_is_enabled(dev) && is_enable_cmd(value)) {
@ -83,6 +103,19 @@ static int command_write(struct pci_dev *dev, int offset, u16 value, void *data)
} }
} }
cmd->val = value;
if (!permissive && (!dev_data || !dev_data->permissive))
return 0;
/* Only allow the guest to control certain bits. */
err = pci_read_config_word(dev, offset, &val);
if (err || val == value)
return err;
value &= PCI_COMMAND_GUEST;
value |= val & ~PCI_COMMAND_GUEST;
return pci_write_config_word(dev, offset, value); return pci_write_config_word(dev, offset, value);
} }
@ -282,6 +315,8 @@ static const struct config_field header_common[] = {
{ {
.offset = PCI_COMMAND, .offset = PCI_COMMAND,
.size = 2, .size = 2,
.init = command_init,
.release = bar_release,
.u.w.read = command_read, .u.w.read = command_read,
.u.w.write = command_write, .u.w.write = command_write,
}, },