git: 749318f2ae56 - main - libusb: capsicumize libusb

From: ShengYi Hung <aokblast_at_FreeBSD.org>
Date: Fri, 14 Aug 2026 15:09:22 UTC
The branch main has been updated by aokblast:

URL: https://cgit.FreeBSD.org/src/commit/?id=749318f2ae56127abdffee405f8f4658efa1c807

commit 749318f2ae56127abdffee405f8f4658efa1c807
Author:     ShengYi Hung <aokblast@FreeBSD.org>
AuthorDate: 2025-08-11 15:05:24 +0000
Commit:     ShengYi Hung <aokblast@FreeBSD.org>
CommitDate: 2026-08-14 15:09:08 +0000

    libusb: capsicumize libusb
    
    FreeBSD's libusb has three components: libusb01, libusb10, and libusb20.
    
    libusb20 handles communication with character devices. We now requires a
    backend context for libusb20. The backend context contains contains the
    capsicumized usbctrl fd and usb directory (/dev/usb) fd so that the
    library user can enter the capiblity mode safely while using libusb.
    
    libusb10 is updated to support capabilities via a context option. Since
    libusb allows general read/write access, we preserve all possible
    capabilities when passing backend context to libusb20. It is the
    responsibility of the libusb user to call cap_enter() at an appropriate
    time.
    
    All base system tools using libusb and libusb20 have been updated to
    support Capsicum.
    
    Reviewed by:    adrian, markj
    Sponsored by: The FreeBSD Foundation
    Differential Revision: https://reviews.freebsd.org/D51865
---
 lib/libusb/Symbol.map                   |  13 ++--
 lib/libusb/libusb.3                     |  17 +++++
 lib/libusb/libusb01.c                   |  15 +++-
 lib/libusb/libusb10.c                   |  18 ++++-
 lib/libusb/libusb10.h                   |   3 +
 lib/libusb/libusb20.3                   |  65 ++++++++++++++++-
 lib/libusb/libusb20.c                   |  29 ++++++--
 lib/libusb/libusb20.h                   |  13 ++--
 lib/libusb/libusb20_be_device_foreach.3 |   2 +-
 lib/libusb/libusb20_dev_open.3          |   2 +-
 lib/libusb/libusb20_int.h               |  22 ++++++
 lib/libusb/libusb20_ugen20.c            | 124 ++++++++++++++++++++++++++------
 share/examples/libusb20/bulk.c          |   9 ++-
 share/examples/libusb20/control.c       |   9 ++-
 tools/tools/usbtest/usb_msc_test.c      |   4 +-
 tools/tools/usbtest/usbtest.c           |  19 +++++
 tools/tools/usbtest/usbtest.h           |   3 +
 usr.sbin/usbconfig/dump.c               |  64 ++++++-----------
 usr.sbin/usbconfig/dump.h               |   1 +
 usr.sbin/usbconfig/usbconfig.c          |  13 +++-
 20 files changed, 354 insertions(+), 91 deletions(-)

