summaryrefslogtreecommitdiff
path: root/src/mm-filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-filter.c')
-rw-r--r--src/mm-filter.c550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/mm-filter.c b/src/mm-filter.c
new file mode 100644
index 00000000..b7a9d2f8
--- /dev/null
+++ b/src/mm-filter.c
@@ -0,0 +1,550 @@
+/* -*- 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 QRTR device, we always allow it. This check comes before
+ * checking for VIRTUAL since qrtr devices don't have a sysfs path, and the
+ * check for VIRTUAL will return FALSE. */
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_QRTR) &&
+ g_str_equal (subsystem, "qrtr")) {
+ mm_obj_dbg (self, "(%s/%s) port allowed: qrtr device", 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_USBMISC) &&
+ (g_strcmp0 (subsystem, "usbmisc") == 0)) {
+ mm_obj_dbg (self, "(%s/%s) port allowed: usbmisc device", subsystem, name);
+ return TRUE;
+ }
+
+ /* If this is a rpmsg channel device, we always allow it */
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_RPMSG) &&
+ (g_strcmp0 (subsystem, "rpmsg") == 0)) {
+ mm_obj_dbg (self, "(%s/%s) port allowed: rpmsg device", subsystem, name);
+ return TRUE;
+ }
+
+ /* If this is a wwan port/device, we always allow it */
+ if ((self->priv->enabled_rules & MM_FILTER_RULE_WWAN) &&
+ (g_strcmp0 (subsystem, "wwan") == 0)) {
+ mm_obj_dbg (self, "(%s/%s) port allowed: wwan 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;
+
+ /* 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;
+ }
+
+ /* 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, DEFAULT_FORBIDDEN must be set. */
+#define VALIDATE_RULE_TTY(rules) (!(rules & MM_FILTER_RULE_TTY) || (rules & (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, " qrtr devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_QRTR));
+ 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, " usbmisc devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_USBMISC));
+ mm_obj_dbg (self, " rpmsg devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_RPMSG));
+ mm_obj_dbg (self, " wwan devices allowed: %s", RULE_ENABLED_STR (MM_FILTER_RULE_WWAN));
+ if (self->priv->enabled_rules & MM_FILTER_RULE_TTY) {
+ mm_obj_dbg (self, " tty devices:");
+ 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_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));
+}