Skip to content

Commit

Permalink
mvebu: improve sysupgrade for FortiGate/FortiWiFi devices
Browse files Browse the repository at this point in the history
Update sysupgrade script (fortinet.sh) for Fortinet devices in
mvebu/cortexa9 and fix the following issues,

- Some individuals of FortiGate/FortiWiFi 30E/5xE devices has wrong
  kernel/rootfs offsets in "firmware-info" partition and they are not
  updated with the current sysupgrade script for Fortinet devices
  (fortinet.sh).
  As a result, the bootloader tries to load kernel data from the wrong
  address and boot with it after OpenWrt installation.
  The new script handles offsets in addition to length values.

and improve the following points.

- Only 2 bytes are handled with the current sysupgrade script
  (fortinet.sh) for kernel/rootfs length. The new script handles 4 bytes
  instead.

- The image names of image1/image2 are not handled and not updated when
  sysupgrade. The new sysupgrade script handles it and update to
  "<dist> <version> <revision>" if firmware metadata is available.
  (ex.: "OpenWrt SNAPSHOT r27440-25384026")

log of new sysupgrade script (fortinet.sh):

Tue Sep 17 10:29:16 UTC 2024 upgrade: Performing system upgrade...
Image Name: "OpenWrt SNAPSHOT r27440-25384026"
            --> "OpenWrt SNAPSHOT r27441-b3a0806a05"

  kernel:
    old: 0x003c4e00@0x00200000
    new: 0x003c4e00@0x00200000

  rootfs:
    old: 0x005c0200@0x00800000
    new: 0x005c0200@0x00800000

Unlocking kernel ...

Signed-off-by: INAGAKI Hiroshi <musashino.open@gmail.com>
  • Loading branch information
musashino205 committed Sep 17, 2024
1 parent 0c64aa2 commit 6b6f5a9
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 18 deletions.
165 changes: 148 additions & 17 deletions target/linux/mvebu/cortexa9/base-files/lib/upgrade/fortinet.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,134 @@
. /lib/functions.sh

fortinet_fwinfo_blocks() {
fortinet_bswap32() {
local val="$(printf %08x $(($1)))"

# swap and print in hex
echo "0x${val:6:2}${val:4:2}${val:2:2}${val:0:2}"
}

fortinet_by2bl() {
local blks="$(($1 / 0x200))"
[ $(($1 % 0x200)) -gt 0 ] && blks=$((blks + 1))

printf "0x%08x" $blks
}

fortinet_bl2by() {
printf "0x%08x" $(($1 * 0x200))
}

fortinet_build_partmap() {
local new="$1" old="$2"
local len="${old%%@*}" ofs="${old##*@}"

case "$new" in
# "@<offset>"
@*)
ofs="$(fortinet_by2bl ${new##@})"
;;

# "<length>@<offset>"
*@*)
len="$(fortinet_by2bl ${new%%@*})"
ofs="$(fortinet_by2bl ${new##*@})"
;;

# "" (empty)
"")
;;

# "<length>"
*)
len="$(fortinet_by2bl ${new%%@*})"
;;
esac

# print N blocks of length/offset in dec
echo "${len}@${ofs}"
}

# Update firmware information in "firmware-info" partition
#
# parameters:
# $1: image index (1/2)
# $2: new image name (up to 32 characters)
# $3: length and/or offset for kernel
# $4: length and/or offset for rootfs
#
# Note: $3 and $4 support multiple formats:
#
# - <length>@<offset>: set <length> and <rootfs>
# - <length> : set <length> and keep the current offset
# - @<offset> : set <offset> and keep the current length
# - "" (empty) : keep the current length and offset
fortinet_update_fwinfo() {
local fwinfo_mtd="$(find_mtd_part firmware-info)"
local offset="$1"
local len="$2"
local blks
local index="$1"
local name="$2"
local offset
local old_kr
local old new tmp part pos
local output

if [ -z "$fwinfo_mtd" ]; then
echo "WARN: MTD device \"firmware-info\" not found"
return 1
# Image Name
case "$index" in
1) offset=0x10 ;;
2) offset=0x30 ;;
*) echo "invalid image index specified!"; return 1 ;;
esac

old="$(dd bs=16 count=2 skip=$((offset / 16)) if=$fwinfo_mtd 2>/dev/null)"
printf "Image Name: \"%s\"\n" "$old"
if [ -n "$name" ]; then
echo -n "$name" | \
dd bs=32 count=1 oflag=seek_bytes seek=$((offset)) \
conv=sync,notrunc of=$fwinfo_mtd 2>/dev/null
printf " --> \"%s\"\n\n" "$name"
else
printf "\n"
fi

