Files
flatpak/common/flatpak-usb.c
T
Sebastian Wick 59ef3bc49a usb: Parse the vnd rule into the vendor union member
It was accidentally parsed into the product union member but because it
has the same layout as the vendor one, this didn't turn into a bug in
practice, but it probably is UB.
2026-07-27 13:41:00 +00:00

450 lines
12 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2024 GNOME Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
* Hubert Figuière <hub@figuiere.net>
*/
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include "libglnx.h"
#include "flatpak-usb-private.h"
void
flatpak_usb_rule_free (FlatpakUsbRule *usb_rule)
{
g_clear_pointer (&usb_rule, g_free);
}
void
flatpak_usb_query_free (FlatpakUsbQuery *usb_query)
{
if (!usb_query)
return;
g_clear_pointer (&usb_query->rules, g_ptr_array_unref);
g_clear_pointer (&usb_query, g_free);
}
static gboolean
validate_hex_uint16 (const char *value,
size_t expected_length,
uint16_t *out_value)
{
size_t len;
char *end;
long n;
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (expected_length > 0 && expected_length <= 4, FALSE);
len = strlen (value);
if (len != expected_length)
return FALSE;
n = strtol (value, &end, 16);
if (end - value != len)
return FALSE;
if (n < 0 || n > UINT16_MAX)
return FALSE;
if (out_value)
*out_value = n;
return TRUE;
}
static gboolean
parse_all_usb_rule (FlatpakUsbRule *dest,
GStrv data,
GError **error)
{
if (g_strv_length (data) != 1)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB device query 'all' must not have data"));
return FALSE;
}
dest->rule_type = FLATPAK_USB_RULE_TYPE_ALL;
return TRUE;
}
static gboolean
parse_cls_usb_rule (FlatpakUsbRule *dest,
GStrv data,
GError **error)
{
const char *subclass;
const char *class;
if (g_strv_length (data) < 3)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB query rule 'cls' must be in the form CLASS:SUBCLASS or CLASS:*"));
return FALSE;
}
class = data[1];
subclass = data[2];
if (!validate_hex_uint16 (class, 2, &dest->d.device_class.class))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Invalid USB class"));
return FALSE;
}
if (g_strcmp0 (subclass, "*") == 0)
{
dest->d.device_class.type = FLATPAK_USB_RULE_CLASS_TYPE_CLASS_ONLY;
}
else if (validate_hex_uint16 (subclass, 2, &dest->d.device_class.subclass))
{
dest->d.device_class.type = FLATPAK_USB_RULE_CLASS_TYPE_CLASS_SUBCLASS;
}
else
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Invalid USB subclass"));
return FALSE;
}
dest->rule_type = FLATPAK_USB_RULE_TYPE_CLASS;
return TRUE;
}
static gboolean
parse_dev_usb_rule (FlatpakUsbRule *dest,
GStrv data,
GError **error)
{
if (g_strv_length (data) != 2)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB query rule 'dev' must have a valid 4-digit hexadecimal product id"));
return FALSE;
}
if (!validate_hex_uint16 (data[1], 4, &dest->d.product.id))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB query rule 'dev' must have a valid 4-digit hexadecimal product id"));
return FALSE;
}
dest->rule_type = FLATPAK_USB_RULE_TYPE_DEVICE;
return TRUE;
}
static gboolean
parse_vnd_usb_rule (FlatpakUsbRule *dest,
GStrv data,
GError **error)
{
if (g_strv_length (data) != 2)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB query rule 'vnd' must have a valid 4-digit hexadecimal vendor id"));
return FALSE;
}
if (!validate_hex_uint16 (data[1], 4, &dest->d.vendor.id))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB query rule 'vnd' must have a valid 4-digit hexadecimal vendor id"));
return FALSE;
}
dest->rule_type = FLATPAK_USB_RULE_TYPE_VENDOR;
return TRUE;
}
static const struct {
const char *name;
gboolean (*parse) (FlatpakUsbRule *dest,
GStrv data,
GError **error);
} rule_parsers[] = {
{ "all", parse_all_usb_rule },
{ "cls", parse_cls_usb_rule },
{ "dev", parse_dev_usb_rule },
{ "vnd", parse_vnd_usb_rule },
};
gboolean
flatpak_usb_parse_usb_rule (const char *data,
FlatpakUsbRule **out_usb_rule,
GError **error)
{
g_autoptr(FlatpakUsbRule) usb_rule = NULL;
g_auto(GStrv) split = NULL;
gboolean parsed = FALSE;
split = g_strsplit (data, ":", 0);
if (!split || g_strv_length (split) > 3)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB device queries must be in the form TYPE:DATA"));
return FALSE;
}
usb_rule = g_new0 (FlatpakUsbRule, 1);
for (size_t i = 0; i < G_N_ELEMENTS (rule_parsers); i++)
{
if (g_strcmp0 (rule_parsers[i].name, split[0]))
continue;
if (!rule_parsers[i].parse (usb_rule, split, error))
return FALSE;
parsed = TRUE;
}
if (!parsed)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("Unknown USB query rule %s"), split[0]);
return FALSE;
}
if (out_usb_rule)
*out_usb_rule = g_steal_pointer (&usb_rule);
return TRUE;
}
gboolean
flatpak_usb_parse_usb (const char *data,
FlatpakUsbQuery **out_usb_query,
GError **error)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
g_autoptr(GHashTable) rule_types = NULL;
g_auto(GStrv) split = NULL;
split = g_strsplit (data, "+", 0);
if (!*split)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, _("Empty USB query"));
return FALSE;
}
usb_query = flatpak_usb_query_new ();
for (size_t i = 0; split[i] != NULL; i++)
{
g_autoptr(FlatpakUsbRule) usb_rule = NULL;
const char *rule = split[i];
if (!flatpak_usb_parse_usb_rule (rule, &usb_rule, error))
return FALSE;
g_ptr_array_add (usb_query->rules, g_steal_pointer (&usb_rule));
}
g_assert (usb_query->rules->len > 0);
rule_types = g_hash_table_new (g_direct_hash, g_direct_equal);
for (size_t i = 0; i < usb_query->rules->len; i++)
{
FlatpakUsbRule *usb_rule = g_ptr_array_index (usb_query->rules, i);
if (!g_hash_table_add (rule_types, GINT_TO_POINTER (usb_rule->rule_type)))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("Multiple USB query rules of the same type is not supported"));
return FALSE;
}
}
if (g_hash_table_contains (rule_types, GINT_TO_POINTER (FLATPAK_USB_RULE_TYPE_ALL)) &&
g_hash_table_size (rule_types) > 1)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("'all' must not contain extra query rules"));
return FALSE;
}
if (g_hash_table_contains (rule_types, GINT_TO_POINTER (FLATPAK_USB_RULE_TYPE_DEVICE)) &&
!g_hash_table_contains (rule_types, GINT_TO_POINTER (FLATPAK_USB_RULE_TYPE_VENDOR)))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("USB queries with 'dev' must also specify vendors"));
return FALSE;
}
if (out_usb_query)
*out_usb_query = g_steal_pointer (&usb_query);
return TRUE;
}
gboolean
flatpak_usb_parse_usb_list (const char *buffer,
GHashTable *enumerable,
GHashTable *hidden,
GError **error)
{
char *aux = NULL;
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
g_autoptr(GInputStream) stream = NULL;
g_autoptr(GDataInputStream) buffered = NULL;
g_autoptr(GError) local_error = NULL;
stream = g_memory_input_stream_new_from_data (buffer, -1, NULL);
if (!stream)
return FALSE;
buffered = g_data_input_stream_new (G_INPUT_STREAM (stream));
while ((aux = g_data_input_stream_read_line (buffered, NULL, NULL, &local_error)))
{
g_autofree char *line = g_steal_pointer (&aux);
g_auto(GStrv) split = NULL;
gboolean blocking = FALSE;
if (line[0] == '#')
continue;
split = g_strsplit (line, ";", 0);
if (!split || !*split)
continue;
for (size_t i = 0; split[i] != NULL; i++)
{
const char *item = split[i];
blocking = item[0] == '!';
if (blocking)
item++;
if (flatpak_usb_parse_usb (item, &usb_query, NULL))
{
GString *string = g_string_new (NULL);
flatpak_usb_query_print (usb_query, string);
if (blocking)
{
g_hash_table_insert (hidden,
g_string_free (string, FALSE),
g_steal_pointer (&usb_query));
}
else
{
g_hash_table_insert (enumerable,
g_string_free (string, FALSE),
g_steal_pointer (&usb_query));
}
}
}
}
g_input_stream_close (G_INPUT_STREAM (buffered), NULL, error);
g_input_stream_close (G_INPUT_STREAM (stream), NULL, error);
if (local_error)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
return TRUE;
}
void
flatpak_usb_rule_print (FlatpakUsbRule *usb_rule,
GString *string)
{
g_return_if_fail (usb_rule != NULL);
g_assert (string != NULL);
switch (usb_rule->rule_type)
{
case FLATPAK_USB_RULE_TYPE_ALL:
g_string_append (string, "all");
break;
case FLATPAK_USB_RULE_TYPE_CLASS:
g_string_append (string, "cls:");
if (usb_rule->d.device_class.type == FLATPAK_USB_RULE_CLASS_TYPE_CLASS_ONLY)
g_string_append_printf (string, "%02x:*", usb_rule->d.device_class.class);
else if (usb_rule->d.device_class.type == FLATPAK_USB_RULE_CLASS_TYPE_CLASS_SUBCLASS)
g_string_append_printf (string, "%02x:%02x", usb_rule->d.device_class.class, usb_rule->d.device_class.subclass);
else
g_assert_not_reached ();
break;
case FLATPAK_USB_RULE_TYPE_DEVICE:
g_string_append_printf (string, "dev:%04x", usb_rule->d.product.id);
break;
case FLATPAK_USB_RULE_TYPE_VENDOR:
g_string_append_printf (string, "vnd:%04x", usb_rule->d.vendor.id);
break;
default:
g_assert_not_reached ();
}
}
FlatpakUsbQuery *
flatpak_usb_query_new (void)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
usb_query = g_new0 (FlatpakUsbQuery, 1);
usb_query->rules = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_usb_rule_free);
return g_steal_pointer (&usb_query);
}
FlatpakUsbQuery *
flatpak_usb_query_copy (const FlatpakUsbQuery *query)
{
FlatpakUsbQuery *copy = flatpak_usb_query_new ();
for (size_t i = 0; i < query->rules->len; i++)
{
FlatpakUsbRule *usb_rule = g_ptr_array_index (query->rules, i);
g_ptr_array_add (copy->rules, g_memdup2 (usb_rule, sizeof (FlatpakUsbRule)));
}
return copy;
}
void
flatpak_usb_query_print (const FlatpakUsbQuery *usb_query,
GString *string)
{
g_assert (usb_query != NULL && usb_query->rules != NULL);
g_assert (string != NULL);
for (size_t i = 0; i < usb_query->rules->len; i++)
{
FlatpakUsbRule *usb_rule = g_ptr_array_index (usb_query->rules, i);
if (i > 0)
g_string_append_c (string, '+');
flatpak_usb_rule_print (usb_rule, string);
}
}