Skip to content

[Bug] Security-related Bug: SWM341 CAN receive path trusts raw DLC and writes past fixed receive buffers #11425

@zephyr-saxon

Description

@zephyr-saxon

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions