commit bdbd151ca0e901331423fc7765724a00702c5c19 Author: CentOS Sources Date: Tue May 16 06:12:18 2023 +0000 import usbredir-0.12.0-4.el8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..460e10a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/usbredir-0.12.0.tar.xz diff --git a/.usbredir.metadata b/.usbredir.metadata new file mode 100644 index 0000000..2bfab34 --- /dev/null +++ b/.usbredir.metadata @@ -0,0 +1 @@ +70940f6dc409b3bdb9ee98f24690c438f1ae999e SOURCES/usbredir-0.12.0.tar.xz diff --git a/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch b/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch new file mode 100644 index 0000000..9d21c34 --- /dev/null +++ b/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch @@ -0,0 +1,193 @@ +From 6bf41a231b445ac5190c32e281b698b1ee5379b4 Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Fri, 24 Jun 2022 23:29:08 +0200 +Subject: [PATCH 1/2] usbredirparser: Fix unserialize on pristine check +Content-type: text/plain + +As mentioned in the bug below, the user is trying to migrate QEMU and +it is failing on the unserialization of usbredirparser at the target +host. The user does not have USB attached to the VM at all. + +I've added a test that shows that serialization is currently broken. +It fails at the 'pristine' check in usbredirparser_unserialize(). + +This check was added with e37d86c "Skip empty write buffers when +unserializing parser" and restricted further with 186c4c7 "Avoid +memory leak from ill-formatted serialization data" + +The issue here is that usbredirparser's initialization sets some +fields and thus it isn't guaranteed to be pristine. + +The parser's basic data is: + + | write_buf_count ... : 1 + | write_buf ........ : 0xbc03e0 + | write_buf_total_size: 80 + | data ............. : (nil) + | header_read: ...... : 0 + | type_header_read .. : 0 + | data_read: ........ : 0 + +The current fix is to to ignore write_buf checks as, again, they are +not guaranteed to be pristine. usbredirparser library should properly +overwrite them when unserializing the data and if there were pending +buffers, they should be freed. + +Related: https://bugzilla.redhat.com/show_bug.cgi?id=2096008 + +Signed-off-by: Victor Toso +--- + tests/meson.build | 1 + + tests/serializer.c | 113 ++++++++++++++++++++++++++++++++ + usbredirparser/usbredirparser.c | 4 +- + 3 files changed, 115 insertions(+), 3 deletions(-) + create mode 100644 tests/serializer.c + +diff --git a/tests/meson.build b/tests/meson.build +index 0d4397b..2a179c9 100644 +--- a/tests/meson.build ++++ b/tests/meson.build +@@ -1,5 +1,6 @@ + tests = [ + 'filter', ++ 'serializer', + ] + + deps = dependency('glib-2.0') +diff --git a/tests/serializer.c b/tests/serializer.c +new file mode 100644 +index 0000000..4bd669e +--- /dev/null ++++ b/tests/serializer.c +@@ -0,0 +1,113 @@ ++/* ++ * Copyright 2022 Red Hat, Inc. ++ * ++ * This library 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 . ++*/ ++#include "config.h" ++ ++#define G_LOG_DOMAIN "serializer" ++#define G_LOG_USE_STRUCTURED ++ ++#include "usbredirparser.h" ++ ++#include ++#include ++#include ++#include ++ ++ ++static void ++log_cb(void *priv, int level, const char *msg) ++{ ++ GLogLevelFlags glog_level; ++ ++ switch(level) { ++ case usbredirparser_error: ++ glog_level = G_LOG_LEVEL_ERROR; ++ break; ++ case usbredirparser_warning: ++ glog_level = G_LOG_LEVEL_WARNING; ++ break; ++ case usbredirparser_info: ++ glog_level = G_LOG_LEVEL_INFO; ++ break; ++ case usbredirparser_debug: ++ case usbredirparser_debug_data: ++ glog_level = G_LOG_LEVEL_DEBUG; ++ break; ++ default: ++ g_warn_if_reached(); ++ return; ++ } ++ g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", msg); ++} ++ ++static struct usbredirparser * ++get_usbredirparser(void) ++{ ++ struct usbredirparser *parser = usbredirparser_create(); ++ g_assert_nonnull(parser); ++ ++ uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; ++ /* Typical caps set by usbredirhost */ ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving); ++#if LIBUSBX_API_VERSION >= 0x01000103 ++ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); ++#endif ++ int parser_flags = usbredirparser_fl_usb_host; ++ ++ parser->log_func = log_cb; ++ usbredirparser_init(parser, ++ PACKAGE_STRING, ++ caps, ++ USB_REDIR_CAPS_SIZE, ++ parser_flags); ++ return parser; ++} ++ ++static void ++simple (gconstpointer user_data) ++{ ++ uint8_t *state = NULL; ++ int ret, len = -1; ++ ++ struct usbredirparser *source = get_usbredirparser(); ++ ret = usbredirparser_serialize(source, &state, &len); ++ g_assert_cmpint(ret, ==, 0); ++ ++ struct usbredirparser *target = get_usbredirparser(); ++ ret = usbredirparser_unserialize(target, state, len); ++ g_assert_cmpint(ret, ==, 0); ++ ++ g_clear_pointer(&state, free); ++ usbredirparser_destroy(source); ++ usbredirparser_destroy(target); ++} ++ ++int ++main(int argc, char **argv) ++{ ++ setlocale(LC_ALL, ""); ++ g_test_init(&argc, &argv, NULL); ++ ++ g_test_add_data_func("/serializer/serialize-and-unserialize", NULL, simple); ++ ++ return g_test_run(); ++} +diff --git a/usbredirparser/usbredirparser.c b/usbredirparser/usbredirparser.c +index cd1136b..a5dd0e7 100644 +--- a/usbredirparser/usbredirparser.c ++++ b/usbredirparser/usbredirparser.c +@@ -1816,9 +1816,7 @@ int usbredirparser_unserialize(struct usbredirparser *parser_pub, + return -1; + } + +- if (!(parser->write_buf_count == 0 && parser->write_buf == NULL && +- parser->write_buf_total_size == 0 && +- parser->data == NULL && parser->header_read == 0 && ++ if (!(parser->data == NULL && parser->header_read == 0 && + parser->type_header_read == 0 && parser->data_read == 0)) { + ERROR("unserialization must use a pristine parser"); + usbredirparser_assert_invariants(parser); +-- +2.37.1 + diff --git a/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch b/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch new file mode 100644 index 0000000..c46aa22 --- /dev/null +++ b/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch @@ -0,0 +1,63 @@ +From b93c4cae1aebda786a478677d6364308e4579ade Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Sat, 25 Jun 2022 00:29:12 +0200 +Subject: [PATCH 2/2] usbredirparser: reset parser's fields on unserialize +Content-type: text/plain + +This is a followup from previous commit and fixes the following leak. + + | 104 (24 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 15 of 19 + | at 0x484A464: calloc (vg_replace_malloc.c:1328) + | by 0x485A238: usbredirparser_queue (usbredirparser.c:1235) + | by 0x485A571: usbredirparser_init (usbredirparser.c:227) + | by 0x40130B: get_usbredirparser (serializer.c:77) + | by 0x401379: simple (serializer.c:95) + | by 0x48FA3DD: ??? (in /usr/lib64/libglib-2.0.so.0.7200.2) + | by 0x48FA144: ??? (in /usr/lib64/libglib-2.0.so.0.7200.2) + | by 0x48FA8E1: g_test_run_suite (in /usr/lib64/libglib-2.0.so.0.7200.2) + | by 0x48FA94C: g_test_run (in /usr/lib64/libglib-2.0.so.0.7200.2) + | by 0x401161: main (serializer.c:112) + | + | LEAK SUMMARY: + | definitely lost: 24 bytes in 1 blocks + | indirectly lost: 80 bytes in 1 blocks + | possibly lost: 0 bytes in 0 blocks + | still reachable: 25,500 bytes in 17 blocks + | suppressed: 0 bytes in 0 blocks + | Reachable blocks (those to which a pointer was found) are not shown. + | To see them, rerun with: --leak-check=full --show-leak-kinds=all + +Signed-off-by: Victor Toso +--- + usbredirparser/usbredirparser.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/usbredirparser/usbredirparser.c b/usbredirparser/usbredirparser.c +index a5dd0e7..9bfc27c 100644 +--- a/usbredirparser/usbredirparser.c ++++ b/usbredirparser/usbredirparser.c +@@ -1823,6 +1823,21 @@ int usbredirparser_unserialize(struct usbredirparser *parser_pub, + return -1; + } + ++ { ++ /* We need to reset parser's state to receive unserialized ++ * data. */ ++ struct usbredirparser_buf *wbuf = parser->write_buf; ++ while (wbuf) { ++ struct usbredirparser_buf *next_wbuf = wbuf->next; ++ free(wbuf->buf); ++ free(wbuf); ++ wbuf = next_wbuf; ++ } ++ parser->write_buf = NULL; ++ parser->write_buf_count = 0; ++ parser->write_buf_total_size = 0; ++ } ++ + if (unserialize_int(parser, &state, &remain, &i, "length")) { + usbredirparser_assert_invariants(parser); + return -1; +-- +2.37.1 + diff --git a/SOURCES/0003-Use-typedef-on-redirect-structure-to-simplify-some-s.patch b/SOURCES/0003-Use-typedef-on-redirect-structure-to-simplify-some-s.patch new file mode 100644 index 0000000..bffa129 --- /dev/null +++ b/SOURCES/0003-Use-typedef-on-redirect-structure-to-simplify-some-s.patch @@ -0,0 +1,135 @@ +From e30b0ce5c46c0e2a75b6e38759876ba1369b2168 Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Fri, 16 Sep 2022 20:14:28 +0100 +Subject: [PATCH 3/8] Use typedef on redirect structure to simplify some + statements + +Signed-off-by: Frediano Ziglio +--- + tools/usbredirect.c | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 98e5a8c..89a42a6 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -22,7 +22,7 @@ + #include + #endif + +-struct redirect { ++typedef struct redirect { + struct { + int vendor; + int product; +@@ -40,7 +40,7 @@ struct redirect { + int watch_server_id; + + GMainLoop *main_loop; +-}; ++} redirect; + + static bool + parse_opt_device(const char *device, int *vendor, int *product) +@@ -124,7 +124,7 @@ parse_opt_uri(const char *uri, char **adr, int *port) + return true; + } + +-static struct redirect * ++static redirect * + parse_opts(int *argc, char ***argv) + { + char *device = NULL; +@@ -132,7 +132,7 @@ parse_opts(int *argc, char ***argv) + char *localaddr = NULL; + gboolean keepalive = FALSE; + gint verbosity = 0; /* none */ +- struct redirect *self = NULL; ++ redirect *self = NULL; + + GOptionEntry entries[] = { + { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected", NULL }, +@@ -161,7 +161,7 @@ parse_opts(int *argc, char ***argv) + goto end; + } + +- self = g_new0(struct redirect, 1); ++ self = g_new0(redirect, 1); + if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); +@@ -201,7 +201,7 @@ end: + static gpointer + thread_handle_libusb_events(gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + + int res = 0; + const char *desc = ""; +@@ -279,7 +279,7 @@ usbredir_log_cb(void *priv, int level, const char *msg) + static int + usbredir_read_cb(void *priv, uint8_t *data, int count) + { +- struct redirect *self = (struct redirect *) priv; ++ redirect *self = (redirect *) priv; + GIOStream *iostream = G_IO_STREAM(self->connection); + GError *err = NULL; + +@@ -307,7 +307,7 @@ usbredir_read_cb(void *priv, uint8_t *data, int count) + static int + usbredir_write_cb(void *priv, uint8_t *data, int count) + { +- struct redirect *self = (struct redirect *) priv; ++ redirect *self = (redirect *) priv; + GIOStream *iostream = G_IO_STREAM(self->connection); + GError *err = NULL; + +@@ -335,7 +335,7 @@ usbredir_write_cb(void *priv, uint8_t *data, int count) + static void + usbredir_write_flush_cb(void *user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + if (!self || !self->usbredirhost) { + return; + } +@@ -386,7 +386,7 @@ usbredir_unlock_lock(void *user_data) + static gboolean + connection_handle_io_cb(GIOChannel *source, GIOCondition condition, gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + + if (condition & G_IO_ERR || condition & G_IO_HUP) { + g_warning("Connection: err=%d, hup=%d - exiting", (condition & G_IO_ERR), (condition & G_IO_HUP)); +@@ -418,7 +418,7 @@ end: + static gboolean + signal_handler(gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + g_main_loop_quit(self->main_loop); + return G_SOURCE_REMOVE; + } +@@ -430,7 +430,7 @@ connection_incoming_cb(GSocketService *service, + GObject *source_object, + gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + self->connection = g_object_ref(client_connection); + + /* Add a GSource watch to handle polling for us and handle IO in the callback */ +@@ -455,7 +455,7 @@ main(int argc, char *argv[]) + goto err_init; + } + +- struct redirect *self = parse_opts(&argc, &argv); ++ redirect *self = parse_opts(&argc, &argv); + if (!self) { + /* specific issues logged in parse_opts() */ + return 1; +-- +2.39.0 + diff --git a/SOURCES/0004-Factor-out-a-function-to-create-watches.patch b/SOURCES/0004-Factor-out-a-function-to-create-watches.patch new file mode 100644 index 0000000..e582e0c --- /dev/null +++ b/SOURCES/0004-Factor-out-a-function-to-create-watches.patch @@ -0,0 +1,74 @@ +From 5399d3d5ce420891adfd4beedb023515a28ceab1 Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Fri, 16 Sep 2022 20:14:28 +0100 +Subject: [PATCH 4/8] Factor out a function to create watches + +--- + tools/usbredirect.c | 37 ++++++++++++++++++++----------------- + 1 file changed, 20 insertions(+), 17 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 89a42a6..6a8b68b 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -414,6 +414,24 @@ end: + return G_SOURCE_REMOVE; + } + ++static void ++create_watch(redirect *self) ++{ ++ GSocket *socket = g_socket_connection_get_socket(self->connection); ++ int socket_fd = g_socket_get_fd(socket); ++ GIOChannel *io_channel = ++#ifdef G_OS_UNIX ++ g_io_channel_unix_new(socket_fd); ++#else ++ g_io_channel_win32_new_socket(socket_fd); ++#endif ++ ++ self->watch_server_id = g_io_add_watch(io_channel, ++ G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, ++ connection_handle_io_cb, ++ self); ++} ++ + #ifdef G_OS_UNIX + static gboolean + signal_handler(gpointer user_data) +@@ -436,12 +454,7 @@ connection_incoming_cb(GSocketService *service, + /* Add a GSource watch to handle polling for us and handle IO in the callback */ + GSocket *connection_socket = g_socket_connection_get_socket(self->connection); + g_socket_set_keepalive(connection_socket, self->keepalive); +- int socket_fd = g_socket_get_fd(connection_socket); +- GIOChannel *io_channel = g_io_channel_unix_new(socket_fd); +- self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, +- connection_handle_io_cb, +- self); ++ create_watch(self); + return G_SOURCE_REMOVE; + } + +@@ -551,17 +564,7 @@ main(int argc, char *argv[]) + + GSocket *connection_socket = g_socket_connection_get_socket(self->connection); + g_socket_set_keepalive(connection_socket, self->keepalive); +- int socket_fd = g_socket_get_fd(connection_socket); +- GIOChannel *io_channel = +-#ifdef G_OS_UNIX +- g_io_channel_unix_new(socket_fd); +-#else +- g_io_channel_win32_new_socket(socket_fd); +-#endif +- self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, +- connection_handle_io_cb, +- self); ++ create_watch(self); + } else { + GSocketService *socket_service; + +-- +2.39.0 + diff --git a/SOURCES/0005-Recreate-watch-if-needed.patch b/SOURCES/0005-Recreate-watch-if-needed.patch new file mode 100644 index 0000000..a527e52 --- /dev/null +++ b/SOURCES/0005-Recreate-watch-if-needed.patch @@ -0,0 +1,106 @@ +From f5e24c5cf77b92ab2737eb230db7ae0b2fb102cc Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Sat, 17 Sep 2022 09:28:08 +0100 +Subject: [PATCH 5/8] Recreate watch if needed + +Do not always watch for output buffer. +Watching for output buffer if we don't have nothing to write +(which is the usual case) is consuming a lot of CPU. + +This fixes https://gitlab.freedesktop.org/spice/usbredir/-/issues/24. + +Signed-off-by: Frediano Ziglio +--- + tools/usbredirect.c | 28 ++++++++++++++++++++++++++-- + 1 file changed, 26 insertions(+), 2 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 6a8b68b..b6a30ad 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -29,6 +29,7 @@ typedef struct redirect { + } device; + bool is_client; + bool keepalive; ++ bool watch_inout; + char *addr; + int port; + int verbosity; +@@ -42,6 +43,8 @@ typedef struct redirect { + GMainLoop *main_loop; + } redirect; + ++static void create_watch(redirect *self); ++ + static bool + parse_opt_device(const char *device, int *vendor, int *product) + { +@@ -162,6 +165,7 @@ parse_opts(int *argc, char ***argv) + } + + self = g_new0(redirect, 1); ++ self->watch_inout = true; + if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); +@@ -276,6 +280,20 @@ usbredir_log_cb(void *priv, int level, const char *msg) + g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", msg); + } + ++static void ++update_watch(redirect *self) ++{ ++ const bool watch_inout = usbredirhost_has_data_to_write(self->usbredirhost) != 0; ++ if (watch_inout == self->watch_inout) { ++ return; ++ } ++ g_source_remove(self->watch_server_id); ++ self->watch_server_id = 0; ++ self->watch_inout = watch_inout; ++ ++ create_watch(self); ++} ++ + static int + usbredir_read_cb(void *priv, uint8_t *data, int count) + { +@@ -321,6 +339,7 @@ usbredir_write_cb(void *priv, uint8_t *data, int count) + if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + /* Try again later */ + nbytes = 0; ++ update_watch(self); + } else { + if (err != NULL) { + g_warning("Failure at %s: %s", __func__, err->message); +@@ -400,13 +419,18 @@ connection_handle_io_cb(GIOChannel *source, GIOCondition condition, gpointer use + goto end; + } + } +- if (condition & G_IO_OUT) { ++ // try to write data in any case, to avoid having another iteration and ++ // creation of another watch if there is space in output buffer ++ if (usbredirhost_has_data_to_write(self->usbredirhost) != 0) { + int ret = usbredirhost_write_guest_data(self->usbredirhost); + if (ret < 0) { + g_critical("%s: Failed to write to guest", __func__); + goto end; + } + } ++ ++ // update the watch if needed ++ update_watch(self); + return G_SOURCE_CONTINUE; + + end: +@@ -427,7 +451,7 @@ create_watch(redirect *self) + #endif + + self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, ++ G_IO_IN | G_IO_HUP | G_IO_ERR | (self->watch_inout ? G_IO_OUT : 0), + connection_handle_io_cb, + self); + } +-- +2.39.0 + diff --git a/SOURCES/0006-Add-documentation-examples-for-using-bus-device-iden.patch b/SOURCES/0006-Add-documentation-examples-for-using-bus-device-iden.patch new file mode 100644 index 0000000..c7ec0d9 --- /dev/null +++ b/SOURCES/0006-Add-documentation-examples-for-using-bus-device-iden.patch @@ -0,0 +1,51 @@ +From b3482e8d8f8970d481b9bfdb6662a8b67a973537 Mon Sep 17 00:00:00 2001 +From: John Call +Date: Mon, 19 Dec 2022 19:01:50 +0000 +Subject: [PATCH 6/8] Add documentation examples for using bus-device + identification + +--- + tools/usbredirect.1 | 6 ++++++ + tools/usbredirect.c | 2 +- + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/tools/usbredirect.1 b/tools/usbredirect.1 +index d6b2a63..2be8be0 100644 +--- a/tools/usbredirect.1 ++++ b/tools/usbredirect.1 +@@ -4,6 +4,9 @@ usbredirect \- exporting an USB device for use from another (virtual) machine + .SH SYNOPSIS + .B usbredirect + [\fI--device vendor:product\fR] [\fI--to addr:port\fR] [\fI--as addr:port\fR] ++.br ++.B usbredirect ++[\fI--device bus-device\fR] [\fI--to addr:port\fR] [\fI--as addr:port\fR] + .SH DESCRIPTION + usbredirect is an usbredir client for exporting an USB device either as TCP + client or server, for use from another (virtual) machine through the usbredir +@@ -11,6 +14,9 @@ protocol. + .PP + You can specify the USB device to export by USB id in the form of + \fI:\fR. ++.br ++Or you can specify the USB device to export in the form of ++\fI-\fR. + .PP + Notice that an instance of usbredirect can only be used to export a single USB + device and it will close once the other side closes the connection. If you +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index b6a30ad..95f3580 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -138,7 +138,7 @@ parse_opts(int *argc, char ***argv) + redirect *self = NULL; + + GOptionEntry entries[] = { +- { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected", NULL }, ++ { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected identified as either VENDOR:PRODUCT \"0123:4567\" or BUS-DEVICE \"5-2\"", NULL }, + { "to", 0, 0, G_OPTION_ARG_STRING, &remoteaddr, "Client URI to connect to", NULL }, + { "as", 0, 0, G_OPTION_ARG_STRING, &localaddr, "Server URI to be run", NULL }, + { "keepalive", 'k', 0, G_OPTION_ARG_NONE, &keepalive, "If we should set SO_KEEPALIVE flag on underlying socket", NULL }, +-- +2.39.0 + diff --git a/SOURCES/0007-usbredirect-allow-multiple-devices-by-vendor-product.patch b/SOURCES/0007-usbredirect-allow-multiple-devices-by-vendor-product.patch new file mode 100644 index 0000000..6040de4 --- /dev/null +++ b/SOURCES/0007-usbredirect-allow-multiple-devices-by-vendor-product.patch @@ -0,0 +1,136 @@ +From 813afe206c8ff10cbbe715bf94980bf8ba6be70f Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Thu, 22 Dec 2022 16:13:08 +0100 +Subject: [PATCH 7/8] usbredirect: allow multiple devices by vendor:product + +Currently, if an user tries to redirect two devices with the same +vendor:product info, the second instance of usbredirect will not +succeed, leading to a segmentation fault. + +The core of the problem is that usbredirect is using +libusb_open_device_with_vid_pid() which always returns the first +instance of a given vendor:product, leading the second instance of +usbredirect to give an usb device that is in use to usbredirhost. + +This patch fixes the situation, making it possible to run usbredirect +with --device $vendor:$product multiple times. We do a early check +that we can claim the usb device, prior to handle it over to +usbredirhost. + +Related: https://gitlab.freedesktop.org/spice/usbredir/-/issues/29 +Signed-off-by: Victor Toso +--- + tools/usbredirect.c | 90 ++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 85 insertions(+), 5 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 95f3580..0b04418 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -466,6 +466,90 @@ signal_handler(gpointer user_data) + } + #endif + ++static bool ++can_claim_usb_device(libusb_device *dev, libusb_device_handle **handle) ++{ ++ int ret = libusb_open(dev, handle); ++ if (ret != 0) { ++ g_debug("Failed to open device"); ++ return false; ++ } ++ ++ /* Opening is not enough. We need to check if device can be claimed ++ * for I/O operations */ ++ struct libusb_config_descriptor *config = NULL; ++ ret = libusb_get_active_config_descriptor(dev, &config); ++ if (ret != 0 || config == NULL) { ++ g_debug("Failed to get active descriptor"); ++ *handle = NULL; ++ return false; ++ } ++ ++#if LIBUSBX_API_VERSION >= 0x01000102 ++ libusb_set_auto_detach_kernel_driver(*handle, 1); ++#endif ++ ++ int i; ++ for (i = 0; i < config->bNumInterfaces; i++) { ++ int interface_num = config->interface[i].altsetting[0].bInterfaceNumber; ++#if LIBUSBX_API_VERSION < 0x01000102 ++ ret = libusb_detach_kernel_driver(handle, interface_num); ++ if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND ++ && ret != LIBUSB_ERROR_NOT_SUPPORTED) { ++ g_error("failed to detach driver from interface %d: %s", ++ interface_num, libusb_error_name(ret)); ++ *handle = NULL; ++ break ++ } ++#endif ++ ret = libusb_claim_interface(*handle, interface_num); ++ if (ret != 0) { ++ g_debug("Could not claim interface"); ++ *handle = NULL; ++ break; ++ } ++ ret = libusb_release_interface(*handle, interface_num); ++ if (ret != 0) { ++ g_debug("Could not release interface"); ++ *handle = NULL; ++ break; ++ } ++ } ++ ++ libusb_free_config_descriptor(config); ++ return *handle != NULL; ++} ++ ++static libusb_device_handle * ++open_usb_device(redirect *self) ++{ ++ struct libusb_device **devs; ++ struct libusb_device_handle *dev_handle = NULL; ++ size_t i, ndevices; ++ ++ ndevices = libusb_get_device_list(NULL, &devs); ++ for (i = 0; i < ndevices; i++) { ++ struct libusb_device_descriptor desc; ++ if (libusb_get_device_descriptor(devs[i], &desc) != 0) { ++ g_warning("Failed to get descriptor"); ++ continue; ++ } ++ ++ if (self->device.vendor != desc.idVendor || ++ self->device.product != desc.idProduct) { ++ continue; ++ } ++ ++ if (can_claim_usb_device(devs[i], &dev_handle)) { ++ break; ++ } ++ } ++ ++ libusb_free_device_list(devs, 1); ++ return dev_handle; ++} ++ ++ + static gboolean + connection_incoming_cb(GSocketService *service, + GSocketConnection *client_connection, +@@ -515,11 +599,7 @@ main(int argc, char *argv[]) + g_unix_signal_add(SIGTERM, signal_handler, self); + #endif + +- /* This is binary is not meant to support plugins so it is safe to pass +- * NULL as libusb_context here and all subsequent calls */ +- libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(NULL, +- self->device.vendor, +- self->device.product); ++ libusb_device_handle *device_handle = open_usb_device(self); + if (!device_handle) { + g_printerr("Failed to open device!\n"); + goto err_init; +-- +2.39.0 + diff --git a/SOURCES/0008-usbredirect-use-the-correct-bus-device.patch b/SOURCES/0008-usbredirect-use-the-correct-bus-device.patch new file mode 100644 index 0000000..94ab888 --- /dev/null +++ b/SOURCES/0008-usbredirect-use-the-correct-bus-device.patch @@ -0,0 +1,153 @@ +From c32774c8a46aa04bf8f882ea552df8cb2d9f3a59 Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Thu, 22 Dec 2022 16:58:43 +0100 +Subject: [PATCH 8/8] usbredirect: use the correct bus-device + +This patch is a continuation from: +"usbredirect: allow multiple devices by vendor:product" + +As we were using libusb_open_device_with_vid_pid(), if an user +requested that device on 2-3 was redirected, we would instead get the +vendor and product info of device on 2-3 and use that info to pick a +usb device. This is wrong when multiple devices that shared +vendor:product are plugged as libbusb_open_device_with_vid_pid() +always return the same (first) device. + +This commit now stores bus-device info and uses that to pick the usb +device to redirect. + +Related: https://gitlab.freedesktop.org/spice/usbredir/-/issues/29 +Signed-off-by: Victor Toso +--- + tools/usbredirect.c | 63 ++++++++++++++++++++------------------------- + 1 file changed, 28 insertions(+), 35 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 0b04418..cad9d23 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -24,9 +24,14 @@ + + typedef struct redirect { + struct { ++ /* vendor:product */ + int vendor; + int product; ++ /* bus-device */ ++ int bus; ++ int device_number; + } device; ++ bool by_bus; + bool is_client; + bool keepalive; + bool watch_inout; +@@ -46,7 +51,7 @@ typedef struct redirect { + static void create_watch(redirect *self); + + static bool +-parse_opt_device(const char *device, int *vendor, int *product) ++parse_opt_device(redirect *self, const char *device) + { + if (!device) { + g_warning("No device to redirect. For testing only\n"); +@@ -54,37 +59,15 @@ parse_opt_device(const char *device, int *vendor, int *product) + } + + if (g_strrstr(device, "-") != NULL) { +- /* Get vendor and product by bus and address number */ ++ self->by_bus = true; + char **usbid = g_strsplit(device, "-", 2); + if (usbid == NULL || usbid[0] == NULL || usbid[1] == NULL || usbid[2] != NULL) { + g_strfreev(usbid); + return false; + } +- gint64 bus = g_ascii_strtoll(usbid[0], NULL, 10); +- gint64 addr = g_ascii_strtoll(usbid[1], NULL, 10); +- +- libusb_device **list = NULL; +- ssize_t i, n; +- +- n = libusb_get_device_list(NULL, &list); +- for (i = 0; i < n; i++) { +- if (libusb_get_bus_number(list[i]) == bus && +- libusb_get_device_address(list[i]) == addr) { +- break; +- } +- } +- +- if (i == n) { +- libusb_free_device_list(list, true); +- return false; +- } +- +- struct libusb_device_descriptor desc; +- libusb_get_device_descriptor(list[i], &desc); +- *vendor = desc.idVendor; +- *product = desc.idProduct; +- +- libusb_free_device_list(list, true); ++ self->device.bus = g_ascii_strtoll(usbid[0], NULL, 10); ++ self->device.device_number = g_ascii_strtoll(usbid[1], NULL, 10); ++ g_strfreev(usbid); + return true; + } + +@@ -94,12 +77,14 @@ parse_opt_device(const char *device, int *vendor, int *product) + return false; + } + +- *vendor = g_ascii_strtoll(usbid[0], NULL, 16); +- *product = g_ascii_strtoll(usbid[1], NULL, 16); ++ self->device.vendor = g_ascii_strtoll(usbid[0], NULL, 16); ++ self->device.product = g_ascii_strtoll(usbid[1], NULL, 16); + g_strfreev(usbid); + +- if (*vendor <= 0 || *vendor > 0xffff || *product < 0 || *product > 0xffff) { +- g_printerr("Bad vendor:product values %04x:%04x", *vendor, *product); ++ if (self->device.vendor <= 0 || self->device.vendor > 0xffff || ++ self->device.product < 0 || self->device.product > 0xffff) { ++ g_printerr("Bad vendor:product values %04x:%04x", ++ self->device.vendor, self->device.product); + return false; + } + +@@ -166,7 +151,7 @@ parse_opts(int *argc, char ***argv) + + self = g_new0(redirect, 1); + self->watch_inout = true; +- if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { ++ if (!parse_opt_device(self, device)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); + goto end; +@@ -535,9 +520,16 @@ open_usb_device(redirect *self) + continue; + } + +- if (self->device.vendor != desc.idVendor || +- self->device.product != desc.idProduct) { +- continue; ++ if (self->by_bus && ++ (self->device.bus != libusb_get_bus_number(devs[i]) || ++ self->device.device_number != libusb_get_device_address(devs[i]))) { ++ continue; ++ } ++ ++ if (!self->by_bus && ++ (self->device.vendor != desc.idVendor || ++ self->device.product != desc.idProduct)) { ++ continue; + } + + if (can_claim_usb_device(devs[i], &dev_handle)) { +@@ -674,6 +666,7 @@ main(int argc, char *argv[]) + + socket_service = g_socket_service_new (); + GInetAddress *iaddr = g_inet_address_new_loopback(G_SOCKET_FAMILY_IPV4); ++ + GSocketAddress *saddr = g_inet_socket_address_new(iaddr, self->port); + g_object_unref(iaddr); + +-- +2.39.0 + diff --git a/SPECS/usbredir.spec b/SPECS/usbredir.spec new file mode 100644 index 0000000..c3ab1ee --- /dev/null +++ b/SPECS/usbredir.spec @@ -0,0 +1,207 @@ +Name: usbredir +Version: 0.12.0 +Release: 4%{?dist} +Summary: USB network redirection protocol libraries +Group: System Environment/Libraries +License: LGPLv2+ +URL: https://www.spice-space.org/usbredir.html +Source0: http://spice-space.org/download/%{name}/%{name}-%{version}.tar.xz +Patch0001: 0001-usbredirparser-Fix-unserialize-on-pristine-check.patch +Patch0002: 0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch +Patch0003: 0003-Use-typedef-on-redirect-structure-to-simplify-some-s.patch +Patch0004: 0004-Factor-out-a-function-to-create-watches.patch +Patch0005: 0005-Recreate-watch-if-needed.patch +Patch0006: 0006-Add-documentation-examples-for-using-bus-device-iden.patch +Patch0007: 0007-usbredirect-allow-multiple-devices-by-vendor-product.patch +Patch0008: 0008-usbredirect-use-the-correct-bus-device.patch +BuildRequires: glib2-devel +BuildRequires: libusb1-devel >= 1.0.9 +BuildRequires: git-core +BuildRequires: meson + +%description +The usbredir libraries allow USB devices to be used on remote and/or virtual +hosts over TCP. The following libraries are provided: + +usbredirparser: +A library containing the parser for the usbredir protocol + +usbredirhost: +A library implementing the USB host side of a usbredir connection. +All that an application wishing to implement a USB host needs to do is: +* Provide a libusb device handle for the device +* Provide write and read callbacks for the actual transport of usbredir data +* Monitor for usbredir and libusb read/write events and call their handlers + + +%package devel +Summary: Development files for %{name} +Group: Development/Libraries +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + + +%package server +Summary: Simple USB host TCP server +Group: System Environment/Daemons +License: GPLv2+ +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description server +A simple USB host TCP server, using libusbredirhost. + + +%prep +%autosetup -S git_am + + +%build +%meson \ + -Dgit_werror=disabled \ + -Dtools=enabled \ + -Dfuzzing=disabled + +%meson_build + + +%install +%meson_install + + +%ldconfig_scriptlets + + +%files +%{!?_licensedir:%global license %%doc} +%license COPYING.LIB +%{_libdir}/libusbredir*.so.* + +%files devel +%doc docs/usb-redirection-protocol.md docs/multi-thread.md ChangeLog.md TODO +%{_includedir}/usbredir*.h +%{_libdir}/libusbredir*.so +%{_libdir}/pkgconfig/libusbredir*.pc + +%files server +%{!?_licensedir:%global license %%doc} +%license COPYING +%{_bindir}/usbredirect +%{_sbindir}/usbredirserver +%{_mandir}/man1/usbredirect.1* +%{_mandir}/man1/usbredirserver.1* + + +%changelog +* Wed Jan 25 2023 Victor Toso - 0.12.0-4 +- Rebuild to fix lack of elf sections + Related: rhbz#2157521 + +* Thu Jan 05 2023 Victor Toso - 0.12.0-3 +- Fixes 100% CPU usage when usbredirect used as TCP server. + Related: rhbz#2157521 +- Fixes USB redirection of identical devices. + Resolves: rhbz#2157521 + +* Wed Jul 27 2022 Victor Toso - 0.12.0-2 +- Fixes unserialization on migration + Resolves: rhbz#2111351 + +* Fri Nov 12 2021 Victor Toso - 0.12.0-1 +- Update to 0.12.0 release +- Resolves: rhbz#2022751 + +* Thu Aug 23 2018 Victor Toso - 0.8.0-1 +- Update to 0.8.0 release +- Resolves: rhbz#1620098 + +* Fri Feb 09 2018 Fedora Release Engineering - 0.7.1-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Feb 05 2018 Igor Gnatenko - 0.7.1-6 +- Switch to %%ldconfig_scriptlets + +* Thu Aug 03 2017 Fedora Release Engineering - 0.7.1-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Thu Jul 27 2017 Fedora Release Engineering - 0.7.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Sat Feb 11 2017 Fedora Release Engineering - 0.7.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Fri Feb 05 2016 Fedora Release Engineering - 0.7.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Mon Nov 02 2015 Fabiano FidĂȘncio 0.7.1-1 +- Update to upstream 0.7.1 release + +* Tue Jun 16 2015 Peter Robinson 0.7-4 +- Use %%license + +* Mon Aug 18 2014 Fedora Release Engineering - 0.7-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sun Jun 08 2014 Fedora Release Engineering - 0.7-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Wed May 21 2014 Hans de Goede - 0.7-1 +- Update to upstream 0.7 release + +* Tue Sep 10 2013 Hans de Goede - 0.6-5 +- Use the new libusb autodetach kernel driver functionality +- Fix a usbredirparser bug which causes tcp/ip redir to not work (rhbz#1005015) + +* Sun Aug 04 2013 Fedora Release Engineering - 0.6-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Mon May 13 2013 Hans de Goede - 0.6-3 +- Fix usbredirserver not listening for ipv6 connections (rhbz#957470) +- Fix a few (harmless) coverity warnings + +* Fri Feb 15 2013 Fedora Release Engineering - 0.6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Thu Dec 13 2012 Hans de Goede - 0.6-1 +- Update to upstream 0.6 release + +* Tue Sep 25 2012 Hans de Goede - 0.5.2-1 +- Update to upstream 0.5.2 release + +* Wed Sep 19 2012 Hans de Goede - 0.5.1-1 +- Update to upstream 0.5.1 release + +* Fri Sep 7 2012 Hans de Goede - 0.5-1 +- Update to upstream 0.5 release + +* Mon Jul 30 2012 Hans de Goede - 0.4.3-3 +- Add 2 fixes from upstream fixing issues with some bulk devices (rhbz#842358) + +* Sun Jul 22 2012 Fedora Release Engineering - 0.4.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Mon Apr 2 2012 Hans de Goede - 0.4.3-1 +- Update to upstream 0.4.3 release + +* Tue Mar 6 2012 Hans de Goede - 0.4.2-1 +- Update to upstream 0.4.2 release + +* Sat Feb 25 2012 Hans de Goede - 0.4.1-1 +- Update to upstream 0.4.1 release + +* Thu Feb 23 2012 Hans de Goede - 0.4-1 +- Update to upstream 0.4 release + +* Thu Jan 12 2012 Hans de Goede - 0.3.3-1 +- Update to upstream 0.3.3 release + +* Tue Jan 3 2012 Hans de Goede 0.3.2-1 +- Update to upstream 0.3.2 release + +* Wed Aug 24 2011 Hans de Goede 0.3.1-1 +- Update to upstream 0.3.1 release + +* Thu Jul 14 2011 Hans de Goede 0.3-1 +- Initial Fedora package