blks=$((len / 0x200))
[ $((len % 0x200)) -gt 0 ] && blks=$((blks + 1))
blks=$(printf "%04x" $blks)
printf "fwinfo: offset-> 0x%x, blocks-> 0x%s (len: 0x%08x)\n" \
$offset $blks $len
# length/offset values of kernel/rootfs
case "$index" in
1) offset=0x180 ;;
2) offset=0x190 ;;
esac

# <kernel offset:4><kernel length:4><rootfs offset:4><rootfs length:4>
old_kr="$(hexdump -n 16 -v -s $((offset)) -e '1/1 "%02x"' $fwinfo_mtd)"

pos=0
for part in kernel rootfs; do
old="$(fortinet_bswap32 0x${old_kr:$((8 + pos)):8})"
old="${old}@$(fortinet_bswap32 0x${old_kr:$((0 + pos)):8})"
new="$(fortinet_build_partmap "$3" "$old")"
shift

printf " %s:\n" $part
printf " old: 0x%08x@0x%08x\n" \
$(fortinet_bl2by ${old%%@*}) $(fortinet_bl2by ${old##*@})
printf " new: 0x%08x@0x%08x\n\n" \
$(fortinet_bl2by ${new%%@*}) $(fortinet_bl2by ${new##*@})

printf "\x${blks:2:2}\x${blks:0:2}" | \
dd bs=2 count=1 seek=$((offset / 2)) conv=notrunc of=${fwinfo_mtd}
tmp="$(fortinet_bswap32 ${new%%@*})@$(fortinet_bswap32 ${new##*@})"
new="$(echo $tmp | sed 's/0x\([0-9a-f]\{8\}\)@0x\([0-9a-f]\{8\}\)/\2\1/')"
output="${output}${new}"

pos=$((pos + 16))
done

data_2bin "$output" | \
dd bs=16 count=1 seek=$((offset / 16)) conv=notrunc \
of=$fwinfo_mtd 2>/dev/null
}

fortinet_do_upgrade() {
local board_dir="$(tar tf "$1" | grep -m 1 '^sysupgrade-.*/$')"
local kern_mtd="$(find_mtd_part kernel)"
local root_mtd="$(find_mtd_part rootfs)"
local kern_len root_len
local kern_len kern_ofs root_len root_ofs
local imgname

board_dir="${board_dir%/}"

Expand All @@ -34,6 +137,14 @@ fortinet_do_upgrade() {
umount -a
reboot -f
fi
kern_ofs=$(cat /sys/class/mtd/${kern_mtd//\/dev\/mtdblock/mtd}/offset)
root_ofs=$(cat /sys/class/mtd/${root_mtd//\/dev\/mtdblock/mtd}/offset)

if [ -z "$kern_ofs" ] || [ -z "$root_ofs" ]; then
echo "ERROR: failed to get offset of kernel or rootfs"
umount -a
reboot -f
fi

kern_len=$( (tar xOf "$1" "$board_dir/kernel" | wc -c) 2> /dev/null)
root_len=$( (tar xOf "$1" "$board_dir/root" | wc -c) 2> /dev/null)
Expand All @@ -44,8 +155,28 @@ fortinet_do_upgrade() {
reboot -f
fi

fortinet_fwinfo_blocks "0x184" "$kern_len"
fortinet_fwinfo_blocks "0x18c" "$root_len"
# try to load and parse /tmp/sysupgrade.meta for image name
if [ -r "/tmp/sysupgrade.meta" ]; then
local key value

sed 'y/,{/\n\n/' < /tmp/sysupgrade.meta > /tmp/sysupgrade.meta.tmp
while read key value; do
key="${key//\"/}"
value="${value//\"/}"

[ -z "$value" ] && continue
case "$key" in
dist:|\
version:|\
revision:) imgname="${imgname}$value " ;;
esac
done < /tmp/sysupgrade.meta.tmp
else
imgname="OpenWrt"
fi

fortinet_update_fwinfo 1 "${imgname%% }" \
"${kern_len}@${kern_ofs}" "${root_len}@${root_ofs}"

tar xOf "$1" "$board_dir/kernel" | \
mtd write - "kernel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Copyright (C) 2016 LEDE-Project.org
#

RAMFS_COPY_BIN='fw_printenv fw_setenv strings'
RAMFS_COPY_BIN='fw_printenv fw_setenv seq strings'
RAMFS_COPY_DATA='/etc/fw_env.config /var/lock/fw_printenv.lock'

PART_NAME=firmware
Expand Down

0 comments on commit 6b6f5a9

Please sign in to comment.