Skip to content

Commit

Permalink
Merge pull request #3141 from balena-os/poc_uboot_crc_checks
Browse files Browse the repository at this point in the history
Add uboot CRC32 checks for kernel and device-tree
  • Loading branch information
alexgg authored Jun 14, 2023
2 parents d654698 + 446bd6d commit b41bc58
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 2 deletions.
40 changes: 38 additions & 2 deletions meta-balena-common/classes/resin-u-boot.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ SRC_URI += "${@bb.utils.contains('DISTRO_FEATURES', 'osdev-image', 'file://balen
SRC_URI:append = " \
file://env_resin.h \
${@bb.utils.contains('UBOOT_KCONFIG_SUPPORT', '1', '', '${INTEGRATION_NON_KCONFIG_PATCH}', d)} \
file://balena_check_crc32.c \
"

python __anonymous() {
Expand Down Expand Up @@ -59,23 +60,32 @@ BALENA_ENV_FILE = "resinOS_uEnv.txt"
BALENA_EXTRA_ENV_FILE = "extra_uEnv.txt"
BALENA_UBOOT_DEVICES ?= "0 1 2"
BALENA_UBOOT_DEVICE_TYPES ?= "mmc"
BALENA_DEVICE_KERNEL_ADDR_VAR ?= "loadaddr"
BALENA_DEVICE_FDT_ADDR_VAR ?= "fdt_addr"

# OS_KERNEL_CMDLINE is a distro wide variable intended to be used in all the
# supported bootloaders
BASE_OS_CMDLINE ?= "${OS_KERNEL_CMDLINE}"
OS_BOOTCOUNT_FILE ?= "bootcount.env"
OS_BOOTCOUNT_SKIP ?= "0"
OS_BOOTCOUNT_LIMIT ?= "3"
OS_OVERLAP_FILE ?= "overlap_detected"

# Some older u-boot versions may
# not support these checks
OS_OVERLAP_CHECK_ENABLED ?= "1"

# These options go into the device headerfile via config_resin.h
CONFIG_RESET_TO_RETRY ?= "1"
CONFIG_BOOT_RETRY_TIME ?= "${@bb.utils.contains('DISTRO_FEATURES', 'osdev-image', '-1', '15', d)}"

CONFIG_CMD_FS_UUID = "1"

CONFIG_CMD_HASH = "1"
UBOOT_VARS = "BALENA_UBOOT_DEVICES \
BALENA_UBOOT_DEVICE_TYPES \
BALENA_BOOT_PART BALENA_DEFAULT_ROOT_PART \
BALENA_DEVICE_KERNEL_ADDR_VAR \
BALENA_DEVICE_FDT_ADDR_VAR \
BALENA_IMAGE_FLAG_FILE \
BALENA_FLASHER_FLAG_FILE \
BALENA_ENV_FILE \
Expand All @@ -84,9 +94,35 @@ UBOOT_VARS = "BALENA_UBOOT_DEVICES \
OS_BOOTCOUNT_FILE \
OS_BOOTCOUNT_SKIP \
OS_BOOTCOUNT_LIMIT \
OS_OVERLAP_FILE \
CONFIG_RESET_TO_RETRY \
CONFIG_BOOT_RETRY_TIME \
CONFIG_CMD_FS_UUID "
CONFIG_CMD_FS_UUID \
"

UBOOT_VARS += "${@bb.utils.contains('OS_OVERLAP_CHECK_ENABLED', '1', 'CONFIG_CMD_HASH', '', d)}"

do_inject_check_crc32_cmd() {
if ${@bb.utils.contains('OS_OVERLAP_CHECK_ENABLED', '1', 'true', 'false', d)}; then
# upstream commit 09140113108 removes the cmd_tbl_t typedef
if ! grep -q -r "cmd_tbl_t" ${S}/cmd/ ; then
sed -i 's/cmd_tbl_t/struct cmd_tbl/g' ${WORKDIR}/balena_check_crc32.c
fi
# Older u-boot versions do not have env.h
if [ ! -f ${S}/include/env.h ]; then
sed -i 's/env.h/common.h/g' ${WORKDIR}/balena_check_crc32.c
fi
cp ${WORKDIR}/balena_check_crc32.c ${S}/cmd/
if ! grep -q "balena_check_crc32" ${S}/cmd/Makefile ; then
cat >> ${S}/cmd/Makefile << EOF
ifndef CONFIG_SPL_BUILD
obj-y += balena_check_crc32.o
endif
EOF
fi
fi
}
addtask do_inject_check_crc32_cmd after do_patch before do_configure

python do_generate_resin_uboot_configuration () {
vars = d.getVar('UBOOT_VARS').split()
Expand Down
110 changes: 110 additions & 0 deletions meta-balena-common/recipes-bsp/u-boot/files/balena_check_crc32.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0+

/* Helper command for saving and comparing CRC32 of the memory areas
* where the kernel and device-tree are loaded. Keeps env_resin
* changes to a minimum.
*/

#include <common.h>
#include <config.h>
#include <command.h>
#include <env.h>
#include <hash.h>
#include <vsprintf.h>

static int do_save_crc32(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], bool final)
{
char *loadAddress = NULL, *hashArray[5];
char saveVar[64] = { 0 }, saveSizeVar[32] = { 0 }, *countStr;

snprintf(saveSizeVar, sizeof(saveSizeVar), "%s%s", argv[0], "_size" /* i.e fdt_addr_size */);

/* u-boot env variable name where the kernel or fdt is loaded, e.g loadaddr, fdt_addr, fdtaddr */
loadAddress = env_get(argv[0]);
if (!loadAddress) {
printf("Address variable not set!\n");
return CMD_RET_FAILURE;
}

/* crc32 will be saved in a variable like loadaddr_crc32 or loadaddr_crc32_final */
snprintf(saveVar, sizeof(saveVar), "%s%s", argv[0], final ? "_crc32_final" : "_crc32");

if (final) {
countStr = env_get(saveSizeVar);
if (!countStr) {
printf("Size was not saved - Invalid usage!\n");
return CMD_RET_FAILURE;
}
} else {
countStr = env_get("filesize");
if (!countStr) {
printf("No file was loaded - Invalid usage!\n");
return CMD_RET_FAILURE;
}
env_set(saveSizeVar, countStr);
}

/* arg 0 must be the command text as per the hash command docs */
hashArray[0] = "hash";
hashArray[1] = "crc32";
hashArray[2] = loadAddress;
hashArray[3] = countStr;
hashArray[4] = saveVar;

return hash_command("crc32" /* algo */, HASH_FLAG_ENV, cmdtp, flag, 4 /* argc */, hashArray + 2);
}

static int do_check_crc32(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
char pre[64], post[64], *preVal, *postVal;

if (!argv[0]) {
printf("Address variable not provided!\n");
return CMD_RET_FAILURE;
}

snprintf(pre, sizeof(pre), "%s_crc32", argv[0]);
snprintf(post, sizeof(post), "%s_crc32_final", argv[0]);

do_save_crc32(cmdtp, flag, argc, argv, true /* final check */);

preVal = env_get(pre);
postVal = env_get(post);

if (preVal && postVal && !strncmp(preVal, postVal, 8 /* crc32 is 8 chars */)) {
printf("CRC32 match for $%s: %s\n", argv[0], preVal);
} else {
printf("CRC32 mismatch for $%s, before: %s, after %s\n", argv[0], preVal, postVal);
return CMD_RET_FAILURE;
}

return CMD_RET_SUCCESS;
}

static int do_balena_crc32(cmd_tbl_t *cmdtp, int flag, int argc,
char *const argv[])
{
if (argc < 3)
return CMD_RET_USAGE;

if (!strcmp(argv[1], "save")) {
return do_save_crc32(cmdtp, flag, argc - 2, argv + 2, false /* initial check */);
} else if (!strcmp(argv[1], "check")) {
return do_check_crc32(cmdtp, flag, argc - 2, argv + 2);
}

return CMD_RET_USAGE;
}

/* Can be called from cmdline as:
* $ balena_crc32 save loadaddr
* $ if test balena crc32 check loadaddr; then do_nothing; else save warning_file; fi;
*/
U_BOOT_CMD(
balena_crc32, CONFIG_SYS_MAXARGS, 1, do_balena_crc32,
"balenaOS crc32 verification commands",
"save <fdtaddr/loadaddr>\n"
" - save crc32 for fdtaddr or loadaddr in <fdtaddr/loadaddr>_crc32\n"
"check <fdtaddr/kerneladdr>\n"
" - Re-calculate and compare crc32 for <fdtaddr/loadaddr> with the one already stored\n"
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ CONFIG_CMD_EXPORTENV=y
CONFIG_CMD_IMPORTENV=y
CONFIG_FAT_WRITE=y
CONFIG_CMD_SAVEENV=n
CONFIG_CMD_HASH=y
8 changes: 8 additions & 0 deletions meta-balena-common/recipes-bsp/u-boot/patches/env_resin.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#define BALENA_ENV \
"resin_env_file=" __stringify(BALENA_ENV_FILE) "\0" \
"balena_extra_env_file=" __stringify(BALENA_EXTRA_ENV_FILE) "\0" \
"os_overlap_file=" __stringify(OS_OVERLAP_FILE) "\0" \
"os_bc_file=" __stringify(OS_BOOTCOUNT_FILE) "\0" \
"os_bc_skip=" __stringify(OS_BOOTCOUNT_SKIP) "\0" \
"os_bc_inced=0 \0" \
Expand All @@ -45,6 +46,8 @@
"resin_boot_part=" __stringify(BALENA_BOOT_PART) "\0" \
"resin_root_part=" __stringify(BALENA_DEFAULT_ROOT_PART) "\0" \
"base_os_cmdline=" __stringify(BASE_OS_CMDLINE) "\0" \
"balena_device_kernel_addr_var=" __stringify(BALENA_DEVICE_KERNEL_ADDR_VAR) "\0"\
"balena_device_fdt_addr_var=" __stringify(BALENA_DEVICE_FDT_ADDR_VAR) "\0"\
"resin_flasher_skip=0 \0" \
\
"resin_find_root_part_uuid=" \
Expand Down Expand Up @@ -75,6 +78,11 @@
"else " \
"echo File ${balena_extra_env_file} not found on scanned device ${resin_scan_dev_type}:${resin_scan_dev_index}; " \
"fi; \0" \
"balena_save_overlap_file=if fatwrite ${resin_dev_type} ${resin_dev_index}:${resin_boot_part} ${resin_kernel_load_addr} ${os_overlap_file} 0xd; then; else; echo OVERLAP FILE WRITE FAILED ; fi;\0" \
"balena_kernel_load_crc_save=if balena_crc32 save ${balena_device_kernel_addr_var}; then; else run balena_save_overlap_file; fi;\0" \
"balena_kernel_load_crc_check=if balena_crc32 check ${balena_device_kernel_addr_var}; then; else run balena_save_overlap_file; fi;\0" \
"balena_fdt_load_crc_save=if balena_crc32 save ${balena_device_fdt_addr_var}; then; else run balena_save_overlap_file; fi;\0" \
"balena_fdt_load_crc_check=if balena_crc32 check ${balena_device_fdt_addr_var}; then; else run balena_save_overlap_file; fi;\0" \
"os_import_bootcount_file=" \
"echo Import ${os_bc_file} in environment;" \
"env import -t ${resin_kernel_load_addr} ${filesize}\0" \
Expand Down
1 change: 1 addition & 0 deletions tests/suites/os/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ module.exports = {
'./tests/secureboot',
'./tests/device-specific-tests/beaglebone-black',
'./tests/device-specific-tests/243390-rpi3',
'./tests/overlap_test/',
'./tests/fingerprint',
'./tests/fsck',
'./tests/os-release',
Expand Down
21 changes: 21 additions & 0 deletions tests/suites/os/tests/overlap_test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
title: 'Kernel and device-tree overlap test',
run: async function(test) {
test.comment(`Checking if the kernel or the device-tree have been overwritten`);
let result = '';
result = await this.context
.get()
.worker.executeCommandInHostOS(
`if [ -e /mnt/boot/overlap_detected ]; then echo "Test failed"; else echo "OK"; fi;`,
this.link,
);

if (result.includes('OK')) {
test.comment(`not ok! - kernel - dtb overlap detected`);
} else {
test.comment(`ok - no overlap detected`);
}

test.is(result.includes('OK'), true, 'Kernel - dtb overlap check completed');
},
};

0 comments on commit b41bc58

Please sign in to comment.