From nobody Sat Jul 25 15:28:32 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 38.145.34.151 as permitted sender) client-ip=38.145.34.151; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.libvirt.org designates 38.145.34.151 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1784275706; cv=none; d=zohomail.com; s=zohoarc; b=HEI8GkP4YvrKq0K52mh9vYsYs94nbYhr9HOM+9gi51z/PeI+Dc5ltC+0whtPiVj1aNwdIXhPnaPnpRJwFLZ4xkAs/xM4aK/aw90/Rqt3bK+aENOcA3LsYkOqnlRS3mYoQQg4eeAC5BwXxsyhamUBDhhWhymqmppR43Rs8TbTA0g= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1784275706; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:List-Subscribe:List-Post:List-Owner:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=oCsHrenZ9vZnxHnieFVDGFMbkc2sgOe0ZOfaFSYL99I=; b=IZxjGv5Avf4wWdNR2cVQ0687jq0EJiRHXQewBCUcFdexfM2yhQckRHc2u3WwsK6oUeLffATxRcslRMAxDWgOdU9TGxaYPLud8BsXLyxP3nR6fKnUn4hil6cawKoOYmOGijkN83QEVoPW2V+8NxmeEZ8v+zyDbRAV2ENk84vJXbg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.libvirt.org designates 38.145.34.151 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [38.145.34.151]) by mx.zohomail.com with SMTPS id 1784275706892252.92423505107354; Fri, 17 Jul 2026 01:08:26 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 993) id 7B27E3FA60; Fri, 17 Jul 2026 04:08:25 -0400 (EDT) Received: from [172.19.199.10] (unknown [10.16.107.18]) by lists.libvirt.org (Postfix) with ESMTP id 968D941E47; Fri, 17 Jul 2026 04:05:04 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 993) id E507441AC8; Thu, 16 Jul 2026 17:33:09 -0400 (EDT) Received: from mail-lj1-x229.google.com (mail-lj1-x229.google.com [IPv6:2a00:1450:4864:20::229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 9F36241908 for ; Thu, 16 Jul 2026 17:33:08 -0400 (EDT) Received: by mail-lj1-x229.google.com with SMTP id 38308e7fff4ca-39da69c5ba3so31708731fa.1 for ; Thu, 16 Jul 2026 14:33:08 -0700 (PDT) Received: from lenovo-legion5 (pppoe-77.220.51.223.ttel.ru. [77.220.51.223]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-39c84ba1b9dsm48494991fa.35.2026.07.16.14.33.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 Jul 2026 14:33:05 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-2.7 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784237587; x=1784842387; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=oCsHrenZ9vZnxHnieFVDGFMbkc2sgOe0ZOfaFSYL99I=; b=Fyktf1UXKANJjT8QrCdX5x1RVlUsOOptZj1eCHHXBnAKaGKnIlLfvDpo188XqbP7Is lBCW85W9wiGTmg/MJZ91JvN2DatMbJn042XeKR4a3zX+5bzNKt5QlhAUoMN0GWdknK7N MT4YkGNbCpk4zmMfj7kET0aWU6BH2WDdzhuetJkNJ0U3oBdZ0X4jvBQHHBTqOBvk3L64 J0gwbqvEDiMJfg9K1Kt26LRvAbzqdvpLeMOrTZqh/vEnUchxV9SFSfhuQffT/U1O0li9 E+YqlKTgwZxBetWc2F6X1X04wLnoGRZ1rYHE+2XMwO6Bv60zKddz+zP6svkTYlzVO9eK ySFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784237587; x=1784842387; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=oCsHrenZ9vZnxHnieFVDGFMbkc2sgOe0ZOfaFSYL99I=; b=sJ9mCBcpZQCpX72tCfJHGJOWVftq1gF0Ct28W9VP+cujicjULWmN2LWmKJKLDXwju6 VB98HH3ZWoLRNIoi7ETHzbpWF9z+DfXalKNui4YntWB1axZTRDv6VFJXqUrdeqk5m2lR XPRRa618iqS0L+Qse0Wcqc/2ardNnUsaxp4obCYKfqercocr89szKyQfHqB9Fb0C3wNM Uy+lrJzM1G1xZInnXR3EV8rKekm/RqmNZ12EpMSer5z3mikOwksG5GTaWkNyBvGgZhCl dBsMVI+DQ1uYNLDnilLR5Jo0/kDy63oIc5B6ST0m4mok2aKRghLNZEG0bGOXeX3ONxrN HjGg== X-Gm-Message-State: AOJu0YwKgnBRF1shgVh/FiAxzQ56qEo5SySiuFfo0tJKY0l1PFZ4KYRH LysHVVU7e+OrHGBlFw2ODIBQqyqNq/fY7jsO1DOOJobg7rlpeptE/rJt9aRu4w== X-Gm-Gg: AfdE7cn1owf3Bdv2NHk8lVOJpHIaVv2gwWPqvbbRKwfVChSWkZFaXRplpYc/9vwbebj ahs7TjVq2pl2zF7hhm21lG+QrsFs3s3UGIPm5KJSGAncMvG6PHNjyD+rsAKhu8QZtfs4ubxZ1qQ BkRq3KLJu/UFWDV6rL9qJ5PD6zg83g1/NEUNAo/LAa/5JQXKSZmSA7QiOiMaTe23pnTW5XGF5kR EjJGI0UaelAZ8JpCjmUJPm6ngqqIIyhWYz5mBqwrGOO8bRHTxovK5Dwdr8ckoFmuM/0Jmc+Xc+P 3XC6fRx3yu4Xzv9Z4nENPSJzzfrFppXBVWTHDmN50pnemHgM8H3dOoQCMzguNuKzlGkOlxRTlU1 gsWVqRZyHh9eaBB6eEK5MfE+jekqiwNUxmrMShEG8L8qxo5dnl1ZwcwqaEq/g6LxjY3w93Jh9tl RLtOoPOcY2kDQKOnaW27PTslXdr87Yij4wa5s= X-Received: by 2002:a05:651c:3134:b0:39c:7089:8052 with SMTP id 38308e7fff4ca-39eaf9cde0fmr1637631fa.19.1784237586744; Thu, 16 Jul 2026 14:33:06 -0700 (PDT) From: Yaroslav Borbat To: devel@lists.libvirt.org Subject: [PATCH] util: virusb: skip USB devices with an inaccessible device node Date: Fri, 17 Jul 2026 00:32:49 +0300 Message-ID: <20260716213249.3163905-1-yaroslav.752@gmail.com> X-Mailer: git-send-email 2.54.0 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-MailFrom: yaroslav.752@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.lists.libvirt.org-0; emergency; member-moderation Message-ID-Hash: EGHT637EW4PP4XM4Z5E256SIUJ4H3QQT X-Message-ID-Hash: EGHT637EW4PP4XM4Z5E256SIUJ4H3QQT X-Mailman-Approved-At: Fri, 17 Jul 2026 08:04:52 +0000 CC: Yaroslav Borbat X-Mailman-Version: 3.3.10 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1784275708059158500 Content-Type: text/plain; charset="utf-8" From: Yaroslav Borbat Inside a container sysfs is usually mounted from the host and exposes all USB devices, while nodes under /dev/bus/usb/ only exist for devices passed into the container's mount namespace. virUSBDeviceSearch() enumerated devices from sysfs alone, so such a device was reported as present: a hostdev with startupPolicy=3D'optional' was not dropped and the domain failed to start. Skip devices whose /dev node is inaccessible during enumeration. virUSBDeviceFind() now ignores them for optional hostdevs and reports an explicit "not accessible in the current mount namespace" error for mandatory ones. Extend the virusb test to cover both cases. Closes: libvirt/libvirt#894 Signed-off-by: Yaroslav Borbat --- src/util/virusb.c | 41 ++++++++++++++++++++++++++++++++++++----- tests/virusbmock.c | 33 ++++++++++++++++++++++++++++++++- tests/virusbtest.c | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/util/virusb.c b/src/util/virusb.c index 85ad3d58ce..1d31912971 100644 --- a/src/util/virusb.c +++ b/src/util/virusb.c @@ -120,7 +120,8 @@ virUSBDeviceSearch(unsigned int vendor, unsigned int devno, const char *port, const char *vroot, - unsigned int flags) + unsigned int flags, + char **inaccessiblePath) { g_autoptr(DIR) dir =3D NULL; bool found =3D false; @@ -198,6 +199,28 @@ virUSBDeviceSearch(unsigned int vendor, if (!usb) goto cleanup; =20 + /* In containerized environments sysfs may expose USB devices + * from the host kernel while the corresponding device node + * under /dev/bus/usb/ is not present in the current mount + * namespace. Such a device cannot be used, so skip it here. + * Remember its path so that the caller can distinguish "no + * matching device at all" from "device present on the host but + * inaccessible in our mount namespace" when reporting errors. */ + if (virUSBDeviceGetPath(usb) && + !virFileExists(virUSBDeviceGetPath(usb))) { + VIR_DEBUG("USB device %03u:%03u found in sysfs but device " + "node '%s' is not accessible in the current mount " + "namespace, skipping", + found_bus, found_devno, + virUSBDeviceGetPath(usb)); + if (inaccessiblePath && !*inaccessiblePath) + *inaccessiblePath =3D g_strdup(virUSBDeviceGetPath(usb)); + g_clear_pointer(&usb, virUSBDeviceFree); + if (found) + break; + continue; + } + if (virUSBDeviceListAdd(list, &usb) < 0) goto cleanup; =20 @@ -226,10 +249,11 @@ virUSBDeviceFind(unsigned int vendor, virUSBDeviceList **devices) { g_autoptr(virUSBDeviceList) list =3D NULL; + g_autofree char *inaccessiblePath =3D NULL; int count; =20 if (!(list =3D virUSBDeviceSearch(vendor, product, bus, devno, port, - vroot, flags))) + vroot, flags, &inaccessiblePath))) return -1; =20 count =3D list->count; @@ -240,9 +264,16 @@ virUSBDeviceFind(unsigned int vendor, return 0; } =20 - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Did not find matching USB device: vid:%1$04x, pi= d:%2$04x, bus:%3$u, device:%4$u, port:%5$s"), - vendor, product, bus, devno, port ? port : ""); + if (inaccessiblePath) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("USB device vid:%1$04x, pid:%2$04x, bus:%3$u,= device:%4$u, port:%5$s is present on the host but its device node '%6$s' i= s not accessible in the current mount namespace"), + vendor, product, bus, devno, port ? port : "", + inaccessiblePath); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Did not find matching USB device: vid:%1$04x= , pid:%2$04x, bus:%3$u, device:%4$u, port:%5$s"), + vendor, product, bus, devno, port ? port : ""); + } return -1; } =20 diff --git a/tests/virusbmock.c b/tests/virusbmock.c index c23bed4528..c67a0ba307 100644 --- a/tests/virusbmock.c +++ b/tests/virusbmock.c @@ -22,18 +22,27 @@ #include #include #include +#include #include +#include =20 #include "virmock.h" =20 #define USB_SYSFS "/sys/bus/usb" #define FAKE_USB_SYSFS "virusbtestdata/sys_bus_usb" +#define USB_DEVFS "/dev/bus/usb/" + +/* Device node of the test device that is present in the fake sysfs but is + * meant to be inaccessible in the current mount namespace (usb4, i.e. the + * root hub 1d6b:0003 on bus 4). See virusbtest.c. */ +#define INACCESSIBLE_USB_NODE USB_DEVFS "004/001" =20 static int (*real_open)(const char *pathname, int flags, ...); #if WITH___OPEN_2 static int (*real___open_2)(const char *path, int flags); #endif static DIR *(*real_opendir)(const char *name); +static int (*real_access)(const char *path, int mode); =20 static void init_syms(void) { @@ -49,6 +58,7 @@ static void init_syms(void) VIR_MOCK_REAL_INIT(__open_2); #endif VIR_MOCK_REAL_INIT(opendir); + VIR_MOCK_REAL_INIT(access); } =20 static char *get_fake_path(const char *real_path) @@ -58,7 +68,7 @@ static char *get_fake_path(const char *real_path) =20 if ((p =3D STRSKIP(real_path, USB_SYSFS))) path =3D g_strdup_printf("%s/%s/%s", abs_srcdir, FAKE_USB_SYSFS, p= ); - else if (!p) + else path =3D g_strdup(real_path); =20 return path; @@ -75,6 +85,27 @@ DIR *opendir(const char *name) return real_opendir(path); } =20 +int access(const char *path, int mode) +{ + init_syms(); + + /* virUSBDeviceSearch() checks whether the /dev/bus/usb node of a devi= ce + * found in sysfs is accessible. There are no such nodes in the test + * environment, so pretend every USB device node exists, except the one + * device that is meant to be inaccessible in the current mount namesp= ace + * (as happens inside a container). This lets us exercise the code pa= th + * without shipping empty device-node files. */ + if (STRPREFIX(path, USB_DEVFS)) { + if (STREQ(path, INACCESSIBLE_USB_NODE)) { + errno =3D ENOENT; + return -1; + } + return 0; + } + + return real_access(path, mode); +} + int open(const char *pathname, int flags, ...) { g_autofree char *path =3D NULL; diff --git a/tests/virusbtest.c b/tests/virusbtest.c index 12ac338df9..58ca769059 100644 --- a/tests/virusbtest.c +++ b/tests/virusbtest.c @@ -259,6 +259,40 @@ testUSBList(const void *opaque G_GNUC_UNUSED) } =20 =20 +/* usb4 (bus 4, device 1) is present in the fake sysfs, but the mock makes + * its /dev/bus/usb node inaccessible, i.e. the device is exposed by the h= ost + * kernel but not accessible in our mount namespace (as happens inside a + * container). Such a device must be treated as absent: skipped for + * startupPolicy 'optional' (mandatory =3D=3D false) and reported as an er= ror + * otherwise. */ +static int +testDeviceFindInaccessible(const void *opaque G_GNUC_UNUSED) +{ + g_autoptr(virUSBDeviceList) devs =3D NULL; + int rv; + + rv =3D virUSBDeviceFind(0x1d6b, 0x0003, 4, 1, NULL, NULL, false, + USB_DEVICE_FIND_BY_DEVICE, &devs); + if (rv !=3D 0 || devs) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "optional inaccessible device: expected no match " + "(rv=3D0 and no device list), got rv=3D%d", rv); + return -1; + } + + rv =3D virUSBDeviceFind(0x1d6b, 0x0003, 4, 1, NULL, NULL, true, + USB_DEVICE_FIND_BY_DEVICE, &devs); + if (rv >=3D 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "mandatory inaccessible device: expected failure, " + "but the device was reported as available"); + return -1; + } + + return 0; +} + + static int mymain(void) { @@ -336,6 +370,10 @@ mymain(void) if (virTestRun("USB List test", testUSBList, NULL) < 0) rv =3D -1; =20 + if (virTestRun("USBDeviceFind inaccessible node", + testDeviceFindInaccessible, NULL) < 0) + rv =3D -1; + if (rv < 0) return EXIT_FAILURE; return EXIT_SUCCESS; --=20 2.54.0