RT-Thread Version
v5.0.2
Hardware Type/Architectures
SWM341 CAN
Develop Toolchain
Other
Describe the bug
SWM341 CAN receive path trusts raw DLC and writes past fixed receive buffers
Describe the bug
The SWM341 CAN receive path copies the raw hardware DLC value into fixed 8-byte CAN receive buffers without clamping it to the classic CAN payload size.
The external input boundary is the CAN controller receive frame. When a frame arrives from the CAN bus, the controller exposes attacker-controlled frame metadata and payload through CANx->FRAME.INFO and CANx->FRAME.DATA[]. The low 4 bits of CANx->FRAME.INFO are used as the DLC. That external DLC value flows directly into a loop bound and causes an out-of-bounds write when it is greater than 8.
The concrete data flow is:
external CAN frame
-> CAN controller RX register CANx->FRAME.INFO[DLC]
-> CAN_Receive(): msg->size
-> loop bound for msg->data[i]
-> out-of-bounds write when i == 8 and msg->size > 8
-> swm_can_recvmsg(): pmsg->len
-> loop bound for pmsg->data[i]
-> out-of-bounds write when i == 8 and pmsg->len > 8
The RT-Thread CAN message type used when RT_CAN_USING_CANFD is not enabled stores only 8 payload bytes:
components/drivers/include/drivers/dev_can.h:510
components/drivers/include/drivers/dev_can.h:526
However, the SWM341 low-level receive routine extracts a 4-bit DLC value from the CAN frame information register:
#define CAN_INFO_DLC_Msk (0x0F << CAN_INFO_DLC_Pos)
/* External input: DLC bits from the received CAN frame. */
msg->size = (CANx->FRAME.INFO & CAN_INFO_DLC_Msk) >> CAN_INFO_DLC_Pos;
and then uses that value directly as a byte count:
/*
* Overflow trigger:
* msg->data has valid indexes [0..7].
* If the external DLC is 9..15, the iteration with i == 8 writes
* msg->data[8], which is past the end of CAN_RXMessage.data.
*/
for(i = 0; i < msg->size; i++)
{
/* CANx->FRAME.DATA[] is the external CAN payload register. */
msg->data[i] = CANx->FRAME.DATA[i+2];
}
CAN_RXMessage.data is only 8 bytes:
typedef struct {
uint32_t id;
uint8_t format;
uint8_t remote;
uint8_t size;
uint8_t data[8];
} CAN_RXMessage;
The higher-level SWM341 RT-Thread driver then copies the same unchecked length into struct rt_can_msg.data[8]:
CAN_RXMessage CAN_RXMsg;
CAN_Receive(can_dev->can_cfg->CANx, &CAN_RXMsg);
/* CAN_RXMsg.size is derived from the external CAN DLC. */
pmsg->len = CAN_RXMsg.size;
/*
* Second overflow point:
* pmsg points to RT-Thread's struct rt_can_msg.
* pmsg->data also has valid indexes [0..7].
* If pmsg->len is still 9..15, i == 8 writes pmsg->data[8].
*/
for(i = 0; i < pmsg->len; i++)
{
pmsg->data[i] = CAN_RXMsg.data[i];
}
The interrupt path reaches this driver callback through the generic RT-Thread CAN ISR:
/* Stack object inside rt_hw_can_isr(). */
struct rt_can_msg tmpmsg;
/* swm_can_recvmsg() receives &tmpmsg as pmsg. */
ch = can->ops->recvmsg(can, &tmpmsg, no);
...
rt_memcpy(&listmsg->data, &tmpmsg, sizeof(struct rt_can_msg));
So a received classic CAN frame whose raw DLC code is 9..15 can corrupt stack memory in the ISR receive path before the message is copied into the RT-Thread RX FIFO.
Locations:
bsp/synwit/libraries/SWM341_CSL/CMSIS/DeviceSupport/SWM341.h:2268
bsp/synwit/libraries/SWM341_CSL/CMSIS/DeviceSupport/SWM341.h:2269
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.h:85
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.h:89
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.h:90
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.c:240
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.c:246
bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.c:248
bsp/synwit/libraries/SWM341_drivers/drv_can.c:413
bsp/synwit/libraries/SWM341_drivers/drv_can.c:416
bsp/synwit/libraries/SWM341_drivers/drv_can.c:438
bsp/synwit/libraries/SWM341_drivers/drv_can.c:440
bsp/synwit/libraries/SWM341_drivers/drv_can.c:442
bsp/synwit/libraries/SWM341_drivers/drv_can.c:453
bsp/synwit/libraries/SWM341_drivers/drv_can.c:466
components/drivers/can/dev_can.c:982
components/drivers/can/dev_can.c:998
components/drivers/can/dev_can.c:1039
components/drivers/include/drivers/dev_can.h:526
Steps to reproduce
I have not reproduced this on physical SWM341 hardware yet. I verified the source-level bug with a standalone reduced check that preserves the relevant register decode and fixed-buffer copy semantics.
The source-level trigger is:
CANx->FRAME.INFO DLC bits = 9..15
RT_CAN_USING_CANFD = not enabled
BSP_USING_CAN = enabled
BSP_USING_CAN0 or CAN1 = enabled
Reduced check:
#include <stdint.h>
typedef struct {
uint32_t INFO;
uint8_t DATA[16];
} CAN_Frame;
typedef struct {
CAN_Frame FRAME;
} CAN_TypeDef;
typedef struct {
uint32_t id;
uint8_t format;
uint8_t remote;
uint8_t size;
uint8_t data[8];
} CAN_RXMessage;
#define CAN_INFO_DLC_Pos 0
#define CAN_INFO_DLC_Msk (0x0F << CAN_INFO_DLC_Pos)
void CAN_Receive(CAN_TypeDef *CANx, CAN_RXMessage *msg)
{
uint32_t i;
/* External input: raw DLC from the received CAN frame. */
msg->size = (CANx->FRAME.INFO & CAN_INFO_DLC_Msk) >> CAN_INFO_DLC_Pos;
for (i = 0; i < msg->size; i++) {
/* OOB when can.FRAME.INFO makes msg->size > 8 and i reaches 8. */
msg->data[i] = CANx->FRAME.DATA[i + 2];
}
}
int main(void)
{
CAN_TypeDef can = {0};
CAN_RXMessage msg = {0};
can.FRAME.INFO = 15; /* external DLC value 15 */
CAN_Receive(&can, &msg);
return 0;
}
Compile it with AddressSanitizer, for example:
clang -x c -fsanitize=address -O0 -g repro.c -o repro && ./repro
Changing the receive path to clamp or reject DLC values above 8 avoids the ASan report.
Relevant log output
AddressSanitizer reports a stack buffer overflow on the write into CAN_RXMessage.data:
ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 1
#0 CAN_Receive ... repro.c
#1 main ... repro.c
This frame has 2 object(s):
[..] 'can'
[..] 'msg' <== Memory access overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow in CAN_Receive
Impact
Potential memory corruption from an externally supplied CAN frame on SWM341 boards when the CAN driver is enabled.
The attacker-controlled value is the raw DLC field observed by the CAN controller. If the controller reports DLC values 9..15 to software, the driver first writes beyond the local CAN_RXMessage.data[8] buffer and then may also copy beyond struct rt_can_msg.data[8] in the RT-Thread receive callback.
This is especially relevant for RTOS deployments because CAN traffic often crosses trust boundaries, and the vulnerable path executes from the interrupt-driven receive flow.
Environment
Initial RT-Thread commit checked: c39e92f4c1
Checked tree description: v5.0.2-2360-gc39e92f4c1-dirty
Affected driver: bsp/synwit/libraries/SWM341_drivers/drv_can.c
Affected low-level CAN code: bsp/synwit/libraries/SWM341_CSL/SWM341_StdPeriph_Driver/SWM341_can.c
Affected board family: Synwit SWM341
Target hardware: not tested on board yet
Verification: host-side AddressSanitizer semantic check
The board configuration exposes this driver when CAN is enabled:
bsp/synwit/swm341-mini/board/Kconfig:115 menuconfig BSP_USING_CAN
bsp/synwit/swm341-mini/board/Kconfig:120 config BSP_USING_CAN0
bsp/synwit/swm341-mini/board/Kconfig:123 config BSP_USING_CAN1
The currently checked default .config has this board's CAN options disabled:
bsp/synwit/swm341-mini/.config:247 # CONFIG_RT_USING_CAN is not set
bsp/synwit/swm341-mini/.config:1415 # CONFIG_BSP_USING_CAN is not set
Additional context
A fix should validate the DLC before it is used as a copy length. For classic CAN, the code should either reject/drop frames whose raw DLC code is above 8 or clamp the payload length to 8 before copying:
msg->size = (CANx->FRAME.INFO & CAN_INFO_DLC_Msk) >> CAN_INFO_DLC_Pos;
if (msg->size > 8) {
msg->size = 8;
}
The same invariant should be preserved in swm_can_recvmsg() before writing pmsg->len and before copying into pmsg->data.
Other additional context
No response
RT-Thread Version
v5.0.2
Hardware Type/Architectures
SWM341 CAN
Develop Toolchain
Other
Describe the bug
SWM341 CAN receive path trusts raw DLC and writes past fixed receive buffers
Describe the bug
The SWM341 CAN receive path copies the raw hardware DLC value into fixed 8-byte CAN receive buffers without clamping it to the classic CAN payload size.
The external input boundary is the CAN controller receive frame. When a frame arrives from the CAN bus, the controller exposes attacker-controlled frame metadata and payload through
CANx->FRAME.INFOandCANx->FRAME.DATA[]. The low 4 bits ofCANx->FRAME.INFOare used as the DLC. That external DLC value flows directly into a loop bound and causes an out-of-bounds write when it is greater than 8.The concrete data flow is:
The RT-Thread CAN message type used when
RT_CAN_USING_CANFDis not enabled stores only 8 payload bytes:However, the SWM341 low-level receive routine extracts a 4-bit DLC value from the CAN frame information register:
and then uses that value directly as a byte count:
CAN_RXMessage.datais only 8 bytes:The higher-level SWM341 RT-Thread driver then copies the same unchecked length into
struct rt_can_msg.data[8]:The interrupt path reaches this driver callback through the generic RT-Thread CAN ISR:
So a received classic CAN frame whose raw DLC code is 9..15 can corrupt stack memory in the ISR receive path before the message is copied into the RT-Thread RX FIFO.
Locations:
Steps to reproduce
I have not reproduced this on physical SWM341 hardware yet. I verified the source-level bug with a standalone reduced check that preserves the relevant register decode and fixed-buffer copy semantics.
The source-level trigger is:
Reduced check:
Compile it with AddressSanitizer, for example:
Changing the receive path to clamp or reject DLC values above 8 avoids the ASan report.
Relevant log output
AddressSanitizer reports a stack buffer overflow on the write into
CAN_RXMessage.data:Impact
Potential memory corruption from an externally supplied CAN frame on SWM341 boards when the CAN driver is enabled.
The attacker-controlled value is the raw DLC field observed by the CAN controller. If the controller reports DLC values 9..15 to software, the driver first writes beyond the local
CAN_RXMessage.data[8]buffer and then may also copy beyondstruct rt_can_msg.data[8]in the RT-Thread receive callback.This is especially relevant for RTOS deployments because CAN traffic often crosses trust boundaries, and the vulnerable path executes from the interrupt-driven receive flow.
Environment
The board configuration exposes this driver when CAN is enabled:
The currently checked default
.confighas this board's CAN options disabled:Additional context
A fix should validate the DLC before it is used as a copy length. For classic CAN, the code should either reject/drop frames whose raw DLC code is above 8 or clamp the payload length to 8 before copying:
The same invariant should be preserved in
swm_can_recvmsg()before writingpmsg->lenand before copying intopmsg->data.Other additional context
No response