mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-19 13:47:56 +00:00
os: add component for filtering USB devices report
The 'usb_report_filter' component takes the devices report from the USB driver and generates a new devices report after checking each entry against its device white-list. Before emitting the new report it changes the configuration of the USB driver to contain the required policy entries. See 'repos/os/src/app/usb_report_filter/README' for more details. Issue #1863.
This commit is contained in:
parent
716eab21e3
commit
e233fe0b71
80
repos/os/src/app/usb_report_filter/README
Normal file
80
repos/os/src/app/usb_report_filter/README
Normal file
@ -0,0 +1,80 @@
|
||||
This directory contains a USB device report filter component. It filters the
|
||||
device report coming from the USB driver by checking each device reported
|
||||
against the given list of devices. Only approved devices are reported to a
|
||||
consumer of the report coming from the filter component.
|
||||
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
A typical example configuration looks as follows:
|
||||
|
||||
!<config>
|
||||
! <client label="component_xyz"/>
|
||||
! <device vendor_id="0x13fe" product_id="0x5200"/>
|
||||
! <device vendor_id="0x148f" product_id="0x2573"/>
|
||||
! <device vendor_id="0x04f9" product_id="0x0051"/>
|
||||
! <device vendor_id="0x1b1c" product_id="0x1a09"/>
|
||||
</config>
|
||||
|
||||
The component that may use the devices is identified by the 'client' node.
|
||||
In addition to the 'vendor_id' and 'product_id' attribute a 'device' node
|
||||
can contain a 'bus' and 'dev' attribute. If these attributes are present they
|
||||
have a stronger significance than the 'vendor_id' and the 'product_id'.
|
||||
|
||||
Whenever the 'usb_report_filter' component receives a new USB device report
|
||||
from the driver it will generate a new driver configuration that contains
|
||||
a policy entry for each matching device. After the driver's configuration has
|
||||
been updated, the filter component will generate a new USB device report that
|
||||
only contains the devices the component is allowed to access.
|
||||
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
In the following example we will give a VirtualBox instance access to a
|
||||
Corsair Voyager USB stick:
|
||||
|
||||
!<start name="usb_report_filter">
|
||||
![...]
|
||||
! <config>
|
||||
! <client label="vbox"
|
||||
! <device vendor_id="0x1b1c" product_id="0x1a09"/>
|
||||
! </config>
|
||||
!</start>
|
||||
|
||||
!<start name="report_rom">
|
||||
! <resource name="RAM" quantum="1M"/>
|
||||
! <provides> <service name="Report"/> <service name="ROM"/> </provides>
|
||||
! <config>
|
||||
! <policy label="usb_report_filter -> devices" report="usb_drv -> devices"/>
|
||||
! <policy label="usb_report_filter -> usb_drv_config" report="usb_drv -> config"/>
|
||||
! <policy label="vbox -> usb_devices" report="usb_report_filter -> usb_devices"/>
|
||||
! </config>
|
||||
!</start>
|
||||
|
||||
After the USB stick has been plugged in, the filter will generate the
|
||||
following USB driver configuration:
|
||||
|
||||
!<start name="usb_drv">
|
||||
![...]
|
||||
! <config uhci="yes" ehci="yes" xhci="yes">
|
||||
! <hid/>
|
||||
! <raw>
|
||||
! <report devices="yes"/>
|
||||
! <policy label="vbox -> usb-1-3" vendor_id="0x1b1c" product_id="0x1a09" bus="0x0001" device="0x0003"/>
|
||||
! </raw>
|
||||
! </config>
|
||||
!</start>
|
||||
|
||||
After the driver has reloaded its configuration it will send a config report
|
||||
that provokes the filter component to send the following USB device report to
|
||||
VirtualBox:
|
||||
|
||||
!<devices>
|
||||
! <device label="usb-1-3" vendor_id="0x1b1c" product_id="0x1a09" bus="0x0001" device="0x0003"/>
|
||||
!</device>
|
||||
|
||||
In return, VirtualBox will try to access the USB device. Since the configuration
|
||||
of the USB driver contains a matching policy entry the access attempt will
|
||||
succeed.
|
434
repos/os/src/app/usb_report_filter/main.cc
Normal file
434
repos/os/src/app/usb_report_filter/main.cc
Normal file
@ -0,0 +1,434 @@
|
||||
/*
|
||||
* \brief Component that filters USB device reports
|
||||
* \author Josef Soentgen
|
||||
* \date 2016-01-13
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/allocator_avl.h>
|
||||
#include <os/attached_rom_dataspace.h>
|
||||
#include <os/config.h>
|
||||
#include <os/reporter.h>
|
||||
#include <os/server.h>
|
||||
#include <util/list.h>
|
||||
#include <util/string.h>
|
||||
#include <util/xml_generator.h>
|
||||
#include <util/xml_node.h>
|
||||
#include <file_system_session/connection.h>
|
||||
#include <file_system/util.h>
|
||||
|
||||
|
||||
namespace Usb_filter {
|
||||
|
||||
using Genode::Xml_node;
|
||||
using Genode::Xml_generator;
|
||||
using Genode::Attached_rom_dataspace;
|
||||
using Genode::snprintf;
|
||||
|
||||
struct Device_registry;
|
||||
struct Main;
|
||||
}
|
||||
|
||||
static bool const verbose = false;
|
||||
|
||||
static char const * const config_file = "usb_drv.config";
|
||||
|
||||
|
||||
class Usb_filter::Device_registry
|
||||
{
|
||||
private:
|
||||
|
||||
Genode::Allocator &_alloc;
|
||||
Server::Entrypoint &_ep;
|
||||
|
||||
Genode::Reporter _reporter { "usb_devices" };
|
||||
|
||||
Attached_rom_dataspace _devices_rom { "devices" };
|
||||
Attached_rom_dataspace _usb_drv_config_rom { "usb_drv_config" };
|
||||
|
||||
Genode::Allocator_avl _fs_packet_alloc { &_alloc };
|
||||
File_system::Connection _fs { _fs_packet_alloc, 128*1024, "usb_drv.config" };
|
||||
File_system::File_handle _file;
|
||||
|
||||
struct Entry : public Genode::List<Entry>::Element
|
||||
{
|
||||
unsigned bus;
|
||||
unsigned dev;
|
||||
unsigned vendor;
|
||||
unsigned product;
|
||||
|
||||
Entry(unsigned b, unsigned d, unsigned v, unsigned p)
|
||||
: bus(b), dev(d), vendor(v), product(p) { }
|
||||
};
|
||||
|
||||
Genode::List<Entry> _list;
|
||||
|
||||
enum { MAX_LABEL_LEN = 512 };
|
||||
typedef Genode::String<MAX_LABEL_LEN> Label;
|
||||
Label _client_label;
|
||||
|
||||
template <typename FUNC>
|
||||
void _for_each_entry(FUNC const &func) const
|
||||
{
|
||||
Entry const *e = _list.first();
|
||||
Entry const *next = nullptr;
|
||||
for (; e; e = next) {
|
||||
|
||||
/*
|
||||
* Obtain next element prior calling the functor because
|
||||
* the functor may remove the current element from the list.
|
||||
*/
|
||||
next = e->next();
|
||||
|
||||
func(*e);
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned _get_value(Xml_node node, char const * const attr) {
|
||||
return node.attribute_value<unsigned long>(attr, 0); }
|
||||
|
||||
static bool _config_has_device(Xml_node config, Entry const &entry)
|
||||
{
|
||||
bool result = false;
|
||||
config.for_each_sub_node("device", [&] (Xml_node usb_device) {
|
||||
|
||||
result |= (_get_value(usb_device, "bus") == entry.bus &&
|
||||
_get_value(usb_device, "dev") == entry.dev);
|
||||
if (result) return;
|
||||
|
||||
result |= (_get_value(usb_device, "vendor") == entry.vendor &&
|
||||
_get_value(usb_device, "product") == entry.product);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool _devices_matches(Xml_node &device, Entry const & entry)
|
||||
{
|
||||
unsigned const bus = _get_value(device, "bus");
|
||||
unsigned const dev = _get_value(device, "dev");
|
||||
unsigned const vendor = _get_value(device, "vendor_id");
|
||||
unsigned const product = _get_value(device, "product_id");
|
||||
|
||||
return (bus == entry.bus && dev == entry.dev) ||
|
||||
(vendor == entry.vendor && product == entry.product);
|
||||
}
|
||||
|
||||
static void _gen_policy_entry(Xml_generator &xml, Xml_node &node,
|
||||
Entry const &entry, char const *label)
|
||||
{
|
||||
xml.node("policy", [&] {
|
||||
char buf[MAX_LABEL_LEN + 16];
|
||||
|
||||
unsigned const bus = _get_value(node, "bus");
|
||||
unsigned const dev = _get_value(node, "dev");
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s -> usb-%d-%d", label, bus, dev);
|
||||
xml.attribute("label", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "vendor_id"));
|
||||
xml.attribute("vendor_id", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "product_id"));
|
||||
xml.attribute("product_id", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", bus);
|
||||
xml.attribute("bus", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", dev);
|
||||
xml.attribute("dev", buf);
|
||||
});
|
||||
}
|
||||
|
||||
void _write_usb_drv_config(Xml_node & usb_devices)
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
try {
|
||||
File_system::Dir_handle root_dir = _fs.dir("/", false);
|
||||
_file = _fs.file(root_dir, config_file, File_system::READ_WRITE, false);
|
||||
} catch (...) {
|
||||
PERR("Could not open '%s'", config_file);
|
||||
return;
|
||||
}
|
||||
|
||||
char old_file[1024];
|
||||
size_t n = File_system::read(_fs, _file, old_file,
|
||||
sizeof(old_file));
|
||||
if (n == 0) {
|
||||
PERR("Could not read '%s'", config_file);
|
||||
return;
|
||||
}
|
||||
|
||||
Xml_node drv_config(old_file, n);
|
||||
|
||||
bool const uhci_enabled = drv_config.attribute_value<bool>("uhci", false);
|
||||
bool const ehci_enabled = drv_config.attribute_value<bool>("ehci", false);
|
||||
bool const xhci_enabled = drv_config.attribute_value<bool>("xhci", false);
|
||||
|
||||
char new_file[1024];
|
||||
Genode::Xml_generator xml(new_file, sizeof(new_file), "config", [&] {
|
||||
if (uhci_enabled) xml.attribute("uhci", "yes");
|
||||
if (ehci_enabled) xml.attribute("ehci", "yes");
|
||||
if (xhci_enabled) xml.attribute("xhci", "yes");
|
||||
|
||||
/* copy other nodes */
|
||||
drv_config.for_each_sub_node([&] (Xml_node &node) {
|
||||
if (!node.has_type("raw")) {
|
||||
xml.append(node.addr(), node.size());
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!drv_config.has_sub_node("raw"))
|
||||
PINF("enable raw support in usb_drv");
|
||||
|
||||
xml.node("raw", [&] {
|
||||
xml.node("report", [&] {
|
||||
xml.attribute("devices", "yes");
|
||||
});
|
||||
|
||||
char const * const label = _client_label.string();
|
||||
|
||||
usb_devices.for_each_sub_node("device", [&] (Xml_node &node) {
|
||||
|
||||
auto add_policy_entry = [&] (Entry const &entry) {
|
||||
if (!_devices_matches(node, entry)) return;
|
||||
|
||||
_gen_policy_entry(xml, node, entry, label);
|
||||
};
|
||||
_for_each_entry(add_policy_entry);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
new_file[xml.used()] = 0;
|
||||
if (verbose)
|
||||
PLOG("new usb_drv configuration:\n%s", new_file);
|
||||
|
||||
n = File_system::write(_fs, _file, new_file, xml.used());
|
||||
if (n == 0)
|
||||
PERR("Could not write '%s'", config_file);
|
||||
|
||||
_fs.close(_file);
|
||||
}
|
||||
|
||||
Genode::Signal_rpc_member<Device_registry> _devices_dispatcher =
|
||||
{ _ep, *this, &Device_registry::_handle_devices };
|
||||
|
||||
void _handle_devices(unsigned)
|
||||
{
|
||||
_devices_rom.update();
|
||||
|
||||
if (!_devices_rom.is_valid()) return;
|
||||
|
||||
if (verbose)
|
||||
PLOG("device report:\n%s", _devices_rom.local_addr<char>());
|
||||
|
||||
Xml_node usb_devices(_devices_rom.local_addr<char>(), _devices_rom.size());
|
||||
|
||||
_write_usb_drv_config(usb_devices);
|
||||
}
|
||||
|
||||
bool _check_config(Xml_node &drv_config)
|
||||
{
|
||||
if (!drv_config.has_sub_node("raw")) {
|
||||
PERR("Could not access <raw> node");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto check_policy = [&] (Entry const &entry) {
|
||||
bool result = false;
|
||||
drv_config.sub_node("raw").for_each_sub_node("policy", [&] (Xml_node &node) {
|
||||
result |= (entry.bus == _get_value(node, "bus") &&
|
||||
entry.dev == _get_value(node, "dev"));
|
||||
if (result) return;
|
||||
|
||||
result |= (entry.vendor == _get_value(node, "vendor_id") &&
|
||||
entry.product == _get_value(node, "product_id"));
|
||||
});
|
||||
|
||||
if (verbose && !result)
|
||||
PWRN("No matching policy was created for device %d-%d (%x:%x)",
|
||||
entry.bus, entry.dev, entry.vendor, entry.product);
|
||||
};
|
||||
_for_each_entry(check_policy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _gen_device_entry(Xml_generator &xml, Xml_node &node,
|
||||
Entry const &entry)
|
||||
{
|
||||
xml.node("device", [&] {
|
||||
char buf[16];
|
||||
|
||||
unsigned const bus = _get_value(node, "bus");
|
||||
unsigned const dev = _get_value(node, "dev");
|
||||
|
||||
snprintf(buf, sizeof(buf), "usb-%d-%d", bus, dev);
|
||||
xml.attribute("label", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "vendor_id"));
|
||||
xml.attribute("vendor_id", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", _get_value(node, "product_id"));
|
||||
xml.attribute("product_id", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", bus);
|
||||
xml.attribute("bus", buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "0x%4x", dev);
|
||||
xml.attribute("dev", buf);
|
||||
});
|
||||
}
|
||||
|
||||
void _report_usb_devices()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
/*
|
||||
* XXX it might happen that the device list has changed after we are
|
||||
* waiting for the usb_drv_config update
|
||||
*/
|
||||
Xml_node usb_devices(_devices_rom.local_addr<char>(), _devices_rom.size());
|
||||
|
||||
Reporter::Xml_generator xml(_reporter, [&] () {
|
||||
usb_devices.for_each_sub_node("device", [&] (Xml_node &node) {
|
||||
|
||||
auto check_entry = [&] (Entry const &entry) {
|
||||
if (!_devices_matches(node, entry)) return;
|
||||
|
||||
_gen_device_entry(xml, node, entry);
|
||||
};
|
||||
_for_each_entry(check_entry);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Genode::Signal_rpc_member<Device_registry> _usb_drv_config_dispatcher =
|
||||
{ _ep, *this, &Device_registry::_handle_usb_drv_config };
|
||||
|
||||
void _handle_usb_drv_config(unsigned)
|
||||
{
|
||||
_usb_drv_config_rom.update();
|
||||
|
||||
if (!_usb_drv_config_rom.is_valid()) return;
|
||||
|
||||
Xml_node config(_usb_drv_config_rom.local_addr<char>(),
|
||||
_usb_drv_config_rom.size());
|
||||
|
||||
if (!_check_config(config)) return;
|
||||
|
||||
/* report devices if the USB drivers has changed its policies */
|
||||
_report_usb_devices();
|
||||
}
|
||||
|
||||
bool _entry_exists(unsigned bus, unsigned dev,
|
||||
unsigned vendor, unsigned product)
|
||||
{
|
||||
bool result = false;
|
||||
auto check_exists = [&] (Entry const &entry) {
|
||||
result |= (bus && dev) &&
|
||||
(entry.bus == bus && entry.dev == dev);
|
||||
result |= (vendor && product) &&
|
||||
(entry.vendor == vendor && entry.product == product);
|
||||
};
|
||||
|
||||
_for_each_entry(check_exists);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Device_registry(Genode::Allocator &alloc,
|
||||
Server::Entrypoint &ep)
|
||||
: _alloc(alloc), _ep(ep)
|
||||
{
|
||||
_reporter.enabled(true);
|
||||
|
||||
_devices_rom.sigh(_devices_dispatcher);
|
||||
|
||||
_usb_drv_config_rom.sigh(_usb_drv_config_dispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update internal device registry
|
||||
*/
|
||||
void update_entries(Genode::Xml_node config)
|
||||
{
|
||||
auto remove_stale_entry = [&] (Entry const &entry) {
|
||||
|
||||
if (_config_has_device(config, entry))
|
||||
return;
|
||||
|
||||
_list.remove(const_cast<Entry *>(&entry));
|
||||
Genode::destroy(&_alloc, const_cast<Entry *>(&entry));
|
||||
};
|
||||
_for_each_entry(remove_stale_entry);
|
||||
|
||||
auto add_new_entry = [&] (Xml_node const &node) {
|
||||
|
||||
unsigned const bus = _get_value(node, "bus");
|
||||
unsigned const dev = _get_value(node, "dev");
|
||||
unsigned const vendor = _get_value(node, "vendor_id");
|
||||
unsigned const product = _get_value(node, "product_id");
|
||||
|
||||
if (_entry_exists(bus, dev, vendor, product)) return;
|
||||
|
||||
Entry *entry = new (&_alloc) Entry(bus, dev, vendor, product);
|
||||
|
||||
_list.insert(entry);
|
||||
};
|
||||
config.for_each_sub_node("device", add_new_entry);
|
||||
|
||||
try {
|
||||
config.sub_node("client").attribute("label").value(&_client_label);
|
||||
} catch (...) {
|
||||
PERR("Could not update client label");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Usb_filter::Main
|
||||
{
|
||||
Server::Entrypoint &ep;
|
||||
|
||||
Genode::Signal_rpc_member<Main> _config_dispatcher =
|
||||
{ ep, *this, &Main::_handle_config };
|
||||
|
||||
void _handle_config(unsigned)
|
||||
{
|
||||
Genode::config()->reload();
|
||||
device_registry.update_entries(Genode::config()->xml_node());
|
||||
}
|
||||
|
||||
Device_registry device_registry { *Genode::env()->heap(), ep };
|
||||
|
||||
Main(Server::Entrypoint &ep) : ep(ep)
|
||||
{
|
||||
Genode::config()->sigh(_config_dispatcher);
|
||||
|
||||
_handle_config(0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace Server {
|
||||
char const *name() { return "usb_report_filter_ep"; }
|
||||
size_t stack_size() { return 4*1024*sizeof(addr_t); }
|
||||
void construct(Entrypoint &ep) { static Usb_filter::Main main(ep); }
|
||||
}
|
3
repos/os/src/app/usb_report_filter/target.mk
Normal file
3
repos/os/src/app/usb_report_filter/target.mk
Normal file
@ -0,0 +1,3 @@
|
||||
TARGET = usb_report_filter
|
||||
SRC_CC = main.cc
|
||||
LIBS = base config server
|
Loading…
Reference in New Issue
Block a user