Skip to content

fix(ecc): reject wrapped memory ranges - #75

Merged
srpatcha merged 1 commit into
embeddedos-org:masterfrom
BhavarSingh:fix/ecc-region-overflow
Aug 31, 2026
Merged

fix(ecc): reject wrapped memory ranges#75
srpatcha merged 1 commit into
embeddedos-org:masterfrom
BhavarSingh:fix/ecc-region-overflow

Conversation

@BhavarSingh

Copy link
Copy Markdown
Contributor

Summary

ECC range validation used addr + len and base + size in 32-bit arithmetic.
Wrapped values could therefore pass both duplicated checks, and initialization
also accepted a configured region whose exclusive end was unrepresentable.

This change validates both ranges with subtraction after ordered lower-bound
checks, removing the overflow paths before any address is converted to a
pointer.

Type of Change

  • fix — Bug fix
  • test — Add or fix tests
  • docs — Documentation

Changes

  • Reject wrapped base/size pairs during eos_ecc_init().
  • Replace duplicate addition-based request checks with overflow-safe offset and
    remaining-capacity comparisons.
  • Preserve the legal zero-length request at the exclusive region end.
  • Register a native test_ecc target covering null contexts, wrapped inputs,
    exact boundaries, and ordinary out-of-range requests.
  • Document the range contract and update the native-suite inventory.

Testing

  • Focused CTest: 1/1 passed
  • Full Release CTest: 19/19 passed
  • Python unit suite: 13 passed, 1 skipped
  • New tests added for new functionality
  • The same regression suite reports both overflow assertions failing
    against the old implementation

Pre-Submission Checklist

  • Code compiles without warnings
  • All Release tests pass
  • New tests added for new functionality
  • Documentation updated if API changed
  • Commit messages follow <type>(<scope>): <description> convention
  • Branch is based on latest master

Related Issues

None.

Screenshots / Logs

Not applicable.

Additional Notes

The host regression uses zero-length or sub-word requests for synthetic 32-bit
addresses, so it exercises validation boundaries without dereferencing those
addresses. ECC access width and scrub scheduling are unchanged.

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The evidence row in this PR claims the tests fail against the old
implementation; I checked that rather than taking it, and it holds — for both
assertions independently.

Verified

Applied on current master:

0 build errors
100% tests passed, 0 tests failed out of 19
ECC range validation tests passed

Then reverted each fix separately.

The range check. Restoring addr + len > base + size:

[FAIL] test_ecc.c:44: eos_ecc_check_region(&ctx, UINT32_MAX, 2) == -1

The init guard. Removing if (size > UINT32_MAX - base) return -1;:

[FAIL] test_ecc.c:33: eos_ecc_init(&ctx, UINT32_MAX - 0xFFu, 0x100u) == -1

Two fixes, two assertions, each failing on its own when its fix is removed. That
is the property that makes a regression test worth having, and a surprising
number of them do not manage it — one earlier in this organisation asserted on
values that were correct both before and after the bug it claimed to cover, and
another passed against unfixed code because it used a message that happened not
to trigger the defect.

The rewrite is correct

if (addr < ctx->base_addr) return -1;
uint32_t offset = addr - ctx->base_addr;
if (offset > ctx->size_bytes || len > ctx->size_bytes - offset) return -1;

addr < base_addr first, so the subtraction cannot wrap; offset > size_bytes
before the second subtraction, same reason. Both are ordered so the guard
precedes the arithmetic that depends on it. The old addr + len > base + size
wrapped on both sides at once, which is why an address at UINT32_MAX slipped
through.

Rejecting a wrapped range at eos_ecc_init() is the better half of the fix.
Validating every request against a range that is itself nonsensical is a
losing position; refusing to configure it means the check downstream has
something coherent to compare against.

Same unsigned-overflow family as eos#95 (p + len > cap), eos#87
(bytes_written + len) and eBoot#76 (slot bounds). Four instances now. The safe
form is always a > limit - b with the precondition checked first, exactly as
written here.

Two details I appreciated

Using zero-length requests to exercise the boundaries, so the test never
dereferences a synthetic 32-bit address on the host — the comment says so, which
saves the next reader working out why the cases look odd.

The target is named eboot_test_ecc. #71 namespaced test targets so eos and
eBoot can be composed into one CMake project by ebuild, and there is a
configure-time guard that rejects an unprefixed one. Getting that right without
being asked means the guard never had to fire.

README test count updated 17 → 19 to match. Verified: 19 is what ctest reports.

@srpatcha
srpatcha merged commit 0c5a4fa into embeddedos-org:master Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants