Skip to content

fix(filesystem): avoid write length wrap when clamping - #95

Merged
srpatcha merged 2 commits into
embeddedos-org:masterfrom
nikhilramakrishna3005:fix/filesystem-write-length-wrap
Sep 1, 2026
Merged

fix(filesystem): avoid write length wrap when clamping#95
srpatcha merged 2 commits into
embeddedos-org:masterfrom
nikhilramakrishna3005:fix/filesystem-write-length-wrap

Conversation

@nikhilramakrishna3005

Copy link
Copy Markdown
Contributor

Summary

Fixes an integer wraparound in eos_fs_write() when clamping a write to the remaining inode capacity.

The previous check used p + len > n->cap. When the file position was non-zero and len was near SIZE_MAX, the addition could wrap, allowing the capacity check to be bypassed and passing the unbounded length to memcpy().

Type of Change

  • feat — New feature
  • fix — Bug fix
  • docs — Documentation only
  • style — Formatting, no code change
  • refactor — Code restructuring without behavior change
  • test — Add or fix tests
  • build — Build system or dependency changes
  • ci — CI/CD pipeline changes
  • perf — Performance improvement

Changes

  • Changed the write-capacity check from p + len > n->cap to len > n->cap - p, after the existing p > n->cap guard.
  • Preserved the existing behavior of clamping oversized writes to the remaining capacity.
  • Added a regression test covering a non-zero file position with a wrapping write length.
  • The regression test verifies the returned byte count, final file position, and filesystem used-byte count.

Testing

  • Unit tests pass
  • Integration tests pass
  • Manual testing performed
  • New tests added for new functionality

Focused filesystem test compiled with:

gcc -std=c11 -Wall -Wextra -Werror -DEOS_ENABLE_FILESYSTEM=1 -Iinclude -Iservices/filesystem/include -o /tmp/test_fs tests/test_filesystem.c services/filesystem/src/filesystem.c

Result: 14/14 filesystem tests passed

The filesystem tests were also run with AddressSanitizer.

Result: 14/14 passed

The regression test was verified against the original capacity check under AddressSanitizer, where the wrapping length reached memcpy() instead of being clamped.

The eos_filesystem CMake target for vbox_test also builds successfully.

The full ctest suite could not be run because the current upstream test configuration contains unrelated duplicate test_crypto_aes and test_crypto_sha512 CMake targets.

Pre-Submission Checklist

  • Code compiles without warnings (-Wall -Wextra -Werror for C)
  • All existing tests pass
  • New tests added for new functionality
  • Documentation updated if API changed
  • Commit messages follow <type>(<scope>): <description> convention
  • Branch is based on current upstream master

Related Issues

None.

Screenshots / Logs

Not applicable.

Additional Notes

This change preserves the existing eos_fs_write() behavior: writes larger than the remaining inode capacity are clamped and the number of bytes actually written is returned.

The existing p > n->cap guard ensures that n->cap - p cannot underflow before it is compared with len.

No public API behavior is changed.

Signed-off-by: Nikhil Rama Krishna <nikhilramakrishna3005@users.noreply.github.com>
srpatcha
srpatcha previously approved these changes Aug 30, 2026

@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.

Correct, and the regression test earns its place.

The wrap is real. p and cap are uint32_t, len is size_t, so p + len
promotes to 64-bit and len = SIZE_MAX with p = 2 gives 1 — not greater than
cap, so the clamp is skipped and memcpy gets an unbounded length.

Comparing remaining capacity instead cannot wrap, and the precondition is
already enforced two lines above:

if (p > n->cap) return -1;
if (len > n->cap - p) len = n->cap - p;

so n->cap - p has no underflow to worry about. The existing comment at line 132
already warns that n->cap - p "would wrap (both are unsigned)" if pos <= cap
ever slipped — the guard is what makes your form safe, and it is there.

Verified against unfixed code

I could not verify this on your branch directly: it is based on master, whose
duplicate test_crypto_aes / test_crypto_sha512 targets make CMake fail at
configure time, so ctest reports "No tests were found" and no test can run at
all. That is #92 / #93, not anything in this PR.

Applying your patch onto the repaired base instead — clean apply, both files:

with fix        100% tests passed, 0 tests failed out of 1
without fix     test_filesystem (Subprocess aborted)

Reverting only the one-line clamp aborts the process, which is the unbounded
memcpy doing what it does. A test that fails on the unfixed code and passes on
the fixed one is the thing that makes this a regression test rather than a
demonstration.

Discovering the inode capacity by writing an oversized buffer, rather than
reaching for the private BLOCK_SIZE, is the right call — it keeps the test
working if that constant changes, and your comment explains why a
merely-oversized write is not enough to reproduce this.

Same class, elsewhere

This is the third unsigned-overflow-invisible-on-a-64-bit-host defect found in
this repository. #87 is the same shape in the OTA path — bytes_written + len
wrapping so an oversized chunk is accepted — and the config parser had a
package-overflow that only ASan surfaced. Worth a sweep for a + b > limit
against unsigned types generally; the safe form is always a > limit - b with
the precondition checked, exactly as you have it here.

Approving. Needs #82 or #93 to land first for CI here to be able to run its own
tests.

@srpatcha
srpatcha merged commit 8fd3184 into embeddedos-org:master Sep 1, 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