From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-4.5 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_LOW,RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.6 Received: by atuin.qyliss.net (Postfix, from userid 496) id 27E4083585; Mon, 18 Oct 2021 12:33:15 +0000 (UTC) Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id C3D05834D1; Mon, 18 Oct 2021 12:33:02 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 496) id 62B1A83529; Mon, 18 Oct 2021 12:33:00 +0000 (UTC) Received: from out4-smtp.messagingengine.com (out4-smtp.messagingengine.com [66.111.4.28]) by atuin.qyliss.net (Postfix) with ESMTPS id F108F834B5 for ; Mon, 18 Oct 2021 12:32:56 +0000 (UTC) Received: from compute6.internal (compute6.nyi.internal [10.202.2.46]) by mailout.nyi.internal (Postfix) with ESMTP id ED7295C016A for ; Mon, 18 Oct 2021 08:32:54 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute6.internal (MEProxy); Mon, 18 Oct 2021 08:32:54 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=alyssa.is; h= from:to:subject:date:message-id:mime-version :content-transfer-encoding; s=fm1; bh=kf1wxdyGv8XAqKgihv8X3MHqqv 8H9/JDAuTgRuGXmW8=; b=kwSOT6jd1syx7LEX25dDDxS6OdupfbOIryRe+6+7Ae AJj+ddsDCs8VfGvRRqZAKO6lZmrX1HzkVenfwvHLQuLg6Q1WYzX2wNZ6ofpN2DD2 EFl4DW6uT5InidOyax6vAJCFwR1JrBTFDROTL/U+/K9wga4SISpFqpVdYXB5vS5s 8j+5OapCZCvlN0KwHo7OiI3e76Y2ALkq1tH0oWYZCjKMxysuwIcse3yFQIcasMQW 9dWCs9l/jc4sNK1u6dY5wUcEQHlm+IWdLxFJmN7zo7amCaB1NMArROiZ+wEcayJF yxzfntskGAM6s3zmPi10jJ1ukUt2Z6fplasr6+aws0wA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=content-transfer-encoding:date:from :message-id:mime-version:subject:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm1; bh=kf1wxdyGv8XAqKgih v8X3MHqqv8H9/JDAuTgRuGXmW8=; b=OnhbzXts5gpDR5UcSYJZTx+nNU0okVEuD vtxviKD9t9IfCoBB0emlUlr5379cKjHqYb6jVGtvY0IOGTVvg09w1VNmX+DDr5Lh emHUpyKlT2ur+7kLFZeLDy/kt0aJ7Br4flvJvoNbn68S/+ZpsVoTN2jhy+Az7C3+ faD4lHatEf1U3QmacIAZrRBH/O1Y73NYoUGUwvZ39O/z88XSmt8StxbtJxafrQA/ zsOK9SwFNleoGo+HIAVHKnmRZaF8cErmqiOpaPCNtRBq2XfchE/XBk/y0QGBj+EP n86Bsz4Mdcrt0Kfphz7Lr6EzE+vkgq3UzWERjGLY1XFFcp3bHsaxA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvtddrvddvtddgheduucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucenucfjughrpefhvffufffkofgggfestdekredtre dttdenucfhrhhomheptehlhihsshgrucftohhsshcuoehhihesrghlhihsshgrrdhisheq necuggftrfgrthhtvghrnhephedvfffghfetieejgfetfedtgffhvdehueehvdejudfgge fgleejgfelfeevgfefnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghi lhhfrhhomhepqhihlhhishhsseigvddvtddrqhihlhhishhsrdhnvght X-ME-Proxy: Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Mon, 18 Oct 2021 08:32:54 -0400 (EDT) Received: by x220.qyliss.net (Postfix, from userid 1000) id D370A1D57; Mon, 18 Oct 2021 12:32:51 +0000 (UTC) From: Alyssa Ross To: devel@spectrum-os.org Subject: [PATCH linux] usbip: tools: usbipd: implement authorization Date: Mon, 18 Oct 2021 12:32:29 +0000 Message-Id: <20211018123229.591851-1-hi@alyssa.is> X-Mailer: git-send-email 2.33.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: VBTQPIWNKJUVL3ZP2BN7DRG7AW5DEEGC X-Message-ID-Hash: VBTQPIWNKJUVL3ZP2BN7DRG7AW5DEEGC X-MailFrom: qyliss@x220.qyliss.net X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.4 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Devices are filtered before all usbipd operations, so that to each client, it's as if the only usbip-able devices are the ones it's been authorized to see. Authorizing a client is as simple as e.g.: ln -s /sys/bus/usb/devices/3-2.2 /var/run/usbipd/2001:db8::1/ Which will make the USB device 3-2.2 available to the client connecting from 2001:db8::1. I selected this filesystem based mechanism because of its simplicity to implement -- no need to parse any files, and granting and revoking permissions is atomic. Technically, it doesn't matter what's actually at that path, as long as it exists, but I've chosen to require it to be a symlink into sysfs, because I think that's the most clear, and actually checking that minimizes valid states, which is generally good practice. Currently, only IP addresses are supported (IPv4 and IPv6). It would be possible to extend this to support hostnames as well, but that would require various additional security considerations. This shouldn't make a difference to any USB functionality when a client _is_ authorized, but just to make sure, I've tested with various USB 2 and USB 3 devices, including hubs. Signed-off-by: Alyssa Ross --- tools/usb/usbip/src/usbipd.c | 112 +++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c index 48398a78e88a..c380e785106f 100644 --- a/tools/usb/usbip/src/usbipd.c +++ b/tools/usb/usbip/src/usbipd.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,7 @@ #define MAIN_LOOP_TIMEOUT 10 #define DEFAULT_PID_FILE "/var/run/" PROGNAME ".pid" +#define PROG_STATE_PATH "/var/run/" PROGNAME static const char usbip_version_string[] = PACKAGE_STRING; @@ -57,6 +59,14 @@ static const char usbipd_help_string[] = " -6, --ipv6\n" " Bind to IPv6. Default is both.\n" "\n" + " -A, --authorization\n" + " Apply authorization rules to clients. Clients\n" + " will only be able to attach to devices, or see\n" + " devices in the device list, that they are\n" + " authorized to access. To authorize a client to\n" + " access a device:\n" + " ln -s /sys/bus/usb/devices/$USB_PATH " PROG_STATE_PATH "/auth/$CLIENT_IP/\n" + "\n" " -e, --device\n" " Run in device mode.\n" " Rather than drive an attached device, create\n" @@ -82,12 +92,78 @@ static const char usbipd_help_string[] = " Show version.\n"; static struct usbip_host_driver *driver; +static bool enable_authorization; static void usbipd_help(void) { printf("%s\n", usbipd_help_string); } +static bool device_visible(struct usbip_exported_device *edev, int connfd) +{ + bool r = false; + int e; + struct sockaddr *addr = NULL; + socklen_t addr_len = 0; + char name[NI_MAXHOST]; + char *path = NULL, *real_path = NULL; + char *expected_path = NULL, *expected_real_path = NULL; + + if (!enable_authorization) + return true; + + if (getpeername(connfd, NULL, &addr_len) == -1) + goto err; + if (!(addr = malloc(addr_len))) + goto err; + if (getpeername(connfd, addr, &addr_len) == -1) + goto err; + + if ((e = getnameinfo(addr, addr_len, + name, sizeof name, NULL, 0, NI_NUMERICHOST))) { + err("getting name of peer: %s", gai_strerror(e)); + goto out; + } + + // Since we're mandating numeric hosts, we don't have to worry + // about what happens if name contains things like slashes. + // If we were to use names as well, we might have to be more + // careful, and use openat2, etc. + if (asprintf(&path, "%s/auth/%s/%s", + PROG_STATE_PATH, name, edev->udev.busid) == -1) + goto err; + + if (!(real_path = realpath(path, NULL))) { + if (errno == ENOENT) + goto out; + goto err; + } + + if (asprintf(&expected_path, "/sys/bus/usb/devices/%s", + edev->udev.busid) == -1) + goto err; + + if (!(expected_real_path = realpath(expected_path, NULL))) + goto err; + + r = !strcmp(real_path, expected_real_path); + goto out; + +err: + err("checking device visibility: %s", strerror(errno)); +out: + free(addr); + free(path); + free(real_path); + free(expected_path); + free(expected_real_path); + return r; +} + +#define CHECK_DEVICE_VISIBILITY(edev, connfd) \ + if (!device_visible(edev, connfd)) \ + continue + static int recv_request_import(int sockfd) { struct op_import_request req; @@ -109,6 +185,7 @@ static int recv_request_import(int sockfd) list_for_each(i, &driver->edev_list) { edev = list_entry(i, struct usbip_exported_device, node); + CHECK_DEVICE_VISIBILITY(edev, sockfd); if (!strncmp(req.busid, edev->udev.busid, SYSFS_BUS_ID_SIZE)) { info("found requested device: %s", req.busid); found = 1; @@ -176,6 +253,7 @@ static int send_reply_devlist(int connfd) /* number of exported devices */ list_for_each(j, &driver->edev_list) { edev = list_entry(j, struct usbip_exported_device, node); + CHECK_DEVICE_VISIBILITY(edev, connfd); if (edev->status != SDEV_ST_USED) reply.ndev += 1; } @@ -196,6 +274,7 @@ static int send_reply_devlist(int connfd) list_for_each(j, &driver->edev_list) { edev = list_entry(j, struct usbip_exported_device, node); + CHECK_DEVICE_VISIBILITY(edev, connfd); if (edev->status == SDEV_ST_USED) continue; @@ -586,17 +665,18 @@ static int do_standalone_mode(int daemonize, int ipv4, int ipv6) int main(int argc, char *argv[]) { static const struct option longopts[] = { - { "ipv4", no_argument, NULL, '4' }, - { "ipv6", no_argument, NULL, '6' }, - { "daemon", no_argument, NULL, 'D' }, - { "daemon", no_argument, NULL, 'D' }, - { "debug", no_argument, NULL, 'd' }, - { "device", no_argument, NULL, 'e' }, - { "pid", optional_argument, NULL, 'P' }, - { "tcp-port", required_argument, NULL, 't' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'v' }, - { NULL, 0, NULL, 0 } + { "ipv4", no_argument, NULL, '4' }, + { "ipv6", no_argument, NULL, '6' }, + { "authorization", no_argument, NULL, 'A' }, + { "daemon", no_argument, NULL, 'D' }, + { "daemon", no_argument, NULL, 'D' }, + { "debug", no_argument, NULL, 'd' }, + { "device", no_argument, NULL, 'e' }, + { "pid", optional_argument, NULL, 'P' }, + { "tcp-port", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } }; enum { @@ -620,7 +700,7 @@ int main(int argc, char *argv[]) cmd = cmd_standalone_mode; driver = &host_driver; for (;;) { - opt = getopt_long(argc, argv, "46DdeP::t:hv", longopts, NULL); + opt = getopt_long(argc, argv, "46ADdeP::t:hv", longopts, NULL); if (opt == -1) break; @@ -632,6 +712,9 @@ int main(int argc, char *argv[]) case '6': ipv6 = 1; break; + case 'A': + enable_authorization = true; + break; case 'D': daemonize = 1; break; @@ -663,6 +746,11 @@ int main(int argc, char *argv[]) if (!ipv4 && !ipv6) ipv4 = ipv6 = 1; + if (enable_authorization && driver != &host_driver) { + err("authorization is only supported when using host driver"); + goto err_out; + } + switch (cmd) { case cmd_standalone_mode: rc = do_standalone_mode(daemonize, ipv4, ipv6); base-commit: 620b74d01b9d4393bef6742bf121908322c2fe0b -- 2.33.0