Skip to content

arch/arm/src/at32/at32_can: Division by Zero in CAN Bit Timing Commands#19141

Open
catalinv-ncc wants to merge 1 commit into
apache:masterfrom
catalinv-ncc:bug/Division_by_Zero_in_CAN_Bit_Timing_Command_Implementations_for_STM32_and_AT32_Devices
Open

arch/arm/src/at32/at32_can: Division by Zero in CAN Bit Timing Commands#19141
catalinv-ncc wants to merge 1 commit into
apache:masterfrom
catalinv-ncc:bug/Division_by_Zero_in_CAN_Bit_Timing_Command_Implementations_for_STM32_and_AT32_Devices

Conversation

@catalinv-ncc

Copy link
Copy Markdown
Contributor

arch/arm/src/at32/at32_can: Division by Zero in CAN Bit Timing Commands

An unchecked integer assignment in the CAN driver may result in a division-by-zero which would result in a kernel crash (denial of service).

Tested locally, builds fine.

Impact

An unchecked integer assignment in the CAN driver may result in a division-by-zero which would result in a kernel crash (denial of service).

Description

The CAN driver can_ioctl function, shown below (drivers/can/can.c), receives commands from user processes. Its received arguments cmd and content of arg are under attacker's control. In the CAN driver, some ioctl commands are hardware specific and are processed by calling dev_ioctl(). Subsequently, depending on the hardware (STM32 or AT32), this function calls fdcan_ioctl or at32can_ioctl, as shown in the snippets that follow.

static int can_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode        *inode  = filep->f_inode;
  FAR struct can_dev_s    *dev    = inode->i_private;
  FAR struct can_reader_s *reader = filep->f_priv;
...
  flags = enter_critical_section();

  /* Handle built-in ioctl commands */
  switch (cmd)
    {
...
      /* Not a "built-in" ioctl command.. perhaps it is unique to this
       * lower-half, device driver. */
      default:
        {
          ret = dev_ioctl(dev, cmd, arg);
        }
        break;
    }

  leave_critical_section(flags);
  return ret;
}

There are a few instances where the user can trigger a kernel crash, caused by a division by zero on CANIOC_SET_BITTIMING command. On STM32 platforms (arch/arm/src/stm32/stm32_fdcan.c), while there is a DEBUGASSERT assertion for bt->bt_baud, the check does not cover value 0.

static const struct can_ops_s g_fdcanops =
{
...
  .co_ioctl         = fdcan_ioctl,
...
};

