kernel/vm: Fix handling of non-page-aligned addresses in get_memory_map_etc.

They were passed to TranslationMap::Query() directly, assuming that the
TranslationMap would just round down to the nearest page address. This
actually isn't guaranteed in the case of X86VMTranslationMap64Bit and
the physical map area, which uses hugepages, and so adds the offset
to the page address itself.

So, here the logic is rewritten to always pass page-aligned addresses
to Query(), and then re-add the offset for the first page only. We then
increment virtualAddress instead of an offset, making the next Query
naturally page-aligned.

This was the cause of #20142: when the BFS I/O hook was disabled,
virtual addresses in the physical map region were passed down to
the disk I/O routines, which were then mistranslated by this function,
resulting in corruption of adjacent pages by DMA, and incorrect
data in the pages where it was supposed to be read into.

Fixes #20142.

Change-Id: Ibacd00b7f5ce23a7b41c620224ddf8d338b6de4a
Reviewed-on: https://review.haiku-os.org/c/haiku/+/11377
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Augustin Cavalier
2026-07-28 16:51:23 +00:00
committed by waddlesplash
parent d653556ecd
commit 88e0124d1a
+16 -18
View File
@@ -5376,19 +5376,15 @@ get_memory_map_etc(team_id team, const void* address, size_t numBytes,
uint32 numEntries = *_numEntries;
*_numEntries = 0;
addr_t virtualAddress = (addr_t)address;
addr_t pageOffset = virtualAddress & (B_PAGE_SIZE - 1);
status_t status = B_OK;
int32 index = -1;
addr_t offset = 0;
bool interrupts = are_interrupts_enabled();
TRACE(("get_memory_map_etc(%" B_PRId32 ", %p, %lu bytes, %" B_PRIu32 " "
"entries)\n", team, address, numBytes, numEntries));
if (numEntries == 0 || numBytes == 0)
return B_BAD_VALUE;
addr_t virtualAddress = (addr_t)address;
addr_t pageOffset = virtualAddress % B_PAGE_SIZE;
// get the address space
VMAddressSpace* addressSpace;
if (IS_USER_ADDRESS(virtualAddress)) {
@@ -5404,19 +5400,20 @@ get_memory_map_etc(team_id team, const void* address, size_t numBytes,
VMAddressSpacePutter addressSpacePutter(addressSpace);
VMTranslationMap* map = addressSpace->TranslationMap();
const bool interrupts = are_interrupts_enabled();
if (interrupts)
map->Lock();
while (offset < numBytes) {
addr_t bytes = min_c(numBytes - offset, B_PAGE_SIZE);
uint32 flags;
status_t status = B_OK;
int32 index = -1;
while (numBytes > 0) {
phys_addr_t physicalAddress;
uint32 flags;
if (interrupts) {
status = map->Query((addr_t)address + offset, &physicalAddress,
&flags);
status = map->Query(virtualAddress - pageOffset,
&physicalAddress, &flags);
} else {
status = map->QueryInterrupt((addr_t)address + offset,
status = map->QueryInterrupt(virtualAddress - pageOffset,
&physicalAddress, &flags);
}
if (status < B_OK)
@@ -5426,10 +5423,10 @@ get_memory_map_etc(team_id team, const void* address, size_t numBytes,
return B_BAD_ADDRESS;
}
if (index < 0 && pageOffset > 0) {
addr_t bytes = min_c(numBytes, B_PAGE_SIZE - pageOffset);
if (pageOffset > 0) {
physicalAddress += pageOffset;
if (bytes > B_PAGE_SIZE - pageOffset)
bytes = B_PAGE_SIZE - pageOffset;
pageOffset = 0;
}
// need to switch to the next physical_entry?
@@ -5446,7 +5443,8 @@ get_memory_map_etc(team_id team, const void* address, size_t numBytes,
table[index].size += bytes;
}
offset += bytes;
virtualAddress += bytes;
numBytes -= bytes;
}
if (interrupts)