Skip to content

Commit 3d4010b

Browse files
committed
amd64: add missing barrier for certain page-table updates
Certain page-table updates that do not require TLB invalidation were previously missing any kind of fence at all to keep table walks coherent. This commit introduces a new barrier for this scenario. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent c61769a commit 3d4010b

5 files changed

Lines changed: 70 additions & 2 deletions

File tree

src/hyperlight_guest_bin/src/arch/amd64/exception/handle.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ fn handle_stack_pagefault(gva: u64) {
7676
executable: false,
7777
}),
7878
);
79+
// This is mapping an entry for the first time, so we don't
80+
// need to worry about TLB maintenance. We do (probably) need
81+
// to worry about coherence between these stores and the page
82+
// table walker, but this page won't be accessed again until
83+
// after [`iret`], which is a serializing instruction, so
84+
// that's already handled as well.
7985
}
8086
}
8187

@@ -113,6 +119,8 @@ fn handle_cow_pagefault(_phys: PhysAddr, virt: VirtAddr, perms: CowMapping) {
113119
executable: perms.executable,
114120
}),
115121
);
122+
// This is updating an entry that was already valid, changing
123+
// its OA, so we need to actually invalidate the TLB for it.
116124
core::arch::asm!("invlpg [{}]", in(reg) target_virt, options(readonly, nostack, preserves_flags));
117125
}
118126
}

src/hyperlight_guest_bin/src/arch/amd64/init.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ unsafe fn init_stack() -> u64 {
121121
executable: false,
122122
}),
123123
);
124+
crate::paging::barrier::first_valid_same_ctx();
124125
}
125126
MAIN_STACK_TOP_GVA
126127
}

src/hyperlight_guest_bin/src/arch/amd64/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ impl ProcCtrl {
229229
executable: false,
230230
}),
231231
);
232+
crate::paging::barrier::first_valid_same_ctx();
232233
let ptr = ptr as *mut Self;
233234
(&raw mut (*ptr).gdt).write_bytes(0u8, 1);
234235
(&raw mut (*ptr).tss).write_bytes(0u8, 1);

src/hyperlight_guest_bin/src/paging.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,58 @@ pub fn virt_to_phys(gva: vmem::VirtAddr) -> impl Iterator<Item = vmem::Mapping>
144144
pub fn phys_to_virt(gpa: vmem::PhysAddr) -> Option<*mut u8> {
145145
GuestMappingOperations::new().try_phys_to_virt(gpa)
146146
}
147+
148+
/// Barriers that other code may need to use when updating page tables
149+
pub mod barrier {
150+
/// Call this function when a virtual address has just been made
151+
/// valid for the first time after the last tlb invalidate that
152+
/// affected it, and it will be used for the first time in the
153+
/// same execution context as has made the modification.
154+
///
155+
/// On most architectures, TLBs will not cache invalid entries, so
156+
/// this does not need to issue a TLB. However, it does need to
157+
/// ensure coherency between the previous writes and any future
158+
/// uses by a page table walker.
159+
///
160+
/// # Architecture-specific (amd64) notes
161+
///
162+
/// The exact details around page walk coherency on amd64 seem a
163+
/// bit fuzzy. The Intel manual notes that a serialising
164+
/// instruction is necessary specifically to synchronise table
165+
/// walks performed during instruction fetch [1], but is
166+
/// relatively quiet about other page walks. The AMD manual notes
167+
/// [2] that "a table entry is allowed to be upgraded (by marking
168+
/// it as present, or by removing its write, execute or supervisor
169+
/// restrictions) without explicitly maintaining TLB coherency",
170+
/// but only states that TLB any upper-level TLB cache entries
171+
/// will be flushed before re-walking to confirm the fault, which
172+
/// does not clearly seem strong enough.
173+
///
174+
/// In some limited testing, `mfence` typically seems to be
175+
/// enough, but as it is not a serializing instruction on Intel
176+
/// platforms, we assume it may not be quite good enough. `cpuid`
177+
/// is likely to be very slow, since we are definitely running
178+
/// under a hypervisor (and often even nested). Currently, for
179+
/// simplicity's sake, this just copies cr0 to itself, but other
180+
/// options (including the `serialize` instruction where
181+
/// available) could be worth exploring.
182+
///
183+
/// [1] Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3: System Programming Guide
184+
/// Chapter 5: Paging
185+
/// §5.10: Caching Translation Information
186+
/// §5.10.4: Invalidation of TLBs and Paging-Structure Caches
187+
/// §5.10.4.3: Optional Invalidation
188+
/// [2] AMD64 Architecture Programmer's Manual, Volume 2: System Programming
189+
/// Section 5: Page Translation and Protection
190+
/// §5.5: Translation-Lookaside Buffer
191+
/// §5.5.3: TLB Management
192+
#[inline(always)]
193+
pub fn first_valid_same_ctx() {
194+
unsafe {
195+
core::arch::asm!("
196+
mov rax, cr0
197+
mov cr0, rax
198+
", out("rax") _);
199+
}
200+
}
201+
}

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ fn read_mapped_buffer(base: u64, len: u64, do_map: bool) -> Vec<u8> {
593593
executable: true,
594594
}),
595595
);
596+
hyperlight_guest_bin::paging::barrier::first_valid_same_ctx();
596597
}
597598
}
598599

@@ -623,7 +624,8 @@ fn write_mapped_buffer(base: u64, len: u64) -> bool {
623624
writable: true,
624625
executable: true,
625626
}),
626-
)
627+
);
628+
hyperlight_guest_bin::paging::barrier::first_valid_same_ctx();
627629
};
628630

629631
let data = unsafe { core::slice::from_raw_parts_mut(base, len) };
@@ -650,7 +652,8 @@ fn exec_mapped_buffer(base: u64, len: u64) -> bool {
650652
writable: true,
651653
executable: true,
652654
}),
653-
)
655+
);
656+
hyperlight_guest_bin::paging::barrier::first_valid_same_ctx();
654657
};
655658

656659
let data = unsafe { core::slice::from_raw_parts(base, len) };

0 commit comments

Comments
 (0)