The Linux kernel is the heart of countless devices—web servers, laptops, embedded systems, and more. From time to time, security researchers uncover subtle bugs hiding deep inside its code. CVE-2022-28390 is one such example. This high-impact bug comes from a double free flaw in the CAN USB driver (specifically in ems_usb_start_xmit in drivers/net/can/usb/ems_usb.c), affecting Linux kernel versions up to 5.17.1.
Let’s break down what happened, why it matters, and how it could be exploited. (I'll even give you simple code snippets and useful links for further reading.)
The Vulnerability In a Nutshell
CVE-2022-28390 is a double free vulnerability. That means the same piece of memory (skb, a socket buffer) could be freed from memory twice—a nasty bug that could lead to system crashes, data leaks, or full-blown privilege escalation.
This bug lives in the ems_usb_start_xmit function, which is called when sending CAN (Controller Area Network) packets over a USB adapter.
Short summary:
vulnerable Code: What Went Wrong?
Here’s a simplified and focused version of the vulnerable code from the Linux kernel source (see official commit):
static netdev_tx_t ems_usb_start_xmit(struct sk_buff *skb, struct net_device *netdev)
{
struct ems_usb *dev = netdev_priv(netdev);
struct ems_usb_tx_urb_context *context;
int i;
// ... some code omitted for clarity ...
context = &dev->tx_contexts[i];
context->skb = skb;
// Try to queue/transmit to USB
err = usb_submit_urb(context->urb, GFP_ATOMIC);
if (err) {
// If sending fails, free skb manually:
dev_kfree_skb_any(skb);
context->skb = NULL;
netdev->stats.tx_errors++;
return NETDEV_TX_OK;
}
// elsewhere in USB code, if send successful:
// dev_kfree_skb_any(context->skb); <-- this can happen AGAIN!
// context->skb = NULL;
// ... rest of the code ...
}
What's the bug?
If usb_submit_urb() fails, the function frees skb by calling dev_kfree_skb_any(skb). But, on error or completion, another part of the driver will _also_ free skb (since context->skb still points to it), leading to double freeing the memory.
What CAN Happen?
- Kernel Panic: Double freeing a socket buffer can corrupt memory. The next time the kernel touches the memory, the system may crash.
- Privilege Escalation: A crafty attacker may carefully time the exploitation, causing the kernel to overwrite sensitive structures—maybe even running malicious code as root.
- Denial of Service: Even without privilege escalation, a crash is a denial-of-service for that device.
Exploitation Path
1. User with Local Access triggers the bug by sending specially crafted CAN packets to the vulnerable driver through a socket (requires hardware or simulation).
They repeat this to force the function down the error path, freeing skb in two places.
3. Malicious code running in user space could heap spray (flood memory with controllable data) and then exploit the kernel crash to gain control.
Here’s a simplified (not weaponized) code snippet showing how a local user might trigger the bug
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
struct ifreq ifr;
struct sockaddr_can addr;
struct can_frame frame;
// Set up CAN interface and bind
strcpy(ifr.ifr_name, "can");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
// Send lots of malformed packets, hoping to cause usb_submit_urb to fail
memset(&frame, , sizeof(frame));
for (int i = ; i < 100; i++) {
// Deliberately send big or weird frames, or mess with device to simulate errors
sendto(s, &frame, sizeof(frame), , (struct sockaddr *)&addr, sizeof(addr));
}
close(s);
(You’d need to trigger actual errors in the driver or USB device, but this is the basic idea.)
The Fix
The Linux kernel maintainers fixed the issue by making sure the socket buffer was only freed in one place—not twice.
Fix commit:
linux commit a7aec255a7c2fd33d6c585b199ae76b9301aba3f
Relevant patch (summary)
- if (err) {
- dev_kfree_skb_any(skb);
- context->skb = NULL;
- netdev->stats.tx_errors++;
- return NETDEV_TX_OK;
- }
+ if (err) {
+ context->skb = NULL;
+ netdev->stats.tx_errors++;
+ return NETDEV_TX_OK;
+ }
The fix: Remove the extra free. Now, dev_kfree_skb_any(context->skb) is only called from the completion handler, not from both paths.
Official Advisory:
Linux Kernel Patch:
Fix double free in ems_usb_start_xmit
oss-sec Mail Thread:
oss-sec: Linux kernel ems_usb double free
Linux CAN Subsystem Docs:
In Summary
CVE-2022-28390 reminds us: even in tiny, specialized drivers, memory bugs can threaten the whole OS.
Simple advice:
If you work with CAN USB devices and run Linux ≤5.17.1, patch your kernel ASAP! If you’re a maintainer, use static analysis tools and code reviews to spot double free bugs before attackers do.
Timeline
Published on: 04/03/2022 21:15:00 UTC
Last modified on: 07/04/2022 11:15:00 UTC