From 7770a0fbbeac3c95842f3ab72b4f0bd7d8d33258 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Wed, 22 Apr 2015 17:06:10 +0200 Subject: [PATCH] pci: enforce policy on device discovery Fixes #1486 --- repos/os/src/drivers/acpi/main.cc | 2 +- repos/os/src/drivers/pci/nonpci_devices.cc | 8 +- .../os/src/drivers/pci/pci_device_component.h | 16 +- .../src/drivers/pci/pci_session_component.h | 302 +++++++++++++++++- 4 files changed, 311 insertions(+), 17 deletions(-) diff --git a/repos/os/src/drivers/acpi/main.cc b/repos/os/src/drivers/acpi/main.cc index 9a4ae7c7a..ea7435556 100644 --- a/repos/os/src/drivers/acpi/main.cc +++ b/repos/os/src/drivers/acpi/main.cc @@ -178,7 +178,7 @@ class Pci_policy : public Genode::Slave_policy, public Pci::Provider void _acpi_session() { Pci::Session_capability session; - const char *args = "ram_quota=4K"; + const char *args = "label=\"acpi_drv\", ram_quota=4K"; try { using namespace Genode; diff --git a/repos/os/src/drivers/pci/nonpci_devices.cc b/repos/os/src/drivers/pci/nonpci_devices.cc index 938caa285..afd27685a 100644 --- a/repos/os/src/drivers/pci/nonpci_devices.cc +++ b/repos/os/src/drivers/pci/nonpci_devices.cc @@ -31,7 +31,7 @@ class Nonpci::Ps2 : public Pci::Device_component Ps2(Genode::Rpc_entrypoint * ep, Pci::Session_component * session) : - Pci::Device_component(ep, IRQ_KEYBOARD), + Pci::Device_component(ep, session, IRQ_KEYBOARD), _irq_mouse(IRQ_MOUSE) { } @@ -72,6 +72,12 @@ Pci::Device_capability Pci::Session_component::device(String const &name) { return Device_capability(); } + if (!permit_device(devices[devices_i])) { + PERR("Denied access to device '%s' for session '%s'", device_name, + _label.string()); + return Device_capability(); + } + try { Device_component * dev = nullptr; diff --git a/repos/os/src/drivers/pci/pci_device_component.h b/repos/os/src/drivers/pci/pci_device_component.h index a7b6a62cb..749b64e7d 100644 --- a/repos/os/src/drivers/pci/pci_device_component.h +++ b/repos/os/src/drivers/pci/pci_device_component.h @@ -19,13 +19,13 @@ #include #include -#include +#include #include "pci_device_config.h" #include "irq.h" -namespace Pci { class Device_component; } +namespace Pci { class Device_component; class Session_component; } class Pci::Device_component : public Genode::Rpc_object, public Genode::List::Element @@ -37,6 +37,7 @@ class Pci::Device_component : public Genode::Rpc_object, Genode::Io_mem_connection *_io_mem; Config_access _config_access; Genode::Rpc_entrypoint *_ep; + Pci::Session_component *_session; Irq_session_component _irq_session; enum { PCI_IRQ = 0x3c }; @@ -47,10 +48,11 @@ class Pci::Device_component : public Genode::Rpc_object, * Constructor */ Device_component(Device_config device_config, Genode::addr_t addr, - Genode::Rpc_entrypoint *ep) + Genode::Rpc_entrypoint *ep, + Pci::Session_component * session) : _device_config(device_config), _config_space(addr), - _io_mem(0), _ep(ep), + _io_mem(0), _ep(ep), _session(session), _irq_session(_device_config.read(&_config_access, PCI_IRQ, Pci::Device::ACCESS_8BIT)) { @@ -60,9 +62,11 @@ class Pci::Device_component : public Genode::Rpc_object, /** * Constructor for non PCI devices */ - Device_component(Genode::Rpc_entrypoint *ep, unsigned irq) + Device_component(Genode::Rpc_entrypoint * ep, + Pci::Session_component * session, unsigned irq) : - _config_space(~0UL), _io_mem(0), _ep(ep), _irq_session(irq) + _config_space(~0UL), _io_mem(0), _ep(ep), _session(session), + _irq_session(irq) { _ep->manage(&_irq_session); } diff --git a/repos/os/src/drivers/pci/pci_session_component.h b/repos/os/src/drivers/pci/pci_session_component.h index 75a788f88..08225f276 100644 --- a/repos/os/src/drivers/pci/pci_session_component.h +++ b/repos/os/src/drivers/pci/pci_session_component.h @@ -22,6 +22,7 @@ /* os */ #include #include +#include #include /* local */ @@ -44,6 +45,8 @@ namespace Pci { Genode::List _device_list; Device_pd_client *_child; Genode::Ram_connection *_ram; + Genode::Session_label _label; + Genode::Session_policy _policy; /** * Scan PCI buses for a device @@ -115,6 +118,173 @@ namespace Pci { return config_space; } + /* + * List of aliases for PCI Class/Subclas/Prog I/F triple used + * by xml config for this platform driver + */ + unsigned class_subclass_prog(const char * name) { + using namespace Genode; + + static struct { + const char * alias; + uint8_t pci_class, pci_subclass, pci_progif; + } const aliases [] = { + { "AHCI" , 0x1, 0x06, 0x0}, + { "ALL" , 0x0, 0x00, 0x0}, + { "ETHERNET", 0x2, 0x00, 0x0}, + { "USB" , 0xc, 0x03, 0x0}, + { "VGA" , 0x3, 0x00, 0x0}, + { "WIFI" , 0x2, 0x80, 0x0} + }; + + for (unsigned i = 0; i < sizeof(aliases) / sizeof(aliases[0]); i++) { + if (strcmp(aliases[i].alias, name)) + continue; + + return 0U | aliases[i].pci_class << 16 | + aliases[i].pci_subclass << 8 | + aliases[i].pci_progif; + } + + return ~0U; + } + + /* + * Check device usage according to session policy + */ + bool permit_device(const char * name) + { + using namespace Genode; + + try { + _policy.for_each_sub_node("device", [&] (Xml_node dev) { + try { + /* enforce restriction based on name name */ + char policy_name[8]; + dev.attribute("name").value(policy_name, + sizeof(policy_name)); + + if (!strcmp(policy_name, name)) + /* found identical match - permit access */ + throw true; + + } catch (Xml_attribute::Nonexistent_attribute) { } + }); + } catch (bool result) { return result; } + + return false; + } + + /* + * Check according session policy device usage + */ + bool permit_device(Genode::uint8_t b, Genode::uint8_t d, + Genode::uint8_t f, unsigned class_code) + { + using namespace Genode; + + try { + _policy.for_each_sub_node("pci", [&] (Xml_node node) { + try { + unsigned bus, device, function; + + node.attribute("bus").value(&bus); + node.attribute("device").value(&device); + node.attribute("function").value(&function); + + if (b == bus && d == device && f == function) + throw true; + + return; + } catch (Xml_attribute::Nonexistent_attribute) { } + + /* enforce restriction based upon classes */ + unsigned class_sub_prog = 0; + + try { + char alias_class[32]; + node.attribute("class").value(alias_class, + sizeof(alias_class)); + + class_sub_prog = class_subclass_prog(alias_class); + } catch (Xml_attribute::Nonexistent_attribute) { + return; + } + /* if this does not identical matches - deny access */ + if ((class_sub_prog & class_code) != class_sub_prog) + return; + + /* if this bdf is used by some policy - deny */ + if (find_dev_in_policy(b, d, f)) + return; + + throw true; + }); + } catch (bool result) { return result; } + + return false; + } + + /** + * Lookup a given device name. + */ + bool find_dev_in_policy(const char * dev_name, bool once = true) + { + using namespace Genode; + + try { + config()->xml_node().for_each_sub_node("policy", [&] (Xml_node policy) { + policy.for_each_sub_node("device", [&] (Xml_node device) { + try { + /* device attribute from policy node */ + char policy_device[8]; + device.attribute("name").value(policy_device, sizeof(policy_device)); + + if (!strcmp(policy_device, dev_name)) { + if (once) + throw true; + once = true; + } + } catch (Xml_node::Nonexistent_attribute) { } + }); + }); + } catch (bool result) { return result; } + + return false; + } + + /** + * Lookup a given device name. + */ + bool find_dev_in_policy(Genode::uint8_t b, Genode::uint8_t d, + Genode::uint8_t f, bool once = true) + { + using namespace Genode; + + try { + Xml_node xml(config()->xml_node()); + xml.for_each_sub_node("policy", [&] (Xml_node policy) { + policy.for_each_sub_node("pci", [&] (Xml_node node) { + try { + unsigned bus, device, function; + + node.attribute("bus").value(&bus); + node.attribute("device").value(&device); + node.attribute("function").value(&function); + + if (b == bus && d == device && f == function) { + if (once) + throw true; + once = true; + } + } catch (Xml_node::Nonexistent_attribute) { } + }); + }); + } catch (bool result) { return result; } + + return false; + } + public: /** @@ -123,9 +293,105 @@ namespace Pci { Session_component(Genode::Rpc_entrypoint *ep, Genode::Allocator *md_alloc, Device_pd_client *child, - Genode::Ram_connection *ram) + Genode::Ram_connection *ram, + const char *args) : - _ep(ep), _md_alloc(md_alloc), _child(child), _ram(ram) { } + _ep(ep), _md_alloc(md_alloc), + _child(child), _ram(ram), _label(args), _policy(_label) + { + /* non-pci devices */ + _policy.for_each_sub_node("device", [&] (Genode::Xml_node device_node) { + try { + char policy_device[8]; + device_node.attribute("name").value(policy_device, + sizeof(policy_device)); + + enum { DOUBLET = false }; + if (!find_dev_in_policy(policy_device, DOUBLET)) + return; + + PERR("'%s' - device '%s' is part of more than one policy", + _label.string(), policy_device); + } catch (Genode::Xml_node::Nonexistent_attribute) { + PERR("'%s' - device node misses a 'name' attribute", + _label.string()); + } + throw Genode::Root::Unavailable(); + }); + + /* pci devices */ + _policy.for_each_sub_node("pci", [&] (Genode::Xml_node node) { + enum { INVALID_CLASS = 0x1000000U }; + unsigned class_sub_prog = INVALID_CLASS; + + using Genode::Xml_attribute; + + /** + * Valid input is either a triple of 'bus', 'device', + * 'function' attributes or a single 'class' attribute. + * All other attribute names are traded as wrong. + */ + try { + char alias_class[32]; + node.attribute("class").value(alias_class, + sizeof(alias_class)); + + class_sub_prog = class_subclass_prog(alias_class); + if (class_sub_prog >= INVALID_CLASS) { + PERR("'%s' - invalid 'class' attribute '%s'", + _label.string(), alias_class); + throw Genode::Root::Unavailable(); + } + } catch (Xml_attribute::Nonexistent_attribute) { } + + /* if we read a class attribute all is fine */ + if (class_sub_prog < INVALID_CLASS) { + /* sanity check that 'class' is the only attribute */ + try { + node.attribute(1); + PERR("'%s' - attributes beside 'class' detected", + _label.string()); + throw Genode::Root::Unavailable(); + } catch (Xml_attribute::Nonexistent_attribute) { } + + /* we have a class and it is the only attribute */ + return; + } + + /* no 'class' attribute - now check for valid bdf triple */ + try { + node.attribute(3); + PERR("'%s' - invalid number of pci node attributes", + _label.string()); + throw Genode::Root::Unavailable(); + } catch (Xml_attribute::Nonexistent_attribute) { } + + try { + unsigned bus, device, function; + + node.attribute("bus").value(&bus); + node.attribute("device").value(&device); + node.attribute("function").value(&function); + + if ((bus >= Device_config::MAX_BUSES) || + (device >= Device_config::MAX_DEVICES) || + (function >= Device_config::MAX_FUNCTIONS)) + throw Xml_attribute::Nonexistent_attribute(); + + enum { DOUBLET = false }; + if (!find_dev_in_policy(bus, device, function, DOUBLET)) + return; + + PERR("'%s' - device '%2x:%2x.%x' is part of more than " + "one policy", _label.string(), bus, device, + function); + } catch (Xml_attribute::Nonexistent_attribute) { + PERR("'%s' - invalid pci node attributes for bdf", + _label.string()); + } + throw Genode::Root::Unavailable(); + }); + } /** * Destructor @@ -191,8 +457,7 @@ namespace Pci { */ Device_config config; - do - { + while (true) { function += 1; if (!_find_next(bus, device, function, &config, &config_access)) return Device_capability(); @@ -201,7 +466,16 @@ namespace Pci { bus = config.bus_number(); device = config.device_number(); function = config.function_number(); - } while ((config.class_code() ^ device_class) & class_mask); + + /* if filter of driver don't match skip and continue */ + if ((config.class_code() ^ device_class) & class_mask) + continue; + + /* check that policy permit access to the matched device */ + if (permit_device(bus, device, function, + config.class_code())) + break; + } /* lookup if we have a extended pci config space */ Genode::addr_t config_space = lookup_config_space(bus, device, @@ -214,7 +488,7 @@ namespace Pci { * FIXME: check and adjust session quota */ Device_component *device_component = - new (_md_alloc) Device_component(config, config_space, _ep); + new (_md_alloc) Device_component(config, config_space, _ep, this); if (!device_component) return Device_capability(); @@ -323,6 +597,10 @@ namespace Pci { for (i = 0; i < config()->xml_node().num_sub_nodes(); i++) { Xml_node node = config()->xml_node().sub_node(i); + + if (!node.has_type("bdf")) + continue; + uint32_t bdf_start = 0; uint32_t func_count = 0; addr_t base = 0; @@ -348,9 +626,15 @@ namespace Pci { /* FIXME: extract quota from args */ /* FIXME: pass quota to session-component constructor */ - return new (md_alloc()) Session_component(ep(), md_alloc(), - _pd_device_client, - &_ram); + try { + return new (md_alloc()) Session_component(ep(), md_alloc(), + _pd_device_client, + &_ram, args); + } catch (Genode::Session_policy::No_policy_defined) { + PERR("Invalid session request, no matching policy for '%s'", + Genode::Session_label(args).string()); + throw Genode::Root::Unavailable(); + } } public: