[PATCH v2] usb: gadget: rndis: validate query and set message buffers
From: Pengpeng Hou
Date: Mon Mar 23 2026 - 04:09:28 EST
rndis_set_response() already checks the host-controlled
InformationBufferOffset/InformationBufferLength pair before using it,
but the QUERY path still passes the same fields straight into
gen_ndis_query_resp(). The parser also does not verify that MsgLength
fits the actual EP0 request buffer before dispatching the message.
Pass the actual request size into rndis_msg_parser(), reject messages
whose MsgLength exceeds the received buffer, and apply the same offset
and length validation to QUERY and SET requests before dereferencing the
embedded information buffer.
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
v2:
- add commit message context and fix rationale
- no code changes
drivers/usb/gadget/function/f_rndis.c | 2 +-
drivers/usb/gadget/function/rndis.c | 49 +++++++++++++++++++--------
drivers/usb/gadget/function/rndis.h | 2 +-
3 files changed, 37 insertions(+), 16 deletions(-)
diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c
index 8b11d8d6d89c..4878b1133582 100644
--- a/drivers/usb/gadget/function/f_rndis.c
+++ b/drivers/usb/gadget/function/f_rndis.c
@@ -443,7 +443,7 @@ static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req)
/* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */
// spin_lock(&dev->lock);
- status = rndis_msg_parser(rndis->params, (u8 *) req->buf);
+ status = rndis_msg_parser(rndis->params, (u8 *)req->buf, req->actual);
if (status < 0)
pr_err("RNDIS command error %d, %d/%d\n",
status, req->actual, req->length);
diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c
index 3da54a7d7aba..3f3201934c12 100644
--- a/drivers/usb/gadget/function/rndis.c
+++ b/drivers/usb/gadget/function/rndis.c
@@ -588,9 +588,22 @@ static int rndis_init_response(struct rndis_params *params,
return 0;
}
+static bool rndis_check_query_set_msg_len(u32 msg_len, u32 buf_offset,
+ u32 buf_length, size_t min_len)
+{
+ if (msg_len < min_len || msg_len > RNDIS_MAX_TOTAL_SIZE)
+ return false;
+
+ if (buf_offset > msg_len - 8)
+ return false;
+
+ return buf_length <= msg_len - buf_offset - 8;
+}
+
static int rndis_query_response(struct rndis_params *params,
- rndis_query_msg_type *buf)
+ rndis_query_msg_type *buf, u32 msg_len)
{
+ u32 buf_length, buf_offset;
rndis_query_cmplt_type *resp;
rndis_resp_t *r;
@@ -598,6 +611,12 @@ static int rndis_query_response(struct rndis_params *params,
if (!params->dev)
return -ENOTSUPP;
+ buf_length = le32_to_cpu(buf->InformationBufferLength);
+ buf_offset = le32_to_cpu(buf->InformationBufferOffset);
+ if (!rndis_check_query_set_msg_len(msg_len, buf_offset, buf_length,
+ sizeof(*buf)))
+ return -EINVAL;
+
/*
* we need more memory:
* gen_ndis_query_resp expects enough space for
@@ -614,9 +633,7 @@ static int rndis_query_response(struct rndis_params *params,
resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
if (gen_ndis_query_resp(params, le32_to_cpu(buf->OID),
- le32_to_cpu(buf->InformationBufferOffset)
- + 8 + (u8 *)buf,
- le32_to_cpu(buf->InformationBufferLength),
+ buf_offset + 8 + (u8 *)buf, buf_length,
r)) {
/* OID not supported */
resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
@@ -631,7 +648,7 @@ static int rndis_query_response(struct rndis_params *params,
}
static int rndis_set_response(struct rndis_params *params,
- rndis_set_msg_type *buf)
+ rndis_set_msg_type *buf, u32 msg_len)
{
u32 BufLength, BufOffset;
rndis_set_cmplt_type *resp;
@@ -639,10 +656,9 @@ static int rndis_set_response(struct rndis_params *params,
BufLength = le32_to_cpu(buf->InformationBufferLength);
BufOffset = le32_to_cpu(buf->InformationBufferOffset);
- if ((BufLength > RNDIS_MAX_TOTAL_SIZE) ||
- (BufOffset > RNDIS_MAX_TOTAL_SIZE) ||
- (BufOffset + 8 >= RNDIS_MAX_TOTAL_SIZE))
- return -EINVAL;
+ if (!rndis_check_query_set_msg_len(msg_len, BufOffset, BufLength,
+ sizeof(*buf)))
+ return -EINVAL;
r = rndis_add_response(params, sizeof(rndis_set_cmplt_type));
if (!r)
@@ -788,13 +804,13 @@ EXPORT_SYMBOL_GPL(rndis_set_host_mac);
/*
* Message Parser
*/
-int rndis_msg_parser(struct rndis_params *params, u8 *buf)
+int rndis_msg_parser(struct rndis_params *params, u8 *buf, u32 buflen)
{
u32 MsgType, MsgLength;
__le32 *tmp;
- if (!buf)
- return -ENOMEM;
+ if (!buf || buflen < 2 * sizeof(*tmp))
+ return -EINVAL;
tmp = (__le32 *)buf;
MsgType = get_unaligned_le32(tmp++);
@@ -803,6 +819,9 @@ int rndis_msg_parser(struct rndis_params *params, u8 *buf)
if (!params)
return -ENOTSUPP;
+ if (MsgLength > buflen || MsgLength > RNDIS_MAX_TOTAL_SIZE)
+ return -EINVAL;
+
/* NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for
* rx/tx statistics and link status, in addition to KEEPALIVE traffic
* and normal HC level polling to see if there's any IN traffic.
@@ -828,10 +847,12 @@ int rndis_msg_parser(struct rndis_params *params, u8 *buf)
case RNDIS_MSG_QUERY:
return rndis_query_response(params,
- (rndis_query_msg_type *)buf);
+ (rndis_query_msg_type *)buf,
+ MsgLength);
case RNDIS_MSG_SET:
- return rndis_set_response(params, (rndis_set_msg_type *)buf);
+ return rndis_set_response(params, (rndis_set_msg_type *)buf,
+ MsgLength);
case RNDIS_MSG_RESET:
pr_debug("%s: RNDIS_MSG_RESET\n",
diff --git a/drivers/usb/gadget/function/rndis.h b/drivers/usb/gadget/function/rndis.h
index 6206b8b7490f..cbb016fdad97 100644
--- a/drivers/usb/gadget/function/rndis.h
+++ b/drivers/usb/gadget/function/rndis.h
@@ -178,7 +178,7 @@ typedef struct rndis_params {
} rndis_params;
/* RNDIS Message parser and other useless functions */
-int rndis_msg_parser(struct rndis_params *params, u8 *buf);
+int rndis_msg_parser(struct rndis_params *params, u8 *buf, u32 buflen);
struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v);
void rndis_deregister(struct rndis_params *params);
int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev,
--
2.50.1 (Apple Git-155)