static int fdcan_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg)
{
...
  switch (cmd)
    {
...
      case CANIOC_SET_BITTIMING:
        {
          const struct canioc_bittiming_s *bt = (const struct canioc_bittiming_s *)arg;
          uint32_t nbrp;
          uint32_t ntseg1;
          uint32_t ntseg2;
          uint32_t nsjw;
          uint32_t ie;
          uint8_t state;

          DEBUGASSERT(bt != NULL);
          DEBUGASSERT(bt->bt_baud < STM32_FDCANCLK_FREQUENCY); // <- not valid
          DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 16);
          DEBUGASSERT(bt->bt_tseg1 > 1 && bt->bt_tseg1 <= 64);
          DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <= 16);

          /* Extract bit timing data */
          ntseg1 = bt->bt_tseg1 - 1;
          ntseg2 = bt->bt_tseg2 - 1;
          nsjw   = bt->bt_sjw   - 1;

          nbrp = (uint32_t)
            (  ((float) STM32_FDCANCLK_FREQUENCY /
               ((float)(ntseg1 + ntseg2 + 3) * (float)bt->bt_baud)) - 1 ); // <- div by 0

Similarly, another division by zero was found in Artery Technology AT32 (arch/arm/src/stm32/stm32_can.c) driver:

static const struct can_ops_s g_canops =
{
...
  .co_ioctl         = at32can_ioctl,
...
};

static int at32can_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg)
{
...
  /* Handle the command */
  switch (cmd)
    {
...
      case CANIOC_SET_BITTIMING:
        {
          const struct canioc_bittiming_s *bt = (const struct canioc_bittiming_s *)arg;
...
          uint32_t tmp;
          uint32_t regval;

          DEBUGASSERT(bt != NULL);
          DEBUGASSERT(bt->bt_baud < AT32_PCLK1_FREQUENCY); // <- not valid
          DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 4);
          DEBUGASSERT(bt->bt_tseg1 > 0 && bt->bt_tseg1 <= 16);
          DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <=  8);

          regval = at32can_getreg(priv, AT32_CAN_BTMG_OFFSET);

          /* Extract bit timing data tmp is in clocks per bit time */
          tmp = AT32_PCLK1_FREQUENCY / bt->bt_baud; // <- div by 0

Instances found are listed in the Location section below. They are not shown in detail to reduce the length of the issue.

Recommendation

Ensure the attacker-controlled data is properly validated before use, to stop division by zero situations. For instance:

DEBUGASSERT(bt->bt_baud > 0 && bt->bt_baud < AT32_PCLK1_FREQUENCY);

Location

  • arch/arm/src/stm32/stm32_fdcan.c
  • arch/arm/src/stm32/stm32_can.c
  • arch/arm/src/sama5/sam_mcan.c
  • arch/arm/src/at32/at32_can.c
  • arch/arm/src/samv7/sam_mcan.c
  • arch/arm/src/stm32f0l0g0/stm32_fdcan.c
  • arch/arm/src/stm32f7/stm32_can.c
  • arch/arm/src/stm32h5/stm32_fdcan.c
  • arch/arm/src/stm32l4/stm32l4_can.c
  • arch/arm/src/tiva/common/tiva_can.c
  • drivers/can/mcp2515.c

@github-actions github-actions Bot added Arch: arm Issues related to ARM (32-bit) architecture Area: Drivers Drivers issues Size: S The size of the change in this PR is small labels Jun 15, 2026
@catalinv-ncc catalinv-ncc force-pushed the bug/Division_by_Zero_in_CAN_Bit_Timing_Command_Implementations_for_STM32_and_AT32_Devices branch from d9873d4 to 1733403 Compare June 15, 2026 05:13
An unchecked integer assignment in the CAN driver may result in a division-by-zero which would result in a kernel crash (denial of service).

Tested locally, builds fine.

An unchecked integer assignment in the CAN driver may result in a division-by-zero which would result in a kernel crash (denial of service).

The CAN driver `can_ioctl` function, shown below (drivers/can/can.c), receives commands from user processes. Its received arguments `cmd` and content of `arg` are under attacker's control. In the CAN driver, some `ioctl` commands are hardware specific and are processed by calling `dev_ioctl()`. Subsequently, depending on the hardware (STM32 or AT32), this function calls `fdcan_ioctl` or `at32can_ioctl`, as shown in the snippets that follow.

```c

static int can_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode        *inode  = filep->f_inode;
  FAR struct can_dev_s    *dev    = inode->i_private;
  FAR struct can_reader_s *reader = filep->f_priv;
...
  flags = enter_critical_section();

  /* Handle built-in ioctl commands */
  switch (cmd)
    {
...
      /* Not a "built-in" ioctl command.. perhaps it is unique to this
       * lower-half, device driver. */
      default:
        {
          ret = dev_ioctl(dev, cmd, arg);
        }
        break;
    }

  leave_critical_section(flags);
  return ret;
}
```

There are a few instances where the user can trigger a kernel crash, caused by a division by zero on `CANIOC_SET_BITTIMING` command. On STM32 platforms (arch/arm/src/stm32/stm32_fdcan.c), while there is a `DEBUGASSERT` assertion for `bt->bt_baud`, the check does not cover value `0`.

```c
static const struct can_ops_s g_fdcanops =
{
...
  .co_ioctl         = fdcan_ioctl,
...
};

static int fdcan_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg)
{
...
  switch (cmd)
    {
...
      case CANIOC_SET_BITTIMING:
        {
          const struct canioc_bittiming_s *bt = (const struct canioc_bittiming_s *)arg;
          uint32_t nbrp;
          uint32_t ntseg1;
          uint32_t ntseg2;
          uint32_t nsjw;
          uint32_t ie;
          uint8_t state;

          DEBUGASSERT(bt != NULL);
          DEBUGASSERT(bt->bt_baud < STM32_FDCANCLK_FREQUENCY); // <- not valid
          DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 16);
          DEBUGASSERT(bt->bt_tseg1 > 1 && bt->bt_tseg1 <= 64);
          DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <= 16);

          /* Extract bit timing data */
          ntseg1 = bt->bt_tseg1 - 1;
          ntseg2 = bt->bt_tseg2 - 1;
          nsjw   = bt->bt_sjw   - 1;

          nbrp = (uint32_t)
            (  ((float) STM32_FDCANCLK_FREQUENCY /
               ((float)(ntseg1 + ntseg2 + 3) * (float)bt->bt_baud)) - 1 ); // <- div by 0
```

Similarly, another division by zero was found in Artery Technology AT32 (arch/arm/src/stm32/stm32_can.c) driver:

```c
static const struct can_ops_s g_canops =
{
...
  .co_ioctl         = at32can_ioctl,
...
};

static int at32can_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg)
{
...
  /* Handle the command */
  switch (cmd)
    {
...
      case CANIOC_SET_BITTIMING:
        {
          const struct canioc_bittiming_s *bt = (const struct canioc_bittiming_s *)arg;
...
          uint32_t tmp;
          uint32_t regval;

          DEBUGASSERT(bt != NULL);
          DEBUGASSERT(bt->bt_baud < AT32_PCLK1_FREQUENCY); // <- not valid
          DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 4);
          DEBUGASSERT(bt->bt_tseg1 > 0 && bt->bt_tseg1 <= 16);
          DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <=  8);

          regval = at32can_getreg(priv, AT32_CAN_BTMG_OFFSET);

          /* Extract bit timing data tmp is in clocks per bit time */
          tmp = AT32_PCLK1_FREQUENCY / bt->bt_baud; // <- div by 0
```

Instances found are listed in the *Location* section below. They are not shown in detail to reduce the length of the issue.

Ensure the attacker-controlled data is properly validated before use, to stop division by zero situations. For instance:

```c
DEBUGASSERT(bt->bt_baud > 0 && bt->bt_baud < AT32_PCLK1_FREQUENCY);
```

* arch/arm/src/stm32/stm32_fdcan.c
* arch/arm/src/stm32/stm32_can.c
* arch/arm/src/sama5/sam_mcan.c
* arch/arm/src/at32/at32_can.c
* arch/arm/src/samv7/sam_mcan.c
* arch/arm/src/stm32f0l0g0/stm32_fdcan.c
* arch/arm/src/stm32f7/stm32_can.c
* arch/arm/src/stm32h5/stm32_fdcan.c
* arch/arm/src/stm32l4/stm32l4_can.c
* arch/arm/src/tiva/common/tiva_can.c
* drivers/can/mcp2515.c

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
@catalinv-ncc catalinv-ncc force-pushed the bug/Division_by_Zero_in_CAN_Bit_Timing_Command_Implementations_for_STM32_and_AT32_Devices branch from 1733403 to a67efa1 Compare June 15, 2026 05:16
DEBUGASSERT(timing.tseg2 <= 8 && timing.tseg1 >= 1);
DEBUGASSERT(timing.tseg2 <= 8 && timing.tseg2 >= 1);
DEBUGASSERT(timing.sjw <= 4 && timing.sjw >= 1);
DEBUGASSERT(bt->bt_baud > 0);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be:

DEBUGASSERT(bt->bt_baud > 0 && SYSCLK_FREQUENCY < bt->bt_baud);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Area: Drivers Drivers issues Size: S The size of the change in this PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants