firmware-utils/tplink-safeloader: add compat level
authorSander Vanheule <sander@svanheule.net>
Sat, 11 Jul 2020 21:06:54 +0000 (23:06 +0200)
committerStijn Tintel <stijn@linux-ipv6.be>
Wed, 9 Sep 2020 17:41:50 +0000 (20:41 +0300)
TP-Link has introduced a compatibility level to prevent certain
downgrades. This information is stored in the soft-version partition,
changing the data length from 0xc to 0x10.

The compatibility level doesn't change frequently. For example, it has
the following values for the EAP245v3 (released 2018-Q4):
* FW v2.2.0  (2019-05-30): compat_level=0
* FW v2.3.0  (2019-07-31): compat_level=0
* FW v2.3.1  (2019-10-29): compat_level=1
* FW v2.20.0 (2020-04-23): compat_level=1

Empty flash values (0xffffffff) are interpreted as compat_level=0.
If a firmware upgrade file has a soft-version block without
compatibility level (data length < 0x10), this is also interpreted as
compat_level=0.

By including a high enough compatibility level in factory images, stock
firmware can be convinced to accept the image. A compatibility level
aware firmware will keep the original value.

Example upgrade log of TP-Link EAP245v3 FWv2.3.0 to FWv2.20.0:
    [NM_Debug](nm_fwup_verifyFwupFile) 02073: curSoftVer:2.3.0 Build
        20190731 Rel. 51932,newSoftVer:2.20.0 Build 20200423 Rel. 36779
    ...
    AddiHardwareVer check: NEW(0x1) >= CUR(0x0), Success.
    ...
    [NM_NOTICE](updateDataToNvram) 00575: Restore old additionalHardVer:
    0x0.(new 0x1)
    [NM_NOTICE](updateDataToNvram) 00607: PTN 07: name = soft-version,
        base = 0x00092000, size = 0x00000100 Bytes, upDataType = 1,
        upDataStart = 7690604b, upDataLen = 00000018
    [NM_Debug](updateDataToNvram) 00738: PTN 07: write bytes = 000002eb

Other firmware upgrades have been observed to modify the compabitility
stored level (e.g. TP-Link EAP225-Outdoor FWv1.4.1 to FWv1.7.0).
Therefore, it seems to be the safest option to set the OpenWrt
compatibility level to the highest known value instead of the highest
possible value (0xfffffffe), to ensure users do not get unexpectedly
refused firmware upgrades when using a device reverted back to stock.

To remain compatible with existing devices and not produce different
images, the image builder doesn't store a compatibility level if it is
zero.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
tools/firmware-utils/src/tplink-safeloader.c

index 145e80855ad604b7d9dd2fc47432721d5f13c0a5..9005ffa4874504d32e8cb4decded1daf46bca04f 100644 (file)
@@ -77,6 +77,7 @@ struct device_info {
        const char *support_list;
        char support_trail;
        const char *soft_ver;
+       uint32_t soft_ver_compat_level;
        struct flash_partition_entry partitions[MAX_PARTITIONS+1];
        const char *first_sysupgrade_partition;
        const char *last_sysupgrade_partition;
@@ -95,7 +96,6 @@ struct __attribute__((__packed__)) soft_version {
        uint8_t month;
        uint8_t day;
        uint32_t rev;
-       uint8_t pad2;
 };
 
 
@@ -2140,8 +2140,13 @@ static inline uint8_t bcd(uint8_t v) {
 
 
 /** Generates the soft-version partition */
-static struct image_partition_entry make_soft_version(uint32_t rev) {
-       struct image_partition_entry entry = alloc_image_partition("soft-version", sizeof(struct soft_version));
+static struct image_partition_entry make_soft_version(struct device_info *info, uint32_t rev) {
+       size_t part_len = sizeof(struct soft_version);
+       if (info->soft_ver_compat_level > 0)
+               part_len += sizeof(uint32_t);
+
+       struct image_partition_entry entry =
+           alloc_image_partition("soft-version", part_len+1);
        struct soft_version *s = (struct soft_version *)entry.data;
 
        time_t t;
@@ -2168,7 +2173,11 @@ static struct image_partition_entry make_soft_version(uint32_t rev) {
        s->day = bcd(tm->tm_mday);
        s->rev = htonl(rev);
 
-       s->pad2 = 0xff;
+       if (info->soft_ver_compat_level > 0)
+               *(uint32_t *)(entry.data + sizeof(struct soft_version)) =
+                   htonl(info->soft_ver_compat_level);
+
+       entry.data[entry.size-1] = 0xff;
 
        return entry;
 }
@@ -2480,7 +2489,7 @@ static void build_image(const char *output,
        if (info->soft_ver)
                parts[1] = make_soft_version_from_string(info->soft_ver);
        else
-               parts[1] = make_soft_version(rev);
+               parts[1] = make_soft_version(info, rev);
 
        parts[2] = make_support_list(info);
        parts[3] = read_file("os-image", kernel_image, false, NULL);