From d0620706f05bf8d4e4eabeb3063d61dcc579b643 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 7 Jun 2024 19:23:56 +0200 Subject: [PATCH] ipq40xx: fix broken image generation for EX6150v2 All NETGEAR EX6150v2 validate the rootfs for which OpenWrt places a fakeheader at the position, where the bootloader expects it. Some EX6150v2 bootloaders do however make a broken assumption about where the rootfs starts. This is due to them calculating the rootfs start not based upon the kernel-length but the string-offset of the FIT-image. We have to be compatible with both this broken as well as the valid calculation. So we do relocate the FDT string section to a block-boundary and enlarge the FIT image to end at this boundary + BLOCKSIZE / 2. This way, both the broken as well as correct calculations do expect the rootfs-header at the same position. It is worth noting, that this is a rare edge-case in which only happens if the image-length as well as the start of the string-section are not placed in the same erase-block. This is an edge-case which happens very rarely (thus it was not spotted prior). Affected: - U-Boot 2012.07 (Jun 16 2016 - 11:59:37) Signed-off-by: David Bauer (cherry picked from commit de59fc45402ff03e320264c8204f6928090534ad) --- target/linux/ipq40xx/image/generic.mk | 9 +- .../ipq40xx/image/netgear-fit-padding.py | 89 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100755 target/linux/ipq40xx/image/netgear-fit-padding.py diff --git a/target/linux/ipq40xx/image/generic.mk b/target/linux/ipq40xx/image/generic.mk index a84031f645..df9047df78 100644 --- a/target/linux/ipq40xx/image/generic.mk +++ b/target/linux/ipq40xx/image/generic.mk @@ -3,6 +3,11 @@ DEVICE_VARS += NETGEAR_BOARD_ID NETGEAR_HW_ID DEVICE_VARS += RAS_BOARD RAS_ROOTFS_SIZE RAS_VERSION DEVICE_VARS += WRGG_DEVNAME WRGG_SIGNATURE +define Build/netgear-fit-padding + ./netgear-fit-padding.py $@ $@.new + mv $@.new $@ +endef + define Device/FitImage KERNEL_SUFFIX := -uImage.itb KERNEL = kernel-bin | gzip | fit gzip $$(KDIR)/image-$$(DEVICE_DTS).dtb @@ -33,8 +38,8 @@ define Device/DniImage NETGEAR_BOARD_ID := NETGEAR_HW_ID := IMAGES += factory.img - IMAGE/factory.img := append-kernel | pad-offset 64k 64 | append-uImage-fakehdr filesystem | append-rootfs | pad-rootfs | netgear-dni - IMAGE/sysupgrade.bin := append-kernel | pad-offset 64k 64 | append-uImage-fakehdr filesystem | \ + IMAGE/factory.img := append-kernel | netgear-fit-padding | append-uImage-fakehdr filesystem | append-rootfs | pad-rootfs | netgear-dni + IMAGE/sysupgrade.bin := append-kernel | netgear-fit-padding | append-uImage-fakehdr filesystem | \ append-rootfs | pad-rootfs | check-size | append-metadata endef diff --git a/target/linux/ipq40xx/image/netgear-fit-padding.py b/target/linux/ipq40xx/image/netgear-fit-padding.py new file mode 100755 index 0000000000..87c0854b5a --- /dev/null +++ b/target/linux/ipq40xx/image/netgear-fit-padding.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# -*- coding: utf-8 -*- + +# NETGEAR EX6150v2 padding tool +# (c) 2024 David Bauer + +import math +import sys + +FLASH_BLOCK_SIZE = 64 * 1024 + + +def read_field(data, offset): + return data[offset + 3] | data[offset + 2] << 8 | data[offset + 1] << 16 | data[offset] << 24 + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print('Usage: {} '.format(sys.argv[0])) + sys.exit(1) + + with open(sys.argv[1], 'rb') as f: + data = f.read() + + file_len = len(data) + + # File-len in fdt header at offset 0x4 + file_len_hdr = read_field(data, 0x4) + # String offset in fdt header at offset 0xc + str_off = read_field(data, 0xc) + + print("file_len={} hdr_file_len={} str_off={}".format(file_len, file_len_hdr, str_off)) + + # Off to NETGEAR calculations - Taken from u-boot source (cmd_dni.c:2145) + # + # rootfs_addr = (ntohl(hdr->ih_size)/CONFIG_SYS_FLASH_SECTOR_SIZE+1) * CONFIG_SYS_FLASH_SECTOR_SIZE + + # 2*sizeof(image_header_t)-sizeof(image_header_t); + # rootfs_addr = rootfs_addr - (0x80 - mem_addr); + + # NETGEAR did fuck up badly. The image uses a FIT header, while the calculation is done on a legacy header + # assumption. 'ih_size' matches 'off_dt_strings' of a fdt_header. + # From my observations, this seems to be fixed on newer bootloader versions. + # However, we need to be compatible with both. + + # This presents a challenge: FDT_STR might end short of a block boundary, colliding with the rootfs_addr + # + # Our dirty solution: + # - Move the string_table to match a block_boundary. + # - Update the total file_len to end on 50% of a block boundary. + # + # This ensures all netgear calculations will be correct, regardless whether they are done based on the + # 'off_dt_strings' or 'totalsize' fields of a fdt header. + + new_dt_strings = int((math.floor(file_len / FLASH_BLOCK_SIZE) + 2) * FLASH_BLOCK_SIZE) + new_image_len = int(new_dt_strings + (FLASH_BLOCK_SIZE / 2)) + new_file_len = int(new_dt_strings + FLASH_BLOCK_SIZE - 64) + print(f"new_file_len={new_file_len} new_hdr_file_len={new_image_len} new_str_offset={new_dt_strings}") + + # Convert data to bytearray + data = bytearray(data) + + # Enlarge byte-array to new size + data.extend(bytearray(new_file_len - file_len)) + + # Assert that the new and old string-tables are at least 256 bytes apart. + # We pad by two blocks, but let's be extra sure. + assert new_dt_strings - str_off >= 256 + + # Move the string table to the new offset + for i in range(0, 256): + data[new_dt_strings + i] = data[str_off + i] + data[str_off + i] = 0 + + # Update the string offset in the header + data[0xc] = (new_dt_strings >> 24) & 0xFF + data[0xd] = (new_dt_strings >> 16) & 0xFF + data[0xe] = (new_dt_strings >> 8) & 0xFF + data[0xf] = new_dt_strings & 0xFF + + # Update the file length in the header + data[0x4] = (new_image_len >> 24) & 0xFF + data[0x5] = (new_image_len >> 16) & 0xFF + data[0x6] = (new_image_len >> 8) & 0xFF + data[0x7] = new_image_len & 0xFF + + # Write the new file + with open(sys.argv[1] + '.new', 'wb') as f: + f.write(data) -- 2.30.2