Files
ModemManager/src/mm-filter.c
T
Aleksander Morgado a5c060ef87 filter: setup automatic per-vid checks in the plugin whitelist
Until now, the plugin whitelist rule in the filter would only handle
those plugins that require specific udev tags, and those that had
explicit full vid:pid pairs.

This update makes a new implicit automatic whitelist for all devices
that match any of the vids managed by the different plugins.

Looking at the current list of devices in the blacklist and greylist
maintained by ModemManager, there are no devices falling under the
list of supported plugin vids that would need to be blacklisted;
except for u-blox GPS modules:

  huawei    -> 0x12d1        -> None in blacklist/greylist
  thuraya   -> 0x1a26        -> None in blacklist/greylist
  telit     -> 0x1bc7        -> None in blacklist/greylist
  dLink     -> 0x2001        -> None in blacklist/greylist
  wavecom   -> 0x114f        -> None in blacklist/greylist
  x22x      -> 0x1bbb,0x0b3c -> None in blacklist/greylist
  anydata   -> 0x16d5        -> None in blacklist/greylist
  quectel   -> 0x2c7c        -> None in blacklist/greylist
  haier     -> 0x201e        -> None in blacklist/greylist
  novatel   -> 0x1410        -> None in blacklist/greylist
  dell      -> 0x413c        -> None in blacklist/greylist
  option    -> 0x0af0,0x1931 -> None in blacklist/greylist
  nokia     -> 0x0421        -> None in blacklist/greylist
  cinterion -> 0x1e2d,0x0681 -> None in blacklist/greylist
  simtech   -> 0x1e0e        -> None in blacklist/greylist
  iridium   -> 0x1edd        -> None in blacklist/greylist
  pantech   -> 0x106c        -> None in blacklist/greylist
  longcheer -> 0x1c9e,0x1bbb -> None in blacklist/greylist
  linktop   -> 0x230d        -> None in blacklist/greylist
  sierra    -> 0x1199        -> None in blacklist/greylist
  ublox     -> 0x1546        -------------> GPS chips blacklisted !!!!!
  foxconn   -> 0x0489        -> None in blacklist/greylist
  broadmobi -> 0x2020        -> None in blacklist/greylist
  fibocom   -> 0x2cb7        -> None in blacklist/greylist
  tplink    -> 0x2357        -> None in blacklist/greylist
  zte       -> 0x19d2        -> None in blacklist/greylist

The rules used to blacklist the u-blox GPS chips have already been
moved to the u-blox plugin itself, and now they use the broader
ID_MM_DEVICE_IGNORE tag that applies in all filter modes.
2020-06-04 11:34:43 +02:00

