[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CACzwLxiLUW46uS6cCOHPix_kYUd_coAEHcSf2hOMtc32zCF_Fw@mail.gmail.com>
Date: Sat, 21 Jun 2025 14:37:07 +0500
From: Sabyrzhan Tasbolatov <snovitoll@...il.com>
To: nicolas.bouchinet@....cyber.gouv.fr
Cc: Greg Kroah-Hartman <gregkh@...uxfoundation.org>, Alan Stern <stern@...land.harvard.edu>,
Kannappan R <r.kannappan@...el.com>,
Krzysztof Kozlowski <krzysztof.kozlowski@...aro.org>,
Stefan Eichenberger <stefan.eichenberger@...adex.com>, Thomas Gleixner <tglx@...utronix.de>,
Pawel Laszczak <pawell@...ence.com>, Ma Ke <make_ruc2021@....com>,
Jeff Johnson <jeff.johnson@....qualcomm.com>, Luc Bonnafoux <luc.bonnafoux@....gouv.fr>,
Luc Bonnafoux <luc.bonnafoux@....cyber.gouv.fr>,
Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>, linux-kernel@...r.kernel.org,
linux-usb@...r.kernel.org
Subject: Re: [RFC PATCH 1/4] usb: core: Introduce netlink usb authentication
policy engine
On Fri, Jun 20, 2025 at 7:27 PM <nicolas.bouchinet@....cyber.gouv.fr> wrote:
>
> From: Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>
>
> 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.
>
> - USBAUTH_CMD_REMOVE_DEV
> - USBAUTH_CMD_RESP_REMOVE_DEV
>
> Those two commands have been provionned but have not been implemented yet.
> If at any time, the policy engine wants to remove the trust in a device,
> then the `USBAUTH_CMD_REMOVE_DEV` would to be sent, the kernel replies
> with an error status through the `USBAUTH_CMD_RESP_REMOVE_DEV` command.
>
> Co-developed-by: Luc Bonnafoux <luc.bonnafoux@....gouv.fr>
> Signed-off-by: Luc Bonnafoux <luc.bonnafoux@....gouv.fr>
> Signed-off-by: Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>
> ---
> drivers/usb/core/authent_netlink.c | 1080 +++++++++++++++++++++++++++++
> drivers/usb/core/authent_netlink.h | 157 +++++
> include/uapi/linux/usb/usb_auth_netlink.h | 67 ++
> 3 files changed, 1304 insertions(+)
>
> diff --git a/drivers/usb/core/authent_netlink.c b/drivers/usb/core/authent_netlink.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..d53a220c762ffc1bd9aeb95bf90dc0dd79c43f09
> --- /dev/null
> +++ b/drivers/usb/core/authent_netlink.c
> @@ -0,0 +1,1080 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * SPDX-FileCopyrightText: (C) 2025 ANSSI
> + *
> + * USB Authentication netlink interface
> + *
> + * Author: Luc Bonnafoux <luc.bonnafoux@....gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>
> + *
> + */
> +
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/err.h>
> +#include <linux/capability.h>
> +
> +#include <net/genetlink.h>
> +
> +#include <uapi/linux/usb/usb_auth_netlink.h>
> +
> +#include "authent_netlink.h"
> +
> +#define WAIT_USERSPACE_TIMEOUT 30
> +#define WAIT_RESPONSE_TIMEOUT 300
> +#define USB_AUTH_MAX_RESP_SIZE 128
> +
> +/**
> + * Define an outstanding request between the kernel and userspace
> + */
> +struct usb_auth_req {
> + uint8_t used; /**< 1 if the slot is currently used, access must be protected */
> + uint8_t done; /**< 1 if the response has been received, used as wait condition */
> + uint8_t error; /**< userspace response error code */
> + uint8_t resp[USB_AUTH_MAX_RESP_SIZE]; /**< arbitrary response buffer */
> +};
> +
> +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 USB_AUTH_MAX_OUTSTANDING_REQS 10
> +// 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[USB_AUTH_MAX_OUTSTANDING_REQS];
Wonder, if hot-plugged low-speed hubs may exhaust the table and block every
other device for 5 min (according to the timeouts in this patch).
With the array hard-capped at 10, the 11-th concurrent device gets -EXFULL
and stalls enumeration. The capacity must become dynamic (e.g. xarray/idr)
or requests must be queued instead of rejected (?). IDR is deprecated,
though is used in drivers/usb.
> +
> +////////////////////////////////////////////////////////////////////////////////
> +// USB request utilities
> +////////////////////////////////////////////////////////////////////////////////
> +
> +/**
> + * @brief Find the first available slot in the outstanding requests array and
> + * reserve it.
> + *
> + * CAUTION: this function will block on the request list mutex
> + *
> + * Possible error codes:
> + * - EXFULL : too many outstanding requests already
> + *
> + * @param [out] index : reserved slot index, valid if return equals 0
> + *
> + * @return 0 on SUCCESS or error code
> + */
> +static int usb_auth_get_reqs_slot(uint32_t *index)
> +{
> + int ret = -EXFULL;
> + uint32_t i = 0;
> +
> + mutex_lock(&usb_auth_reqs_mutex);
> +
> + // Take the first available slot
> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++) {
> + if (usb_auth_outstanding_reqs[i].used == 0) {
> + usb_auth_outstanding_reqs[i].used = 1;
> + usb_auth_outstanding_reqs[i].done = 0;
> + usb_auth_outstanding_reqs[i].error = USBAUTH_OK;
> + memset(usb_auth_outstanding_reqs[i].resp, 0,
> + USB_AUTH_MAX_RESP_SIZE);
> + *index = i;
> + ret = 0;
> + break;
> + }
> + }
> +
> + mutex_unlock(&usb_auth_reqs_mutex);
> +
> + return ret;
> +}
> +
> +/**
> + * @brief release a request slot
> + *
> + * CAUTION: this function will block on the request list mutex
> + *
> + * @param [in] index : slot index to be released
> + */
> +static void usb_auth_release_reqs_slot(const uint32_t index)
> +{
> + mutex_lock(&usb_auth_reqs_mutex);
> +
> + usb_auth_outstanding_reqs[index].used = 0;
> +
> + mutex_unlock(&usb_auth_reqs_mutex);
> +}
> +
> +////////////////////////////////////////////////////////////////////////////////
> +// Generic netlink socket utilities
> +////////////////////////////////////////////////////////////////////////////////
> +
> +/**
> + * @brief Handle a registration request from userspace
> + *
> + * It will overwrite the current userspace registered PID with the one provided
> + * in the request
> + */
> +static int usb_auth_register_req_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + int ret = 0;
> + void *hdr = NULL;
> + struct sk_buff *msg = NULL;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + // Register Policy engine PID, overwrite value if already set
> + pol_eng_pid = info->snd_portid;
info->snd_portid is the per-net-ns socket port ID of the sender.
Any later caller, inside the same or a different network namespace
simply replaces the old value. Can we reject the second registration?
capable(CAP_SYS_ADMIN) succeeds in any user-namespace where the task
has that bit set. A container root therefore passes the check and can
issue USBAUTH_CMD_REGISTER, hijacking the channel.
> + pol_eng_net = genl_info_net(info);
Similarly, if that sender lives in a container’s net-namespace, pol_eng_net
now points there.
> +
> + wake_up_all(&usb_req_wq);
> +
> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (msg == NULL) {
> + pr_err("failed to allocate message buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
> + &usbauth_genl_fam, 0, USBAUTH_CMD_REGISTER);
> + if (hdr == NULL) {
> + pr_err("failed to create genetlink header\n");
> + nlmsg_free(msg);
> + return -EMSGSIZE;
> + }
> +
> + genlmsg_end(msg, hdr);
> +
> + ret = genlmsg_reply(msg, info);
> +
> + pr_info("reply sent\n");
> +
> + return ret;
> +}
> +
> +/**
> + * @brief Handle a CHECK_DIGEST response from userspace
> + *
> + * The response must contain:
> + * - USBAUTH_A_REQ_ID
> + * - USBAUTH_A_ERROR_CODE
> + * - USBAUTH_A_DEV_ID
> + * - USBAUTH_A_KNOWN
> + * - USBAUTH_A_BLOCKED
> + *
> + */
> +static int usb_auth_digest_resp_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + uint32_t index = 0;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + if (!info->attrs[USBAUTH_A_REQ_ID]) {
> + pr_err("digest_resp_doit: invalid response: no req ID\n");
> + return -EINVAL;
> + }
> +
> + index = nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]);
> +
> + // Test for error
> + if (!info->attrs[USBAUTH_A_ERROR_CODE]) {
> + pr_err("digest_resp_doit: invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
AFAIK, `usb_auth_outstanding_reqs` fields `done/error/resp[]` are not protected
by the mutex that guards used. `usb_auth_reqs_mutex` is not held in
either place.
Potential race between any *_resp_doit() writer and the reader
in the matching usb_policy_engine_*() helper.
Perhaps, you can replace `error, resp[], done` by a single
`struct completion`, plus a private response struct that the reader accesses
only after some `complete()`.
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("digest_resp_doit: response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_KNOWN] ||
> + !info->attrs[USBAUTH_A_BLOCKED]) {
> + pr_err("digest_resp_doit: invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_KNOWN]);
> + usb_auth_outstanding_reqs[index].resp[1] =
> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/**
> + * @brief Handle a CHECK_CERTIFICATE response from userspace
> + *
> + * The response must contain:
> + * - USBAUTH_A_REQ_ID
> + * - USBAUTH_A_ERROR_CODE
> + * - USBAUTH_A_VALID
> + * - USBAUTH_A_BLOCKED
> + * - USBAUTH_A_DEV_ID
> + *
> + */
> +static int usb_auth_cert_resp_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + uint32_t index = 0;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + if (!info->attrs[USBAUTH_A_REQ_ID]) {
> + pr_err("invalid response: no req ID\n");
> + return -EINVAL;
> + }
> +
> + index = nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]);
> +
> + // Test for error
> + if (!info->attrs[USBAUTH_A_ERROR_CODE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_DEV_ID] || !info->attrs[USBAUTH_A_VALID] ||
> + !info->attrs[USBAUTH_A_BLOCKED]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> + usb_auth_outstanding_reqs[index].resp[1] =
> + nla_get_u8(info->attrs[USBAUTH_A_BLOCKED]);
> + ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0] =
> + nla_get_u32(info->attrs[USBAUTH_A_DEV_ID]);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/**
> + * @brief Handle a REMOVE_DEV response from userspace
> + *
> + * The response must contain:
> + * - USBAUTH_A_REQ_ID
> + * - USBAUTH_A_ERROR_CODE
> + * - USBAUTH_A_VALID
> + *
> + */
> +static int usb_auth_remove_dev_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + uint32_t index = 0;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + if (!info->attrs[USBAUTH_A_REQ_ID]) {
> + pr_err("invalid response: no req ID\n");
> + return -EINVAL;
> + }
> +
> + index = nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]);
> +
> + // Test for error
> + if (!info->attrs[USBAUTH_A_ERROR_CODE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_VALID]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/**
> + * @brief Handle a GEN_NONCE response from userspace
> + *
> + * The response must contain:
> + * - USBAUTH_A_REQ_ID
> + * - USBAUTH_A_ERROR_CODE
> + * - USBAUTH_A_NONCE
> + *
> + */
> +static int usb_auth_gen_nonce_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + uint32_t index = 0;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + if (!info->attrs[USBAUTH_A_REQ_ID]) {
> + pr_err("invalid response: no req ID\n");
> + return -EINVAL;
> + }
> +
> + index = nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]);
> +
> + // Test for error
> + if (!info->attrs[USBAUTH_A_ERROR_CODE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_NONCE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + nla_memcpy(usb_auth_outstanding_reqs[index].resp, info->attrs[USBAUTH_A_NONCE], 32);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/**
> + * @brief Handle a CHECK_CHALL response from userspace
> + *
> + * The response must contain:
> + * - USBAUTH_A_REQ_ID
> + * - USBAUTH_A_ERROR_CODE
> + * - USBAUTH_A_VALID
> + *
> + */
> +static int usb_auth_check_chall_doit(struct sk_buff *skb, struct genl_info *info)
> +{
> + uint32_t index = 0;
> +
> + pr_info("message received\n");
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + pr_err("invalid permissions\n");
> + return -EPERM;
> + }
> +
> + if (!info->attrs[USBAUTH_A_REQ_ID]) {
> + pr_err("invalid response: no req ID\n");
> + return -EINVAL;
> + }
> +
> + index = nla_get_u32(info->attrs[USBAUTH_A_REQ_ID]);
> +
> + // Test for error
> + if (!info->attrs[USBAUTH_A_ERROR_CODE]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].error =
> + nla_get_u8(info->attrs[USBAUTH_A_ERROR_CODE]);
> +
> + if (usb_auth_outstanding_reqs[index].error != USBAUTH_OK) {
> + pr_err("response error\n");
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + if (!info->attrs[USBAUTH_A_VALID]) {
> + pr_err("invalid response: missing attributes\n");
> + usb_auth_outstanding_reqs[index].error = USBAUTH_INVRESP;
> + usb_auth_outstanding_reqs[index].done = 1;
> + return -EINVAL;
> + }
> +
> + usb_auth_outstanding_reqs[index].resp[0] =
> + nla_get_u8(info->attrs[USBAUTH_A_VALID]);
> +
> + usb_auth_outstanding_reqs[index].done = 1;
> +
> + wake_up_all(&usb_req_wq);
> +
> + return 0;
> +}
> +
> +/* Attribute validation policy */
> +static struct nla_policy usbauth_attr_pol[USBAUTH_A_MAX + 1] = {
> + [USBAUTH_A_REQ_ID] = {.type = NLA_U32,},
> + [USBAUTH_A_DEV_ID] = {.type = NLA_U32,},
> + [USBAUTH_A_DIGEST] = {.type = NLA_UNSPEC, .len = 32,},
> + [USBAUTH_A_DIGESTS] = {.type = NLA_UNSPEC, .len = 256,},
> + [USBAUTH_A_SLOT_MASK] = {.type = NLA_U8,},
> + [USBAUTH_A_KNOWN] = {.type = NLA_U8,},
> + [USBAUTH_A_BLOCKED] = {.type = NLA_U8,},
> + [USBAUTH_A_VALID] = {.type = NLA_U8,},
> + [USBAUTH_A_CERTIFICATE] = {.type = NLA_UNSPEC, .max = 4096,},
> + [USBAUTH_A_CERT_LEN] = {.type = NLA_U32},
> + [USBAUTH_A_ROUTE] = {.type = NLA_U32},
> + [USBAUTH_A_NONCE] = {.type = NLA_BINARY, .len = 32,},
> + [USBAUTH_A_CHALL] = {.type = NLA_UNSPEC, .len = 204,},
> + [USBAUTH_A_DESCRIPTOR] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_DESC_SIZE},
> + [USBAUTH_A_BOS] = {.type = NLA_UNSPEC, .len = USBAUTH_MAX_BOS_SIZE},
> + [USBAUTH_A_ERROR_CODE] = {.type = NLA_U8},
> +};
> +
> +/* Operations */
> +static struct genl_ops usbauth_genl_ops[] = {
> + {
> + .cmd = USBAUTH_CMD_REGISTER,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_register_req_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_DIGEST,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_digest_resp_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_CERTIFICATE,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_cert_resp_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_REMOVE_DEV,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_remove_dev_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_GEN_NONCE,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_gen_nonce_doit,
> + },
> + {
> + .cmd = USBAUTH_CMD_RESP_CHECK_CHALL,
> + .policy = usbauth_attr_pol,
> + .doit = usb_auth_check_chall_doit,
> + }
> +};
> +
> +/* USB Authentication netlink family definition */
> +static struct genl_family usbauth_genl_fam = {
> + .name = USBAUTH_GENL_NAME,
> + .version = USBAUTH_GENL_VERSION,
> + .maxattr = USBAUTH_A_MAX,
> + .ops = usbauth_genl_ops,
> + .n_ops = ARRAY_SIZE(usbauth_genl_ops),
> + .mcgrps = NULL,
> + .n_mcgrps = 0,
> +};
> +
> +int usb_auth_init_netlink(void)
> +{
> + int ret = 0;
> + uint8_t i = 0;
> +
> + for (i = 0; i < USB_AUTH_MAX_OUTSTANDING_REQS; i++)
> + usb_auth_outstanding_reqs[i].used = 0;
> +
> + init_waitqueue_head(&usb_req_wq);
> +
> + ret = genl_register_family(&usbauth_genl_fam);
> + if (unlikely(ret)) {
> + pr_err("failed to init netlink: %d\n",
> + ret);
> + return ret;
> + }
> +
> + pr_info("initialized\n");
> +
> + return ret;
> +}
> +
> +////////////////////////////////////////////////////////////////////////////////
> +// Policy engine API
> +////////////////////////////////////////////////////////////////////////////////
> +
> +int usb_policy_engine_check_digest(const uint32_t route, const uint8_t *const digests,
> + const uint8_t mask, uint8_t *is_known, uint8_t *is_blocked, uint32_t *id)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (digests == NULL) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
WAIT_USERSPACE_TIMEOUT is 30 sec and WAIT_RESPONSE_TIMEOUT is 5 min.
Can we keep a modest deadline, e.g. 5 sec for daemon presence and
10 sec for its reply, or something similar that makes sense?
> + pr_err("userspace not available\n");
> + return -ECOMM;
> + }
> +
> + pr_info("got connection to userspace\n");
> +
> + // Get a slot in the outstanding request list
> + if (usb_auth_get_reqs_slot(&index)) {
> + pr_err("failed to get request slot\n");
> + return -ECOMM;
> + }
> + pr_info("got request slot\n");
> +
> + // Create digests check request
> + skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_DIGEST);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_REQ_ID, index);
> + if (ret) {
> + pr_err("failed to place req ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_ROUTE, route);
> + if (ret) {
> + pr_err("failed to place route\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put(skb, USBAUTH_A_DIGESTS, 260, digests);
Attribute is declared as 256 in the policy
(usbauth_attr_pol[USBAUTH_A_DIGESTS].len = 256
but you emit 260 here. Is it wrong or where do 4 bytes come from?
> + if (ret) {
> + pr_err("failed to place digests\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u8(skb, USBAUTH_A_SLOT_MASK, mask);
> + if (ret) {
> + pr_err("failed to place slot mask\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + genlmsg_end(skb, hdr);
> +
> + // Send message to userspace
> + ret = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 0) {
> + pr_err("failed to send message: %d\n",
> + ret);
> + return -ECOMM;
> + }
> + pr_info("sent CHECK_DIGEST request\n");
> +
> + // Wait for userspace response
> + // Arbitrary 5 min wait before giving up
> + if (!wait_event_timeout(usb_req_wq,
> + usb_auth_outstanding_reqs[index].done == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
Here we have 5 min enumeration stall while holding usb_lock_device().
Can we replace the blocking helper with a work-queue item?
So we could have pushed to the work and continue enumeration.
struct usb_auth_work {
struct work_struct work;
struct usb_device *udev;
/* request-specific data */
};
> + return -ECOMM;
> + }
> +
> + pr_info("received CHECK_DIGEST response\n");
> +
> + // Get response
> + if (usb_auth_outstanding_reqs[index].error == USBAUTH_INVRESP) {
> + pr_err("userspace response error: %d\n",
> + usb_auth_outstanding_reqs[index].error);
> + usb_auth_release_reqs_slot(index);
> + return -ECOMM;
> + }
> +
> + *is_known = usb_auth_outstanding_reqs[index].resp[0];
> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
> + *id = ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0];
> +
> + // Release request slot
> + usb_auth_release_reqs_slot(index);
> +
> + return 0;
> +}
> +
> +int usb_policy_engine_check_cert_chain(const uint32_t route,
> + const uint8_t *const digest, const uint8_t *const chain,
> + const size_t chain_len, uint8_t *is_valid, uint8_t *is_blocked, uint32_t *id)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (chain == NULL || chain_len > 4096 || digest == NULL) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
> + pr_err("userspace not available\n");
> + return -ECOMM;
> + }
> +
> + pr_info("got connection to userspace\n");
> +
> + // Get a slot in the outstanding request list
> + if (usb_auth_get_reqs_slot(&index)) {
> + pr_err("failed to get request slot\n");
> + return -ECOMM;
> + }
> + pr_info("got request slot\n");
> +
> + // Create digest check request
> + skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_CERTIFICATE);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_REQ_ID, index);
> + if (ret) {
> + pr_err("failed to place req ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_ROUTE, route);
> + if (ret) {
> + pr_err("failed to place route\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put(skb, USBAUTH_A_DIGEST, 32, digest);
> + if (ret) {
> + pr_err("failed to place digest\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put(skb, USBAUTH_A_CERTIFICATE, chain_len, chain);
> + if (ret) {
> + pr_err("failed to place certificate\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_CERT_LEN, chain_len);
> + if (ret) {
> + pr_err("failed to place chain length\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + genlmsg_end(skb, hdr);
> +
> + // Send message to userspace
> + ret = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 0) {
> + pr_err("failed to send message: %d\n",
> + ret);
> + return -ECOMM;
> + }
> + pr_info("sent CHECK_CERTIFICATE request\n");
> +
> + // Wait for userspace response
> + // Arbitrary 5 min wait before giving up
> + if (!wait_event_timeout(usb_req_wq,
> + usb_auth_outstanding_reqs[index].done == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
> + return -ECOMM;
> + }
> +
> + pr_info("received CHECK_CERTIFICATE response\n");
> +
> + // Get response
> + *is_valid = usb_auth_outstanding_reqs[index].resp[0];
> + *is_blocked = usb_auth_outstanding_reqs[index].resp[1];
> + *id = ((uint32_t *)usb_auth_outstanding_reqs[index].resp + 2)[0];
> +
> + // Release request slot
> + usb_auth_release_reqs_slot(index);
> +
> + return 0;
> +}
> +
> +int usb_policy_engine_remove_dev(const uint32_t id)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
> + pr_err("userspace not available\n");
> + return -ECOMM;
> + }
> +
> + pr_info("got connection to userspace\n");
> +
> + // Get a slot in the outstanding request list
> + if (usb_auth_get_reqs_slot(&index)) {
> + pr_err("failed to get request slot\n");
> + return -ECOMM;
> + }
> + pr_info("got request slot\n");
> +
> + // Create digest check request
> + skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_REMOVE_DEV);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_REQ_ID, index);
> + if (ret) {
> + pr_err("failed to place req ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_DEV_ID, id);
> + if (ret) {
> + pr_err("failed to place dev ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + genlmsg_end(skb, hdr);
> +
> + // Send message to userspace
> + ret = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 0) {
> + pr_err("failed to send message: %d\n",
> + ret);
> + return -ECOMM;
> + }
> + pr_info("sent REMOVE_DEV request\n");
> +
> + // Wait for userspace response
> + // Arbitrary 5 min wait before giving up
> + if (!wait_event_timeout(usb_req_wq,
> + usb_auth_outstanding_reqs[index].done == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
> + return -ECOMM;
> + }
> +
> + pr_info("received REMOVE_DEV response\n");
> +
> + // Release request slot
> + usb_auth_release_reqs_slot(index);
> +
> + return 0;
> +}
> +
> +int usb_policy_engine_generate_challenge(const uint32_t id, uint8_t *nonce)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
> + pr_err("userspace not available\n");
> + return -ECOMM;
> + }
> +
> + pr_info("got connection to userspace\n");
> +
> + // Get a slot in the outstanding request list
> + if (usb_auth_get_reqs_slot(&index)) {
> + pr_err("failed to get request slot\n");
> + return -ECOMM;
> + }
> + pr_info("got request slot\n");
> +
> + // Create digest check request
> + skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_GEN_NONCE);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_REQ_ID, index);
> + if (ret) {
> + pr_err("failed to place req ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_DEV_ID, id);
> + if (ret) {
> + pr_err("failed to place dev ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + genlmsg_end(skb, hdr);
> +
> + // Send message to userspace
> + ret = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 0) {
> + pr_err("failed to send message: %d\n", ret);
> + return -ECOMM;
> + }
> + pr_info("sent GEN_NONCE request\n");
> +
> + // Wait for userspace response
> + // Arbitrary 5 min wait before giving up
> + if (!wait_event_timeout(usb_req_wq,
> + usb_auth_outstanding_reqs[index].done == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
> + return -ECOMM;
> + }
> +
> + pr_info("received GEN_NONCE response\n");
> +
> + // Get response
> + memcpy(nonce, usb_auth_outstanding_reqs[index].resp, 32);
> +
> + // Release request slot
> + usb_auth_release_reqs_slot(index);
> +
> + return 0;
> +}
> +
> +int usb_policy_engine_check_challenge(const uint32_t id,
> + const uint8_t *const challenge, const uint8_t *const context,
> + const size_t context_size, uint8_t *is_valid)
> +{
> + int ret = -1;
> + void *hdr = NULL;
> + struct sk_buff *skb = NULL;
> + uint32_t index = 0;
> +
> + if (unlikely(challenge == NULL || context == NULL ||
> + context_size > USBAUTH_MAX_BOS_SIZE)) {
> + pr_err("invalid inputs\n");
> + return -EINVAL;
> + }
> +
> + // Arbitrary 30s wait before giving up
> + if (!wait_event_timeout(usb_req_wq, pol_eng_pid != 0, HZ * WAIT_USERSPACE_TIMEOUT)) {
> + pr_err("userspace not available\n");
> + return -ECOMM;
> + }
> +
> + pr_info("got connection to userspace\n");
> +
> + // Get a slot in the outstanding request list
> + if (usb_auth_get_reqs_slot(&index)) {
> + pr_err("failed to get request slot\n");
> + return -ECOMM;
> + }
> + pr_info("got request slot\n");
> +
> + // Create digest check request
> + skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (skb == NULL) {
> + pr_err("failed to allocated buffer\n");
> + return -ENOMEM;
> + }
> +
> + hdr = genlmsg_put(skb, 0, 0, &usbauth_genl_fam, 0,
> + USBAUTH_CMD_CHECK_CHALL);
> + if (unlikely(hdr == NULL)) {
> + pr_err("failed to place header\n");
> + nlmsg_free(skb);
> + return -ENOMEM;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_REQ_ID, index);
> + if (ret) {
> + pr_err("failed to place req ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put(skb, USBAUTH_A_CHALL, 204, challenge);
> + if (ret) {
> + pr_err("failed to place challenge\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put(skb, USBAUTH_A_DESCRIPTOR, context_size, context);
> + if (ret) {
> + pr_err("failed to place descriptor\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + ret = nla_put_u32(skb, USBAUTH_A_DEV_ID, id);
> + if (ret) {
> + pr_err("failed to place dev ID\n");
> + genlmsg_cancel(skb, hdr);
> + nlmsg_free(skb);
> + return ret;
> + }
> +
> + genlmsg_end(skb, hdr);
> +
> + // Send message to userspace
> + ret = genlmsg_unicast(pol_eng_net, skb, pol_eng_pid);
> + if (ret != 0) {
> + pr_err("failed to send message: %d\n",
> + ret);
> + return -ECOMM;
> + }
> + pr_info("sent CHECK_CHALL request\n");
> +
> + // Wait for userspace response
> + // Arbitrary 5 min wait before giving up
> + if (!wait_event_timeout(usb_req_wq,
> + usb_auth_outstanding_reqs[index].done == 1,
> + HZ * WAIT_RESPONSE_TIMEOUT)) {
> + pr_err("userspace response not available\n");
> + usb_auth_release_reqs_slot(index);
> + return -ECOMM;
> + }
> +
> + pr_info("received CHECK_CHALL response\n");
> +
> + // Get response
> + *is_valid = usb_auth_outstanding_reqs[index].resp[0];
> +
> + // Release request slot
> + 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..504da32547b75b85b4328f3ea7df43b0a565dd18
> --- /dev/null
> +++ b/drivers/usb/core/authent_netlink.h
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * SPDX-FileCopyrightText: (C) 2025 ANSSI
> + *
> + * USB Authentication netlink interface
> + *
> + * Author: Luc Bonnafoux <luc.bonnafoux@....gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>
> + *
> + */
> +
> +#ifndef __USB_CORE_AUTHENT_NETLINK_H_
> +#define __USB_CORE_AUTHENT_NETLINK_H_
> +
> +int usb_auth_init_netlink(void);
> +
> +/**
> + * @brief Check if a digest match a device
> + *
> + * 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.
> + *
> + * Possible errors:
> + * - EINVAL : if digest is NULL
> + * - ECOMM : if no userspace policy engine is available
> + * or already userspace is busy
> + * or message transmission failed
> + * - ENOMEM : if message creation failed
> + * - EMSGSIZE : if message creation failed
> + *
> + * @param [in] digest : USB Authentication digest, must be 256 B
> + * @param [in] mask : USB Authentication slot mask
> + * @param [out] is_known : 1 at each index with a known digest, 0 otherwise
> + * @param [out] is_blocked : 1 if the device is known and banned, 0 otherwise
> + * @param [out] id : if is_known and !is_blocked then contains the device handle
> + *
> + * @return 0 on SUCCESS else error code
> + */
> +int usb_policy_engine_check_digest(const uint32_t route,
> + const uint8_t *const digests,
> + const uint8_t mask, uint8_t *is_known,
> + uint8_t *is_blocked, uint32_t *id);
> +
> +/**
> + * @brief Check if a certificate chain is valid and authorized
> + *
> + * A certificate chain is valid if it can be successfully verified with one of the
> + * root CA in store.
> + * A certificate chain is blocked if one of the certificate of chain is blocked,
> + * due to revocation, blacklist...
> + *
> + * Context: task context, might sleep.
> + *
> + * Possible errors:
> + * - EINVAL : if digest is NULL
> + * - ECOMM : if no userspace policy engine is available
> + * or already userspace is busy
> + * or message transmission failed
> + * - ENOMEM : if message creation failed
> + * - EMSGSIZE : if message creation failed
> + *
> + * TODO: see if it is necessary to have a separate boolean for is_blocked
> + *
> + * @param [in] route : Information on the device to construct the ID
> + * @param [in] digest : Digest corresponding to the certificate chain
> + * @param [in] chain : Certificate chain to check, at most 4096 bytes
> + * @param [in] chain_length : Certificate chain length
> + * @param [out] is_valid : 1 if the certificate chain can be validated
> + * @param [out] is_blocked : 1 if the chain is valid but one of the certificate is blocked
> + * @param [out] id : if is_known and !is_blocked then contains the device handle
> + *
> + * @return 0 on SUCCESS else -1
> + */
> +int usb_policy_engine_check_cert_chain(const uint32_t route,
> + const uint8_t *const digest,
> + const uint8_t *const chain,
> + const size_t chain_len,
> + uint8_t *is_valid, uint8_t *is_blocked,
> + uint32_t *id);
> +
> +/**
> + * @brief Remove a device from the policy engine
> + *
> + * Context: task context, might sleep.
> + *
> + * Possible errors:
> + * - EINVAL : if digest is NULL
> + * - ECOMM : if no userspace policy engine is available
> + * or already userspace is busy
> + * or message transmission failed
> + * - ENOMEM : if message creation failed
> + * - EMSGSIZE : if message creation failed
> + *
> + * @param [in] id : Device handle
> + *
> + * @return 0 on SUCCESS else -1
> + */
> +int usb_policy_engine_remove_dev(const uint32_t id);
> +
> +/**
> + * @brief Generate a nonce for the authentication challenge
> + *
> + * Context: task context, might sleep.
> + *
> + * Possible errors:
> + * - EINVAL : if digest is NULL
> + * - ECOMM : if no userspace policy engine is available
> + * or already userspace is busy
> + * or message transmission failed
> + * - ENOMEM : if message creation failed
> + * - EMSGSIZE : if message creation failed
> + *
> + * @param [in] id : device ID
> + * @param [out] nonce : 32 bytes nonce buffer, caller allocated
> + *
> + * @return 0 on SUCCESS else -1
> + */
> +int usb_policy_engine_generate_challenge(const uint32_t id, uint8_t *nonce);
> +
> +/**
> + * @brief Validate the authentication challenge
> + *
> + * Context: task context, might sleep.
> + *
> + * Possible errors:
> + * - EINVAL : if challenge, desc or bos is NULL or invalid parameter size
> + * - ECOMM : if no userspace policy engine is available
> + * or already userspace is busy
> + * or message transmission failed
> + * - ENOMEM : if message creation failed
> + * - EMSGSIZE : if message creation failed
> + *
> + * Challenge is the concatenation of : message (140B) | signature (64B)
> + *
> + * Check that the response challenge contains the right nonce
> + * Check that the device signature is valid
> + *
> + * @param [in] id : device handle
> + * @param [in] challenge : challenge response, must be 204 bytes
> + * @param [in] desc : device descriptor
> + * @param [in] desc_size : descriptor size in bytes
> + * @param [in] bos : device BOS
> + * @param [in] bos_size : BOS size in bytes
> + * @param [out] is_valid : 1 if the signature is valid, 0 otherwise
> + *
> + * @return 0 on SUCCESS else -1
> + */
> +int usb_policy_engine_check_challenge(const uint32_t id,
> + const uint8_t *const challenge,
> + const uint8_t *const context,
> + const size_t context_size,
> + uint8_t *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..e5b1e0e130a1ffb320aac4805161d579923a5b29
> --- /dev/null
> +++ b/include/uapi/linux/usb/usb_auth_netlink.h
> @@ -0,0 +1,67 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * SPDX-FileCopyrightText: (C) 2025 ANSSI
> + *
> + * USB Authentication netlink interface definitions shared with userspace
> + *
> + * Author: Luc Bonnafoux <luc.bonnafoux@....gouv.fr>
> + * Author: Nicolas Bouchinet <nicolas.bouchinet@....gouv.fr>
> + *
> + */
> +
> +#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 = 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_A_MAX (__USBAUTH_A_MAX - 1)
> +
> +/* Commands */
> +enum usbauth_genl_cmds {
> + USBAUTH_CMD_REGISTER,
> + USBAUTH_CMD_CHECK_DIGEST,
> + USBAUTH_CMD_CHECK_CERTIFICATE,
> + USBAUTH_CMD_REMOVE_DEV,
> + USBAUTH_CMD_GEN_NONCE,
> + USBAUTH_CMD_CHECK_CHALL,
> + USBAUTH_CMD_RESP_DIGEST,
> + USBAUTH_CMD_RESP_CERTIFICATE,
> + USBAUTH_CMD_RESP_CREATE_DEV,
> + USBAUTH_CMD_RESP_REMOVE_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_ */
>
> --
> 2.50.0
>
Powered by blists - more mailing lists