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:
parent
85e40b0539
commit
af6fc858a3
3 changed files with 50 additions and 13 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue