mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
fix(wear_levelling): guard WL_Flash::write()/read() against size==0 underflow
WL_Flash::write() and WL_Flash::read() computed:
uint32_t count = (size - 1) / this->cfg.wl_page_size;
`size` is `size_t` (unsigned). Neither the public wl_write()/wl_read() API
(wear_levelling.cpp), nor the newer wl_bdl_write()/wl_bdl_read() block-device
path (wl_blockdev.cpp), reject size == 0 before calling into WL_Flash, and
wear_levelling.h does not document size == 0 as invalid (a 0-byte
write/read is a reasonable no-op, mirroring POSIX write()/read() with
count == 0).
When size == 0, `size - 1` wraps around to SIZE_MAX, so `count` becomes an
enormous page count instead of 0. The functions then loop that many times,
reading (write()) or writing (read()) `wl_page_size` bytes per iteration
through the flash partition, immediately walking past the caller-supplied
buffer on the very first iteration:
- write(): out-of-bounds *read* from the caller's `src` buffer.
- read(): out-of-bounds *write* into the caller's `dest` buffer -- the
more severe case, since it corrupts caller memory with flash
content instead of merely over-reading.
Verified with a standalone reproduction that compiles the unmodified
WL_Flash.cpp against a mock Flash_Access partition: calling
`wl.write(0, an_8_byte_buffer, 0)` with no other change immediately
segfaults (confirmed count == 0xFFFFFFFF for wl_page_size == 4096); with
this fix applied the same call returns ESP_OK without touching memory
outside the buffer, and normal non-zero-size read/write is unaffected.
Add an early `size == 0` return (mirroring the existing `!initialized`
guard) to both functions, and a host_test regression case exercising
wl_write()/wl_read() with size == 0 through the public API.
Disclosure: this fix was prepared with AI assistance (Claude) and reviewed
by me before submission.
Signed-off-by: yi chen <94xhn1@gmail.com>
This commit is contained in:
@@ -578,6 +578,13 @@ esp_err_t WL_Flash::write(size_t dest_addr, const void *src, size_t size)
|
||||
if (!this->initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (size == 0) {
|
||||
// Nothing to do. Guard this explicitly: size is unsigned, so
|
||||
// `size - 1` below would otherwise wrap around to SIZE_MAX and turn
|
||||
// "count" into a huge page count, walking far past the caller's
|
||||
// buffer (see components/wear_levelling/host_test).
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGD(TAG, "%s - dest_addr= 0x%08" PRIx32 ", size= 0x%08" PRIx32 , __func__, (uint32_t) dest_addr, (uint32_t) size);
|
||||
uint32_t count = (size - 1) / this->cfg.wl_page_size;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -597,6 +604,11 @@ esp_err_t WL_Flash::read(size_t src_addr, void *dest, size_t size)
|
||||
if (!this->initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (size == 0) {
|
||||
// See the matching guard in WL_Flash::write() above: size==0 must
|
||||
// not be allowed to reach the `size - 1` computation below.
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGD(TAG, "%s - src_addr= 0x%08" PRIx32 ", size= 0x%08" PRIx32 , __func__, (uint32_t) src_addr, (uint32_t) size);
|
||||
uint32_t count = (size - 1) / this->cfg.wl_page_size;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
|
||||
@@ -99,6 +99,39 @@ TEST_CASE("write and read back data", "[wear_levelling]")
|
||||
free(read);
|
||||
}
|
||||
|
||||
TEST_CASE("write and read with zero size are safe no-ops", "[wear_levelling]")
|
||||
{
|
||||
esp_err_t result;
|
||||
wl_handle_t wl_handle;
|
||||
|
||||
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
|
||||
|
||||
// Mount wear-levelled partition
|
||||
result = wl_mount(partition, &wl_handle);
|
||||
REQUIRE(result == ESP_OK);
|
||||
|
||||
// wl_write()/wl_read() do not document size==0 as invalid (analogous to
|
||||
// POSIX write()/read() with count==0), so it must not be treated as an
|
||||
// out-of-bounds request. Previously, WL_Flash::write()/read() computed
|
||||
// `(size - 1) / wl_page_size` without checking for size==0 first; since
|
||||
// size is unsigned, size==0 wrapped this to a huge page count and walked
|
||||
// far past the caller-provided buffer.
|
||||
uint8_t dummy = 0xAA;
|
||||
result = wl_write(wl_handle, 0, &dummy, 0);
|
||||
REQUIRE(result == ESP_OK);
|
||||
|
||||
uint8_t read_back = 0x55;
|
||||
result = wl_read(wl_handle, 0, &read_back, 0);
|
||||
REQUIRE(result == ESP_OK);
|
||||
|
||||
// Untouched by a genuine zero-length read.
|
||||
REQUIRE(read_back == 0x55);
|
||||
|
||||
// Unmount
|
||||
result = wl_unmount(wl_handle);
|
||||
REQUIRE(result == ESP_OK);
|
||||
}
|
||||
|
||||
TEST_CASE("power down test", "[wear_levelling]")
|
||||
{
|
||||
esp_err_t result;
|
||||
|
||||
Reference in New Issue
Block a user