Root Cause Analysis
1. Relevant Files, Functions, and Data Structures
- Source Files:
- Functions:
- Structures:
struct gb_operation_msg_hdr (8 bytes: size [2 bytes], operation_id [2 bytes], type [1 byte], result [1 byte], pad [2 bytes])
struct gb_operation
struct gb_message
struct gb_svc_version_request (2 bytes: major [1 byte], minor [1 byte])
2. Detailed Technical Mechanism
When a Greybus host device receives incoming data over its transport layer (such as USB/ES2 via cport_in_callback() in drivers/greybus/es2.c), it passes the data buffer and received length to greybus_data_rcvd(), which calls gb_connection_recv().
In gb_connection_recv():
if (size < sizeof(header)) {
dev_err_ratelimited(dev, "%s: short message received\n",
connection->name);
return;
}
/* Use memcpy as data may be unaligned */
memcpy(&header, data, sizeof(header));
msg_size = le16_to_cpu(header.size);
if (size < msg_size) {
dev_err_ratelimited(dev, ...);
return;
}
if (header.type & GB_MESSAGE_TYPE_RESPONSE) {
gb_connection_recv_response(connection, &header, data, msg_size);
} else {
gb_connection_recv_request(connection, &header, data, msg_size);
}
The function checks:
size < sizeof(header): Ensures that the raw received buffer contains at least 8 bytes (sizeof(struct gb_operation_msg_hdr)).
size < msg_size: Ensures that the raw buffer has enough bytes to hold the total message length indicated in header.size.
However, it fails to validate that msg_size >= sizeof(header).
In a well-formed Greybus frame, every message contains at least a full struct gb_operation_msg_hdr (8 bytes), so header.size must be $\ge 8$. If an incoming packet specifies a header size smaller than the header itself (e.g., header.size = 7 while the received frame size is $\ge 8$):
size < sizeof(header) is false.
size < msg_size is false (e.g. $8 < 7$ is false).
gb_connection_recv() invokes gb_connection_recv_request(..., data, msg_size) passing size = 7.
In gb_connection_recv_request(), gb_operation_create_incoming() is called with size = 7:
static struct gb_operation *
gb_operation_create_incoming(struct gb_connection *connection, u16 id,
u8 type, void *data, size_t size)
{
...
/* Caller has made sure we at least have a message header. */
request_size = size - sizeof(struct gb_operation_msg_hdr);
gb_operation_create_incoming() relies on its caller having verified size >= sizeof(struct gb_operation_msg_hdr). Because size is 7 and sizeof(header) is 8, the subtraction underflows unsigned size_t:
$$\text{request_size} = 7 - 8 = \text{SIZE_MAX} ; (\text{0xffffffffffffffff})$$
gb_operation_create_common() then calls gb_operation_message_alloc() with payload_size = request_size:
size_t message_size = payload_size + sizeof(*header);
Due to standard integer wrapping, $\text{SIZE_MAX} + 8 = 7$.
message->buffer = kzalloc(message_size, gfp_flags) allocates only a 7-byte buffer (from the kmalloc-8 slab cache).
Next, gb_operation_message_init() initializes message fields:
header = message->buffer;
message->header = header;
message->payload = payload_size ? header + 1 : NULL;
message->payload_size = payload_size;
Because payload_size is non-zero (SIZE_MAX), message->payload is set to header + 1 (offset 8), which is 1 byte past the end of the allocated 7-byte buffer [buffer, buffer + 7). Furthermore, message->payload_size remains SIZE_MAX.
gb_operation_create_incoming() copies the received header data via memcpy(operation->request->header, data, 7) and queues the operation for asynchronous execution via gb_operation_work().
3. Manifestation in gb_svc_version_request()
When the worker thread executes gb_operation_work(), it calls gb_operation_request_handle() which dispatches the incoming request to gb_svc_request_handler(), calling gb_svc_version_request(op):
if (op->request->payload_size < sizeof(*request)) {
dev_err(&svc->dev, "short version request (%zu < %zu)\n",
op->request->payload_size,
sizeof(*request));
return -EINVAL;
}
request = op->request->payload;
if (request->major > GB_SVC_VERSION_MAJOR) { ... }
Because op->request->payload_size is SIZE_MAX, the check op->request->payload_size < sizeof(*request) passes.
Dereferencing request->major attempts to read byte 0 of request at op->request->payload, which is offset 8 of op->request->buffer (address ffff88811f35f368, exactly 1 byte past the 7-byte allocated buffer ffff88811f35f360 in kmalloc-8). KASAN immediately detects this slab-out-of-bounds read and reports the crash.
Note that a similar underflow vulnerability exists in gb_connection_recv_response() if size < sizeof(*header) when computing message->payload_size = size - sizeof(*header).