From nobody Tue Oct 7 08:35:47 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1110521FF39; Fri, 11 Jul 2025 09:06:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752224823; cv=none; b=BLLlyITh4fjVMVjdgGKKrxDR1Uj31ETFCo7DJFrgq/IN0JTJsGDPeEA/Gq00ltbUpRmQm9ZMOkIIB373NIz6cMMIb5HxiTNDnRu09meQo4ZzXtaiPiHCMxcr47yu6gVt3CTWu+a77ZWj32OCQ+2VQUxkoK6hISrB6QP3uFYuoxE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752224823; c=relaxed/simple; bh=ajhQOe2zRb52yuHwP8gbqFRP5X4zdlzOC0dbzNSJvQ8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=luCIRQRVHm77P/QhxTJAhzMYDmKjuHUBwqfOOeG+SaOh3t7OXl99zbtaMxQwc1zFKMJdSOj+4fMFYkYRoYhKMk5k/oyXpuSeeiZKP8G47pK5Jhj79abvZiLgIOFVMZ03JobniTljJUepgghHsA3exmnKcwzzKOuC+WafKDkL42o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=Uf7bTIoB; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="Uf7bTIoB" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=1WfNKNhYv/UCA5BP7FU4Nwnn3iz/WP77bbs0m8XXzlc=; b=Uf7bTIoBRlv6QyqE7Oo9o6/AJB 1ZqI7gXfYB4XktlRoQma7lorb4bD+ZhA2mp3h9WNmqi4R8msTgcQYDidlZzdRAfHyZmKdh3IJPj4h FCaTaqRnSO+ZXlQt/Xts7dXRhRalxiFnUeJFxE0AZTZaAflXFDw+4xdiRXeO0JUA0KYEDnvqcgbEY 0mCtO7luOlnYPrsgeAf3/JSCw7OItR0g/lOE/zyQRDd81EMPWEgnr8/M4J6HNhTdhCA6QFBLIpZlt DRL3sdyTQ/j2lG5LKABUG/RhUMsm0YNqBLQS/b6LIGYPYluqLdBSLB4M0EQDN/lDAPu9ONK3VIg0a AmlbgDkw==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:16749 helo=[10.224.9.2]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1ua9K5-0000000DVFH-37sU; Fri, 11 Jul 2025 10:41:36 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 11 Jul 2025 10:41:22 +0200 Subject: [RFC PATCH v2 1/4] usb: core: Introduce netlink usb authentication policy engine Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250711-usb_authentication-v2-1-2878690e6b6d@ssi.gouv.fr> References: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> In-Reply-To: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Luc Bonnafoux , Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet The usb authentication feature needs a policy engine in order to authorize or deny usb devices based on a user defined policy. In order to reduce the attack surface and in-kernel complexity, policy management, crypto operations and complex parsing have been implemented in userspace using the generic netlink API. The full authentication protocol is kernel driven. The following unicast netlink commands have been defined in order to fulfill device authentication : - USBAUTH_CMD_REGISTER This is the beginning of any authentication. The kernel first wait for the userspace service to connect to the socket using the `USBAUTH_CMD_REGISTER` netlink command. Upon connection, the kernel check that the userspace service has the `CAP_SYS_ADMIN` capability beforing enrolling the service. Only one userspace service can be registered. - USBAUTH_CMD_CHECK_DIGEST The kernel then sends a `USBAUTH_CMD_CHECK_DIGEST` netlink command to the policy engine to be verified. The policy engine checks if the device ceritificates has already been encountered. - USBAUTH_CMD_RESP_DIGEST After the policy engine has received an usb device certificate digest list from kernel, it needs to reply if it knows one of them using the `USBAUTH_CMD_RESP_DIGEST` netlink command. - USBAUTH_CMD_CHECK_CERTIFICATE The kernel then sends a `USBAUTH_CMD_CHECK_CERTIFICATE` netlink command to the policy engine. Each command contains one certificate chain. The policy engine verifies if the device certificate chain is trusted. - USBAUTH_CMD_RESP_CERTIFICATE After checking the certificate chain, the policy engine sends a `USBAUTH_CMD_RESP_CERTIFICATE` response. It tells the kernel if the device certificate chain is trusted and thus if the device authentication should continue. Once device has been validated either through the digest or certificate chain validation, an authentication session is started and a device ID is associated for this session. The ID will then be used in all the following commands. - USBAUTH_CMD_GEN_NONCE Kernel then asks for a nonce generation in order to challenge the device using the `USBAUTH_GEN_NONCE` netlink command. - USBAUTH_CMD_RESP_GEN_NONCE When the nonce has been generated by the policy engine it is sent back to the kernel using the `USBAUTH_CMD_RESP_GEN_NONCE` netlink command. - USBAUTH_CMD_CHECK_CHALL Once the kernel has received a device challenge response, it forwards the response to the policy engine for validation using the `USBAUTH_CMD_CHECK_CHALL` netlink command. - USBAUTH_CMD_RESP_CHECK_CHALL The policy engine then verifies the challenge and replies its decision to the kernel using the `USBAUTH_CMD_RESP_CHECK_CHALL` netlink command. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/authent_netlink.c | 1031 +++++++++++++++++++++++++= ++++ drivers/usb/core/authent_netlink.h | 33 + include/uapi/linux/usb/usb_auth_netlink.h | 70 ++ 3 files changed, 1134 insertions(+) diff --git a/drivers/usb/core/authent_netlink.c b/drivers/usb/core/authent_= netlink.c new file mode 100644 index 0000000000000000000000000000000000000000..9848f219e0e4807563f0f0432a0= f1108cd6a0454 --- /dev/null +++ b/drivers/usb/core/authent_netlink.c @@ -0,0 +1,1031 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "authent.h" +#include "authent_netlink.h" + +#define WAIT_USERSPACE_TIMEOUT 30 +#define WAIT_RESPONSE_TIMEOUT 300 +#define USBAUTH_MAX_RESP_SIZE 128 + +/** + * struct usb_auth_req - Define an outstanding request between the kernel = and userspace + * + * @used: 1 if the slot is currently used, access must be protected. + * @done: 1 if the response has been received, used as wait condition. + * @error: userspace response error code. + * @resp: arbitrary response buffer. + */ +struct usb_auth_req { + u8 used; + u8 done; + u8 error; + u8 resp[USBAUTH_MAX_RESP_SIZE]; +}; + +static struct genl_family usbauth_genl_fam; + +/* TODO: add mutex for PID access */ +static u32 pol_eng_pid; +static struct net *pol_eng_net; + +static wait_queue_head_t usb_req_wq; + +#define USBAUTH_MAX_OUTSTANDING_REQS USB_MAXCHILDREN +/* Mutex is used to protect access to the `used` field */ +DEFINE_MUTEX(usb_auth_reqs_mutex); +static struct usb_auth_req usb_auth_outstanding_reqs[USBAUTH_MAX_OUTSTANDI= NG_REQS]; + +//////////////////////////////////////////////////////////////////////////= ////// +// USB request utilities + +/** + * usb_auth_get_reqs_slot() - Find the first available slot in the outstan= ding requests array and + * reserve it. + * + * @index: [out] reserved slot index, valid if return equals 0 + * + * Context: this function will block on the request list mutex + * + * Returns: + * * %0 - OK + * * %-EXFULL - too many outstanding requests + * + */ +static int usb_auth_get_reqs_slot(u32 *index) +{ + int ret =3D -EXFULL; + u32 i =3D 0; + + mutex_lock(&usb_auth_reqs_mutex); + + /* Take the first available slot */ + for (i =3D 0; i < USBAUTH_MAX_OUTSTANDING_REQS; i++) { + if (usb_auth_outstanding_reqs[i].used =3D=3D 0) { + usb_auth_outstanding_reqs[i].used =3D 1; + usb_auth_outstanding_reqs[i].done =3D 0; + usb_auth_outstanding_reqs[i].error =3D USBAUTH_OK; + memset(usb_auth_outstanding_reqs[i].resp, 0, + USBAUTH_MAX_RESP_SIZE); + *index =3D i; + ret =3D 0; + break; + } + } + + mutex_unlock(&usb_auth_reqs_mutex); + + return ret; +} + +/** + * usb_auth_release_reqs_slot() - release a request slot + * + * @index : [in] slot index to be released + * + * context: this function will block on the request list mutex + */ +static void usb_auth_release_reqs_slot(const u32 index) +{ + mutex_lock(&usb_auth_reqs_mutex); + + usb_auth_outstanding_reqs[index].used =3D 0; + + mutex_unlock(&usb_auth_reqs_mutex); +} + +//////////////////////////////////////////////////////////////////////////= ////// +// Generic netlink socket utilities + +/** + * usb_auth_register_req_doit() - Handle a registration request from users= pace + * + * @skb: [in] Netlink socket buffer + * @info: [in] Generic Netlink receiving information + * + * It will overwrite the current userspace registered PID with the one pro= vided + * in the request. + * + * Returns: + * * %0 - OK + * * %-EPERM - Permission denied for netlink usage + * * %-ENOMEM - if message creation failed + * * %-EMSGSIZE - if message creation failed + */ +static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_inf= o *info) +{ + int ret =3D 0; + void *hdr =3D NULL; + struct sk_buff *msg =3D NULL; + + if (!netlink_capable(skb, CAP_SYS_ADMIN)) { + pr_err("%s: invalid permissions\n", __func__); + return -EPERM; + } + + pol_eng_pid =3D info->snd_portid; + pol_eng_net =3D genl_info_net(info); + + wake_up_all(&usb_req_wq); + + msg =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (msg =3D=3D NULL) { + pr_err("%s: failed to allocate message buffer\n", __func__); + return -ENOMEM; + } + + hdr =3D genlmsg_put(msg, info->snd_portid, info->snd_seq, + &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER); + if (hdr =3D=3D NULL) { + pr_err("%s: failed to create genetlink header\n", __func__); + nlmsg_free(msg); + return -EMSGSIZE; + } + + genlmsg_end(msg, hdr); + + ret =3D genlmsg_reply(msg, info); + + return ret; +} + +/** + * usb_auth_digest_resp_doit() - Handle a CHECK_DIGEST response from users= pace + * + * @skb: [in] Netlink socket buffer + * @info: [in] Generic Netlink receiving information + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_DEV_ID + * - USBAUTH_A_KNOWN + * - USBAUTH_A_BLOCKED + * + * Returns: + * * %0 - OK + * * %-EPERM - Permission denied for netlink usage. + * * %-ECOMM - Netlink communication failure + * * %-EINVAL - Invalid value in messages + */ +static int usb_auth_digest_resp_doit(struct sk_buff *skb, struct genl_info= *info) +{ + u32 index =3D 0; + + if (!netlink_capable(skb, CAP_SYS_ADMIN)) { + pr_err("%s: invalid permissions\n", __func__); + return -EPERM; + } + + if (!pol_eng_pid || !pol_eng_net) { + pr_err("%s: No policy engine registered\n", __func__); + return -ECOMM; + } + + if (info->snd_portid !=3D pol_eng_pid) { + pr_err("Sender id differ from registered policy engine\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("%s: invalid response: no req ID\n", __func__); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("%s: response error\n", __func__); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_KNOWN] || + !info->attrs[USBAUTH_A_BLOCKED]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_KNOWN]); + usb_auth_outstanding_reqs[index].resp[1] =3D + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]); + ((u32 *)usb_auth_outstanding_reqs[index].resp + 2)[0] =3D + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * usb_auth_cert_resp_doit() - Handle a CHECK_CERTIFICATE response from us= erspace + * + * @skb: [in] Netlink socket buffer + * @info: [in] Generic Netlink receiving information + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_VALID + * - USBAUTH_A_BLOCKED + * - USBAUTH_A_DEV_ID + * + * Returns: + * * %0 - OK + * * %-EPERM - Permission denied for netlink usage + * * %-ECOMM - Netlink communication failure + * * %-EINVAL - Invalid value in messages + * + */ +static int usb_auth_cert_resp_doit(struct sk_buff *skb, struct genl_info *= info) +{ + u32 index =3D 0; + + if (!netlink_capable(skb, CAP_SYS_ADMIN)) { + pr_err("%s: invalid permissions\n", __func__); + return -EPERM; + } + + if (!pol_eng_pid || !pol_eng_net) { + pr_err("%s: No policy engine registered", __func__); + return -ECOMM; + } + + if (info->snd_portid !=3D pol_eng_pid) { + pr_err("Sender id differ from registered policy engine\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("%s: invalid response: no req ID\n", __func__); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("%s: response error\n", __func__); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_VALID] || + !info->attrs[USBAUTH_A_BLOCKED]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].done =3D 1; + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_VALID]); + usb_auth_outstanding_reqs[index].resp[1] =3D + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]); + ((u32 *)usb_auth_outstanding_reqs[index].resp + 2)[0] =3D + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]); + + usb_auth_outstanding_reqs[index].done =3D 1; + + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * usb_auth_gen_nonce_doit() - Handle a GEN_NONCE response from userspace + * + * @skb: [in] Netlink socket buffer + * @info: [in] Generic Netlink receiving information + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_NONCE + * + * Returns: + * * %0 - OK + * * %-EPERM - Permission denied for netlink usage + * * %-ECOMM - Netlink communication failure + * * %-EINVAL - Invalid value in messages + */ +static int usb_auth_gen_nonce_doit(struct sk_buff *skb, struct genl_info *= info) +{ + u32 index =3D 0; + + if (!netlink_capable(skb, CAP_SYS_ADMIN)) { + pr_err("%s: invalid permissions\n", __func__); + return -EPERM; + } + + if (!pol_eng_pid || !pol_eng_net) { + pr_err("%s: No policy engine registered", __func__); + return -ECOMM; + } + + if (info->snd_portid !=3D pol_eng_pid) { + pr_err("Sender id differ from registered policy engine\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("%s: invalid response: no req ID\n", __func__); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("%s: response error\n", __func__); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_NONCE]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + nla_memcpy(usb_auth_outstanding_reqs[index].resp, + info->attrs[USBAUTH_A_NONCE], + USBAUTH_NONCE_SIZE); + usb_auth_outstanding_reqs[index].done =3D 1; + wake_up_all(&usb_req_wq); + + return 0; +} + +/** + * usb_auth_check_chall_doit() - Handle a CHECK_CHALL response from usersp= ace + * + * @skb: [in] Netlink socket buffer + * @info: [in] Generic Netlink receiving information + * + * The response must contain: + * - USBAUTH_A_REQ_ID + * - USBAUTH_A_ERROR_CODE + * - USBAUTH_A_VALID + * + * Returns: + * * %0 - OK + * * %-EPERM - Permission denied for netlink usage + * * %-ECOMM - Netlink communication failure + * * %-EINVAL - Invalid value in messages + */ +static int usb_auth_check_chall_doit(struct sk_buff *skb, struct genl_info= *info) +{ + u32 index =3D 0; + + if (!netlink_capable(skb, CAP_SYS_ADMIN)) { + pr_err("%s: invalid permissions\n", __func__); + return -EPERM; + } + + if (!pol_eng_pid || !pol_eng_net) { + pr_err("%s: No policy engine registered", __func__); + return -ECOMM; + } + + if (info->snd_portid !=3D pol_eng_pid) { + pr_err("Sender id differ from registered policy engine\n"); + return -EPERM; + } + + if (!info->attrs[USBAUTH_A_REQ_ID]) { + pr_err("%s: invalid response: no req ID\n", __func__); + return -EINVAL; + } + + index =3D nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]); + + if (!info->attrs[USBAUTH_A_ERROR_CODE]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].error =3D + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]); + + if (usb_auth_outstanding_reqs[index].error !=3D USBAUTH_OK) { + pr_err("%s: response error\n", __func__); + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + if (!info->attrs[USBAUTH_A_VALID]) { + pr_err("%s: invalid response: missing attributes\n", __func__); + usb_auth_outstanding_reqs[index].error =3D USBAUTH_INVRESP; + usb_auth_outstanding_reqs[index].done =3D 1; + return -EINVAL; + } + + usb_auth_outstanding_reqs[index].resp[0] =3D + nla_get_u8(info->attrs[USBAUTH_A_VALID]); + usb_auth_outstanding_reqs[index].done =3D 1; + wake_up_all(&usb_req_wq); + + return 0; +} + +static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] =3D { + [USBAUTH_A_REQ_ID] =3D {.type =3D NLA_U32,}, + [USBAUTH_A_DEV_ID] =3D {.type =3D NLA_U32,}, + [USBAUTH_A_DIGEST] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_DIGEST_SIZ= E,}, + [USBAUTH_A_DIGESTS] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_DIGESTS_S= IZE,}, + [USBAUTH_A_SLOT_MASK] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_KNOWN] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_BLOCKED] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_VALID] =3D {.type =3D NLA_U8,}, + [USBAUTH_A_CERTIFICATE] =3D {.type =3D NLA_UNSPEC, .max =3D USBAUTH_MAX_C= ERT_SIZE,}, + [USBAUTH_A_CERT_LEN] =3D {.type =3D NLA_U32}, + [USBAUTH_A_ROUTE] =3D {.type =3D NLA_U32}, + [USBAUTH_A_NONCE] =3D {.type =3D NLA_BINARY, .len =3D USBAUTH_NONCE_SIZE,= }, + [USBAUTH_A_CHALL] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_CHALL_SIZE,= }, + [USBAUTH_A_DESCRIPTOR] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_MAX_DE= SC_SIZE}, + [USBAUTH_A_BOS] =3D {.type =3D NLA_UNSPEC, .len =3D USBAUTH_MAX_BOS_SIZE}, + [USBAUTH_A_ERROR_CODE] =3D {.type =3D NLA_U8}, +}; + +static struct genl_ops usbauth_genl_ops[] =3D { + { + .cmd =3D USBAUTH_CMD_REGISTER, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_register_req_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_DIGEST, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_digest_resp_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_CERTIFICATE, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_cert_resp_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_GEN_NONCE, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_gen_nonce_doit, + }, + { + .cmd =3D USBAUTH_CMD_RESP_CHECK_CHALL, + .policy =3D usbauth_attr_pol, + .doit =3D usb_auth_check_chall_doit, + } +}; + +static struct genl_family usbauth_genl_fam =3D { + .name =3D USBAUTH_GENL_NAME, + .version =3D USBAUTH_GENL_VERSION, + .maxattr =3D USBAUTH_A_MAX, + .ops =3D usbauth_genl_ops, + .n_ops =3D ARRAY_SIZE(usbauth_genl_ops), + .mcgrps =3D NULL, + .n_mcgrps =3D 0, +}; + +/** + * usb_auth_init_netlink() - Initialises the netlink socket for userspace = usb + * authentication policy engine to register. + * + * Returns: + * * %0 - OK + */ + +int usb_auth_init_netlink(void) +{ + int ret =3D 0; + u8 i =3D 0; + + for (i =3D 0; i < USBAUTH_MAX_OUTSTANDING_REQS; i++) + usb_auth_outstanding_reqs[i].used =3D 0; + + init_waitqueue_head(&usb_req_wq); + ret =3D genl_register_family(&usbauth_genl_fam); + if (ret) { + pr_err("%s: failed to init netlink: %d\n", + __func__, ret); + return ret; + } + + return ret; +} + +//////////////////////////////////////////////////////////////////////////= ////// +// Policy engine API + +/** + * usb_policy_engine_check_digest - Check if a digest match a device + * + * @route: [in] Information on the device to construct the ID + * @digests: [in] USB Authentication digest, must be 256 B + * @mask: [in] USB Authentication slot mask + * @is_known: [out] 1 at each index with a known digest, 0 otherwise + * @is_blocked: [out] 1 if the device is known and banned, 0 otherwise + * @id: [out] if is_known and !is_blocked then contains the device handle + * + * This function blocks until a response has been received from userspace = or in + * case of timeout. + * The function blocks if no policy engine is registered with a timeout. + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - if digest is NULL + * * %-ECOMM - if userspace policy engine is not available or busy + * or message transmission failed + * * %-ENOMEM - if message creation failed + * * %-EMSGSIZE - if message creation failed + * + * + */ +int usb_policy_engine_check_digest(const u32 route, const u8 *const digest= s, + const u8 mask, u8 *is_known, u8 *is_blocked, u32 *id) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + u32 index =3D 0; + + if (digests =3D=3D NULL) { + pr_err("%s: invalid inputs\n", __func__); + return -EINVAL; + } + + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("%s: userspace not available\n", __func__); + return -ECOMM; + } + + if (usb_auth_get_reqs_slot(&index)) { + pr_err("%s: failed to get request slot\n", __func__); + return -ECOMM; + } + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("%s: failed to allocated buffer\n", __func__); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_DIGEST); + if (hdr =3D=3D NULL) { + pr_err("%s: failed to place header\n", __func__); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("%s: failed to place req ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_ROUTE, route); + if (ret) { + pr_err("%s: failed to place route\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DIGESTS, USBAUTH_DIGESTS_SIZE, digests); + if (ret) { + pr_err("%s: failed to place digests\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u8(skb, USBAUTH_A_SLOT_MASK, mask); + if (ret) { + pr_err("%s: failed to place slot mask\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("%s: failed to send message: %d\n", + __func__, ret); + return -ECOMM; + } + + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("%s: userspace response not available\n", __func__); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + + if (usb_auth_outstanding_reqs[index].error =3D=3D USBAUTH_INVRESP) { + pr_err("%s: userspace response error: %d\n", + __func__, usb_auth_outstanding_reqs[index].error); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + *is_known =3D usb_auth_outstanding_reqs[index].resp[0]; + *is_blocked =3D usb_auth_outstanding_reqs[index].resp[1]; + *id =3D ((u32 *)usb_auth_outstanding_reqs[index].resp + 2)[0]; + + usb_auth_release_reqs_slot(index); + + return 0; +} + +/** + * usb_policy_engine_check_cert_chain() - Check if a certificate chain is = valid and authorized + * + * @route: [in] Information on the device to construct the ID + * @digest: [in] Digest corresponding to the certificate chain + * @chain: [in] Certificate chain to check, at most 4096 bytes + * @chain_len: [in] Certificate chain length + * @is_valid: [out] 1 if the certificate chain can be validated + * @is_blocked: [out] 1 if the chain is valid but one of the certificate i= s blocked + * @id: [out] If is_known and !is_blocked then contains the device handle + * + * A certificate chain is valid if it can be successfully verified with on= e of the + * root CA in store. + * A certificate chain is blocked if one of the certificate of chain is bl= ocked, + * due to revocation, blacklist... + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - if digest is NULL + * * %-ECOMM - if userspace policy engine is not available or busy + * or message transmission failed + * * %-ENOMEM - if message creation failed + * * %-EMSGSIZE - if message creation failed + * + */ + +int usb_policy_engine_check_cert_chain(const u32 route, + const u8 *const digest, const u8 *const chain, + const size_t chain_len, u8 *is_valid, u8 *is_blocked, u32 *id) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + u32 index =3D 0; + + if (chain =3D=3D NULL || chain_len > USBAUTH_MAX_CERT_SIZE || digest =3D= =3D NULL) { + pr_err("%s: invalid inputs\n", __func__); + return -EINVAL; + } + + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("%s: userspace not available\n", __func__); + return -ECOMM; + } + + if (usb_auth_get_reqs_slot(&index)) { + pr_err("%s: failed to get request slot\n", __func__); + return -ECOMM; + } + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("%s: failed to allocated buffer\n", __func__); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_CERTIFICATE); + if (hdr =3D=3D NULL) { + pr_err("%s: failed to place header\n", __func__); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("%s: failed to place req ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_ROUTE, route); + if (ret) { + pr_err("%s: failed to place route\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DIGEST, USBAUTH_DIGEST_SIZE, digest); + if (ret) { + pr_err("%s: failed to place digest\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_CERTIFICATE, chain_len, chain); + if (ret) { + pr_err("%s: failed to place certificate\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_CERT_LEN, chain_len); + if (ret) { + pr_err("%s: failed to place chain length\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("%s: failed to send message: %d\n", + __func__, ret); + return -ECOMM; + } + + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("%s: userspace response not available\n", __func__); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + *is_valid =3D usb_auth_outstanding_reqs[index].resp[0]; + *is_blocked =3D usb_auth_outstanding_reqs[index].resp[1]; + *id =3D ((u32 *)usb_auth_outstanding_reqs[index].resp + 2)[0]; + + usb_auth_release_reqs_slot(index); + + return 0; +} + +/** + * usb_policy_engine_generate_challenge() - Generate a nonce for the authe= ntication challenge + * + * @id: [in] Device ID + * @nonce: [out] 32 bytes nonce buffer, caller allocated + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - if digest is NULL + * * %-ECOMM - if userspace policy engine is not available or busy + * or message transmission failed + * * %-ENOMEM - if message creation failed + * * %-EMSGSIZE - if message creation failed + */ +int usb_policy_engine_generate_challenge(const u32 id, u8 *nonce) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + u32 index =3D 0; + + /* Arbitrary 30s wait before giving up */ + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("%s: userspace not available\n", __func__); + return -ECOMM; + } + + if (usb_auth_get_reqs_slot(&index)) { + pr_err("%s: failed to get request slot\n", __func__); + return -ECOMM; + } + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("%s: failed to allocated buffer\n", __func__); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_GEN_NONCE); + if (hdr =3D=3D NULL) { + pr_err("%s: failed to place header\n", __func__); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("%s: failed to place req ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_DEV_ID, id); + if (ret) { + pr_err("%s: failed to place dev ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("%s: failed to send message: %d\n", __func__, ret); + return -ECOMM; + } + + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("%s: userspace response not available\n", __func__); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + memcpy(nonce, usb_auth_outstanding_reqs[index].resp, USBAUTH_NONCE_SIZE); + usb_auth_release_reqs_slot(index); + + return 0; +} + +/** + * usb_policy_engine_check_challenge() - Validate the authentication chall= enge + * + * @id: [in] device handle + * @challenge: [in] challenge response, must be 204 bytes. + * @challenge is the concatenation of : + * message (140B) | signature (64B) + * @context: [in] usb device context + * @context_size: [in] usb device context size + * @is_valid: [out] 1 if the signature is valid, 0 otherwise + * + * Check that the response challenge contains the right nonce + * Check that the device signature is valid + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - if challenge, desc or bos is NULL or invalid parameter size + * * %-ECOMM - if userspace policy engine is not available or busy + * or message transmission failed + * * %-ENOMEM - if message creation failed + * * %-EMSGSIZE - if message creation failed + */ +int usb_policy_engine_check_challenge(const u32 id, + const u8 *const challenge, const u8 *const context, + const size_t context_size, u8 *is_valid) +{ + int ret =3D -1; + void *hdr =3D NULL; + struct sk_buff *skb =3D NULL; + u32 index =3D 0; + + if (challenge =3D=3D NULL || context =3D=3D NULL || + context_size > USBAUTH_MAX_BOS_SIZE) { + pr_err("%s: invalid inputs\n", __func__); + return -EINVAL; + } + + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + pr_err("%s: userspace not available\n", __func__); + return -ECOMM; + } + + if (usb_auth_get_reqs_slot(&index)) { + pr_err("%s: failed to get request slot\n", __func__); + return -ECOMM; + } + skb =3D genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb =3D=3D NULL) { + pr_err("%s: failed to allocated buffer\n", __func__); + return -ENOMEM; + } + + hdr =3D genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0, + USBAUTH_CMD_CHECK_CHALL); + if (hdr =3D=3D NULL) { + pr_err("%s: failed to place header\n", __func__); + nlmsg_free(skb); + return -ENOMEM; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_REQ_ID, index); + if (ret) { + pr_err("%s: failed to place req ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_CHALL, USBAUTH_CHALL_SIZE, challenge); + if (ret) { + pr_err("%s: failed to place challenge\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put(skb, USBAUTH_A_DESCRIPTOR, context_size, context); + if (ret) { + pr_err("%s: failed to place descriptor\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + ret =3D nla_put_u32(skb, USBAUTH_A_DEV_ID, id); + if (ret) { + pr_err("%s: failed to place dev ID\n", __func__); + genlmsg_cancel(skb, hdr); + nlmsg_free(skb); + return ret; + } + + genlmsg_end(skb, hdr); + + ret =3D genlmsg_unicast(pol_eng_net, skb, pol_eng_pid); + if (ret !=3D 0) { + pr_err("%s: failed to send message: %d\n", + __func__, ret); + return -ECOMM; + } + if (!wait_event_timeout(usb_req_wq, + usb_auth_outstanding_reqs[index].done =3D=3D 1, + HZ * WAIT_RESPONSE_TIMEOUT)) { + pr_err("%s: userspace response not available\n", __func__); + usb_auth_release_reqs_slot(index); + return -ECOMM; + } + + *is_valid =3D usb_auth_outstanding_reqs[index].resp[0]; + usb_auth_release_reqs_slot(index); + + return 0; +} diff --git a/drivers/usb/core/authent_netlink.h b/drivers/usb/core/authent_= netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..d458fcf620f2de9a00896d2c09b= abcdcc843d3f0 --- /dev/null +++ b/drivers/usb/core/authent_netlink.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_CORE_AUTHENT_NETLINK_H_ +#define __USB_CORE_AUTHENT_NETLINK_H_ + +int usb_auth_init_netlink(void); +int usb_policy_engine_check_digest(const u32 route, + const u8 *const digests, + const u8 mask, u8 *is_known, + u8 *is_blocked, u32 *id); +int usb_policy_engine_check_cert_chain(const u32 route, + const u8 *const digest, + const u8 *const chain, + const size_t chain_len, + u8 *is_valid, u8 *is_blocked, + u32 *id); +int usb_policy_engine_generate_challenge(const u32 id, u8 *nonce); +int usb_policy_engine_check_challenge(const u32 id, + const u8 *const challenge, + const u8 *const context, + const size_t context_size, + u8 *is_valid); + +#endif /* __USB_CORE_AUTHENT_NETLINK_H_ */ diff --git a/include/uapi/linux/usb/usb_auth_netlink.h b/include/uapi/linux= /usb/usb_auth_netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..7ca49ebf6612f99742947c38903= 039d74a770e79 --- /dev/null +++ b/include/uapi/linux/usb/usb_auth_netlink.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface definitions shared with userspace + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_AUTHENT_NETLINK_H_ +#define __USB_AUTHENT_NETLINK_H_ + +#define USBAUTH_GENL_NAME "usb_auth_genl" +#define USBAUTH_GENL_VERSION 1 + +/* Attributes */ +enum usbauth_genl_attrs { + USBAUTH_A_REQ_ID =3D 1, + USBAUTH_A_DEV_ID, + USBAUTH_A_DIGEST, + USBAUTH_A_DIGESTS, + USBAUTH_A_SLOT_MASK, + USBAUTH_A_KNOWN, + USBAUTH_A_BLOCKED, + USBAUTH_A_VALID, + USBAUTH_A_CERTIFICATE, + USBAUTH_A_CERT_LEN, + USBAUTH_A_ROUTE, + USBAUTH_A_NONCE, + USBAUTH_A_CHALL, + USBAUTH_A_DESCRIPTOR, + USBAUTH_A_BOS, + USBAUTH_A_ERROR_CODE, + __USBAUTH_A_MAX, +}; + +#define USBAUTH_MAX_DESC_SIZE 1024 +#define USBAUTH_MAX_BOS_SIZE 1024 +#define USBAUTH_DIGEST_SIZE 32 +#define USBAUTH_NONCE_SIZE 32 +#define USBAUTH_CHALL_SIZE 204 +#define USBAUTH_DIGESTS_SIZE 256 +#define USBAUTH_MAX_CERT_SIZE 4096 + +#define USBAUTH_A_MAX (__USBAUTH_A_MAX - 1) + +/* Commands */ +enum usbauth_genl_cmds { + USBAUTH_CMD_REGISTER, + USBAUTH_CMD_CHECK_DIGEST, + USBAUTH_CMD_CHECK_CERTIFICATE, + USBAUTH_CMD_GEN_NONCE, + USBAUTH_CMD_CHECK_CHALL, + USBAUTH_CMD_RESP_DIGEST, + USBAUTH_CMD_RESP_CERTIFICATE, + USBAUTH_CMD_RESP_CREATE_DEV, + USBAUTH_CMD_RESP_GEN_NONCE, + USBAUTH_CMD_RESP_CHECK_CHALL, + __USBAUTH_CMD_MAX, +}; + +#define USBAUTH_CMD_MAX (__USBAUTH_CMD_MAX - 1) + +/* Error codes */ +#define USBAUTH_OK 0 +#define USBAUTH_INVRESP 1 + +#endif /* __USB_AUTHENT_NETLINK_H_ */ --=20 2.50.0 From nobody Tue Oct 7 08:35:47 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B10AD1B3925; Fri, 11 Jul 2025 09:06:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752224817; cv=none; b=lrCOg8Cullx25UBcaJpFsmx/jQy/ylbm/nLPq/zInyGohjqXL64BVvIzKTe3+gEtfp5/uWpmUNg2Wod+CdcKRZipaRwEvAUrgveU+xv1Xag+9TLqlWQtNGG2iOCzNaPS7XCJZ4JRUuTyZhJFt6X3uEvuuUQ+2aFbRViFipGFBVE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752224817; c=relaxed/simple; bh=gIOnfD6Ucu6cXq75rqww/mHyT3XMSlgr9ui4hu/G25A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=poYORdeHFc2ikTZksqY0ndmDlAmRiBtT/vRTYFOke7aXlP8FbHmAl/JJp+miHu26p0YjaURLrDWbmAW+rPFt4yABhk4Dy6mAtYcSKe6Mu8MDfCQVuWUQkm+mqPaeu9iRQkcTteKzycLd1xhbZGxk8dCi3xwKucu+Yw+HTSZaf0g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=oXk1KQ1L; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="oXk1KQ1L" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=IL+rfgs1JWscThNTsA76nW7lNRb+bG4ZaKPaQgB79Fs=; b=oXk1KQ1LbLlHA7GOW/SjRQTV6B +4FyKjAsgyTOqLTVbhb74B0YspQEijQOss87LdN7Hh5+EErvV3SMZnCmVtLx0lTlHLeYCDgKLjy7S HEllncKGnuSbLBgKl9OTG7kexVc0kP6sERex7fdz9EVHFnAu8yGb3fNuXWFuAm3S3M6DrcbCcrtOa RsDlNEgI5ar1VpVvJLjeZPPFyNqr/UwJXNUGuZrxMBiSI/LhMWqBKkL+4wgsSbPdcy6k9MtSNRb8y IPxg3PyBB5e1hnz66cBAi8KhRoDypSjY4KsE5hNaRDXtX3ul/sd7KJtdqpTGiB0I5HfyCYllIVyJf 2nrBt2oA==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:16749 helo=[10.224.9.2]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1ua9K6-0000000DVFH-3um2; Fri, 11 Jul 2025 10:41:36 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 11 Jul 2025 10:41:23 +0200 Subject: [RFC PATCH v2 2/4] usb: core: Introduce usb authentication feature Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250711-usb_authentication-v2-2-2878690e6b6d@ssi.gouv.fr> References: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> In-Reply-To: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Luc Bonnafoux , Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet This includes the usb authentication protocol implementation bulk exposed by the public usb_authenticate_device function. The protocol exchange is driven by the host and can be decomposed into three, mostly independent, phases: - The Host can request a digest of each certificate own by the peripheral. - If the Host does not recognize the peripheral from one of its digests, it can read one or more certificates from the device until a valid one is found. - The Host can issue an authentication challenge to the peripheral. The usb_authenticate_device function implements the usb authentication protocol. It implements the three phases of the protocol : First, it needs to communicate with the usb device in order to fetch its certificate digests (usb_authent_req_digest). Then if the device is unknown, the host fetches the device certificate chains (usb_authent_read_cert_part, usb_authent_read_certificate). Once at least a digest has been recognized or a certificate chain has been validated the host challenges the device in order to authenticate it (usb_authent_challenge_dev). It also needs to communicate with a policy engine using the following functions : usb_policy_engine_check_digest usb_policy_engine_check_cert_chain usb_policy_engine_generate_challenge usb_policy_engine_check_challenge Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/authent.c | 586 +++++++++++++++++++++++++++++++++++++++++= ++++ drivers/usb/core/authent.h | 178 ++++++++++++++ 2 files changed, 764 insertions(+) diff --git a/drivers/usb/core/authent.c b/drivers/usb/core/authent.c new file mode 100644 index 0000000000000000000000000000000000000000..9a2d4ef27ad26d802f2ba65cb8f= e72474eb93464 --- /dev/null +++ b/drivers/usb/core/authent.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication protocol implementation + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "authent_netlink.h" +#include "authent.h" + +/** + * usb_authent_req_digest() - Check if device is known via its digest + * + * @dev: [in] pointer to the usb device to query. + * @buffer: [in, out] buffer to hold request data. + * @digest: [out] device digest. + * @mask: [out] USB Authentication slot mask + * + * Context: task context, might sleep. + * + * This function sends a digest request to the usb device. + * + * Returns: + * * %0 - OK + * * %-ECOMM - Failed to send or received a message to the device + * * %-EINVAL - If buffer or mask is NULL + */ + +static int usb_authent_req_digest(struct usb_device *dev, u8 *const buffer, + u8 digest[USBAUTH_DIGESTS_SIZE], u8 *mask) +{ + int ret =3D 0; + struct usb_authent_digest_resp *digest_resp =3D NULL; + + if (buffer =3D=3D NULL || mask =3D=3D NULL) { + dev_err(&dev->dev, "invalid arguments\n"); + return -EINVAL; + } + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_DIGEST_REQ_TYPE, + 0, buffer, USBAUTH_DIGEST_RSP_SIZE, USB_CTRL_GET_TIMEOUT); + if (ret !=3D USBAUTH_DIGEST_RSP_SIZE) { + dev_err(&dev->dev, "Failed to get digest: %d\n", ret); + ret =3D -ECOMM; + goto exit; + } + + digest_resp =3D (struct usb_authent_digest_resp *)buffer; + *mask =3D digest_resp->slotMask; + memcpy(digest, digest_resp->digests, USBAUTH_DIGESTS_SIZE); + + ret =3D 0; + +exit: + + return ret; +} + +/* + * This structure is sent as is on USB BUS and thus needs to be packed. + */ +struct usb_auth_cert_req { + u16 offset; + u16 length; +} __packed; + +/** + * usb_auth_read_cert_part() - Request a specific part of a certificate c= hain from the device + * + * @dev: [in] handle to the USB device + * @buffer: [in,out] buffer used for communication, caller allocated + * @slot: [in] slot in which to read the certificate + * @offset: [in] offset at which the certificate fragment must be read + * @length: [in] length of the certificate fragment to read + * @cert_part: [out] buffer to hold the fragment, caller allocated + * + * Context: task context, might sleep + * + * Returns: + * * %x00 - OK + * * %-ECOMM - failed to send or receive a message to the device + * * %-EINVAL - if buffer or cert_part is NULL + */ +static int usb_auth_read_cert_part(struct usb_device *dev, u8 *const buffe= r, + const u8 slot, const u16 offset, + const u16 length, u8 *cert_part) +{ + struct usb_auth_cert_req cert_req =3D {0}; + int ret =3D -1; + + if (buffer =3D=3D NULL || cert_part =3D=3D NULL) { + dev_err(&dev->dev, "invalid argument\n"); + return -EINVAL; + } + + cert_req.offset =3D cpu_to_le16(offset); + cert_req.length =3D cpu_to_le16(length); + + memcpy(buffer, &cert_req, sizeof(struct usb_auth_cert_req)); + ret =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), AUTH_OUT, + USB_DIR_OUT, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CERTIFICATE_REQ_TYPE, + (slot << 8), buffer, + sizeof(struct usb_auth_cert_req), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + dev_err(&dev->dev, "Failed to send certificate request: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CERTIFICATE_RESP_TYPE, + (slot << 8), buffer, length + 4, + USB_CTRL_GET_TIMEOUT); + if (ret !=3D (length + 4)) { + dev_err(&dev->dev, "Failed to get certificate from peripheral: %d\n", re= t); + ret =3D -ECOMM; + goto cleanup; + } + + /* TODO: parse received header */ + memcpy(cert_part, buffer + 4, length); + + ret =3D 0; + +cleanup: + + return ret; +} + +/** + * usb_authent_read_certificate() - Read a device certificate + * + * @dev: [in] pointer to the usb device to query + * @buffer: [in, out] buffer to hold request data, caller allocated + * @slot: [in] certificate chain to be read + * @cert_der: [out] buffer to hold received certificate chain + * @cert_len: [out] length of received certificate + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - NULL pointer or invalid slot value + * * %-ECOMM - failed to send request to device + * * %-ENOMEM - failed to allocate memory for certificate + * + */ +static int usb_authent_read_certificate(struct usb_device *dev, u8 *const = buffer, + u8 slot, u8 **cert_der, size_t *cert_len) +{ + u16 read_offset =3D 0; + u16 read_length =3D 0; + u8 chain_part[64] =3D {0}; + + if (slot >=3D 8 || buffer =3D=3D NULL || cert_der =3D=3D NULL || cert_len= =3D=3D NULL) { + dev_err(&dev->dev, "invalid arguments\n"); + return -EINVAL; + } + + if (usb_auth_read_cert_part(dev, buffer, slot, 0, + USBAUTH_CHAIN_HEADER_SIZE, + chain_part) !=3D 0) { + dev_err(&dev->dev, "Failed to get first certificate part\n"); + return -ECOMM; + } + + *cert_len =3D le16_to_cpu(((u16 *)chain_part)[0]); + + *cert_der =3D kzalloc(*cert_len, GFP_KERNEL); + if (!(*cert_der)) + return -ENOMEM; + + memcpy(*cert_der, chain_part, USBAUTH_CHAIN_HEADER_SIZE); + read_offset =3D USBAUTH_CHAIN_HEADER_SIZE; + + while (read_offset < *cert_len) { + read_length =3D (*cert_len - read_offset) >=3D 64 ? 64 : (*cert_len - re= ad_offset); + + if (usb_auth_read_cert_part(dev, buffer, slot, read_offset, + read_length, chain_part) !=3D 0) { + dev_err(&dev->dev, "USB AUTH: Failed to get certificate part\n"); + return -ECOMM; + } + + memcpy(*cert_der + read_offset, chain_part, read_length); + read_offset +=3D read_length; + } + + return 0; +} + +/** + * usb_authent_challenge_dev() - Challenge a device + * + * @dev: [in] pointer to the usb device to query + * @buffer: [in] pointer to the buffer allocated for USB query + * @slot: [in] certificate chain to be used + * @slot_mask: [in] slot mask of the device + * @nonce: [in] nonce to use for the challenge, 32 bytes long + * @chall: [out] buffer for chall response, 204 bytes long, caller allocat= ed + * + * Context: task context, might sleep. + * + * Returns: + * * %0 - OK + * * %-EINVAL - NULL input pointer or invalid slot value + * * %-ECOMM - failed to send or receive message from the device + */ +static int usb_authent_challenge_dev(struct usb_device *dev, u8 *buffer, + const u8 slot, const u8 slot_mask, const u8 *const nonce, + u8 *const chall) +{ + int ret =3D -1; + + if (buffer =3D=3D NULL || slot >=3D 8 || nonce =3D=3D NULL) { + dev_err(&dev->dev, "invalid arguments\n"); + return -EINVAL; + } + + memcpy(buffer, nonce, USBAUTH_NONCE_SIZE); + ret =3D usb_control_msg(dev, usb_sndctrlpipe(dev, 0), AUTH_OUT, + USB_DIR_OUT, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CHALLENGE_REQ_TYPE, + (slot << 8), buffer, USBAUTH_NONCE_SIZE, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + dev_err(&dev->dev, "Failed to send challenge request: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + ((struct usb_chall_req_hd *) chall)->protocolVersion =3D USB_SECURITY_PRO= TOCOL_VERSION; + ((struct usb_chall_req_hd *) chall)->messageType =3D USB_AUTHENT_CHALLENG= E_REQ_TYPE; + ((struct usb_chall_req_hd *) chall)->slotNumber =3D slot; + ((struct usb_chall_req_hd *) chall)->reserved =3D 0x00; + memcpy(chall+4, nonce, USBAUTH_NONCE_SIZE); + + ret =3D usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), AUTH_IN, USB_DIR_IN, + (USB_SECURITY_PROTOCOL_VERSION << 8) + + USB_AUTHENT_CHALLENGE_RESP_TYPE, + (slot << 8) + slot_mask, buffer, USBAUTH_CHALL_RSP_SIZE, + USB_CTRL_GET_TIMEOUT); + if (ret !=3D USBAUTH_CHALL_RSP_SIZE) { + dev_err(&dev->dev, "Failed to get challenge response: %d\n", ret); + ret =3D -ECOMM; + goto cleanup; + } + + memcpy(chall + USBAUTH_CHAIN_HEADER_SIZE, buffer, USBAUTH_CHALL_RSP_SIZE); + ret =3D 0; + +cleanup: + + return ret; +} + +/** + * usb_auth_create_dev_ctx() - Create a device context according to USB Ty= pe-C Authentication Specification, chapter 5.5 + * + * @dev: [in] handle to the USB device + * @ctx: [in, out] buffer to hold the device context, caller allocated + * @buf_size: [in] available size in the context buffer + * @ctx_size: [out] total size of the context if return equals 0 + * + * The device context is composed of : + * 1. Device Descriptor + * 2. Complete BOS Descriptor (if present) + * 3. Complete Configuration 1 Descriptor + * 4. Complete Configuration 2 Descriptor (if present) + * 5. ... + * 6. Complete Configuration n Descriptor (if present) + * + * FIXME: Ensure the validity of the device context is complete: + * - Will the config order consistent ? + * - Do we need to also get the sub configuration strings ? + * + * Returns: + * * %0 - OK + * * %-EINVAL - invalid dev, ctx or size + * + */ +static int usb_auth_create_dev_ctx(struct usb_device *dev, u8 *ctx, + const size_t buf_size, size_t *ctx_size) +{ + int cfgno =3D 0; + int desc_size =3D 0; + + if (dev =3D=3D NULL || ctx =3D=3D NULL || ctx_size =3D=3D NULL) { + dev_err(&dev->dev, "invalid inputs\n"); + return -EINVAL; + } + + *ctx_size =3D 0; + + if (buf_size < (size_t)dev->descriptor.bLength) { + dev_err(&dev->dev, "buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx, (void *) &dev->descriptor, (size_t) dev->descriptor.bLength); + *ctx_size +=3D (size_t) dev->descriptor.bLength; + + if (dev->bos =3D=3D NULL || dev->bos->desc =3D=3D NULL) { + dev_err(&dev->dev, "invalid BOS\n"); + return -EINVAL; + } + + desc_size =3D le16_to_cpu(dev->bos->desc->wTotalLength); + if (buf_size < (*ctx_size + desc_size)) { + dev_err(&dev->dev, "buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx + (*ctx_size), (void *) dev->bos->desc, desc_size); + *ctx_size +=3D desc_size; + + if (dev->config =3D=3D NULL) { + dev_err(&dev->dev, "invalid configuration\n"); + return -EINVAL; + } + + for (cfgno =3D 0; cfgno < dev->descriptor.bNumConfigurations; cfgno++) { + desc_size =3D le16_to_cpu(dev->config[cfgno].desc.wTotalLength); + + if (buf_size < (*ctx_size + desc_size)) { + dev_err(&dev->dev, "buffer too small\n"); + return -EINVAL; + } + + memcpy(ctx + (*ctx_size), (void *) &dev->config[cfgno].desc, USB_DT_CONF= IG_SIZE); + *ctx_size +=3D USB_DT_CONFIG_SIZE; + } + + return 0; +} + +/** + * usb_auth_try_resume() - Check that the authentication can resume after = a sleep + * + * @dev: [in] the usb device + * @hub: [in] the parent hub + * + * Returns: + * * %0 - OK + * * %-ENODEV - hub has been disconnected + * + */ +static int usb_auth_try_resume(struct usb_device *dev, struct usb_device *= hub) +{ + if (hub =3D=3D NULL || dev =3D=3D NULL || + dev->port_is_suspended =3D=3D 1 || + dev->reset_in_progress =3D=3D 1) { + return -ENODEV; + } + + /* + * TODO: test if the device has not been disconnected + * TODO: test if the device has not been disconnected then replaced with = another one + */ + + return 0; +} + +static bool usb_has_authentication_capability(const struct usb_device *con= st dev) +{ + return dev->bos && dev->bos->authent_cap; +} + +/** + * usb_authenticate_device() - Challenge a device + * + * @dev: [in, out] pointer to device + * + * Authentication is done in the following steps: + * 1. Get device certificates digest to determine if it is already known + * if yes, go to 3. + * 2. Get device certificates + * 3. Challenge device + * 4. Based on previous result, determine if device is allowed under local + * security policy. + * + * Context: task context, might sleep. + * TODO: complete all possible error case. + * TODO: handle root hub device. + * + * Returns: + * * %0 - OK + * * %-ENOMEM - failed to allocate memory for exchange + * + */ +int usb_authenticate_device(struct usb_device *dev) +{ + int ret =3D 0; + u8 is_valid =3D 0; + u8 is_known =3D 0; + u8 is_blocked =3D 0; + u8 chain_nb =3D 0; + u8 slot_mask =3D 0; + u8 slot =3D 0; + u8 digests[USBAUTH_DIGESTS_SIZE] =3D {0}; + u8 nonce[USBAUTH_NONCE_SIZE] =3D {0}; + u8 chall[USBAUTH_CHALL_SIZE] =3D {0}; + u32 dev_id =3D 0; + size_t ctx_size =3D 0; + int i =3D 0; + + u8 *cert_der =3D NULL; + u8 *buffer =3D NULL; + size_t cert_len =3D 0; + + if (dev =3D=3D NULL) + return -ENODEV; + + dev->authenticated =3D 0; + if (!usb_has_authentication_capability(dev)) { + dev_notice(&dev->dev, "No authentication capability\n"); + goto cleanup; + } + + if (dev->parent =3D=3D NULL) + return -ENODEV; + + struct usb_device *hub =3D dev->parent; + + buffer =3D kzalloc(512, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + ret =3D usb_authent_req_digest(dev, buffer, digests, &slot_mask); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to get digest: %d\n", ret); + goto cleanup; + } + + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_digest(dev->route, digests, slot_mask, + &is_known, &is_blocked, &dev_id); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to check digest: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + dev_info(&dev->dev, "waking up\n"); + usb_lock_device(hub); + ret =3D usb_auth_try_resume(dev, hub); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to resume: %d\n", ret); + goto cleanup; + } + + if (is_known) + goto device_known; + + /* + * If device is not already known try to obtain a valid certificate + * Iterate over every device certificate slots, it gets them one by one + * in order to avoid spamming the device. + */ + if (!is_known) { + for (i =3D 0; i < 8; i++) { + if (1 =3D=3D ((slot_mask >> i) & 1)) { + ret =3D usb_authent_read_certificate(dev, buffer, + chain_nb, + &cert_der, + &cert_len); + if (ret !=3D 0) { + goto cleanup; + } + + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_cert_chain( + dev->route, digests + i * USBAUTH_DIGEST_SIZE, cert_der, + cert_len, &is_valid, &is_blocked, + &dev_id); + if (ret !=3D 0) { + dev_err(&dev->dev, + "failed to validate certificate: %d\n", + ret); + usb_lock_device(hub); + goto cleanup; + } + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to resume: %d\n", ret); + goto cleanup; + } + + if (is_valid) { + slot =3D i; + goto device_known; + } + } + } + goto done; + } else { + for (i =3D 0; i < 8; i++) { + if (1 =3D=3D ((is_known >> i) & 1)) { + slot =3D i; + break; + } + } + } + +device_known: + /* + * Device is known, authenticate the device with a challenge request + */ + usb_unlock_device(hub); + ret =3D usb_policy_engine_generate_challenge(dev_id, nonce); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to generate challenge: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to resume: %d\n", ret); + goto cleanup; + } + + ret =3D usb_authent_challenge_dev(dev, buffer, slot, slot_mask, nonce, + chall); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to challenge device: %d\n", ret); + goto cleanup; + } + + ret =3D usb_auth_create_dev_ctx(dev, buffer, 512, &ctx_size); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to create context: %d\n", ret); + goto cleanup; + } + + usb_unlock_device(hub); + ret =3D usb_policy_engine_check_challenge(dev_id, chall, buffer, ctx_size, + &is_valid); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to check challenge: %d\n", ret); + usb_lock_device(hub); + goto cleanup; + } + usb_lock_device(hub); + + ret =3D usb_auth_try_resume(dev, hub); + if (ret !=3D 0) { + dev_err(&dev->dev, "failed to resume: %d\n", ret); + goto cleanup; + } + +done: + ret =3D 0; + if (is_valid) { + dev->authenticated =3D 1; + } else { + dev->authenticated =3D 0; + dev_err(&dev->dev, "Device authentication failure\n"); + } +cleanup: + kfree(buffer); + kfree(cert_der); + + return ret; +} diff --git a/drivers/usb/core/authent.h b/drivers/usb/core/authent.h new file mode 100644 index 0000000000000000000000000000000000000000..2cf6d577131084a97f5c30fadaa= ce1eac7e83c11 --- /dev/null +++ b/drivers/usb/core/authent.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication protocol definition + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#ifndef __USB_CORE_AUTHENT_H_ +#define __USB_CORE_AUTHENT_H_ + +#include +#include +#include +#include +#include + +/* From USB Type-C Authentication spec, Table 5-2 */ +#define USB_AUTHENT_CAP_TYPE 0x0e + +/* From USB Security Foundation spec, Table 5-2 */ +#define USB_SECURITY_PROTOCOL_VERSION 0x10 + +#define AUTH_IN 0x18 +#define AUTH_OUT 0x19 + +/* USB_DT_AUTHENTICATION_CAP */ +struct usb_authent_cap_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDevCapabilityType; /* Shall be set to USB_AUTHENT_CAP_TYPE */ + /* + * bit 0: set to 1 if firmware can be updated + * bit 1: set to 1 to indicate the Device changes interface when updated + * bits 2-7: reserved, set to 0 + */ + __u8 bmAttributes; + __u8 bcdProtocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSIO= N */ + __u8 bcdCapability; /* Set to 0x01 */ + +} __packed; + +/* Certificate chain header, Table 3-1 */ +struct usb_cert_chain_hd { + __u16 length; /* Chain total length including header, little endian */ + __u16 reserved; /* Shall be set to zero */ + __u8 rootHash[32]; /* Hash of root certificate, big endian */ +} __packed; + +/* Challenge request header, Table 5-7 */ +struct usb_chall_req_hd { + __u8 protocolVersion; + __u8 messageType; + __u8 slotNumber; + __u8 reserved; +}; + +/* From USB Security Foundation spec, Table 5-3 and Table 5-9 */ +#define USB_AUTHENT_DIGEST_RESP_TYPE 0x01 +#define USB_AUTHENT_CERTIFICATE_RESP_TYPE 0x02 +#define USB_AUTHENT_CHALLENGE_RESP_TYPE 0x03 +#define USB_AUTHENT_ERROR_TYPE 0x7f +#define USB_AUTHENT_DIGEST_REQ_TYPE 0x81 +#define USB_AUTHENT_CERTIFICATE_REQ_TYPE 0x82 +#define USB_AUTHENT_CHALLENGE_REQ_TYPE 0x83 + +#define USBAUTH_DIGEST_RSP_SIZE 260 +#define USBAUTH_CHALL_RSP_SIZE 168 +#define USBAUTH_CHAIN_HEADER_SIZE 36 + +/* USB Authentication GET_DIGEST Request Header */ +struct usb_authent_digest_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_DIGEST_REQ_TYPE */ + __u8 param1; /* Reserved */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication GET_CERTIFICATE Request Header */ +struct usb_authent_certificate_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication GET_CERTIFICATE Request */ +struct usb_authent_certificate_req { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ + __u16 offset; /* Read index of Certificate Chain in bytes and little endi= an*/ + __u16 length; /* Length of read request, little endian */ +} __packed; + +/* USB Authentication CHALLENGE Request Header */ +struct usb_authent_challenge_req_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication CHALLENGE Request Header */ +struct usb_authent_challenge_req { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_REQ_TYPE */ + __u8 certChainSlotNumber; /* Must be between 0 and 7 inclusive */ + __u8 param2; /* Reserved */ + __u32 nonce; /* Random Nonce chosen for the challenge */ +} __packed; + +/* USB Authentication DIGEST response Header */ +struct usb_authent_digest_resp { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_DIGEST_RESP_TYPE */ + __u8 capability; /* Shall be set to 0x01 */ + __u8 slotMask; /* Bit set to 1 if slot is set, indicates number of digest= s */ + __u8 digests[8][32]; /* List of digests */ +} __packed; + +/* USB Authentication CERTIFICATE response Header */ +struct usb_authent_certificate_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CERTIFICATE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 param2; /* Reserved */ +} __packed; + +/* USB Authentication CHALLENGE response Header */ +struct usb_authent_challenge_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 slotMask; /* Bit set to 1 if slot is set */ +} __packed; + +/* USB Authentication CHALLENGE response */ +struct usb_authent_challenge_resp { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_CHALLENGE_RESP_TYPE */ + __u8 slotNumber; /* Slot number of certificate chain returned */ + __u8 slotMask; /* Bit set to 1 if slot is set */ + __u8 minProtocolVersion; + __u8 maxProtocolVersion; + __u8 capabilities; /* Shall be set to 0x01 */ + __u8 orgName; /* Organisation Name, USB-IF: 0 */ + __u32 certChainHash; /* SHA256 digest of certificate chain, big endian */ + __u32 salt; /* Chosen by responder */ + __u32 contextHash; /* SHA256 digest of product information, big endian */ + __u64 signature; /* ECDSA signature of request and response */ +} __packed; + +/* USB Authentication error codes, Foundation Table 5-18 */ +#define USB_AUTHENT_INVALID_REQUEST_ERROR 0x01 +#define USB_AUTHENT_UNSUPPORTED_PROTOCOL_ERROR 0x02 +#define USB_AUTHENT_BUSY_ERROR 0x03 +#define USB_AUTHENT_UNSPECIFIED_ERROR 0x04 + +/* USB Authentication response header */ +struct usb_authent_error_resp_hd { + __u8 protocolVersion; /* Shall be set to USB_SECURITY_PROTOCOL_VERSION */ + __u8 messageType; /* Shall be set to USB_AUTHENT_ERROR_TYPE */ + __u8 errorCode; + __u8 errorData; +} __packed; + +#ifdef CONFIG_USB_AUTHENTICATION +int usb_authenticate_device(struct usb_device *dev); +#else +static inline int usb_authenticate_device(struct usb_device *dev) { return= 0; } +#endif + +#endif /* __USB_CORE_AUTHENT_H_ */ --=20 2.50.0 From nobody Tue Oct 7 08:35:47 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 68C6E148FE6; Fri, 11 Jul 2025 08:41:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752223307; cv=none; b=YMFpcUFDHzUC+SKXPzw+6K+U1uwJcQtMFECxbMGwZTKrR5at51Mw0FV6QviHwCSADz6TIm23llytwSOaGdK3CugKlXvnWsZfiNdhDwJz5nYBA4TdpNhn41eNrBeblOwThoLYQrhJywm5gWJssatolN2Akn6e+lk/B0rs+BCY044= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752223307; c=relaxed/simple; bh=aq2NFXtFhdvdGO529/0s8V4wILV4P+WzR0aD43M2I2U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YN61JGuOSYJrxegyp/SE3AMDOjvNHXqP5bwbwZPlpjnzLHp1orlB2fQDoqDIkiGnYI3Elqih52UVVgbF4spiy97vb7xkEyUpBSyGmGHIJgH9AcEAAeQUoK5Ko6TPEPeuvFq5DfuxMQ3wndzud3nYpSv3XDxwCN+DMkucDKj2gw4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=lxLITDer; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="lxLITDer" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=b4Duk1ArXxdluAY87Blb383NUqI6E02pnWIrXqcJ6pM=; b=lxLITDertU5X2kyCAzaqBQvTeZ rYP8Ngj2ntEfr+c85+54f0G0q0lSrL3+d3M2gbAFbZnVfjw3slZEaiFwXWwUmzZdjpFcNjHexGPFu zeoRrllDg5xnfR3wzDI3k7Oq/qBtal8xjqArHIPwR+RET82QcMnIB9zPb/zvcr5WRUGPXCrVZbk9H q9itu7YzBzpjeRXiUU/eRFGR1HbKcX1z4TudFKIAKji2MokP0rtpsDghVA0QpTpzhKiJnheDN0WhC f7OH/7PLSpIw0wLqM3Fa2w98NhEBxnZEJ6SKtKMnb7YmyHkk6sklGN/oLrjymkqA5Z6D5MESsBd3d synugDtw==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:16749 helo=[10.224.9.2]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1ua9K7-0000000DVFH-3gDp; Fri, 11 Jul 2025 10:41:37 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 11 Jul 2025 10:41:24 +0200 Subject: [RFC PATCH v2 3/4] usb: core: Plug the usb authentication capability Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250711-usb_authentication-v2-3-2878690e6b6d@ssi.gouv.fr> References: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> In-Reply-To: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Luc Bonnafoux , Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet The authentication bulk is implemented by the usb_authenticate_device function. The usb_authenticate_device returns 0 or an error code. If 0 is returned, a per-device authenticated field is updated with the result of the authentication. The authenticated field can be used to track the result of the authentication process in userspace thanks to a sysfs exposed file. The device enforcement point is done in the usb_probe_interface() function. This allows for more complex security policy in userspace: the user could manually authorize a device that failed the authentication or manually deauthorize a device that was previously authenticated. If set to false (default), the authentication decision is not enforced, the device authentication field is set for userspace information. The legacy kernel authorization decision is used: +----------+------------+-------------+ | | authorized | !authorized | +----------+------------+-------------+ | authent | OK | NOK | +----------+------------+-------------+ | !authent | OK | NOK | +----------+------------+-------------+ If set to true, the authentication decision is enforced, the following decision is made: +----------+------------+-------------+ | | authorized | !authorized | +----------+------------+-------------+ | authent | OK | NOK | +----------+------------+-------------+ | !authent | NOK | NOK | +----------+------------+-------------+ Note that combined with the CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=3D2: - internal devices should be authorized and !authenticated =3D> OK - external qemu dev-auth is !authorized and authenticated =3D> NOK at first but then authorization can be granted via sysfs. - external qemu non auth dev is !authorized and !authenticated =3D> NOK and authorization can be granted via sysfs The default enforcement decision can be configured thanks to the new USB_AUTHENTICATION_ENFORCE configuration option and can be overridden using the usbcore.enforce_authentication command line or module parameter. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- Documentation/usb/authentication.rst | 15 +++++++++++++++ Documentation/usb/index.rst | 1 + drivers/usb/core/Kconfig | 26 ++++++++++++++++++++++++++ drivers/usb/core/Makefile | 4 ++++ drivers/usb/core/config.c | 22 ++++++++++++++++++++-- drivers/usb/core/driver.c | 31 +++++++++++++++++++++++++++++++ drivers/usb/core/hub.c | 5 +++++ drivers/usb/core/sysfs.c | 16 ++++++++++++++++ drivers/usb/core/usb.c | 8 ++++++++ include/linux/usb.h | 2 ++ 10 files changed, 128 insertions(+), 2 deletions(-) diff --git a/Documentation/usb/authentication.rst b/Documentation/usb/authe= ntication.rst new file mode 100644 index 0000000000000000000000000000000000000000..828a7f7b07017a684fa039224fd= efd6d07a78ec1 --- /dev/null +++ b/Documentation/usb/authentication.rst @@ -0,0 +1,15 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Linux usb authentication documentation +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +usb authentication +------------------ + +.. kernel-doc:: drivers/usb/core/authent.c + :internal: + +usb authentication netlink API +------------------------------ + +.. kernel-doc:: drivers/usb/core/authent_netlink.c + :internal: diff --git a/Documentation/usb/index.rst b/Documentation/usb/index.rst index 826492c813acd6ec5162c97312ff9a2a4b27633a..9ebf7ef52c3dadd0c2e099ed357= 0d67109e94e43 100644 --- a/Documentation/usb/index.rst +++ b/Documentation/usb/index.rst @@ -7,6 +7,7 @@ USB support =20 acm authorization + authentication chipidea dwc3 ehci diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 58e3ca7e479392112f656384c664efc36bb1151a..31dace7e2e794f0f31f77d53bc3= c7f3c5339b4ca 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -143,3 +143,29 @@ config USB_DEFAULT_AUTHORIZATION_MODE ACPI selecting value 2 is analogous to selecting value 0. =20 If unsure, keep the default value. + +config USB_AUTHENTICATION + bool "Enable USB authentication function" + depends on USB + help + Enables the USB Authentication function. This activates the + hook points in the USB stack. + +config USB_AUTHENTICATION_ENFORCE + bool "Default authentication mode for USB devices" + depends on USB && USB_AUTHENTICATION + help + Select the default USB device authentication mode. Can be overridden + with usbcore.enforce_authentication command line or module parameter. + + This option allows you to choose whether USB devices that are + connected to the system needs to be authenticated, or if they are + locked down. + + With value 1 all connected USB devices with the exception of root hub + require device authentication before they can be used. + + With value 0 (default) no device authentication is required to use + connected USB devices. + + If unsure, keep the default value. diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index ac006abd13b3ad8362dc7baa115124c11eaafc84..7ba1a89cf3de7a398889eee1820= f2bfbbc4280f5 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -8,6 +8,10 @@ usbcore-y +=3D config.o file.o buffer.o sysfs.o endpoint.o usbcore-y +=3D devio.o notify.o generic.o quirks.o devices.o usbcore-y +=3D phy.o port.o =20 +ifdef CONFIG_USB_AUTHENTICATION +usbcore-y +=3D authent.o authent_netlink.o +endif + usbcore-$(CONFIG_OF) +=3D of.o usbcore-$(CONFIG_USB_PCI) +=3D hcd-pci.o usbcore-$(CONFIG_ACPI) +=3D usb-acpi.o diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 13bd4ec4ea5f7a6fef615b6f50b1acb3bbe44ba4..bbb835043a0d452c0e8305ac90b= 5e99cfb94ae24 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -13,7 +13,7 @@ #include #include #include "usb.h" - +#include "authent.h" =20 #define USB_MAXALTSETTING 128 /* Hard limit */ =20 @@ -824,7 +824,21 @@ static int usb_parse_configuration(struct usb_device *= dev, int cfgidx, kref_init(&intfc->ref); } =20 - /* FIXME: parse the BOS descriptor */ + /* If device USB version is above 2.0, get BOS descriptor + * + * Requirement for bcdUSB >=3D 2.10 is defined in USB 3.2 =C2=A79.2.6.6 + * "Devices with a value of at least 0210H in the bcdUSB field of their + * device descriptor shall support GetDescriptor (BOS Descriptor) request= s." + * + * To discuss, BOS request could be also sent for bcdUSB >=3D 0x0201 + */ + + if (le16_to_cpu(dev->descriptor.bcdUSB) >=3D 0x0201) { + dev_notice(ddev, "bcdUSB >=3D 0x0201\n"); + retval =3D usb_get_bos_descriptor(dev); + if (retval < 0) + dev_notice(ddev, "Device does not have a BOS descriptor\n"); + } =20 /* Skip over any Class Specific or Vendor Specific descriptors; * find the first interface descriptor */ @@ -1122,6 +1136,10 @@ int usb_get_bos_descriptor(struct usb_device *dev) dev->bos->ptm_cap =3D (struct usb_ptm_cap_descriptor *)buffer; break; + case USB_AUTHENT_CAP_TYPE: + dev->bos->authent_cap =3D + (struct usb_authent_cap_descriptor *)buffer; + break; default: break; } diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 460d4dde5994e783bdcad08b2abb6bb85ab3258f..d393691aced3cedd23bd359a79e= 7158b41f2729c 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -34,6 +34,14 @@ =20 #include "usb.h" =20 +#ifdef CONFIG_USB_AUTHENTICATION_ENFORCE +static int enforce_authentication =3D CONFIG_USB_AUTHENTICATION_ENFORCE; +module_param(enforce_authentication, int, S_IRUGO); +MODULE_PARM_DESC(enforce_authentication, + "Default USB device authentication enforcement mode: 0 unauthenticated d= evices can be authorized, 1 enforces authentication for all devices"); +#else +static int enforce_authentication =3D 0; +#endif =20 /* * Adds a new dynamic USBdevice ID to this driver, @@ -331,15 +339,38 @@ static int usb_probe_interface(struct device *dev) if (usb_device_is_owned(udev)) return error; =20 + /* Simple security policy + * + * +----------+------------+-------------+ + * | | authorized | !authorized | + * +----------+------------+-------------+ + * | authent | OK | NOK | + * +----------+------------+-------------+ + * | !authent | NOK | NOK | + * +----------+------------+-------------+ + * + * with CONFIG_USB_DEFAULT_AUTHORIZATION_MODE=3D2 + * - internal devices should be authorized and !authenticated =3D> OK + * - external qemu dev-auth is !authorized and authenticated =3D> NOK a= t first + * but then authorization can be granted via sysfs. + * - external qemu non auth dev is !authorized and !authenticated =3D> N= OK and + * authorization can be granted via sysfs + * + */ if (udev->authorized =3D=3D 0) { dev_err(&intf->dev, "Device is not authorized for usage\n"); return error; + } else if (udev->authenticated =3D=3D 0 && enforce_authentication =3D=3D = 1) { + dev_err(&intf->dev, "Device is not autenticated for usage\n"); + return error; } else if (intf->authorized =3D=3D 0) { dev_err(&intf->dev, "Interface %d is not authorized for usage\n", intf->altsetting->desc.bInterfaceNumber); return error; } =20 + dev_info(&intf->dev, "Device has been authorized for usage\n"); + id =3D usb_match_dynamic_id(intf, driver); if (!id) id =3D usb_match_id(intf, driver->id_table); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0e1dd6ef60a719f59a22d86caeb20f86991b5b29..088dba8355f850b4cb28863116e= 3654100bb86e5 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -40,6 +40,7 @@ #include "hub.h" #include "phy.h" #include "otg_productlist.h" +#include "authent.h" =20 #define USB_VENDOR_GENESYS_LOGIC 0x05e3 #define USB_VENDOR_SMSC 0x0424 @@ -2631,6 +2632,10 @@ int usb_new_device(struct usb_device *udev) usb_disable_autosuspend(udev); =20 err =3D usb_enumerate_device(udev); /* Read descriptors */ + if (err < 0) + goto fail; + + err =3D usb_authenticate_device(udev); if (err < 0) goto fail; dev_dbg(&udev->dev, "udev %d, busnum %d, minor =3D %d\n", diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 23f3cb1989f408ecb6b679e6cbe4857384188ae4..53ad9ffdbc4498b1501677fd2ec= 336dc22c7ce6a 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -529,6 +529,7 @@ static ssize_t usb2_lpm_l1_timeout_show(struct device *= dev, char *buf) { struct usb_device *udev =3D to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->l1_params.timeout); } =20 @@ -552,6 +553,7 @@ static ssize_t usb2_lpm_besl_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_device *udev =3D to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->l1_params.besl); } =20 @@ -731,6 +733,15 @@ static ssize_t authorized_show(struct device *dev, return sysfs_emit(buf, "%u\n", usb_dev->authorized); } =20 +/* show if the device is authenticated (1) or not (0) */ +static ssize_t authenticated_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *usb_dev =3D to_usb_device(dev); + + return sysfs_emit(buf, "%u\n", usb_dev->authenticated); +} + /* * Authorize a device to be used in the system * @@ -755,6 +766,10 @@ static ssize_t authorized_store(struct device *dev, static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, S_IRUGO | S_IWUSR, authorized_show, authorized_store); =20 + +static DEVICE_ATTR_IGNORE_LOCKDEP(authenticated, S_IRUGO, + authenticated_show, NULL); + /* "Safely remove a device" */ static ssize_t remove_store(struct device *dev, struct device_attribute *a= ttr, const char *buf, size_t count) @@ -805,6 +820,7 @@ static struct attribute *dev_attrs[] =3D { &dev_attr_quirks.attr, &dev_attr_avoid_reset_quirk.attr, &dev_attr_authorized.attr, + &dev_attr_authenticated.attr, &dev_attr_remove.attr, &dev_attr_ltm_capable.attr, #ifdef CONFIG_OF diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 0b4685aad2d50337f3cacb2198c95a68ae8eff86..421cec9966912ccc62ce163733f= 46cab05503bd6 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -46,6 +46,7 @@ #include =20 #include "hub.h" +#include "authent_netlink.h" =20 const char *usbcore_name =3D "usbcore"; =20 @@ -1080,6 +1081,13 @@ static int __init usb_init(void) usb_debugfs_init(); =20 usb_acpi_register(); + +#ifdef CONFIG_USB_AUTHENTICATION + retval =3D usb_auth_init_netlink(); + if (retval) + goto hub_init_failed; +#endif + retval =3D bus_register(&usb_bus_type); if (retval) goto bus_register_failed; diff --git a/include/linux/usb.h b/include/linux/usb.h index b46738701f8dc46085f251379873b6a8a008d99d..e9037c8120b43556f8610f9acb3= ad4129e847b98 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -431,6 +431,8 @@ struct usb_host_bos { struct usb_ssp_cap_descriptor *ssp_cap; struct usb_ss_container_id_descriptor *ss_id; struct usb_ptm_cap_descriptor *ptm_cap; + /* Authentication capability */ + struct usb_authent_cap_descriptor *authent_cap; }; =20 int __usb_get_extra_descriptor(char *buffer, unsigned size, --=20 2.50.0 From nobody Tue Oct 7 08:35:47 2025 Received: from pf-012.whm.fr-par.scw.cloud (pf-012.whm.fr-par.scw.cloud [51.159.173.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E0E41198A11; Fri, 11 Jul 2025 08:41:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.159.173.17 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752223307; cv=none; b=X2tOOmVght4f5Rl6Zz9xglFfzvGIuUnTpOn8DL6Lq3xzYHM4mVyq2qGsqCVVhf7R5Qgh3nbMqNd0+dYIQ6d1Y493EcqRC/3Yem2TO8s2nSxJxlgNXxPlYXYoU43S/KMcENbKi2a2AqfvqqFnzBT+52PzMBlF+7qDZMQobhEqJsM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752223307; c=relaxed/simple; bh=zTHB4hJlY161JLI5sZk0fMz9boITT7j07uRbioWEq6w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=n3w3rlzmmDChshhFN/B5PfmqPvR3F1dPHlDlE8G34ejp1n2++EpJPlua4MESTdhxO70U/MKzZmE311LZ+yqMoov77LIyD+Kwv/tu605fxhT0SF2hjrZkmumAYM36SEQu3pgtk16uQ7rghhq2/P++CUdRdE7gKrBidx+Jq5Yakbo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr; spf=pass smtp.mailfrom=oss.cyber.gouv.fr; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b=bke9qQyZ; arc=none smtp.client-ip=51.159.173.17 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.cyber.gouv.fr Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oss.cyber.gouv.fr header.i=@oss.cyber.gouv.fr header.b="bke9qQyZ" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=oss.cyber.gouv.fr; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=bXZZPHhYhs3NROUrn2Pw9xRIp4mzednHSudkcwoo9VY=; b=bke9qQyZud7VsACxSw3v3nwMX8 0aNv7ALivbogtyDy6BKklWNMBayV1t0lnXEHuz34AOi5lYNQ4PnLf6vtHvHRQFRayhsfjDhnUIN10 XNXY+xTjhWznI5jK0byrXh8lzZ01p051uswtHKK1AIuf8Wu/hZZg6LM2GCeibBg8+/Dz010LjXVjw izCARQnnCtDYDmPsP1l7L4anwjRTgFNWr6D0iPo8WAxvhDJhNwHqfbk7mtY25NOAStHFT35cBT1vk 2WYD8ZevxteHt3iC0uv+QeW4VUipz20WX2xG4FQy/PShgVo+ewFh1cYp1h9w6HeRoXYHbtFo0oMhv wSIeZmug==; Received: from laubervilliers-658-1-215-187.w90-63.abo.wanadoo.fr ([90.63.246.187]:16749 helo=[10.224.9.2]) by pf-012.whm.fr-par.scw.cloud with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1ua9KD-0000000DVFH-2hsS; Fri, 11 Jul 2025 10:41:43 +0200 From: nicolas.bouchinet@oss.cyber.gouv.fr Date: Fri, 11 Jul 2025 10:41:25 +0200 Subject: [RFC PATCH v2 4/4] usb: core: Add sysctl to configure authentication timeouts Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250711-usb_authentication-v2-4-2878690e6b6d@ssi.gouv.fr> References: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> In-Reply-To: <20250711-usb_authentication-v2-0-2878690e6b6d@ssi.gouv.fr> To: Greg Kroah-Hartman Cc: Luc Bonnafoux , Alan Stern , Kannappan R , Sabyrzhan Tasbolatov , Krzysztof Kozlowski , Stefan Eichenberger , Thomas Gleixner , Pawel Laszczak , Ma Ke , Jeff Johnson , Luc Bonnafoux , Nicolas Bouchinet , linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org X-Mailer: b4 0.14.2 X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - pf-012.whm.fr-par.scw.cloud X-AntiAbuse: Original Domain - vger.kernel.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - oss.cyber.gouv.fr X-Get-Message-Sender-Via: pf-012.whm.fr-par.scw.cloud: authenticated_id: nicolas.bouchinet@oss.cyber.gouv.fr X-Authenticated-Sender: pf-012.whm.fr-par.scw.cloud: nicolas.bouchinet@oss.cyber.gouv.fr X-Source: X-Source-Args: X-Source-Dir: From: Nicolas Bouchinet The kernel.usb.authent_engine_register_timeout let a user configure in seconds the time the kernel will wait for a userspace usb authentication policy engine to register itself. The kernel.usb.authent_engine_response_timeout let a user configure in seconds the time the kernel will wait for the registered userspace usb authentication policy engine to reply to messages. Co-developed-by: Luc Bonnafoux Signed-off-by: Luc Bonnafoux Signed-off-by: Nicolas Bouchinet --- drivers/usb/core/Makefile | 1 + drivers/usb/core/authent.h | 14 ++++++++++ drivers/usb/core/authent_netlink.c | 26 +++++++++++------- drivers/usb/core/sysctl.c | 55 ++++++++++++++++++++++++++++++++++= ++++ drivers/usb/core/usb.c | 8 ++++++ include/linux/usb.h | 9 +++++++ 6 files changed, 104 insertions(+), 9 deletions(-) diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 7ba1a89cf3de7a398889eee1820f2bfbbc4280f5..2ec59764fe5ade682368890b4cd= 30bb3cdad7746 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -15,6 +15,7 @@ endif usbcore-$(CONFIG_OF) +=3D of.o usbcore-$(CONFIG_USB_PCI) +=3D hcd-pci.o usbcore-$(CONFIG_ACPI) +=3D usb-acpi.o +usbcore-$(CONFIG_SYSCTL) +=3D sysctl.o =20 ifdef CONFIG_USB_ONBOARD_DEV usbcore-y +=3D ../misc/onboard_usb_dev_pdevs.o diff --git a/drivers/usb/core/authent.h b/drivers/usb/core/authent.h index 2cf6d577131084a97f5c30fadaace1eac7e83c11..7c3264793c6ab5d44fa453b3b70= c41882c96ff0d 100644 --- a/drivers/usb/core/authent.h +++ b/drivers/usb/core/authent.h @@ -169,6 +169,20 @@ struct usb_authent_error_resp_hd { __u8 errorData; } __packed; =20 +extern uint usb_auth_wait_userspace_timeout; +extern uint usb_auth_wait_response_timeout; + +#define DEFAULT_USB_AUTHENT_WAIT_USERSPACE_TIMEOUT 30 +#define DEFAULT_USB_AUTHENT_WAIT_RESPONSE_TIMEOUT 300 + +#ifdef CONFIG_SYSCTL +extern int usb_register_sysctl(void); +extern void usb_unregister_sysctl(void); +#else +# define usb_auth_init_sysctl() (0) +# define usb_auth_exit_sysctl() do { } while (0) +#endif + #ifdef CONFIG_USB_AUTHENTICATION int usb_authenticate_device(struct usb_device *dev); #else diff --git a/drivers/usb/core/authent_netlink.c b/drivers/usb/core/authent_= netlink.c index 9848f219e0e4807563f0f0432a0f1108cd6a0454..731ecadee934ae712735dae5932= c0e595720245d 100644 --- a/drivers/usb/core/authent_netlink.c +++ b/drivers/usb/core/authent_netlink.c @@ -21,11 +21,15 @@ #include #include "authent.h" #include "authent_netlink.h" +#include "authent.h" =20 #define WAIT_USERSPACE_TIMEOUT 30 #define WAIT_RESPONSE_TIMEOUT 300 #define USBAUTH_MAX_RESP_SIZE 128 =20 +uint usb_auth_wait_userspace_timeout =3D DEFAULT_USB_AUTHENT_WAIT_USERSPAC= E_TIMEOUT; +uint usb_auth_wait_response_timeout =3D DEFAULT_USB_AUTHENT_WAIT_RESPONSE_= TIMEOUT; + /** * struct usb_auth_req - Define an outstanding request between the kernel = and userspace * @@ -609,7 +613,8 @@ int usb_policy_engine_check_digest(const u32 route, con= st u8 *const digests, return -EINVAL; } =20 - if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, + HZ * usb_auth_wait_userspace_timeout)) { pr_err("%s: userspace not available\n", __func__); return -ECOMM; } @@ -675,7 +680,7 @@ int usb_policy_engine_check_digest(const u32 route, con= st u8 *const digests, =20 if (!wait_event_timeout(usb_req_wq, usb_auth_outstanding_reqs[index].done =3D=3D 1, - HZ * WAIT_RESPONSE_TIMEOUT)) { + HZ * usb_auth_wait_response_timeout)) { pr_err("%s: userspace response not available\n", __func__); usb_auth_release_reqs_slot(index); return -ECOMM; @@ -740,7 +745,8 @@ int usb_policy_engine_check_cert_chain(const u32 route, return -EINVAL; } =20 - if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, + HZ * usb_auth_wait_userspace_timeout)) { pr_err("%s: userspace not available\n", __func__); return -ECOMM; } @@ -814,7 +820,7 @@ int usb_policy_engine_check_cert_chain(const u32 route, =20 if (!wait_event_timeout(usb_req_wq, usb_auth_outstanding_reqs[index].done =3D=3D 1, - HZ * WAIT_RESPONSE_TIMEOUT)) { + HZ * usb_auth_wait_response_timeout)) { pr_err("%s: userspace response not available\n", __func__); usb_auth_release_reqs_slot(index); return -ECOMM; @@ -852,8 +858,8 @@ int usb_policy_engine_generate_challenge(const u32 id, = u8 *nonce) struct sk_buff *skb =3D NULL; u32 index =3D 0; =20 - /* Arbitrary 30s wait before giving up */ - if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, + HZ * usb_auth_wait_userspace_timeout)) { pr_err("%s: userspace not available\n", __func__); return -ECOMM; } @@ -902,7 +908,7 @@ int usb_policy_engine_generate_challenge(const u32 id, = u8 *nonce) =20 if (!wait_event_timeout(usb_req_wq, usb_auth_outstanding_reqs[index].done =3D=3D 1, - HZ * WAIT_RESPONSE_TIMEOUT)) { + HZ * usb_auth_wait_response_timeout)) { pr_err("%s: userspace response not available\n", __func__); usb_auth_release_reqs_slot(index); return -ECOMM; @@ -953,7 +959,8 @@ int usb_policy_engine_check_challenge(const u32 id, return -EINVAL; } =20 - if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, HZ * WAIT_USERSPA= CE_TIMEOUT)) { + if (!wait_event_timeout(usb_req_wq, pol_eng_pid !=3D 0, + HZ * usb_auth_wait_userspace_timeout)) { pr_err("%s: userspace not available\n", __func__); return -ECOMM; } @@ -1016,9 +1023,10 @@ int usb_policy_engine_check_challenge(const u32 id, __func__, ret); return -ECOMM; } + if (!wait_event_timeout(usb_req_wq, usb_auth_outstanding_reqs[index].done =3D=3D 1, - HZ * WAIT_RESPONSE_TIMEOUT)) { + HZ * usb_auth_wait_response_timeout)) { pr_err("%s: userspace response not available\n", __func__); usb_auth_release_reqs_slot(index); return -ECOMM; diff --git a/drivers/usb/core/sysctl.c b/drivers/usb/core/sysctl.c new file mode 100644 index 0000000000000000000000000000000000000000..a9f917e34e8e914cb60653a56fa= 90a4790bf6011 --- /dev/null +++ b/drivers/usb/core/sysctl.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPDX-FileCopyrightText: (C) 2025 ANSSI + * + * USB Authentication netlink interface + * + * Author: Luc Bonnafoux + * Author: Nicolas Bouchinet + * + */ + +#include +#include +#include "authent.h" + +static const unsigned long max_ms =3D 3600; + +static const struct ctl_table usb_sysctls[] =3D { +#ifdef CONFIG_USB_AUTHENTICATION + { + .procname =3D "authent_engine_register_timeout", + .data =3D &usb_auth_wait_userspace_timeout, + .maxlen =3D sizeof(usb_auth_wait_userspace_timeout), + .mode =3D 0644, + .proc_handler =3D proc_douintvec_minmax, + .extra1 =3D SYSCTL_ZERO, + .extra2 =3D (void*)&max_ms, + }, + { + .procname =3D "authent_engine_response_timeout", + .data =3D &usb_auth_wait_response_timeout, + .maxlen =3D sizeof(usb_auth_wait_response_timeout), + .mode =3D 0644, + .proc_handler =3D proc_douintvec_minmax, + .extra1 =3D SYSCTL_ZERO, + .extra2 =3D (void*)&max_ms, + }, +#endif +}; + +static struct ctl_table_header *usb_sysctl_table; + +int __init usb_register_sysctl(void) +{ + usb_sysctl_table =3D register_sysctl("kernel/usb", usb_sysctls); + if (!usb_sysctl_table) + return -ENOMEM; + return 0; +} + +void usb_unregister_sysctl(void) +{ + unregister_sysctl_table(usb_sysctl_table); + usb_sysctl_table =3D NULL; +} diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 421cec9966912ccc62ce163733f46cab05503bd6..0d88a072146dc0ec88314733ae9= 2c835585f722d 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -46,6 +46,7 @@ #include =20 #include "hub.h" +#include "authent.h" #include "authent_netlink.h" =20 const char *usbcore_name =3D "usbcore"; @@ -1082,6 +1083,10 @@ static int __init usb_init(void) =20 usb_acpi_register(); =20 + retval =3D usb_register_sysctl(); + if (retval) + goto sysctl_init_failed; + #ifdef CONFIG_USB_AUTHENTICATION retval =3D usb_auth_init_netlink(); if (retval) @@ -1127,6 +1132,8 @@ static int __init usb_init(void) bus_notifier_failed: bus_unregister(&usb_bus_type); bus_register_failed: + usb_unregister_sysctl(); +sysctl_init_failed: usb_acpi_unregister(); usb_debugfs_cleanup(); out: @@ -1151,6 +1158,7 @@ static void __exit usb_exit(void) class_unregister(&usbmisc_class); bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_unregister(&usb_bus_type); + usb_unregister_sysctl(); usb_acpi_unregister(); usb_debugfs_cleanup(); idr_destroy(&usb_bus_idr); diff --git a/include/linux/usb.h b/include/linux/usb.h index e9037c8120b43556f8610f9acb3ad4129e847b98..b616c0fb79be33aace2c052ea3e= cd1dd0641a024 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -2062,6 +2062,15 @@ extern void usb_led_activity(enum usb_led_event ev); static inline void usb_led_activity(enum usb_led_event ev) {} #endif =20 +/* sysctl.c */ +#ifdef CONFIG_SYSCTL +extern int usb_register_sysctl(void); +extern void usb_unregister_sysctl(void); +#else +static inline int usb_register_sysctl(void) { return 0; } +static inline void usb_unregister_sysctl(void) { } +#endif /* CONFIG_SYSCTL */ + #endif /* __KERNEL__ */ =20 #endif --=20 2.50.0