diff --git a/lib/libusb/Symbol.map b/lib/libusb/Symbol.map
index 46bd1d8aeace..310bb4c35a21 100644
--- a/lib/libusb/Symbol.map
+++ b/lib/libusb/Symbol.map
@@ -120,10 +120,6 @@ FBSD_1.8 {
 	libusb_wait_for_event;
 	libusb_wrap_sys_device;
 	libusb20_be_add_dev_quirk;
-	libusb20_be_alloc;
-	libusb20_be_alloc_default;
-	libusb20_be_alloc_linux;
-	libusb20_be_alloc_ugen20;
 	libusb20_be_dequeue_device;
 	libusb20_be_device_foreach;
 	libusb20_be_enqueue_device;
@@ -258,3 +254,12 @@ FBSD_1.8 {
 	usb_set_debug;
 	usb_strerror;
 };
+
+FBSD_1.9 {
+	libusb20_be_alloc;
+	libusb20_be_alloc_default;
+	libusb20_be_alloc_linux;
+	libusb20_be_alloc_ugen20;
+	libusb20_be_ctx_alloc;
+	libusb20_be_ctx_free;
+};
diff --git a/lib/libusb/libusb.3 b/lib/libusb/libusb.3
index 42b2e90f148e..a946e998df9e 100644
--- a/lib/libusb/libusb.3
+++ b/lib/libusb/libusb.3
@@ -945,8 +945,25 @@ The library is also compliant with LibUSB version 0.1.12.
 .Fn usb_get_driver_np
 .Fn usb_detach_kernel_driver_np
 .Fn usb_attach_kernel_driver_np
+.Sh CAPABILITY MODE
+Every
+.Nm
+context opens the file descriptors needed to reach the USB devices at
+.Fn libusb_init
+time and opens device nodes relative to a per-context directory
+descriptor using
+.Xr openat 2 .
+All operations on the context, including enumeration of hotplugged
+devices, therefore keep working after entering capability mode, see
+.Xr capsicum 4 .
+The file descriptors are limited with
+.Xr cap_rights_limit 2 .
+Initialise the context before calling
+.Xr cap_enter 2 .
 .Sh SEE ALSO
+.Xr cap_enter 2 ,
 .Xr libusb20 3 ,
+.Xr capsicum 4 ,
 .Xr usb 4 ,
 .Xr usbconfig 8 ,
 .Xr usbdump 8
diff --git a/lib/libusb/libusb01.c b/lib/libusb/libusb01.c
index 84367765815a..865c115efd68 100644
--- a/lib/libusb/libusb01.c
+++ b/lib/libusb/libusb01.c
@@ -85,6 +85,13 @@ static struct usb_bus usb_global_bus = {
 
 static struct libusb20_backend *usb_backend = NULL;
 
+/*
+ * The backend context lives for the lifetime of the process, because
+ * opened devices are dequeued from the backend and survive backend
+ * rescans. The libusb v0.1 API has no exit function anyway.
+ */
+static struct libusb20_be_ctx *usb_be_ctx = NULL;
+
 struct usb_parse_state {
 
 	struct {
@@ -901,8 +908,14 @@ usb_find_devices(void)
 
 	libusb20_be_free(usb_backend);
 
+	if (usb_be_ctx == NULL) {
+		usb_be_ctx = libusb20_be_ctx_alloc();
+		if (usb_be_ctx == NULL) {
+			return (-1);
+		}
+	}
 	/* do a new backend device search */
-	usb_backend = libusb20_be_alloc_default();
+	usb_backend = libusb20_be_alloc_default(usb_be_ctx);
 	if (usb_backend == NULL) {
 		return (-1);
 	}
diff --git a/lib/libusb/libusb10.c b/lib/libusb/libusb10.c
index f47b301ea302..2fc1dfe71f25 100644
--- a/lib/libusb/libusb10.c
+++ b/lib/libusb/libusb10.c
@@ -302,6 +302,21 @@ libusb_init_context(libusb_context **context,
 		return (LIBUSB_ERROR_OTHER);
 	}
 
+	/*
+	 * The backend context acquires the file descriptors needed to
+	 * reach the USB devices, so that the context keeps working
+	 * after cap_enter(2) has been called.
+	 */
+	ctx->be_ctx = libusb20_be_ctx_alloc();
+	if (ctx->be_ctx == NULL) {
+		close(ctx->event);
+		pthread_mutex_destroy(&ctx->ctx_lock);
+		pthread_mutex_destroy(&ctx->hotplug_lock);
+		pthread_cond_destroy(&ctx->ctx_cond);
+		free(ctx);
+		return (LIBUSB_ERROR_NO_MEM);
+	}
+
 	libusb10_add_pollfd(ctx, &ctx->ctx_poll, NULL, ctx->event, POLLIN);
 
 	pthread_mutex_lock(&default_context_lock);
@@ -353,6 +368,7 @@ libusb_exit(libusb_context *ctx)
 
 	libusb10_remove_pollfd(ctx, &ctx->ctx_poll);
 	close(ctx->event);
+	libusb20_be_ctx_free(ctx->be_ctx);
 	pthread_mutex_destroy(&ctx->ctx_lock);
 	pthread_mutex_destroy(&ctx->hotplug_lock);
 	pthread_cond_destroy(&ctx->ctx_cond);
@@ -384,7 +400,7 @@ libusb_get_device_list(libusb_context *ctx, libusb_device ***list)
 	if (list == NULL)
 		return (LIBUSB_ERROR_INVALID_PARAM);
 
-	usb_backend = libusb20_be_alloc_default();
+	usb_backend = libusb20_be_alloc_default(ctx->be_ctx);
 	if (usb_backend == NULL)
 		return (LIBUSB_ERROR_NO_MEM);
 
diff --git a/lib/libusb/libusb10.h b/lib/libusb/libusb10.h
index cd90531d1a7b..1036270da00b 100644
--- a/lib/libusb/libusb10.h
+++ b/lib/libusb/libusb10.h
@@ -120,6 +120,9 @@ struct libusb_context {
 	void   *fd_cb_user_data;
 	libusb_log_cb log_cb;
 	int no_discovery;
+
+	/* backend context holding the USB file descriptors */
+	struct libusb20_be_ctx *be_ctx;
 };
 
 struct libusb_device {
diff --git a/lib/libusb/libusb20.3 b/lib/libusb/libusb20.3
index f2405e90ce7a..67ed44515a27 100644
--- a/lib/libusb/libusb20.3
+++ b/lib/libusb/libusb20.3
@@ -198,11 +198,15 @@ USB access library (libusb -lusb)
 .Ft int
 .Fn libusb20_be_remove_dev_quirk "struct libusb20_backend *pbe" "struct libusb20_quirk *pq"
 .Ft struct libusb20_backend *
-.Fn libusb20_be_alloc_default "void"
+.Fn libusb20_be_alloc_default "struct libusb20_be_ctx *pctx"
 .Ft struct libusb20_backend *
-.Fn libusb20_be_alloc_freebsd "void"
+.Fn libusb20_be_alloc_freebsd "struct libusb20_be_ctx *pctx"
 .Ft struct libusb20_backend *
-.Fn libusb20_be_alloc_linux "void"
+.Fn libusb20_be_alloc_linux "struct libusb20_be_ctx *pctx"
+.Ft struct libusb20_be_ctx *
+.Fn libusb20_be_ctx_alloc "void"
+.Ft void
+.Fn libusb20_be_ctx_free "struct libusb20_be_ctx *pctx"
 .Ft struct libusb20_device *
 .Fn libusb20_be_device_foreach  "struct libusb20_backend *pbe" "struct libusb20_device *pdev"
 .Ft void
@@ -994,6 +998,33 @@ returned.
 These functions are used to allocate a specific USB backend or the operating system
 default USB backend.
 Allocating a backend is a way to scan for currently present USB devices.
+The backend and the USB devices it enumerates borrow the given
+backend context, which holds the file descriptors used to reach the
+USB devices and must stay alive for as long as they are in use.
+If the
+.Fa pctx
+argument is
+.Dv NULL,
+the backend allocates its own context and releases
+it when the backend is freed.
+USB devices which have been dequeued from such a backend must not be
+used after the backend has been freed.
+.Pp
+.
+.Fn libusb20_be_ctx_alloc
+allocates a backend context, which opens and holds the file
+descriptors used to reach the USB devices.
+The context is immutable after allocation and may therefore be shared
+between threads, backends and devices without any locking.
+Sharing one context across multiple backend allocations allows
+devices to be enumerated and opened after the process has entered
+capability mode.
+.
+.Fn libusb20_be_ctx_free
+frees the given backend context.
+No backend or device allocated from the context may be used
+afterwards.
+This function is NULL safe.
 .Pp
 .
 .Fn libusb20_be_device_foreach
@@ -1086,8 +1117,36 @@ This function does not return NULL.
 .Bl -tag -width Pa
 .It Pa /dev/usb
 .El
+.Sh CAPABILITY MODE
+The backend context holds file descriptors for
+.Pa /dev/usbctl
+and the
+.Pa /dev/usb
+directory, and device nodes are opened relative to the directory
+descriptor using
+.Xr openat 2 .
+All backend operations, including enumeration of hotplugged devices,
+therefore keep working after entering capability mode, see
+.Xr capsicum 4 ,
+as long as the backend context was allocated before
+.Xr cap_enter 2
+was called.
+Applications which allocate more than one USB backend from capability
+mode must allocate a backend context with
+.Fn libusb20_be_ctx_alloc
+up front and pass it to every backend allocation.
+The file descriptors are limited with
+.Xr cap_rights_limit 2 ,
+and USB device file descriptors derived from the directory descriptor
+inherit the
+.Dv CAP_PREAD , CAP_PWRITE , CAP_EVENT
+and
+.Dv CAP_IOCTL
+rights.
 .Sh SEE ALSO
+.Xr cap_enter 2 ,
 .Xr libusb 3 ,
+.Xr capsicum 4 ,
 .Xr usb 4 ,
 .Xr usbconfig 8 ,
 .Xr usbdump 8
diff --git a/lib/libusb/libusb20.c b/lib/libusb/libusb20.c
index 2cd8a6ea7037..4fe3d3068905 100644
--- a/lib/libusb/libusb20.c
+++ b/lib/libusb/libusb20.c
@@ -30,6 +30,7 @@
 #else
 #include <ctype.h>
 #include <poll.h>
+#include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -1273,7 +1274,8 @@ libusb20_be_device_foreach(struct libusb20_backend *pbe, struct libusb20_device
 }
 
 struct libusb20_backend *
-libusb20_be_alloc(const struct libusb20_backend_methods *methods)
+libusb20_be_alloc(const struct libusb20_backend_methods *methods,
+    struct libusb20_be_ctx *pctx)
 {
 	struct libusb20_backend *pbe;
 
@@ -1283,6 +1285,17 @@ libusb20_be_alloc(const struct libusb20_backend_methods *methods)
 	}
 	memset(pbe, 0, sizeof(*pbe));
 
+	if (pctx == NULL) {
+		/* the backend owns its own context */
+		pctx = libusb20_be_ctx_alloc();
+		if (pctx == NULL) {
+			free(pbe);
+			return (NULL);
+		}
+		pbe->be_ctx_owner = 1;
+	}
+	pbe->be_ctx = pctx;
+
 	TAILQ_INIT(&(pbe->usb_devs));
 
 	pbe->methods = methods;		/* set backend methods */
@@ -1295,29 +1308,29 @@ libusb20_be_alloc(const struct libusb20_backend_methods *methods)
 }
 
 struct libusb20_backend *
-libusb20_be_alloc_linux(void)
+libusb20_be_alloc_linux(struct libusb20_be_ctx *pctx)
 {
 	return (NULL);
 }
 
 struct libusb20_backend *
-libusb20_be_alloc_ugen20(void)
+libusb20_be_alloc_ugen20(struct libusb20_be_ctx *pctx)
 {
-	return (libusb20_be_alloc(&libusb20_ugen20_backend));
+	return (libusb20_be_alloc(&libusb20_ugen20_backend, pctx));
 }
 
 struct libusb20_backend *
-libusb20_be_alloc_default(void)
+libusb20_be_alloc_default(struct libusb20_be_ctx *pctx)
 {
 	struct libusb20_backend *pbe;
 
 #ifdef __linux__
-	pbe = libusb20_be_alloc_linux();
+	pbe = libusb20_be_alloc_linux(pctx);
 	if (pbe) {
 		return (pbe);
 	}
 #endif
-	pbe = libusb20_be_alloc_ugen20();
+	pbe = libusb20_be_alloc_ugen20(pctx);
 	if (pbe) {
 		return (pbe);
 	}
@@ -1340,6 +1353,8 @@ libusb20_be_free(struct libusb20_backend *pbe)
 	if (pbe->methods->exit_backend) {
 		pbe->methods->exit_backend(pbe);
 	}
+	if (pbe->be_ctx_owner)
+		libusb20_be_ctx_free(pbe->be_ctx);
 	/* free backend */
 	free(pbe);
 }
diff --git a/lib/libusb/libusb20.h b/lib/libusb/libusb20.h
index 6a95042eda44..6fa08b2b485e 100644
--- a/lib/libusb/libusb20.h
+++ b/lib/libusb/libusb20.h
@@ -177,6 +177,7 @@ struct usb_device_info;
 struct libusb20_transfer;
 struct libusb20_backend;
 struct libusb20_backend_methods;
+struct libusb20_be_ctx;
 struct libusb20_device;
 struct libusb20_device_methods;
 struct libusb20_config;
@@ -294,11 +295,13 @@ int	libusb20_be_set_template(struct libusb20_backend *pbe, int temp);
 
 /* USB backend operations */
 
-struct libusb20_backend *libusb20_be_alloc(const struct libusb20_backend_methods *methods);
-struct libusb20_backend *libusb20_be_alloc_default(void);
-struct libusb20_backend *libusb20_be_alloc_freebsd(void);
-struct libusb20_backend *libusb20_be_alloc_linux(void);
-struct libusb20_backend *libusb20_be_alloc_ugen20(void);
+struct libusb20_backend *libusb20_be_alloc(const struct libusb20_backend_methods *methods, struct libusb20_be_ctx *pctx);
+struct libusb20_backend *libusb20_be_alloc_default(struct libusb20_be_ctx *pctx);
+struct libusb20_backend *libusb20_be_alloc_freebsd(struct libusb20_be_ctx *pctx);
+struct libusb20_backend *libusb20_be_alloc_linux(struct libusb20_be_ctx *pctx);
+struct libusb20_backend *libusb20_be_alloc_ugen20(struct libusb20_be_ctx *pctx);
+struct libusb20_be_ctx *libusb20_be_ctx_alloc(void);
+void	libusb20_be_ctx_free(struct libusb20_be_ctx *pctx);
 struct libusb20_device *libusb20_be_device_foreach(struct libusb20_backend *pbe, struct libusb20_device *pdev);
 void	libusb20_be_dequeue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev);
 void	libusb20_be_enqueue_device(struct libusb20_backend *pbe, struct libusb20_device *pdev);
diff --git a/lib/libusb/libusb20_be_device_foreach.3 b/lib/libusb/libusb20_be_device_foreach.3
index bc741813ddb0..672e2a488d31 100644
--- a/lib/libusb/libusb20_be_device_foreach.3
+++ b/lib/libusb/libusb20_be_device_foreach.3
@@ -39,7 +39,7 @@ Otherwise this is a pointer to the next device.
 .Sh EXAMPLES
 .Bd -literal
     #include <libusb20.h>
-    struct libusb20_backend *be = libusb20_be_alloc_default();
+    struct libusb20_backend *be = libusb20_be_alloc_default(NULL);
     struct libusb20_device *device = NULL;
     while ( (device = libusb20_be_device_foreach(be, device)) != NULL ) {
         if (libusb20_dev_open(device, 0) == LIBUSB20_SUCCESS) {
diff --git a/lib/libusb/libusb20_dev_open.3 b/lib/libusb/libusb20_dev_open.3
index fa5d1746ff23..2b494771e547 100644
--- a/lib/libusb/libusb20_dev_open.3
+++ b/lib/libusb/libusb20_dev_open.3
@@ -53,7 +53,7 @@ and
 .Sh EXAMPLES
 .Bd -literal
     #include <libusb20.h>
-    struct libusb20_backend *be = libusb20_be_alloc_default();
+    struct libusb20_backend *be = libusb20_be_alloc_default(NULL);
     struct libusb20_device *device = NULL;
     while ( (device = libusb20_be_device_foreach(be, device)) != NULL ) {
         if (libusb20_dev_open(device, 0) == LIBUSB20_SUCCESS) {
diff --git a/lib/libusb/libusb20_int.h b/lib/libusb/libusb20_int.h
index b9d614ffd8ff..238cd8d71df4 100644
--- a/lib/libusb/libusb20_int.h
+++ b/lib/libusb/libusb20_int.h
@@ -43,6 +43,21 @@ union libusb20_session_data {
 	uint32_t plugtime;
 };
 
+/*
+ * The backend context holds the file descriptors needed to reach the
+ * USB devices. It is immutable after allocation, so it can be shared
+ * between threads, backends and devices without any locking. Sharing
+ * it between backend allocations keeps enumeration working after the
+ * process has entered capability mode, see capsicum(4).
+ *
+ * The context must stay alive for as long as any backend or device
+ * allocated from it is in use.
+ */
+struct libusb20_be_ctx {
+	int	ctrl_fd;		/* /dev/usbctl */
+	int	usb_dfd;		/* /dev/usb directory */
+};
+
 /* USB backend specific */
 typedef const char *(libusb20_get_backend_name_t)(void);
 typedef int (libusb20_root_get_dev_quirk_t)(struct libusb20_backend *pbe, uint16_t index, struct libusb20_quirk *pq);
@@ -144,6 +159,10 @@ struct libusb20_device_methods {
 struct libusb20_backend {
 	TAILQ_HEAD(, libusb20_device) usb_devs;
 	const struct libusb20_backend_methods *methods;
+
+	/* borrowed backend context, owned when "be_ctx_owner" is set */
+	struct libusb20_be_ctx *be_ctx;
+	uint8_t	be_ctx_owner;
 };
 
 struct libusb20_transfer {
@@ -195,6 +214,9 @@ struct libusb20_device {
 	/* backend methods */
 	const struct libusb20_backend_methods *beMethods;
 
+	/* borrowed backend context */
+	struct libusb20_be_ctx *be_ctx;
+
 	/* list of USB transfers */
 	struct libusb20_transfer *pTransfer;
 
diff --git a/lib/libusb/libusb20_ugen20.c b/lib/libusb/libusb20_ugen20.c
index 43d870fbe112..7d5aa439197a 100644
--- a/lib/libusb/libusb20_ugen20.c
+++ b/lib/libusb/libusb20_ugen20.c
@@ -35,6 +35,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <time.h>
+#include <sys/capsicum.h>
 #include <sys/queue.h>
 #include <sys/types.h>
 #endif
@@ -69,6 +70,67 @@ const struct libusb20_backend_methods libusb20_ugen20_backend = {
 	LIBUSB20_BACKEND(LIBUSB20_DECLARE, ugen20)
 };
 
+struct libusb20_be_ctx *
+libusb20_be_ctx_alloc(void)
+{
+	cap_rights_t rights;
+	struct libusb20_be_ctx *pctx;
+	int fd;
+
+	pctx = malloc(sizeof(*pctx));
+	if (pctx == NULL)
+		return (NULL);
+
+	/*
+	 * The context is immutable once this function returns, which
+	 * makes it safe to share between threads without any locking.
+	 * Open failures are tolerated, so that allocation also
+	 * succeeds on systems without USB support.
+	 */
+	fd = open("/dev/" USB_DEVICE_NAME, O_RDONLY | O_CLOEXEC);
+	if (fd > -1) {
+		cap_rights_init(&rights, CAP_READ, CAP_EVENT, CAP_IOCTL);
+		if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) {
+			close(fd);
+			fd = -1;
+		}
+	}
+	pctx->ctrl_fd = fd;
+
+	fd = open("/dev/" USB_DEVICE_DIR, O_DIRECTORY | O_PATH | O_CLOEXEC);
+	if (fd > -1) {
+		/*
+		 * Device descriptors derived through openat(2)
+		 * inherit these rights, so they must cover all
+		 * operations performed on open devices, including by
+		 * applications using libusb20_dev_get_fd().
+		 */
+		cap_rights_init(&rights, CAP_LOOKUP, CAP_PREAD, CAP_PWRITE,
+		    CAP_EVENT, CAP_IOCTL);
+		if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) {
+			close(fd);
+			fd = -1;
+		}
+	}
+	pctx->usb_dfd = fd;
+
+	return (pctx);
+}
+
+void
+libusb20_be_ctx_free(struct libusb20_be_ctx *pctx)
+{
+	if (pctx == NULL) {
+		/* be NULL safe */
+		return;
+	}
+	if (pctx->ctrl_fd > -1)
+		close(pctx->ctrl_fd);
+	if (pctx->usb_dfd > -1)
+		close(pctx->usb_dfd);
+	free(pctx);
+}
+
 /* USB device specific */
 static libusb20_get_config_desc_full_t ugen20_get_config_desc_full;
 static libusb20_get_config_index_t ugen20_get_config_index;
@@ -173,6 +235,25 @@ ugen20_path_convert_one(const char **pp)
 	return (temp);
 }
 
+static int
+ugen20_open_dev(struct libusb20_be_ctx *pctx, int bus_num, int dev_addr,
+    int flag)
+{
+	char path[48];
+	int usb_dfd;
+
+	usb_dfd = pctx->usb_dfd;
+	if (usb_dfd > -1) {
+		snprintf(path, sizeof(path), "%u.%u.0", bus_num, dev_addr);
+		return (openat(usb_dfd, path, flag));
+	}
+
+	/* fall back when the /dev/usb directory is not available */
+	snprintf(path, sizeof(path), "/dev/" USB_GENERIC_NAME "%u.%u",
+	    bus_num, dev_addr);
+	return (open(path, flag));
+}
+
 static int
 ugen20_enumerate(struct libusb20_device *pdev, const char *id)
 {
@@ -181,17 +262,14 @@ ugen20_enumerate(struct libusb20_device *pdev, const char *id)
 	struct usb_device_info devinfo;
 	struct usb_device_port_path udpp;
 	uint32_t plugtime;
-	char buf[64];
 	int f;
 	int error;
 
 	pdev->bus_number = ugen20_path_convert_one(&tmp);
 	pdev->device_address = ugen20_path_convert_one(&tmp);
 
-	snprintf(buf, sizeof(buf), "/dev/" USB_GENERIC_NAME "%u.%u",
-	    pdev->bus_number, pdev->device_address);
-
-	f = open(buf, O_RDWR);
+	f = ugen20_open_dev(pdev->be_ctx, pdev->bus_number,
+	    pdev->device_address, O_RDWR);
 	if (f < 0) {
 		return (LIBUSB20_ERROR_OTHER);
 	}
@@ -332,7 +410,7 @@ ugen20_init_backend(struct libusb20_backend *pbe)
 
 	memset(&state, 0, sizeof(state));
 
-	state.f = open("/dev/" USB_DEVICE_NAME, O_RDONLY);
+	state.f = pbe->be_ctx->ctrl_fd;
 	if (state.f < 0)
 		return (LIBUSB20_ERROR_OTHER);
 
@@ -348,6 +426,12 @@ ugen20_init_backend(struct libusb20_backend *pbe)
 		if (pdev == NULL) {
 			continue;
 		}
+		/*
+		 * The device borrows the backend context, which must
+		 * stay alive for as long as the device is in use.
+		 */
+		pdev->be_ctx = pbe->be_ctx;
+
 		if (ugen20_enumerate(pdev, state.src + 4)) {
 			libusb20_dev_free(pdev);
 			continue;
@@ -355,7 +439,6 @@ ugen20_init_backend(struct libusb20_backend *pbe)
 		/* put the device on the backend list */
 		libusb20_be_enqueue_device(pbe, pdev);
 	}
-	close(state.f);
 	return (0);			/* success */
 }
 
@@ -422,24 +505,22 @@ static int
 ugen20_open_device(struct libusb20_device *pdev, uint16_t nMaxTransfer)
 {
 	uint32_t plugtime;
-	char buf[64];
 	int f;
 	int g;
 	int error;
 
-	snprintf(buf, sizeof(buf), "/dev/" USB_GENERIC_NAME "%u.%u",
-	    pdev->bus_number, pdev->device_address);
-
 	/*
 	 * We need two file handles, one for the control endpoint and one
 	 * for BULK, INTERRUPT and ISOCHRONOUS transactions due to optimised
 	 * kernel locking.
 	 */
-	g = open(buf, O_RDWR);
+	g = ugen20_open_dev(pdev->be_ctx, pdev->bus_number,
+	    pdev->device_address, O_RDWR);
 	if (g < 0) {
 		return (LIBUSB20_ERROR_NO_DEVICE);
 	}
-	f = open(buf, O_RDWR);
+	f = ugen20_open_dev(pdev->be_ctx, pdev->bus_number,
+	    pdev->device_address, O_RDWR);
 	if (f < 0) {
 		close(g);
 		return (LIBUSB20_ERROR_NO_DEVICE);
@@ -991,12 +1072,12 @@ ugen20_tr_cancel_async(struct libusb20_transfer *xfer)
 }
 
 static int
-ugen20_be_ioctl(uint32_t cmd, void *data)
+ugen20_be_ioctl(struct libusb20_be_ctx *pctx, uint32_t cmd, void *data)
 {
 	int f;
 	int error;
 
-	f = open("/dev/" USB_DEVICE_NAME, O_RDONLY);
+	f = pctx->ctrl_fd;
 	if (f < 0)
 		return (LIBUSB20_ERROR_OTHER);
 	error = ioctl(f, cmd, data);
@@ -1007,7 +1088,6 @@ ugen20_be_ioctl(uint32_t cmd, void *data)
 			error = LIBUSB20_ERROR_OTHER;
 		}
 	}
-	close(f);
 	return (error);
 }
 
@@ -1050,7 +1130,7 @@ ugen20_root_get_dev_quirk(struct libusb20_backend *pbe,
 
 	q.index = quirk_index;
 
-	error = ugen20_be_ioctl(IOUSB(USB_DEV_QUIRK_GET), &q);
+	error = ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_DEV_QUIRK_GET), &q);
 
 	if (error) {
 		if (errno == EINVAL) {
@@ -1077,7 +1157,7 @@ ugen20_root_get_quirk_name(struct libusb20_backend *pbe, uint16_t quirk_index,
 
 	q.index = quirk_index;
 
-	error = ugen20_be_ioctl(IOUSB(USB_QUIRK_NAME_GET), &q);
+	error = ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_QUIRK_NAME_GET), &q);
 
 	if (error) {
 		if (errno == EINVAL) {
@@ -1104,7 +1184,7 @@ ugen20_root_add_dev_quirk(struct libusb20_backend *pbe,
 	q.bcdDeviceHigh = pq->bcdDeviceHigh;
 	strlcpy(q.quirkname, pq->quirkname, sizeof(q.quirkname));
 
-	error = ugen20_be_ioctl(IOUSB(USB_DEV_QUIRK_ADD), &q);
+	error = ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_DEV_QUIRK_ADD), &q);
 	if (error) {
 		if (errno == ENOMEM) {
 			return (LIBUSB20_ERROR_NO_MEM);
@@ -1128,7 +1208,7 @@ ugen20_root_remove_dev_quirk(struct libusb20_backend *pbe,
 	q.bcdDeviceHigh = pq->bcdDeviceHigh;
 	strlcpy(q.quirkname, pq->quirkname, sizeof(q.quirkname));
 
-	error = ugen20_be_ioctl(IOUSB(USB_DEV_QUIRK_REMOVE), &q);
+	error = ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_DEV_QUIRK_REMOVE), &q);
 	if (error) {
 		if (errno == EINVAL) {
 			return (LIBUSB20_ERROR_NOT_FOUND);
@@ -1140,11 +1220,11 @@ ugen20_root_remove_dev_quirk(struct libusb20_backend *pbe,
 static int
 ugen20_root_set_template(struct libusb20_backend *pbe, int temp)
 {
-	return (ugen20_be_ioctl(IOUSB(USB_SET_TEMPLATE), &temp));
+	return (ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_SET_TEMPLATE), &temp));
 }
 
 static int
 ugen20_root_get_template(struct libusb20_backend *pbe, int *ptemp)
 {
-	return (ugen20_be_ioctl(IOUSB(USB_GET_TEMPLATE), ptemp));
+	return (ugen20_be_ioctl(pbe->be_ctx, IOUSB(USB_GET_TEMPLATE), ptemp));
 }
diff --git a/share/examples/libusb20/bulk.c b/share/examples/libusb20/bulk.c
index 2f7588bd592f..f61175c1545b 100644
--- a/share/examples/libusb20/bulk.c
+++ b/share/examples/libusb20/bulk.c
@@ -32,6 +32,7 @@
  */
 
 
+#include <capsicum_helpers.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdint.h>
@@ -219,12 +220,18 @@ main(int argc, char **argv)
   struct libusb20_backend *be;
   struct libusb20_device *dev;
 
-  if ((be = libusb20_be_alloc_default()) == NULL)
+  if ((be = libusb20_be_alloc_default(NULL)) == NULL)
     {
       perror("libusb20_be_alloc()");
       return 1;
     }
 
+  if (caph_enter() < 0)
+    {
+      perror("caph_enter()");
+      return 1;
+    }
+
   dev = NULL;
   while ((dev = libusb20_be_device_foreach(be, dev)) != NULL)
     {
diff --git a/share/examples/libusb20/control.c b/share/examples/libusb20/control.c
index e2ae8f74cc32..fa044925325b 100644
--- a/share/examples/libusb20/control.c
+++ b/share/examples/libusb20/control.c
@@ -30,6 +30,7 @@
  */
 
 
+#include <capsicum_helpers.h>
 #include <limits.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -391,12 +392,18 @@ main(int argc, char **argv)
   struct libusb20_backend *be;
   struct libusb20_device *dev;
 
-  if ((be = libusb20_be_alloc_default()) == NULL)
+  if ((be = libusb20_be_alloc_default(NULL)) == NULL)
     {
       perror("libusb20_be_alloc()");
       return 1;
     }
 
+  if (caph_enter() < 0)
+    {
+      perror("caph_enter()");
+      return 1;
+    }
+
   dev = NULL;
   while ((dev = libusb20_be_device_foreach(be, dev)) != NULL)
     {
diff --git a/tools/tools/usbtest/usb_msc_test.c b/tools/tools/usbtest/usb_msc_test.c
index 1b9c3192a472..733fc6ce21b0 100644
--- a/tools/tools/usbtest/usb_msc_test.c
+++ b/tools/tools/usbtest/usb_msc_test.c
@@ -870,7 +870,7 @@ show_host_device_selection(uint8_t level, struct uaddr *puaddr)
 	const char *ptr;
 
 top:
-	pbe = libusb20_be_alloc_default();
+	pbe = libusb20_be_alloc_default(usb_be_ctx);
 	pdev = NULL;
 	index = 0;
 
@@ -916,7 +916,7 @@ top:
 struct libusb20_device *
 find_usb_device(struct uaddr uaddr)
 {
-	struct libusb20_backend *pbe = libusb20_be_alloc_default();
+	struct libusb20_backend *pbe = libusb20_be_alloc_default(usb_be_ctx);
 	struct libusb20_device *pdev = NULL;
 	struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
 
diff --git a/tools/tools/usbtest/usbtest.c b/tools/tools/usbtest/usbtest.c
index adb46ef44b34..e04573da037d 100644
--- a/tools/tools/usbtest/usbtest.c
+++ b/tools/tools/usbtest/usbtest.c
@@ -31,9 +31,13 @@
 #include <stdarg.h>
 #include <stdlib.h>
 
+#include <capsicum_helpers.h>
+
 #include <sys/types.h>
 #include <sys/sysctl.h>
 
+#include <libusb20.h>
+
 #include <dev/usb/usb_ioctl.h>
 
 #include "usbtest.h"
@@ -45,6 +49,8 @@
 
 static uint8_t usb_ts_select[USB_TS_MAX_LEVELS];
 
+struct libusb20_be_ctx *usb_be_ctx;
+
 const char *indent[USB_TS_MAX_LEVELS] = {
 	" ",
 	"   ",
@@ -805,7 +811,20 @@ show_mode_select(uint8_t level)
 int
 main(int argc, char **argv)
 {
+	/*
+	 * The backend context acquires the descriptors needed to
+	 * reach the USB devices before entering capability mode.
+	 */
+	usb_be_ctx = libusb20_be_ctx_alloc();
+	if (usb_be_ctx == NULL)
+		err(1, "could not allocate USB backend context");
+
+	if (caph_enter() < 0)
+		err(1, "caph_enter() failed");
+
 	show_mode_select(1);
 
+	libusb20_be_ctx_free(usb_be_ctx);
+
 	return (0);
 }
diff --git a/tools/tools/usbtest/usbtest.h b/tools/tools/usbtest/usbtest.h
index efe484bc7d37..94157047473d 100644
--- a/tools/tools/usbtest/usbtest.h
+++ b/tools/tools/usbtest/usbtest.h
@@ -65,4 +65,7 @@ extern int get_integer(void);
 extern uint8_t usb_ts_show_menu(uint8_t, const char *, const char *,...);
 extern int32_t usb_ts_rand_noise(void);
 
+struct libusb20_be_ctx;
+extern struct libusb20_be_ctx *usb_be_ctx;
+
 #endif				/* _USBTEST_H_ */
diff --git a/usr.sbin/usbconfig/dump.c b/usr.sbin/usbconfig/dump.c
index 10ff2125853e..b474ece22017 100644
--- a/usr.sbin/usbconfig/dump.c
+++ b/usr.sbin/usbconfig/dump.c
@@ -42,7 +42,6 @@
 #include <pwd.h>
 #include <grp.h>
 #include <ctype.h>
-#include <fcntl.h>
 
 #include <libusb20.h>
 #include <libusb20_desc.h>
@@ -52,10 +51,6 @@
 #include "dump.h"
 #include "pathnames.h"
 
-#ifndef IOUSB
-#define IOUSB(a) a
-#endif
-
 #define	DUMP0(n,type,field,...) dump_field(pdev, "  ", #field, n->field);
 #define	DUMP0L(n,type,field,...) dump_fieldl(pdev, "  ", #field, n->field);
 #define	DUMP1(n,type,field,...) dump_field(pdev, "    ", #field, n->field);
@@ -77,6 +72,8 @@ struct usb_vendor_info {
 
 STAILQ_HEAD(usb_vendors, usb_vendor_info);
 
+static struct usb_vendors *usb_vendors = NULL;
+
 const char *
 dump_mode(uint8_t value)
 {
@@ -352,18 +349,24 @@ dump_iface(struct libusb20_device *pdev,
 	}
 }
 
-static struct usb_vendors *
-load_vendors(void)
+/*
+ * Read the USB vendor database.  This has to happen before entering
+ * capability mode, because the database is looked up by pathname.
+ */
+void
+dump_vendors_init(void)
 {
 	const char *dbf;
 	FILE *db = NULL;
 	struct usb_vendor_info *cv;
 	struct usb_product_info *cd;
-	struct usb_vendors *usb_vendors;
 	char buf[1024], str[1024];
 	char *ch;
 	int id;
 
+	if (usb_vendors != NULL)
+		return;
+
 	usb_vendors = malloc(sizeof(*usb_vendors));
 	if (usb_vendors == NULL)
 		err(1, "out of memory");
@@ -375,7 +378,7 @@ load_vendors(void)
 		if ((db = fopen(dbf, "r")) == NULL) {
 			dbf = _PATH_USBVDB;
 			if ((db = fopen(dbf, "r")) == NULL)
-				return (usb_vendors);
+				return;
 		}
 	}
 	cv = NULL;
@@ -433,7 +436,6 @@ load_vendors(void)
 
 	fclose(db);
 	/* cleanup */
-	return (usb_vendors);
 }
 
 enum _device_descr_list_type {
@@ -446,7 +448,6 @@ static char *
 _device_desc(struct libusb20_device *pdev,
     enum _device_descr_list_type list_type)
 {
-	static struct usb_vendors *usb_vendors = NULL;
 	char *desc = NULL;
 	const char *vendor = NULL, *product = NULL;
 	uint16_t vid;
@@ -465,9 +466,6 @@ _device_desc(struct libusb20_device *pdev,
 	vid = libusb20_dev_get_device_desc(pdev)->idVendor;
 	pid = libusb20_dev_get_device_desc(pdev)->idProduct;
 
-	if (usb_vendors == NULL)
-		usb_vendors = load_vendors();
-
 	STAILQ_FOREACH(vi, usb_vendors, link) {
 		if (vi->id == vid) {
 			vendor = vi->desc;
@@ -483,34 +481,18 @@ _device_desc(struct libusb20_device *pdev,
 		}
*** 90 LINES SKIPPED ***