-
Notifications
You must be signed in to change notification settings - Fork 163
ide: Draft changes to address the DMA base address range issue #2373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
This PR modifies files containing For more on why we check whole files, instead of just diffs, check out the Rustonomicon |
mattkur
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks for doing this. A few comments, but the core logic looks right.
vm/devices/storage/ide/src/lib.rs
Outdated
| tracing::trace!(paged_range = ?PagedRange::new(0, gpns.len() * PAGE_SIZE64 as usize , &gpns), "PagedRange of GPNs"); | ||
| tracing::trace!(gpns_vector = ?gpns, gpns_len = ?gpns.len(), "PagedRange values"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not this line specifically, but it's when this raised to the level of my attention: do we really need this much tracing here? It seems like we're tracing every possible line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were added as part of my debugging, removed them now.
| } | ||
|
|
||
| #[async_test] | ||
| async fn enlightened_cmd_test_invalid_dma_base() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this fails without your fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't fail rather it silently ignores the error when doing the DMA transfer.
if let Err(err) = r {
tracelimit::error_ratelimited!(
error = &err as &dyn std::error::Error,
"dma transfer failed"
);
}
f509705 to
b544b5e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds validation for IDE DMA base addresses against guest memory, using the new GuestMemory paged-range probing utilities, and introduces a negative test for out-of-bounds DMA descriptors.
Changes:
- Import and use
guestmem::ranges::PagedRangeplus a page-size helper to model DMA buffers as paged ranges. - Extend
Channel::perform_dma_memory_phaseto validate each DMA descriptor’s buffer range viaprobe_gpn_readable_range/probe_gpn_writable_range, aborting the transfer and flagging an error on invalid ranges or arithmetic overflow. - Add an async test
enlightened_cmd_test_invalid_dma_baseto exercise an invalid DMA base address scenario for enlightened commands.
| /* | ||
| This is a negative test case where the DMA base address is invalid. | ||
| The test sets the DMA base address to an out-of-bounds memory | ||
| address of the guest range and expects the device to not read any data. | ||
| */ |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This negative test documents that it "expects the device to not read any data" for an invalid DMA base, but it only drives the enlightened command and waits for completion without asserting any resulting device state (e.g., DMA error flag, PRD exhaustion, or unchanged guest buffers). To fully cover the new DMA base validation behavior, please add explicit assertions that the transfer was rejected (for example by inspecting the bus master status or drive state) so that the test would fail if the device still attempted a DMA.
| device_select(&mut ide_device, &dev_path).await; | ||
| prep_ide_channel(&mut ide_device, DriveType::Hard, &dev_path); | ||
|
|
||
| // READ SECTORS - enlightened |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline comment "// READ SECTORS - enlightened" does not match the command being invoked in this test, which uses WRITE_DMA_ALT; consider updating the comment to describe the write-DMA path being exercised to avoid confusion when reading the test.
| // READ SECTORS - enlightened | |
| // WRITE DMA (enlightened) with invalid DMA base |
| let r = if let Some(end_gpa) = end_gpa { | ||
| let start_gpn = Self::gpa_to_gpn(cur_desc_table_entry.mem_physical_base.into()); | ||
| let end_gpn = Self::gpa_to_gpn(end_gpa.into()); | ||
| let gpns: Vec<u64> = (start_gpn..end_gpn).collect(); | ||
|
|
||
| let paged_range = PagedRange::new(0, gpns.len() * PAGE_SIZE64 as usize, &gpns).unwrap(); |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DMA range validation here builds gpns using start_gpn..end_gpn and then creates a PagedRange with offset 0 and length gpns.len() * PAGE_SIZE64, which means that when the DMA buffer lies entirely within a single page (start_gpn == end_gpn), gpns is empty and the probe becomes a no-op, so out-of-range single-page transfers will not be detected. In addition, the PagedRange being anchored at offset 0 on the first page does not reflect a non–page-aligned mem_physical_base, so the probed region can be a strict superset of the actual DMA buffer and require extra pages/bytes to be valid. To correctly validate the buffer, compute the first and last page numbers using a ceiling-style calculation (similar to storvsp/src/test_helpers.rs:217-221), derive the byte offset as mem_physical_base % PAGE_SIZE, and pass the actual transfer length (dma.transfer_bytes_left) into PagedRange::new, so that the probed range exactly matches the DMA buffer the device will access.
| let r = if let Some(end_gpa) = end_gpa { | |
| let start_gpn = Self::gpa_to_gpn(cur_desc_table_entry.mem_physical_base.into()); | |
| let end_gpn = Self::gpa_to_gpn(end_gpa.into()); | |
| let gpns: Vec<u64> = (start_gpn..end_gpn).collect(); | |
| let paged_range = PagedRange::new(0, gpns.len() * PAGE_SIZE64 as usize, &gpns).unwrap(); | |
| let r = if let Some(end_gpa_exclusive) = end_gpa { | |
| let start_gpn = | |
| Self::gpa_to_gpn(cur_desc_table_entry.mem_physical_base.into()); | |
| // end_gpa_exclusive is one past the last byte; subtract 1 to get the | |
| // last byte included in the transfer, then compute its page number. | |
| let last_gpa_inclusive = end_gpa_exclusive - 1; | |
| let end_gpn = Self::gpa_to_gpn(last_gpa_inclusive.into()); | |
| let gpns: Vec<u64> = (start_gpn..=end_gpn).collect(); | |
| // The DMA buffer may start at a non–page-aligned address; reflect that | |
| // in the probed range, and use the actual transfer length. | |
| let offset = | |
| (cur_desc_table_entry.mem_physical_base % PAGE_SIZE64) as usize; | |
| let length = dma.transfer_bytes_left as usize; | |
| let paged_range = PagedRange::new(offset, length, &gpns).unwrap(); |
Context: This fix is to address the issue reported by IDE fuzzer.
Changes made --