Use low level API to replace sgx_mm_(commit/commit_data/modify_permissions)

Reduce the EMA management overhead and the global lock of emm module of
Intel SGX SDK
This commit is contained in:
Hui, Chunyang 2023-10-20 08:39:41 +00:00 committed by volcano
parent d7131a1a5b
commit 6930e606ef
7 changed files with 269 additions and 85 deletions

@ -48,6 +48,7 @@ dcap = [] # DCAP support. The compilation relies on DCAP package.
cov = ["sgx_cov"] # Enable coverage colletcion.
hyper_mode = [] # For running in hyper mode.
pku = [] # PKU Support
sim_mode = [] # For running in SGX simulation mode
[target.'cfg(not(target_env = "sgx"))'.dependencies]
sgx_types = { path = "../../deps/rust-sgx-sdk/sgx_types" }

@ -61,7 +61,12 @@ else
endif
LIBOS_CORE_A := $(OBJ_DIR)/libos/lib/lib$(LIBOS_CORE_LIB_NAME).a
LIBOS_CORE_RS_A := $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a
ifeq ($(SGX_MODE), SIM)
LIBOS_CORE_RS_A := $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs_sim.a
else
LIBOS_CORE_RS_A := $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a
endif
# All source code
RUST_SRCS := $(wildcard src/*.rs src/*/*.rs src/*/*/*.rs src/*/*/*/*.rs src/*/*/*/*/*.rs)
@ -140,20 +145,27 @@ ifeq ($(SGX_MODE), HYPER)
LIBOS_FEATURES += hyper_mode
endif
ifeq ($(SGX_MODE), SIM)
LIBOS_FEATURES += sim_mode
endif
# Release build is for production use. We enable code coverage only for debug
# build. It also simplifies the implementation as the release and debug build
# have different output paths.
ifeq ($(OCCLUM_RELEASE_BUILD), 1)
$(LIBOS_CORE_RS_A): $(RUST_SRCS)
@RUSTC_BOOTSTRAP=1 RUSTC_WRAPPER=$(RUSTC_WRAPPER) cargo build --release --target-dir=$(RUST_TARGET_DIR) -Z unstable-options --out-dir=$(RUST_OUT_DIR) --features "$(LIBOS_FEATURES)"
@mv $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a $@ || true
@echo "CARGO (release) => $@"
else ifneq ($(OCCLUM_COV),)
$(LIBOS_CORE_RS_A): $(RUST_SRCS)
@CARGO_INCREMENTAL=0 RUSTC_BOOTSTRAP=1 RUSTFLAGS=$(COV_FLAGS) cargo build --target-dir=$(RUST_TARGET_DIR) -Z unstable-options --out-dir=$(RUST_OUT_DIR) --features "$(LIBOS_FEATURES)"
@mv $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a $@ || true
@echo "CARGO (debug + cov) => $@"
else
$(LIBOS_CORE_RS_A): $(RUST_SRCS)
@RUSTC_BOOTSTRAP=1 RUSTC_WRAPPER=$(RUSTC_WRAPPER) cargo build --target-dir=$(RUST_TARGET_DIR) -Z unstable-options --out-dir=$(RUST_OUT_DIR) --features "$(LIBOS_FEATURES)"
@mv $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a $@ || true
@echo "CARGO (debug) => $@"
endif

@ -282,6 +282,6 @@ impl ShmManager {
return;
}
vma.set_perms(perms);
vma.modify_permissions_for_committed_pages(perms);
vma.modify_permissions_for_committed_pages(old_perms, perms);
}
}

