Commit af6fc858 authored by Jan Beulich's avatar Jan Beulich Committed by David Vrabel

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: default avatarJan Beulich <jbeulich@suse.com>
Reviewed-by: default avatarKonrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarDavid Vrabel <david.vrabel@citrix.com>
parent 85e40b05
...@@ -16,7 +16,7 @@ ...@@ -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 { ...@@ -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 @@ ...@@ -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,22 +24,36 @@ struct pci_bar_info { ...@@ -20,22 +24,36 @@ 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)
static int command_read(struct pci_dev *dev, int offset, u16 *value, void *data) /* 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)
{ {
int i; struct pci_cmd_info *cmd = kmalloc(sizeof(*cmd), GFP_KERNEL);
int ret; int err;
ret = xen_pcibk_read_config_word(dev, offset, value, data); if (!cmd)
if (!pci_is_enabled(dev)) return ERR_PTR(-ENOMEM);
return ret;
err = pci_read_config_word(dev, PCI_COMMAND, &cmd->val);
for (i = 0; i < PCI_ROM_RESOURCE; i++) { if (err) {
if (dev->resource[i].flags & IORESOURCE_IO) kfree(cmd);
*value |= PCI_COMMAND_IO; return ERR_PTR(err);
if (dev->resource[i].flags & IORESOURCE_MEM)
*value |= PCI_COMMAND_MEMORY;
} }
return cmd;
}
static int command_read(struct pci_dev *dev, int offset, u16 *value, void *data)
{
int ret = pci_read_config_word(dev, offset, value);
const struct pci_cmd_info *cmd = data;
*value &= PCI_COMMAND_GUEST;
*value |= cmd->val & ~PCI_COMMAND_GUEST;
return ret; return ret;
} }
...@@ -43,6 +61,8 @@ static int command_write(struct pci_dev *dev, int offset, u16 value, void *data) ...@@ -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) ...@@ -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[] = { ...@@ -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,
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment