Skip to content

Commit 2de3715

Browse files
committed
fix aliasing issues
1 parent 5d61efd commit 2de3715

File tree

1 file changed

+146
-64
lines changed

1 file changed

+146
-64
lines changed

src/binary/load_kernel.rs

Lines changed: 146 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
use core::mem::align_of;
2+
13
use crate::{
24
binary::{level_4_entries::UsedLevel4Entries, PAGE_SIZE},
35
boot_info::TlsTemplate,
46
};
57
use x86_64::{
68
align_up,
79
structures::paging::{
8-
mapper::MapperAllSizes, FrameAllocator, Page, PageSize, PageTableFlags as Flags, PhysFrame,
9-
Size4KiB,
10+
mapper::{MappedFrame, MapperAllSizes, TranslateResult},
11+
FrameAllocator, Page, PageSize, PageTableFlags as Flags, PhysFrame, Size4KiB, Translate,
1012
},
1113
PhysAddr, VirtAddr,
1214
};
@@ -17,6 +19,9 @@ use xmas_elf::{
1719
ElfFile,
1820
};
1921

22+
/// Used by [`Inner::make_mut`] and [`Inner::clean_copied_flag`].
23+
const COPIED: Flags = Flags::BIT_9;
24+
2025
struct Loader<'a, M, F> {
2126
elf_file: ElfFile<'a>,
2227
inner: Inner<'a, M, F>,
@@ -31,7 +36,7 @@ struct Inner<'a, M, F> {
3136

3237
impl<'a, M, F> Loader<'a, M, F>
3338
where
34-
M: MapperAllSizes,
39+
M: MapperAllSizes + Translate,
3540
F: FrameAllocator<Size4KiB>,
3641
{
3742
fn new(
@@ -75,14 +80,6 @@ where
7580
program::sanity_check(program_header, &self.elf_file)?;
7681
}
7782

78-
// Apply relocations in physical memory.
79-
for program_header in self.elf_file.program_iter() {
80-
if let Type::Dynamic = program_header.get_type()? {
81-
self.inner
82-
.handle_dynamic_segment(program_header, &self.elf_file)?
83-
}
84-
}
85-
8683
// Load the segments into virtual memory.
8784
let mut tls_template = None;
8885
for program_header in self.elf_file.program_iter() {
@@ -106,6 +103,17 @@ where
106103
| Type::ProcessorSpecific(_) => {}
107104
}
108105
}
106+
107+
// Apply relocations in virtual memory.
108+
for program_header in self.elf_file.program_iter() {
109+
if let Type::Dynamic = program_header.get_type()? {
110+
self.inner
111+
.handle_dynamic_segment(program_header, &self.elf_file)?
112+
}
113+
}
114+
115+
self.inner.remove_copied_flags(&self.elf_file).unwrap();
116+
109117
Ok(tls_template)
110118
}
111119

@@ -123,7 +131,7 @@ where
123131

124132
impl<'a, M, F> Inner<'a, M, F>
125133
where
126-
M: MapperAllSizes,
134+
M: MapperAllSizes + Translate,
127135
F: FrameAllocator<Size4KiB>,
128136
{
129137
fn handle_load_segment(&mut self, segment: ProgramHeader) -> Result<(), &'static str> {
@@ -175,7 +183,6 @@ where
175183
log::info!("Mapping bss section");
176184

177185
let virt_start_addr = VirtAddr::new(segment.virtual_addr()) + self.virtual_address_offset;
178-
let phys_start_addr = self.kernel_offset + segment.offset();
179186
let mem_size = segment.mem_size();
180187
let file_size = segment.file_size();
181188

@@ -220,47 +227,16 @@ where
220227
// the remaining part of the frame since the frame is no longer shared with other
221228
// segments now.
222229

223-
// calculate the frame where the last segment page is mapped
224-
let orig_frame: PhysFrame =
225-
PhysFrame::containing_address(phys_start_addr + file_size - 1u64);
226-
// allocate a new frame to replace `orig_frame`
227-
let new_frame = self.frame_allocator.allocate_frame().unwrap();
228-
229-
// zero new frame, utilizing that it's identity-mapped
230-
{
231-
let new_frame_ptr = new_frame.start_address().as_u64() as *mut PageArray;
232-
unsafe { new_frame_ptr.write(ZERO_ARRAY) };
233-
}
234-
235-
// copy the data bytes from orig_frame to new_frame
236-
{
237-
log::info!("Copy contents");
238-
let orig_bytes_ptr = orig_frame.start_address().as_u64() as *mut u8;
239-
let new_bytes_ptr = new_frame.start_address().as_u64() as *mut u8;
240-
241-
for offset in 0..(data_bytes_before_zero as isize) {
242-
unsafe {
243-
let orig_byte = orig_bytes_ptr.offset(offset).read();
244-
new_bytes_ptr.offset(offset).write(orig_byte);
245-
}
246-
}
247-
}
248-
249-
// remap last page from orig_frame to `new_frame`
250-
log::info!("Remap last page");
251230
let last_page = Page::containing_address(virt_start_addr + file_size - 1u64);
252-
self.page_table
253-
.unmap(last_page.clone())
254-
.map_err(|_err| "Failed to unmap last segment page because of bss memory")?
255-
.1
256-
.ignore();
257-
let flusher = unsafe {
258-
self.page_table
259-
.map_to(last_page, new_frame, segment_flags, self.frame_allocator)
231+
let new_frame = unsafe { self.make_mut(last_page) };
232+
let new_bytes_ptr = new_frame.start_address().as_u64() as *mut u8;
233+
unsafe {
234+
core::ptr::write_bytes(
235+
new_bytes_ptr,
236+
0,
237+
(Size4KiB::SIZE - data_bytes_before_zero) as usize,
238+
);
260239
}
261-
.map_err(|_err| "Failed to remap last segment page because of bss memory")?;
262-
// we operate on an inactive page table, so we don't need to flush our changes
263-
flusher.ignore();
264240
}
265241

266242
// map additional frames for `.bss` memory that is not present in source file
@@ -288,6 +264,104 @@ where
288264
Ok(())
289265
}
290266

