cameo-tag.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. #!/usr/bin/python3
  2. # SPDX-License-Identifier: GPL-2.0-or-later
  3. #
  4. # Copyright (C) 2022 OpenWrt.org
  5. #
  6. # ./cameo-tag.py <uImageFileName> <OffsetOfRootFS>
  7. #
  8. # CAMEO tag generator used for the D-Link DGS-1210 switches. Their U-Boot
  9. # loader checks for the string CAMEOTAG and a checksum in the kernel and
  10. # rootfs partitions. If not found it complains about the boot image.
  11. # Nevertheless it will boot if the tags are available in the secondary
  12. # boot partitions. If some day we want to overwrite the original vendor
  13. # partition we must have the tags in place. To solve this we insert the
  14. # tag two times into the kernel image.
  15. #
  16. # To understand what we do here it is helpful to explain how the original
  17. # CAMEO tag generation/checking works. The firmware consists of two parts.
  18. # A kernel uImage (<1.5MB) and a rootfs image (<12MB) that are written to
  19. # their respective mtd partitions. The default generator simply checksums
  20. # both parts and appends 16 bytes [<CAMEOTAG><0001><checksum>] to each part.
  21. # The checksum is only an addition of all preceding bytes (b0+b1+b2+...).
  22. # A tag does not interfere with any data in the images itself. During boot
  23. # the loader will scan all primary/secondary partitions (2*kernel, 2*rootfs)
  24. # until it finds the CAMEO tag. If checksums match everything is fine.
  25. # If all 4 fail we are lost. Luckily the loader does not care about where
  26. # the tags are located and ignores any data beyond a tag.
  27. #
  28. # The libreCMC image consists of a kernel (>1.5MB) and a rootfs. There is
  29. # no chance to add CAMEO tags at the default locations, since the kernel spans
  30. # both the original kernel partition and the start of the rootfs partition.
  31. # This would leave the kernel partition without a tag. So we must find suitable
  32. # space.
  33. #
  34. # Location for original kernel partition is at the end of the uImage header.
  35. # We will reuse the last bytes of the IH_NAME field. This is the tricky part
  36. # because we have the header CRC and the CAMEO checksum that must match the
  37. # whole header. uImage header CRC checksums all data except the CRC itself. The
  38. # for CAMEO checksum in turn, checksums all preceding data except itself.
  39. # Changing one of both results in a change of the other, but data trailing the
  40. # CAMEO checksum only influences the CRC.
  41. #
  42. # Location for original rootfs partition is very simple. It is behind the
  43. # libreCMC compressed kernel image file that spans into the rootfs. So
  44. # the tag will be written somewhere to the following rootfs partition and
  45. # can be found by U-Boot. The CAMEO checksum calculation must start at the
  46. # offset of the original rootfs partition and includes the "second" half of the
  47. # "split" kernel uImage.
  48. import argparse
  49. import os
  50. import zlib
  51. READ_UNTIL_EOF = -1
  52. UIMAGE_HEADER_SIZE = 64
  53. UIMAGE_CRC_OFF = 4
  54. UIMAGE_CRC_END = 8
  55. UIMAGE_NAME_OFF = 32
  56. UIMAGE_NAME_END = 56
  57. UIMAGE_SUM_OFF = 56
  58. UIMAGE_SUM_END = 60
  59. UIMAGE_INV_OFF = 60
  60. UIMAGE_INV_END = 64
  61. CAMEO_TAG = bytes([0x43, 0x41, 0x4d, 0x45, 0x4f, 0x54, 0x41, 0x47, 0x00, 0x00, 0x00, 0x01])
  62. IMAGE_NAME = bytes([0x4f, 0x70, 0x65, 0x6e, 0x57, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00])
  63. CRC_00 = bytes([0x00] * 4)
  64. CRC_FF = bytes([0xff] * 4)
  65. def read_buffer(offset, count):
  66. args.uimage_file.seek(offset)
  67. return bytearray(args.uimage_file.read(count))
  68. def write_buffer(whence, buf):
  69. args.uimage_file.seek(0, whence)
  70. args.uimage_file.write(buf)
  71. def cameosum(buf):
  72. return (sum(buf) & 0xffffffff).to_bytes(4, 'big')
  73. def invertcrc(buf):
  74. return (zlib.crc32(buf) ^ 0xffffffff).to_bytes(4, 'little')
  75. def checksum_header(buf):
  76. # To efficently get a combination, we will make use of the following fact:
  77. # crc32(data + littleendian(crc32(data) ^ 0xffffffff)) = 0xffffffff
  78. #
  79. # After manipulation the uImage header looks like this:
  80. # [...<ffffffff>...<libreCMC><000000><CAMEOTAG><0001><checksum><InvCRC>]
  81. buf[UIMAGE_NAME_OFF:UIMAGE_NAME_END] = IMAGE_NAME + CAMEO_TAG
  82. buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_FF
  83. buf[UIMAGE_SUM_OFF:UIMAGE_SUM_END] = cameosum(buf[0:UIMAGE_NAME_END])
  84. buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_00
  85. buf[UIMAGE_INV_OFF:UIMAGE_INV_END] = invertcrc(buf[0:UIMAGE_SUM_END])
  86. buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_FF
  87. return buf
  88. parser = argparse.ArgumentParser(description='Insert CAMEO firmware tags.')
  89. parser.add_argument('uimage_file', type=argparse.FileType('r+b'))
  90. parser.add_argument('rootfs_start', type=int)
  91. args = parser.parse_args()
  92. args.uimage_file.seek(0, os.SEEK_END)
  93. if args.uimage_file.tell() <= args.rootfs_start:
  94. raise ValueError(f"uImage must be larger than {args.rootfs_start} bytes")
  95. # tag for the uImage Header of 64 bytes inside the kernel
  96. # partition. Read and mangle it so it contains a valid CAMEO tag
  97. # and checksum that matches perfectly to the uImage header CRC.
  98. buf = checksum_header(read_buffer(0, UIMAGE_HEADER_SIZE))
  99. write_buffer(os.SEEK_SET, buf)
  100. # tag for the second part of the kernel that resides in the
  101. # vendor rootfs partition. For this we will add the CAMEO tag
  102. # and the checksum to the end of the image.
  103. buf = read_buffer(args.rootfs_start, READ_UNTIL_EOF)
  104. write_buffer(os.SEEK_END, CAMEO_TAG + cameosum(buf + CAMEO_TAG))