556 lines
21 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2017 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <string.h>
#include <ModemManager.h>
#include <ModemManager-tags.h>
#include "mm-daemon-enums-types.h"
#include "mm-filter.h"
#include "mm-log-object.h"
#define FILTER_PORT_MAYBE_FORBIDDEN "maybe-forbidden"
static void log_object_iface_init (MMLogObjectInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMFilter, mm_filter, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
enum {
PROP_0,
PROP_ENABLED_RULES,
LAST_PROP
};
struct _MMFilterPrivate {
MMFilterRule enabled_rules;
GList *plugin_whitelist_tags;
GArray *plugin_whitelist_vendor_ids;
GArray *plugin_whitelist_product_ids;
};
/*****************************************************************************/
void
mm_filter_register_plugin_whitelist_tag (MMFilter *self,
const gchar *tag)
{
if (!g_list_find_custom (self->priv->plugin_whitelist_tags, tag, (GCompareFunc) g_strcmp0)) {
mm_obj_dbg (self, "registered plugin whitelist tag: %s", tag);
self->priv->plugin_whitelist_tags = g_list_prepend (self->priv->plugin_whitelist_tags, g_strdup (tag));
}
}
void
mm_filter_register_plugin_whitelist_vendor_id (MMFilter *self,
guint16 vid)
{
guint i;
if (!self->priv->plugin_whitelist_vendor_ids)
self->priv->plugin_whitelist_vendor_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint16), 64);
for (i = 0; i < self->priv->plugin_whitelist_vendor_ids->len; i++) {
guint16 item;
item = g_array_index (self->priv->plugin_whitelist_vendor_ids, guint16, i);
if (item == vid)
return;
}
g_array_append_val (self->priv->plugin_whitelist_vendor_ids, vid);
mm_obj_dbg (self, "registered plugin whitelist vendor id: %04x", vid);
}
void
mm_filter_register_plugin_whitelist_product_id (MMFilter *self,
guint16 vid,
guint16 pid)
{
mm_uint16_pair new_item;
guint i;
if (!self->priv->plugin_whitelist_product_ids)
self->priv->plugin_whitelist_product_ids = g_array_sized_new (FALSE, FALSE, sizeof (mm_uint16_pair), 10);
for (i = 0; i < self->priv->plugin_whitelist_product_ids->len; i++) {
mm_uint16_pair *item;
item = &g_array_index (self->priv->plugin_whitelist_product_ids, mm_uint16_pair, i);
if (item->l == vid && item->r == pid)
return;
}
new_item.l = vid;
new_item.r = pid;
g_array_append_val (self->priv->plugin_whitelist_product_ids, new_item);
mm_obj_dbg (self, "registered plugin whitelist product id: %04x:%04x", vid, pid);
}
/*****************************************************************************/
gboolean
mm_filter_port (MMFilter *self,
MMKernelDevice *port,
gboolean manual_scan)
{
const gchar *subsystem;
const gchar *name;
subsystem = mm_kernel_device_get_subsystem (port);
name = mm_kernel_device_get_name (port);
/* If the device is explicitly whitelisted, we process every port. Also
* allow specifying this flag per-port instead of for the full device, e.g.
* for platform tty ports where there's only one port anyway. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_EXPLICIT_WHITELIST) &&
(mm_kernel_device_get_global_property_as_boolean (port, ID_MM_DEVICE_PROCESS) ||
mm_kernel_device_get_property_as_boolean (port, ID_MM_DEVICE_PROCESS))) {
mm_obj_dbg (self, "(%s/%s) port allowed: device is whitelisted", subsystem, name);
return TRUE;
}
/* If the device is explicitly blacklisted, we ignore every port. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_EXPLICIT_BLACKLIST) &&
(mm_kernel_device_get_global_property_as_boolean (port, ID_MM_DEVICE_IGNORE))) {
mm_obj_dbg (self, "(%s/%s): port filtered: device is blacklisted", subsystem, name);
return FALSE;
}
/* If the device is whitelisted by a plugin, we allow it. */
if (self->priv->enabled_rules & MM_FILTER_RULE_PLUGIN_WHITELIST) {
GList *l;
guint16 vid = 0;
guint16 pid = 0;
for (l = self->priv->plugin_whitelist_tags; l; l = g_list_next (l)) {
if (mm_kernel_device_get_global_property_as_boolean (port, (const gchar *)(l->data)) ||
mm_kernel_device_get_property_as_boolean (port, (const gchar *)(l->data))) {
mm_obj_dbg (self, "(%s/%s) port allowed: device is whitelisted by plugin (tag)", subsystem, name);
return TRUE;
}
}
vid = mm_kernel_device_get_physdev_vid (port);
if (vid)
pid = mm_kernel_device_get_physdev_pid (port);
if (vid && pid && self->priv->plugin_whitelist_product_ids) {
guint i;
for (i = 0; i < self->priv->plugin_whitelist_product_ids->len; i++) {
mm_uint16_pair *item;
item = &g_array_index (self->priv->plugin_whitelist_product_ids, mm_uint16_pair, i);
if (item->l == vid && item->r == pid) {
mm_obj_dbg (self, "(%s/%s) port allowed: device is whitelisted by plugin (vid/pid)", subsystem, name);
return TRUE;
}
}
}
if (vid && self->priv->plugin_whitelist_vendor_ids) {
guint i;
for (i = 0; i < self->priv->plugin_whitelist_vendor_ids->len; i++) {
guint16 item;
item = g_array_index (self->priv->plugin_whitelist_vendor_ids, guint16, i);
if (item == vid) {
mm_obj_dbg (self, "(%s/%s) port allowed: device is whitelisted by plugin (vid)", subsystem, name);
return TRUE;
}
}
}
}
/* If this is a virtual device, don't allow it */
if ((self->priv->enabled_rules & MM_FILTER_RULE_VIRTUAL) &&
(!mm_kernel_device_get_physdev_sysfs_path (port))) {
mm_obj_dbg (self, "(%s/%s) port filtered: virtual device", subsystem, name);
return FALSE;
}
/* If this is a net device, we always allow it */
if ((self->priv->enabled_rules & MM_FILTER_RULE_NET) &&
(g_strcmp0 (subsystem, "net") == 0)) {
mm_obj_dbg (self, "(%s/%s) port allowed: net device", subsystem, name);
return TRUE;
}
/* If this is a cdc-wdm device, we always allow it */
if ((self->priv->enabled_rules & MM_FILTER_RULE_CDC_WDM) &&
(g_strcmp0 (subsystem, "usb") == 0 || g_strcmp0 (subsystem, "usbmisc") == 0) &&
(name && g_str_has_prefix (name, "cdc-wdm"))) {
mm_obj_dbg (self, "(%s/%s) port allowed: cdc-wdm device", subsystem, name);
return TRUE;
}
/* If this is a tty device, we may allow it */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY) &&
(g_strcmp0 (subsystem, "tty") == 0)) {
const gchar *physdev_subsystem;
const gchar *driver;
/* Blacklist rules first */
/* Ignore blacklisted tty devices. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_BLACKLIST) &&
(mm_kernel_device_get_global_property_as_boolean (port, ID_MM_TTY_BLACKLIST))) {
mm_obj_dbg (self, "(%s/%s): port filtered: tty is blacklisted", subsystem, name);
return FALSE;
}
/* Is the device in the manual-only greylist? If so, return if this is an
* automatic scan. */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY) &&
(!manual_scan && mm_kernel_device_get_global_property_as_boolean (port, ID_MM_TTY_MANUAL_SCAN_ONLY))) {
mm_obj_dbg (self, "(%s/%s): port filtered: tty probed only in manual scan", subsystem, name);
return FALSE;
}
/* Mixed blacklist/whitelist rules */
/* If the physdev is a 'platform' or 'pnp' device that's not whitelisted, ignore it */
physdev_subsystem = mm_kernel_device_get_physdev_subsystem (port);
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_PLATFORM_DRIVER) &&
(!g_strcmp0 (physdev_subsystem, "platform") ||
!g_strcmp0 (physdev_subsystem, "pci") ||
!g_strcmp0 (physdev_subsystem, "pnp") ||
!g_strcmp0 (physdev_subsystem, "sdio"))) {
mm_obj_dbg (self, "(%s/%s): port filtered: tty platform driver", subsystem, name);
return FALSE;
}
/* Default allowed? */
if (self->priv->enabled_rules & MM_FILTER_RULE_TTY_DEFAULT_ALLOWED) {
mm_obj_dbg (self, "(%s/%s) port allowed", subsystem, name);
return TRUE;
}
/* Whitelist rules last */
/* If the TTY kernel driver is one expected modem kernel driver, allow it */
driver = mm_kernel_device_get_driver (port);
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_DRIVER) &&
(!g_strcmp0 (driver, "option1") ||
!g_strcmp0 (driver, "qcserial") ||
!g_strcmp0 (driver, "qcaux") ||
!g_strcmp0 (driver, "nozomi") ||
!g_strcmp0 (driver, "sierra"))) {
mm_obj_dbg (self, "(%s/%s): port allowed: modem-specific kernel driver detected", subsystem, name);
return TRUE;
}
/*
* If the TTY kernel driver is cdc-acm and the interface is not
* class=2/subclass=2/protocol=[1-6], forbidden.
*
* Otherwise, we'll require the modem to have more ports other
* than the ttyACM one (see mm_filter_device_and_port()), because
* there are lots of Arduino devices out there exposing a single
* ttyACM port and wrongly claiming AT protocol support...
*
* Class definitions for Communication Devices 1.2
* Communications Interface Class Control Protocol Codes:
* 00h | USB specification | No class specific protocol required
* 01h | ITU-T V.250 | AT Commands: V.250 etc
* 02h | PCCA-101 | AT Commands defined by PCCA-101
* 03h | PCCA-101 | AT Commands defined by PCCA-101 & Annex O
* 04h | GSM 7.07 | AT Commands defined by GSM 07.07
* 05h | 3GPP 27.07 | AT Commands defined by 3GPP 27.007
* 06h | C-S0017-0 | AT Commands defined by TIA for CDMA
* 07h | USB EEM | Ethernet Emulation Model
* 08h-FDh | | RESERVED (future use)
* FEh | | External Protocol: Commands defined by Command Set Functional Descriptor
* FFh | USB Specification | Vendor-specific
*/
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_ACM_INTERFACE) &&
(!g_strcmp0 (driver, "cdc_acm")) &&
((mm_kernel_device_get_interface_class (port) != 2) ||
(mm_kernel_device_get_interface_subclass (port) != 2) ||
(mm_kernel_device_get_interface_protocol (port) < 1) ||
(mm_kernel_device_get_interface_protocol (port) > 6))) {
mm_obj_dbg (self, "(%s/%s): port filtered: cdc-acm interface is not AT-capable", subsystem, name);
return FALSE;
}
/* Default forbidden? flag the port as maybe-forbidden, and go on */
if (self->priv->enabled_rules & MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN) {
g_object_set_data (G_OBJECT (port), FILTER_PORT_MAYBE_FORBIDDEN, GUINT_TO_POINTER (TRUE));
return TRUE;
}
g_assert_not_reached ();
}
/* Otherwise forbidden */
mm_obj_dbg (self, "(%s/%s) port filtered: forbidden port type", subsystem, name);
return FALSE;
}
/*****************************************************************************/
static gboolean
device_has_net_port (MMDevice *device)
{
GList *l;
for (l = mm_device_peek_port_probe_list (device); l; l = g_list_next (l)) {
if (!g_strcmp0 (mm_port_probe_get_port_subsys (MM_PORT_PROBE (l->data)), "net"))
return TRUE;
}
return FALSE;
}
static gboolean
device_has_multiple_ports (MMDevice *device)
{
return (g_list_length (mm_device_peek_port_probe_list (device)) > 1);
}
gboolean
mm_filter_device_and_port (MMFilter *self,
MMDevice *device,
MMKernelDevice *port)
{
const gchar *subsystem;
const gchar *name;
const gchar *driver;
/* If it wasn't flagged as maybe forbidden, there's nothing to do */
if (!GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (port), FILTER_PORT_MAYBE_FORBIDDEN)))
return TRUE;
subsystem = mm_kernel_device_get_subsystem (port);
name = mm_kernel_device_get_name (port);
/* Check whether this device holds a NET port in addition to this TTY */
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_WITH_NET) &&
device_has_net_port (device)) {
mm_obj_dbg (self, "(%s/%s): port allowed: device also exports a net interface", subsystem, name);
return TRUE;
}
/* Check whether this device holds any other port in addition to the ttyACM port */
driver = mm_kernel_device_get_driver (port);
if ((self->priv->enabled_rules & MM_FILTER_RULE_TTY_ACM_INTERFACE) &&
(!g_strcmp0 (driver, "cdc_acm")) &&
device_has_multiple_ports (device)) {
mm_obj_dbg (self, "(%s/%s): port allowed: device exports multiple interfaces", subsystem, name);
return TRUE;
}
mm_obj_dbg (self, "(%s/%s) port filtered: forbidden", subsystem, name);
return FALSE;
}
/*****************************************************************************/
/* Use filter rule names as environment variables to control them on startup:
* - MM_FILTER_RULE_XXX=1 to explicitly enable the rule.
* - MM_FILTER_RULE_XXX=0 to explicitly disable the rule.
*/
static MMFilterRule
filter_rule_env_process (MMFilterRule enabled_rules)
{
MMFilterRule updated_rules = enabled_rules;
GFlagsClass *flags_class;
guint i;
flags_class = g_type_class_ref (MM_TYPE_FILTER_RULE);
for (i = 0; (1 << i) & MM_FILTER_RULE_ALL; i++) {
GFlagsValue *flags_value;
const gchar *env_value;
flags_value = g_flags_get_first_value (flags_class, (1 << i));
g_assert (flags_value);
env_value = g_getenv (flags_value->value_name);
if (!env_value)
continue;
if (g_str_equal (env_value, "0"))
updated_rules &= ~(1 << i);
else if (g_str_equal (env_value, "1"))
updated_rules |= (1 << i);
}
g_type_class_unref (flags_class);
return updated_rules;
}
/*****************************************************************************/
gboolean
mm_filter_check_rule_enabled (MMFilter *self,
MMFilterRule rule)
{
return !!(self->priv->enabled_rules & rule);
}
/*****************************************************************************/
static gchar *
log_object_build_id (MMLogObject *_self)
{
return g_strdup ("filter");
}
/*****************************************************************************/
/* If TTY rule enabled, either DEFAULT_ALLOWED or DEFAULT_FORBIDDEN must be set. */
#define VALIDATE_RULE_TTY(rules) (!(rules & MM_FILTER_RULE_TTY) || \
((rules & (MM_FILTER_RULE_TTY_DEFAULT_ALLOWED | MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN)) && \
((rules & (MM_FILTER_RULE_TTY_DEFAULT_ALLOWED | MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN)) != \
(MM_FILTER_RULE_TTY_DEFAULT_ALLOWED | MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN))))
MMFilter *
mm_filter_new (MMFilterRule enabled_rules,
GError **error)
{
MMFilter *self;
MMFilterRule updated_rules;
/* The input enabled rules are coming from predefined filter profiles. */
g_assert (VALIDATE_RULE_TTY (enabled_rules));
updated_rules = filter_rule_env_process (enabled_rules);
if (!VALIDATE_RULE_TTY (updated_rules)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"Invalid rules after processing envvars");
return NULL;
}
self = g_object_new (MM_TYPE_FILTER,
MM_FILTER_ENABLED_RULES, updated_rules,
NULL);
#define RULE_ENABLED_STR(flag) ((self->priv->enabled_rules & flag) ? "yes" : "no")
mm_obj_dbg (self, "created");
mm_obj_dbg (self, " explicit whitelist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_EXPLICIT_WHITELIST));
mm_obj_dbg (self, " explicit blacklist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_EXPLICIT_BLACKLIST));
mm_obj_dbg (self, " plugin whitelist: %s", RULE_ENABLED_STR (MM_FILTER_RULE_PLUGIN_WHITELIST));
mm_obj_dbg (self, " virtual devices forbidden: %s", RULE_ENABLED_STR (MM_FILTER_RULE_VIRTUAL));
mm_obj_dbg (self, " net devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_NET));
mm_obj_dbg (self, " cdc-wdm devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_CDC_WDM));
if (self->priv->enabled_rules & MM_FILTER_RULE_TTY) {
mm_obj_dbg (self, " tty devices:");
mm_obj_dbg (self, " blacklist applied: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_BLACKLIST));
mm_obj_dbg (self, " manual scan only applied: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY));
mm_obj_dbg (self, " platform driver check: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_PLATFORM_DRIVER));
mm_obj_dbg (self, " driver check: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_DRIVER));
mm_obj_dbg (self, " cdc-acm interface check: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_ACM_INTERFACE));
mm_obj_dbg (self, " with net check: %s", RULE_ENABLED_STR (MM_FILTER_RULE_TTY_WITH_NET));
if (self->priv->enabled_rules & MM_FILTER_RULE_TTY_DEFAULT_ALLOWED)
mm_obj_dbg (self, " default: allowed");
else if (self->priv->enabled_rules & MM_FILTER_RULE_TTY_DEFAULT_FORBIDDEN)
mm_obj_dbg (self, " default: forbidden");
else
g_assert_not_reached ();
} else
mm_obj_dbg (self, " tty devices: no");
#undef RULE_ENABLED_STR
return self;
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMFilter *self = MM_FILTER (object);
switch (prop_id) {
case PROP_ENABLED_RULES:
self->priv->enabled_rules = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MMFilter *self = MM_FILTER (object);
switch (prop_id) {
case PROP_ENABLED_RULES:
g_value_set_flags (value, self->priv->enabled_rules);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
mm_filter_init (MMFilter *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_FILTER, MMFilterPrivate);
}
static void
finalize (GObject *object)
{
MMFilter *self = MM_FILTER (object);
g_clear_pointer (&self->priv->plugin_whitelist_vendor_ids, g_array_unref);
g_clear_pointer (&self->priv->plugin_whitelist_product_ids, g_array_unref);
g_list_free_full (self->priv->plugin_whitelist_tags, g_free);
G_OBJECT_CLASS (mm_filter_parent_class)->finalize (object);
}
static void
log_object_iface_init (MMLogObjectInterface *iface)
{
iface->build_id = log_object_build_id;
}
static void
mm_filter_class_init (MMFilterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMFilterPrivate));
/* Virtual methods */
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->finalize = finalize;
g_object_class_install_property (
object_class, PROP_ENABLED_RULES,
g_param_spec_flags (MM_FILTER_ENABLED_RULES,
"Enabled rules",
"Mask of rules enabled in the filter",
MM_TYPE_FILTER_RULE,
MM_FILTER_RULE_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}