From 8dbc177e3dd380498c31b6812ccddf7d8ac5bec3 Mon Sep 17 00:00:00 2001 From: Neil Berkman Date: Tue, 17 Mar 2026 15:39:02 -0700 Subject: [PATCH 1/3] boot/nxboot: add flush barriers and CRC-validate primary before boot Two hardening fixes for nxboot power-loss resilience: 1. Add flash_partition_flush() calls between critical partition operations in perform_update(). Without explicit flush barriers, writes may remain buffered in RAM (e.g. via FTL rwbuffer) when nxboot proceeds to the next phase. A power loss between phases can leave the recovery image uncommitted while the staging partition has already been consumed. Flush points added: - After copy_partition(primary, recovery) completes - After copy_partition(update, primary) completes, before erasing the staging first sector 2. Replace validate_image_header() with validate_image() in the final primary validation path of nxboot_perform_update(). The header-only check validates magic and platform identifier but does not CRC-check the image body. After an interrupted update, a corrupt primary with an intact header would pass this check and be booted, resulting in a persistent boot failure. Signed-off-by: Neil Berkman --- boot/nxboot/loader/boot.c | 6 ++++-- boot/nxboot/loader/flash.c | 20 ++++++++++++++++++++ boot/nxboot/loader/flash.h | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/boot/nxboot/loader/boot.c b/boot/nxboot/loader/boot.c index 6de9659ac02..8bcd24cef1d 100644 --- a/boot/nxboot/loader/boot.c +++ b/boot/nxboot/loader/boot.c @@ -417,6 +417,7 @@ static int perform_update(struct nxboot_state *state, bool check_only) syslog(LOG_INFO, "Creating recovery image.\n"); nxboot_progress(nxboot_progress_start, recovery_create); copy_partition(primary, recovery, state, false); + flash_partition_flush(recovery); nxboot_progress(nxboot_progress_end); nxboot_progress(nxboot_progress_start, validate_recovery); successful = validate_image(recovery); @@ -444,6 +445,8 @@ static int perform_update(struct nxboot_state *state, bool check_only) nxboot_progress(nxboot_progress_start, update_from_update); if (copy_partition(update, primary, state, true) >= 0) { + flash_partition_flush(primary); + /* Erase the first sector of update partition. This marks the * partition as updated so we don't end up in an update loop. * The sector is written back again during the image @@ -919,8 +922,7 @@ int nxboot_perform_update(bool check_only) return ERROR; } - get_image_header(primary, &header); - if (!validate_image_header(&header)) + if (!validate_image(primary)) { ret = ERROR; } diff --git a/boot/nxboot/loader/flash.c b/boot/nxboot/loader/flash.c index f1a3595737f..ab6a243202e 100644 --- a/boot/nxboot/loader/flash.c +++ b/boot/nxboot/loader/flash.c @@ -76,6 +76,26 @@ int flash_partition_open(const char *path) return fd; } +/**************************************************************************** + * Name: flash_partition_flush + * + * Description: + * Flushes any buffered writes to the underlying storage. This ensures + * data is physically committed to flash before the caller proceeds. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_flush(int fd) +{ + return fsync(fd); +} + /**************************************************************************** * Name: flash_partition_close * diff --git a/boot/nxboot/loader/flash.h b/boot/nxboot/loader/flash.h index ef1dee09d06..69906490d23 100644 --- a/boot/nxboot/loader/flash.h +++ b/boot/nxboot/loader/flash.h @@ -79,6 +79,22 @@ int flash_partition_open(const char *path); int flash_partition_close(int fd); +/**************************************************************************** + * Name: flash_partition_flush + * + * Description: + * Flushes any buffered writes to the underlying storage. + * + * Input parameters: + * fd: Valid file descriptor. + * + * Returned Value: + * 0 on success, -1 on failure. + * + ****************************************************************************/ + +int flash_partition_flush(int fd); + /**************************************************************************** * Name: flash_partition_write * From 66c9805f733248df9033b881b88c68e10b212898 Mon Sep 17 00:00:00 2001 From: Neil Berkman Date: Wed, 18 Mar 2026 00:33:41 -0700 Subject: [PATCH 2/3] boot/nxboot: update stale comment to reflect CRC validation The comment previously stated CRC was not calculated before boot. This is no longer accurate after adding full image CRC validation in validate_image(). Signed-off-by: Neil Berkman --- boot/nxboot/loader/boot.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boot/nxboot/loader/boot.c b/boot/nxboot/loader/boot.c index 8bcd24cef1d..703148754ad 100644 --- a/boot/nxboot/loader/boot.c +++ b/boot/nxboot/loader/boot.c @@ -911,9 +911,9 @@ int nxboot_perform_update(bool check_only) } } - /* Check whether there is a valid image in the primary slot. This just - * checks whether the header is valid, but does not calculate the CRC - * of the image as this would prolong the boot process. + /* Check whether there is a valid image in the primary slot. Validates + * both the header and the full image CRC to ensure integrity before + * booting. */ primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH); From 4772efaf993e8b2e3697e81dba5779272d2ccec8 Mon Sep 17 00:00:00 2001 From: Neil Berkman Date: Wed, 18 Mar 2026 00:42:13 -0700 Subject: [PATCH 3/3] boot/nxboot: remove unused header variable The header variable in nxboot_perform_update() is no longer used after validate_image() was changed to take only the fd. Signed-off-by: Neil Berkman --- boot/nxboot/loader/boot.c | 1 - 1 file changed, 1 deletion(-) diff --git a/boot/nxboot/loader/boot.c b/boot/nxboot/loader/boot.c index 703148754ad..c4f936d233e 100644 --- a/boot/nxboot/loader/boot.c +++ b/boot/nxboot/loader/boot.c @@ -887,7 +887,6 @@ int nxboot_perform_update(bool check_only) int ret; int primary; struct nxboot_state state; - struct nxboot_img_header header; ret = nxboot_get_state(&state); if (ret < 0)