[PATCH] env: flash: add catch-all for unrecognized flags in env_flash_init()

Neil Berkman neil at xuku.com
Wed Mar 18 22:15:15 CET 2026


When both environment copies have valid CRCs but the flag bytes do not
match any recognized pair, env_flash_init() falls through without
setting gd->env_addr or gd->env_valid. This is a problem because:

1. env_init() then sets gd->env_addr = &default_environment (in RAM).

2. In env_flash_load(), the pointer comparison
   gd->env_addr != &flash_addr->data evaluates true (RAM != flash),
   triggering the pointer swap that selects the secondary copy.

3. The repair logic writes OBSOLETE (0x00) to the non-active flag but
   cannot promote the other flag from 0x00 to ACTIVE (0x01) because
   NOR flash requires a sector erase to set bits. Both copies end up
   with flag=0x00.

4. On every subsequent boot, flag1 == flag2 triggers the ENV_REDUND
   path, printing a spurious "recovered successfully" warning until
   an explicit saveenv erases and rewrites the sectors.

The recognized flag values are ACTIVE (0x01), OBSOLETE (0x00), and
erased (0xFF). Of the 256 possible flag values, the existing chain of
if/else-if handles only three: 253 of 256 values fall through without
setting gd->env_addr. Combined with 0x00 (already stuck on NOR),
254 of 256 values eventually reach the persistent-warning state.

Other env backends (SPI flash, NAND, MMC) handle this through
env_check_redund() in env/common.c, which uses a numeric comparison
of the flags as a serial counter and always reaches a decision. The
CFI flash backend is the only one that uses its own flag-matching
logic.

Add a catch-all else clause that defaults to the primary copy with
ENV_REDUND status, matching the existing behavior for the flag1==flag2
case. This ensures gd->env_addr is always set, preventing the
unintended pointer swap. The condition is recoverable via saveenv.

Signed-off-by: Neil Berkman <neil at xuku.com>

Reproducer: https://gist.github.com/neilberkman/4155612a7942d3f510f204eb85e61943

The SPI flash backend (env/sf.c) has a related but distinct issue:
it retained legacy boolean save semantics but its load path now uses
the common serial-number logic in env_check_redund(), creating an
inconsistency under interrupted updates. That has wider implications
for fw_env.c and would need separate discussion.
---
 env/flash.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/env/flash.c b/env/flash.c
index 0f7393d8..31dd1656 100644
--- a/env/flash.c
+++ b/env/flash.c
@@ -108,6 +108,14 @@ static int env_flash_init(void)
 	} else if (flag2 == 0xFF) {
 		gd->env_addr	= addr2;
 		gd->env_valid	= ENV_REDUND;
+	} else {
+		/*
+		 * Unrecognized flag pair (e.g. bit-flip on NOR flash).
+		 * Default to primary copy to prevent unintended pointer
+		 * swap in env_flash_load().
+		 */
+		gd->env_addr	= addr1;
+		gd->env_valid	= ENV_REDUND;
 	}
 
 	return 0;
-- 
2.43.0



More information about the U-Boot mailing list