@ -231,7 +231,7 @@ impl VMArea {
// Set memory permissions
if !options.perms().is_default() {
vm_area.modify_protection_force(None, vm_area.perms());
vm_area.modify_protection_force(None, VMPerms::DEFAULT, vm_area.perms());
}
}
// Do nothing if this vma has no committed memory
@ -274,7 +274,7 @@ impl VMArea {
debug_assert!(self.range().is_superset_of(target_range));
let buf = unsafe { target_range.as_slice_mut() };
if !self.perms().is_default() {
self.modify_protection_force(Some(&target_range), VMPerms::default());
self.modify_protection_force(Some(&target_range), self.perms(), VMPerms::default());
}
if need_flush {
@ -296,13 +296,17 @@ impl VMArea {
}
}
pub fn modify_permissions_for_committed_pages(&self, new_perms: VMPerms) {
pub fn modify_permissions_for_committed_pages(
&self,
current_perms: VMPerms,
new_perms: VMPerms,
) {
if self.is_fully_committed() {
self.modify_protection_force(None, new_perms);
self.modify_protection_force(None, current_perms, new_perms);
} else if self.is_partially_committed() {
let committed = true;
for range in self.pages().get_ranges(committed) {
self.modify_protection_force(Some(&range), new_perms);
self.modify_protection_force(Some(&range), current_perms, new_perms);
}
}
}
@ -638,11 +642,21 @@ impl VMArea {
// Current implementation with "unwrap()" can help us find the error quickly by panicing directly. Also, restoring VM state
// when this function fails will require some work and is not that simple.
// TODO: Return with Result instead of "unwrap()"" in this function.
fn modify_protection_force(&self, protect_range: Option<&VMRange>, new_perms: VMPerms) {
fn modify_protection_force(
&self,
protect_range: Option<&VMRange>,
current_perms: VMPerms,
new_perms: VMPerms,
) {
let protect_range = protect_range.unwrap_or_else(|| self.range());
self.epc_type
.modify_protection(protect_range.start(), protect_range.size(), new_perms)
.modify_protection(
protect_range.start(),
protect_range.size(),
current_perms,
new_perms,
)
.unwrap()
}
@ -668,7 +682,7 @@ impl VMArea {
}
VMInitializer::DoNothing() => {
if !self.perms().is_default() {
self.modify_protection_force(Some(target_range), perms);
self.modify_protection_force(Some(target_range), VMPerms::DEFAULT, perms);
}
}
VMInitializer::FillZeros() => {
@ -677,7 +691,7 @@ impl VMArea {
buf.iter_mut().for_each(|b| *b = 0);
}
if !perms.is_default() {
self.modify_protection_force(Some(target_range), perms);
self.modify_protection_force(Some(target_range), VMPerms::DEFAULT, perms);
}
}
_ => todo!(),
@ -732,7 +746,7 @@ impl VMArea {
.map_err(|_| errno!(EACCES, "failed to init memory from file"))?;
if !new_perm.is_default() {
self.modify_protection_force(Some(target_range), new_perm);
self.modify_protection_force(Some(target_range), VMPerms::DEFAULT, new_perm);
}
Ok(())
@ -766,8 +780,6 @@ impl VMArea {
range.resize(commit_once_size - total_commit_size);
}
// We don't take care the file-backed memory here
debug_assert!(self.backed_file().is_none());
self.init_memory_internal(&range, None)?;
total_commit_size += range.size();
@ -787,7 +799,6 @@ impl VMArea {
// Only used to handle PF triggered by the kernel
fn commit_current_vma_whole(&mut self) -> Result<()> {
debug_assert!(!self.is_fully_committed());
debug_assert!(self.backed_file().is_none());
let mut uncommitted_ranges = self.pages.as_ref().unwrap().get_ranges(false);
for range in uncommitted_ranges {
@ -797,35 +808,6 @@ impl VMArea {
Ok(())
}
// TODO: We can re-enable this when we support lazy extend permissions.
#[allow(dead_code)]
fn page_fault_handler_extend_permission(&mut self, pf_addr: usize) -> Result<()> {
let permission = self.perms();
// This is intended by the application.
if permission == VMPerms::NONE {
return_errno!(EPERM, "trying to access PROT_NONE memory");
}
if self.is_fully_committed() {
self.modify_protection_force(None, permission);
return Ok(());
}
let committed = true;
let committed_ranges = self.pages().get_ranges(committed);
for range in committed_ranges.iter() {
if !range.contains(pf_addr) {
continue;
}
self.epc_type
.modify_protection(range.start(), range.size(), permission)?;
}
Ok(())
}
}
impl Deref for VMArea {

@ -293,7 +293,8 @@ impl ChunkManager {
if intersection_vma.range() == containing_vma.range() {
// The whole containing_vma is mprotected
containing_vma.set_perms(new_perms);
containing_vma.modify_permissions_for_committed_pages(containing_vma.perms());
containing_vma
.modify_permissions_for_committed_pages(old_perms, containing_vma.perms());
containing_vmas.replace_with(VMAObj::new_vma_obj(containing_vma));
containing_vmas.move_next();
continue;
@ -317,7 +318,10 @@ impl ChunkManager {
new_perms,
VMAccess::Private(current_pid),
);
new_vma.modify_permissions_for_committed_pages(new_vma.perms());
new_vma.modify_permissions_for_committed_pages(
containing_vma.perms(),
new_vma.perms(),
);
let new_vma = VMAObj::new_vma_obj(new_vma);
// Another new VMA
@ -351,7 +355,10 @@ impl ChunkManager {
VMAccess::Private(current_pid),
);
new_vma.modify_permissions_for_committed_pages(new_vma.perms());
new_vma.modify_permissions_for_committed_pages(
containing_vma.perms(),
new_vma.perms(),
);
if remain_vma.start() == containing_vma.start() {
// mprotect right side of the vma

@ -69,7 +69,12 @@ pub trait EPCAllocator {
return_errno!(ENOSYS, "operation not supported");
}
fn modify_protection(addr: usize, length: usize, protection: VMPerms) -> Result<()> {
fn modify_protection(
addr: usize,
length: usize,
current_protection: VMPerms,
new_protection: VMPerms,
) -> Result<()> {
return_errno!(ENOSYS, "operation not supported");
}
@ -99,11 +104,16 @@ impl EPCAllocator for ReservedMem {
Ok(())
}
fn modify_protection(addr: usize, length: usize, protection: VMPerms) -> Result<()> {
fn modify_protection(
addr: usize,
length: usize,
current_protection: VMPerms,
new_protection: VMPerms,
) -> Result<()> {
let mut ret_val = 0;
let ret = if rsgx_is_supported_EDMM() {
unsafe {
sgx_tprotect_rsrv_mem(addr as *const c_void, length, protection.bits() as i32)
sgx_tprotect_rsrv_mem(addr as *const c_void, length, new_protection.bits() as i32)
}
} else {
// For platforms without EDMM, sgx_tprotect_rsrv_mem is actually useless.
@ -113,12 +123,13 @@ impl EPCAllocator for ReservedMem {
&mut ret_val as *mut i32,
addr as *const c_void,
length,
protection.bits() as i32,
new_protection.bits() as i32,
)
}
};
if ret != sgx_status_t::SGX_SUCCESS || ret_val != 0 {
error!("ocall ret = {:?}, ret_val = {:?}", ret, ret_val);
return_errno!(ENOMEM, "reserved memory modify protection failure");
}
@ -147,17 +158,26 @@ impl EPCAllocator for UserRegionMem {
Ok(())
}
fn modify_protection(addr: usize, length: usize, protection: VMPerms) -> Result<()> {
fn modify_protection(
addr: usize,
length: usize,
current_protection: VMPerms,
new_protection: VMPerms,
) -> Result<()> {
trace!(
"user region modify protection, protection = {:?}, range = {:?}",
protection,
new_protection,
VMRange::new_with_size(addr, length).unwrap()
);
let ptr = NonNull::<u8>::new(addr as *mut u8).unwrap();
unsafe {
EmmAlloc.modify_permissions(ptr, length, Perm::from_bits(protection.bits()).unwrap())
// Simulation mode doesn't have the symbol used here
#[cfg(not(feature = "sim_mode"))]
{
EDMMLocalApi::modify_permissions(addr, length, current_protection, new_protection)?;
}
.map_err(|e| errno!(Errno::from(e as u32)))?;
#[cfg(feature = "sim_mode")]
unreachable!();
Ok(())
}
@ -169,8 +189,12 @@ impl EPCAllocator for UserRegionMem {
impl UserRegionMem {
fn commit_memory(start_addr: usize, size: usize) -> Result<()> {
let ptr = NonNull::<u8>::new(start_addr as *mut u8).unwrap();
unsafe { EmmAlloc.commit(ptr, size) }.map_err(|e| errno!(Errno::from(e as u32)))?;
#[cfg(not(feature = "sim_mode"))]
EDMMLocalApi::commit_memory(start_addr, size)?;
#[cfg(feature = "sim_mode")]
unreachable!();
Ok(())
}
@ -179,16 +203,19 @@ impl UserRegionMem {
size: usize,
new_perms: VMPerms,
) -> Result<()> {
let ptr = NonNull::<u8>::new(start_addr as *mut u8).unwrap();
let perm = Perm::from_bits(new_perms.bits()).unwrap();
if size == PAGE_SIZE {
unsafe { EmmAlloc::commit_with_data(ptr, ZERO_PAGE.as_slice(), perm) }
.map_err(|e| errno!(Errno::from(e as u32)))?;
} else {
let data = ZeroPage::new_page_aligned_vec(size);
unsafe { EmmAlloc::commit_with_data(ptr, data.as_slice(), perm) }
.map_err(|e| errno!(Errno::from(e as u32)))?;
#[cfg(not(feature = "sim_mode"))]
{
if size == PAGE_SIZE {
EDMMLocalApi::commit_with_data(start_addr, ZERO_PAGE.as_slice(), new_perms)?;
} else {
let data = ZeroPage::new_page_aligned_vec(size);
EDMMLocalApi::commit_with_data(start_addr, data.as_slice(), new_perms)?;
}
}
#[cfg(feature = "sim_mode")]
unreachable!();
Ok(())
}
@ -199,16 +226,19 @@ impl UserRegionMem {
file_offset: usize,
new_perms: VMPerms,
) -> Result<()> {
let mut data = ZeroPage::new_page_aligned_vec(size);
let len = file
.read_at(file_offset, data.as_mut_slice())
.map_err(|_| errno!(EACCES, "failed to init memory from file"))?;
#[cfg(not(feature = "sim_mode"))]
{
let mut data = ZeroPage::new_page_aligned_vec(size);
let len = file
.read_at(file_offset, data.as_mut_slice())
.map_err(|_| errno!(EACCES, "failed to init memory from file"))?;
let ptr = NonNull::<u8>::new(start_addr as *mut u8).unwrap();
let perm = Perm::from_bits(new_perms.bits()).unwrap();
EDMMLocalApi::commit_with_data(start_addr, data.as_slice(), new_perms)?;
}
#[cfg(feature = "sim_mode")]
unreachable!();
unsafe { EmmAlloc::commit_with_data(ptr, data.as_slice(), perm) }
.map_err(|e| errno!(Errno::from(e as u32)))?;
Ok(())
}
}
@ -324,21 +354,33 @@ impl EPCMemType {
}
}
pub fn modify_protection(&self, addr: usize, length: usize, protection: VMPerms) -> Result<()> {
pub fn modify_protection(
&self,
addr: usize,
length: usize,
current_protection: VMPerms,
new_protection: VMPerms,
) -> Result<()> {
// PT_GROWSDOWN should only be applied to stack segment or a segment mapped with the MAP_GROWSDOWN flag set.
// Since the memory are managed by our own, mprotect ocall shouldn't use this flag. Otherwise, EINVAL will be thrown.
let mut prot = protection.clone();
let mut prot = new_protection;
let mut current_prot = current_protection;
prot.remove(VMPerms::GROWSDOWN);
current_prot.remove(VMPerms::GROWSDOWN);
match self {
EPCMemType::Reserved => ReservedMem::modify_protection(addr, length, prot),
EPCMemType::UserRegion => UserRegionMem::modify_protection(addr, length, prot),
EPCMemType::Reserved => {
ReservedMem::modify_protection(addr, length, current_prot, prot)
}
EPCMemType::UserRegion => {
UserRegionMem::modify_protection(addr, length, current_prot, prot)
}
}
}
}
pub fn commit_memory(start_addr: usize, size: usize, new_perms: Option<VMPerms>) -> Result<()> {
trace!(
debug!(
"commit epc: {:?}, new permission: {:?}",
VMRange::new_with_size(start_addr, size).unwrap(),
new_perms
@ -397,4 +439,139 @@ extern "C" {
len: usize,
prot: i32,
) -> sgx_status_t;
fn sgx_mm_modify_ocall(addr: usize, size: usize, flags_from: i32, flags_to: i32) -> i32;
// EACCEPT
fn do_eaccept(si: *const sec_info_t, addr: usize) -> i32;
// EMODPE
fn do_emodpe(si: *const sec_info_t, addr: usize) -> i32;
// EACCEPTCOPY
fn do_eacceptcopy(si: *const sec_info_t, dest: usize, src: usize) -> i32;
}
#[allow(non_camel_case_types)]
#[repr(C, align(512))]
struct sec_info_t {
flags: u64,
reserved: [u64; 7],
}
impl sec_info_t {
const SGX_EMA_STATE_PENDING: u64 = 0x08; // pending state
const SGX_EMA_STATE_PR: u64 = 0x20; // permission restriction state
fn new_for_modify_permission(new_protection: &VMPerms) -> Self {
Self {
flags: ((new_protection.bits() | SGX_EMA_PAGE_TYPE_REG) as u64)
| Self::SGX_EMA_STATE_PR,
reserved: [0; 7],
}
}
fn new_for_commit_memory() -> Self {
Self {
flags: ((VMPerms::DEFAULT.bits() | SGX_EMA_PAGE_TYPE_REG) as u64)
| Self::SGX_EMA_STATE_PENDING,
reserved: [0; 7],
}
}
fn new_for_commit_with_data(protection: &VMPerms) -> Self {
Self {
flags: (protection.bits() | SGX_EMA_PAGE_TYPE_REG) as u64,
reserved: [0; 7],
}
}
}
#[cfg(not(feature = "sim_mode"))]
struct EDMMLocalApi;
#[cfg(not(feature = "sim_mode"))]
impl EDMMLocalApi {
// To replace sgx_mm_commit
fn commit_memory(start_addr: usize, size: usize) -> Result<()> {
let si = sec_info_t::new_for_commit_memory();
for page in (start_addr..start_addr + size).step_by(PAGE_SIZE) {
let ret = unsafe { do_eaccept(&si as *const sec_info_t, page) };
if ret != 0 {
return_errno!(EFAULT, "do_eaccept failure");
}
}
Ok(())
}
// To replace sgx_mm_commit_data
fn commit_with_data(addr: usize, data: &[u8], perm: VMPerms) -> Result<()> {
let si = sec_info_t::new_for_commit_with_data(&perm);
let size = data.len();
let mut src_raw_ptr = data.as_ptr() as usize;
for dest_page in (addr..addr + size).step_by(PAGE_SIZE) {
let ret = unsafe { do_eacceptcopy(&si as *const sec_info_t, dest_page, src_raw_ptr) };
if ret != 0 {
return_errno!(EFAULT, "do_eacceptcopy failure");
}
Self::modify_permissions(dest_page, PAGE_SIZE, VMPerms::DEFAULT, perm)?;
src_raw_ptr += PAGE_SIZE;
}
Ok(())
}
// To replace sgx_mm_modify_permissions
fn modify_permissions(
addr: usize,
length: usize,
current_protection: VMPerms,
new_protection: VMPerms,
) -> Result<()> {
if current_protection == new_protection {
return Ok(());
}
let flags_from = current_protection.bits() | SGX_EMA_PAGE_TYPE_REG;
let flags_to = new_protection.bits() | SGX_EMA_PAGE_TYPE_REG;
let ret = unsafe { sgx_mm_modify_ocall(addr, length, flags_from as i32, flags_to as i32) };
if ret != 0 {
return_errno!(EFAULT, "sgx_mm_modify_ocall failure");
}
let si = sec_info_t::new_for_modify_permission(&new_protection);
for page in (addr..addr + length).step_by(PAGE_SIZE) {
debug_assert!(page % PAGE_SIZE == 0);
if new_protection.bits() | current_protection.bits() != current_protection.bits() {
unsafe { do_emodpe(&si as *const sec_info_t, page) };
// Check this return value is useless. RAX is set to SE_EMODPE which is 6 defined in SDK.
}
// If new permission is RWX, no EMODPR needed in untrusted part, hence no EACCEPT
if new_protection != VMPerms::ALL {
let ret = unsafe { do_eaccept(&si, page) };
if ret != 0 {
return_errno!(EFAULT, "do_eaccept failure");
}
}
}
// ???
if new_protection == VMPerms::NONE {
let ret = unsafe {
sgx_mm_modify_ocall(
addr,
length,
(SGX_EMA_PAGE_TYPE_REG | SGX_EMA_PROT_NONE) as i32,
(SGX_EMA_PAGE_TYPE_REG | SGX_EMA_PROT_NONE) as i32,
)
};
if ret != 0 {
return_errno!(EFAULT, "sgx_mm_modify_ocall failure for permission None");
}
}
Ok(())
}
}

@ -778,7 +778,10 @@ impl InternalVMManager {
let mut vma = new_chunk.get_vma_for_single_vma_chunk();
// Reset memory permissions
if !vma.perms().is_default() {
vma.modify_permissions_for_committed_pages(VMPerms::default())
vma.modify_permissions_for_committed_pages(
vma.perms(),
VMPerms::default(),
)
}
// Reset memory contents
unsafe {
@ -926,7 +929,7 @@ impl InternalVMManager {
(true, true) => {
// Exact the same vma
containing_vma.set_perms(new_perms);
containing_vma.modify_permissions_for_committed_pages(new_perms);
containing_vma.modify_permissions_for_committed_pages(old_perms, new_perms);
return Ok(());
}
(false, false) => {
@ -942,7 +945,8 @@ impl InternalVMManager {
new_perms,
VMAccess::Private(current_pid),
);
new_vma.modify_permissions_for_committed_pages(new_perms);
new_vma
.modify_permissions_for_committed_pages(containing_vma.perms(), new_perms);
let remaining_old_vma = {
let range = VMRange::new(protect_range.end(), old_end).unwrap();
@ -966,7 +970,8 @@ impl InternalVMManager {
new_perms,
VMAccess::Private(current_pid),
);
new_vma.modify_permissions_for_committed_pages(new_perms);
new_vma
.modify_permissions_for_committed_pages(containing_vma.perms(), new_perms);
if same_start {
// Protect range is at left side of the containing vma