267+
/// This method is intended for making the memory loaded by a Load segment mutable.
268+
///
269+
/// All memory from a Load segment starts out by mapped to the same frames that
270+
/// contain the elf file. Thus writing to memory in that state will cause aliasing issues.
271+
/// To avoid that, we allocate a new frame, copy all bytes from the old frame to the new frame,
272+
/// and remap the page to the new frame. At this point the page no longer aliases the elf file
273+
/// and we can write to it.
274+
///
275+
/// When we map the new frame we also set [`COPIED`] flag in the page table flags, so that
276+
/// we can detect if the frame has already been copied when we try to modify the page again.
277+
///
278+
/// ## Safety
279+
/// - `page` should be a page mapped by a Load segment.
280+
///
281+
/// ## Panics
282+
/// Panics if the page is not mapped in `self.page_table`.
283+
unsafe fn make_mut(&mut self, page: Page) -> PhysFrame {
284+
let (frame, flags) = match self.page_table.translate(page.start_address()) {
285+
TranslateResult::Mapped {
286+
frame,
287+
offset: _,
288+
flags,
289+
} => (frame, flags),
290+
TranslateResult::NotMapped => panic!("{:?} is not mapped", page),
291+
TranslateResult::InvalidFrameAddress(_) => unreachable!(),
292+
};
293+
let frame = if let MappedFrame::Size4KiB(frame) = frame {
294+
frame
295+
} else {
296+
// We only map 4k pages.
297+
unreachable!()
298+
};
299+
300+
if flags.contains(COPIED) {
301+
// The frame was already copied, we are free to modify it.
302+
return frame;
303+
}
304+
305+
// Allocate a new frame and copy the memory, utilizing that both frames are identity mapped.
306+
let new_frame = self.frame_allocator.allocate_frame().unwrap();
307+
let frame_ptr = frame.start_address().as_u64() as *const u8;
308+
let new_frame_ptr = new_frame.start_address().as_u64() as *mut u8;
309+
unsafe {
310+
core::ptr::copy_nonoverlapping(frame_ptr, new_frame_ptr, Size4KiB::SIZE as usize);
311+
}
312+
313+
// Replace the underlying frame and update the flags.
314+
self.page_table.unmap(page).unwrap().1.ignore();
315+
let new_flags = flags | COPIED;
316+
unsafe {
317+
self.page_table
318+
.map_to(page, new_frame, new_flags, self.frame_allocator)
319+
.unwrap()
320+
.ignore();
321+
}
322+
323+
new_frame
324+
}
325+
326+
/// Cleans up the custom flags set by [`Inner::make_mut`].
327+
fn remove_copied_flags(&mut self, elf_file: &ElfFile) -> Result<(), &'static str> {
328+
for program_header in elf_file.program_iter() {
329+
if let Type::Load = program_header.get_type()? {
330+
let start = self.virtual_address_offset + program_header.virtual_addr();
331+
let end = start + program_header.mem_size();
332+
let start = VirtAddr::new(start);
333+
let end = VirtAddr::new(end);
334+
let start_page = Page::containing_address(start);
335+
let end_page = Page::containing_address(end - 1u64);
336+
for page in Page::<Size4KiB>::range_inclusive(start_page, end_page) {
337+
// Translate the page and get the flags.
338+
let res = self.page_table.translate(page.start_address());
339+
let flags = match res {
340+
TranslateResult::Mapped {
341+
frame: _,
342+
offset: _,
343+
flags,
344+
} => flags,
345+
TranslateResult::NotMapped | TranslateResult::InvalidFrameAddress(_) => {
346+
unreachable!("has the elf file not been mapped correctly?")
347+
}
348+
};
349+
350+
if flags.contains(COPIED) {
351+
// Remove the flag.
352+
unsafe {
353+
self.page_table
354+
.update_flags(page, flags & !COPIED)
355+
.unwrap()
356+
.ignore();
357+
}
358+
}
359+
}
360+
}
361+
}
362+
Ok(())
363+
}
364+
291365
fn handle_tls_segment(&mut self, segment: ProgramHeader) -> Result<TlsTemplate, &'static str> {
292366
Ok(TlsTemplate {
293367
start_addr: segment.virtual_addr() + self.virtual_address_offset,
@@ -385,19 +459,27 @@ where
385459
match rela.get_type() {
386460
// R_AMD64_RELATIVE
387461
8 => {
388-
let offset_in_file = find_offset(elf_file, rela.get_offset())?
389-
.ok_or("Destination of relocation is not mapped in physical memory")?;
390-
let dest_addr = self.kernel_offset + offset_in_file;
391-
let dest_ptr = dest_addr.as_u64() as *mut u64;
392-
462+
check_is_in_load(elf_file, rela.get_offset())?;
463+
let addr = self.virtual_address_offset + rela.get_offset();
393464
let value = self
394465
.virtual_address_offset
395466
.checked_add(rela.get_addend())
396467
.unwrap();
397468

469+
let ptr = addr as *mut u64;
470+
if ptr as usize % align_of::<u64>() != 0 {
471+
return Err("destination of relocation is not aligned");
472+
}
473+
474+
let virt_addr = VirtAddr::from_ptr(ptr);
475+
let page = Page::containing_address(virt_addr);
476+
let offset_in_page = virt_addr - page.start_address();
477+
478+
let new_frame = unsafe { self.make_mut(page) };
479+
let phys_addr = new_frame.start_address() + offset_in_page;
480+
let addr = phys_addr.as_u64() as *mut u64;
398481
unsafe {
399-
// write new value, utilizing that the address identity-mapped
400-
dest_ptr.write(value);
482+
addr.write(value);
401483
}
402484
}
403485
ty => unimplemented!("relocation type {:x} not supported", ty),
@@ -408,19 +490,19 @@ where
408490
}
409491
}
410492

411-
/// Locate the offset into the elf file corresponding to a virtual address.
412-
fn find_offset(elf_file: &ElfFile, virt_offset: u64) -> Result<Option<u64>, &'static str> {
493+
/// Check that the virtual offset belongs to a load segment.
494+
fn check_is_in_load(elf_file: &ElfFile, virt_offset: u64) -> Result<(), &'static str> {
413495
for program_header in elf_file.program_iter() {
414496
if let Type::Load = program_header.get_type()? {
415497
if program_header.virtual_addr() <= virt_offset {
416498
let offset_in_segment = virt_offset - program_header.virtual_addr();
417499
if offset_in_segment < program_header.file_size() {
418-
return Ok(Some(program_header.offset() + offset_in_segment));
500+
return Ok(());
419501
}
420502
}
421503
}
422504
}
423-
Ok(None)
505+
Err("offset is not in load segment")
424506
}
425507

426508
/// Loads the kernel ELF file given in `bytes` in the given `page_table`.
@@ -429,7 +511,7 @@ fn find_offset(elf_file: &ElfFile, virt_offset: u64) -> Result<Option<u64>, &'st
429511
/// and a structure describing which level 4 page table entries are in use.
430512
pub fn load_kernel(
431513
bytes: &[u8],
432-
page_table: &mut impl MapperAllSizes,
514+
page_table: &mut (impl MapperAllSizes + Translate),
433515
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
434516
) -> Result<(VirtAddr, Option<TlsTemplate>, UsedLevel4Entries), &'static str> {
435517
let mut loader = Loader::new(bytes, page_table, frame_allocator)?;

0 commit comments

Comments
 (0)