]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
can: softing: fw_parse(): validate firmware record spans
authorPengpeng Hou <pengpeng@iscas.ac.cn>
Wed, 22 Jul 2026 04:43:47 +0000 (12:43 +0800)
committerMarc Kleine-Budde <mkl@pengutronix.de>
Wed, 29 Jul 2026 10:00:00 +0000 (12:00 +0200)
fw_parse() reads a fixed record header, a firmware-provided payload,
and a trailing checksum without knowing the end of the firmware blob. A
truncated record can therefore make those reads exceed the blob.

The same record also supplies addresses and lengths for writes into
DPRAM. The generic loader uses wrap-prone mixed signed arithmetic for its
bounds check, while the application loader does not bound the staging
copy at all.

Pass the firmware end to the parser and validate the full source record.
Use a signed wide offset for generic DPRAM records and validate the
application staging span against the mapped DPRAM before copying.

Fixes: 03fd3cf5a179 ("can: add driver for Softing card")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
Link: https://patch.msgid.link/20260722044347.2708-1-pengpeng@iscas.ac.cn
Cc: stable@kernel.org
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
drivers/net/can/softing/softing_fw.c

index 721df91cdbfbc0a93ea6e931ef1889e87d807b04..282570daf3efed9ffd813c8079c2a3db1df2530b 100644 (file)
@@ -91,12 +91,12 @@ int softing_bootloader_command(struct softing *card, int16_t cmd,
        return ret;
 }
 
-static int fw_parse(const uint8_t **pmem, uint16_t *ptype, uint32_t *paddr,
-               uint16_t *plen, const uint8_t **pdat)
+static int fw_parse(const u8 **pmem, const u8 *limit, u16 *ptype,
+                   u32 *paddr, u16 *plen, const u8 **pdat)
 {
        uint16_t checksum[2];
-       const uint8_t *mem;
-       const uint8_t *end;
+       const u8 *mem;
+       const u8 *record_end;
 
        /*
         * firmware records are a binary, unaligned stream composed of:
@@ -114,14 +114,21 @@ static int fw_parse(const uint8_t **pmem, uint16_t *ptype, uint32_t *paddr,
         * endianness & alignment.
         */
        mem = *pmem;
+       /* A record needs an 8-byte prefix and a 2-byte checksum. */
+       if (mem > limit || limit - mem < 10)
+               return -EINVAL;
+
        *ptype = le16_to_cpup((void *)&mem[0]);
        *paddr = le32_to_cpup((void *)&mem[2]);
        *plen = le16_to_cpup((void *)&mem[6]);
+       if (*plen > limit - mem - 10)
+               return -EINVAL;
+
        *pdat = &mem[8];
        /* verify checksum */
-       end = &mem[8 + *plen];
-       checksum[0] = le16_to_cpup((void *)end);
-       for (checksum[1] = 0; mem < end; ++mem)
+       record_end = &mem[8 + *plen];
+       checksum[0] = le16_to_cpup((void *)record_end);
+       for (checksum[1] = 0; mem < record_end; ++mem)
                checksum[1] += *mem;
        if (checksum[0] != checksum[1])
                return -EINVAL;
@@ -139,6 +146,7 @@ int softing_load_fw(const char *file, struct softing *card,
        uint16_t type, len;
        uint32_t addr;
        uint8_t *buf = NULL, *new_buf;
+       s64 dpram_offset;
        int buflen = 0;
        int8_t type_end = 0;
 
@@ -153,7 +161,7 @@ int softing_load_fw(const char *file, struct softing *card,
        mem = fw->data;
        end = &mem[fw->size];
        /* look for header record */
-       ret = fw_parse(&mem, &type, &addr, &len, &dat);
+       ret = fw_parse(&mem, end, &type, &addr, &len, &dat);
        if (ret < 0)
                goto failed;
        if (type != 0xffff)
@@ -164,7 +172,7 @@ int softing_load_fw(const char *file, struct softing *card,
        }
        /* ok, we had a header */
        while (mem < end) {
-               ret = fw_parse(&mem, &type, &addr, &len, &dat);
+               ret = fw_parse(&mem, end, &type, &addr, &len, &dat);
                if (ret < 0)
                        goto failed;
                if (type == 3) {
@@ -179,9 +187,13 @@ int softing_load_fw(const char *file, struct softing *card,
                        goto failed;
                }
 
-               if ((addr + len + offset) > size)
+               dpram_offset = (s64)addr + offset;
+               if (dpram_offset < 0 || dpram_offset > size ||
+                   len > size - dpram_offset) {
+                       ret = -EINVAL;
                        goto failed;
-               memcpy_toio(&dpram[addr + offset], dat, len);
+               }
+               memcpy_toio(&dpram[dpram_offset], dat, len);
                /* be sure to flush caches from IO space */
                mb();
                if (len > buflen) {
@@ -195,7 +207,7 @@ int softing_load_fw(const char *file, struct softing *card,
                        buf = new_buf;
                }
                /* verify record data */
-               memcpy_fromio(buf, &dpram[addr + offset], len);
+               memcpy_fromio(buf, &dpram[dpram_offset], len);
                if (memcmp(buf, dat, len)) {
                        /* is not ok */
                        dev_alert(&card->pdev->dev, "DPRAM readback failed\n");
@@ -237,7 +249,7 @@ int softing_load_app_fw(const char *file, struct softing *card)
        mem = fw->data;
        end = &mem[fw->size];
        /* look for header record */
-       ret = fw_parse(&mem, &type, &addr, &len, &dat);
+       ret = fw_parse(&mem, end, &type, &addr, &len, &dat);
        if (ret)
                goto failed;
        ret = -EINVAL;
@@ -253,7 +265,7 @@ int softing_load_app_fw(const char *file, struct softing *card)
        }
        /* ok, we had a header */
        while (mem < end) {
-               ret = fw_parse(&mem, &type, &addr, &len, &dat);
+               ret = fw_parse(&mem, end, &type, &addr, &len, &dat);
                if (ret)
                        goto failed;
 
@@ -279,6 +291,12 @@ int softing_load_app_fw(const char *file, struct softing *card)
                /* work in 16bit (target) */
                sum &= 0xffff;
 
+               if (card->pdat->app.offs > card->dpram_size ||
+                   len > card->dpram_size - card->pdat->app.offs) {
+                       ret = -EINVAL;
+                       goto failed;
+               }
+
                memcpy_toio(&card->dpram[card->pdat->app.offs], dat, len);
                iowrite32(card->pdat->app.offs + card->pdat->app.addr,
                                &card->dpram[DPRAM_COMMAND + 2]);