Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions kernel/src/memory/process_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,61 @@ impl ProcessPageTable {
Ok(frame)
}

/// Update the flags of an already-mapped page
///
/// This is used by mprotect to change page protections without remapping.
/// The page must already be mapped; if not, this returns an error.
pub fn update_page_flags(
&mut self,
page: Page<Size4KiB>,
new_flags: PageTableFlags,
) -> Result<(), &'static str> {
// First, check if the page is mapped and get the physical frame
let frame = self
.mapper
.translate_page(page)
.map_err(|_| "Page not mapped")?;

// Unmap the page (get the frame back)
let (unmapped_frame, _flush) = self
.mapper
.unmap(page)
.map_err(|_| "Failed to unmap page for flag update")?;

// Sanity check: the frame should match
if unmapped_frame != frame {
log::error!(
"update_page_flags: frame mismatch! translate returned {:?}, unmap returned {:?}",
frame,
unmapped_frame
);
return Err("Frame mismatch during flag update");
}

// Remap with new flags
// We need to determine appropriate table flags based on the new page flags
let table_flags = if new_flags.contains(PageTableFlags::USER_ACCESSIBLE) {
PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::USER_ACCESSIBLE
} else {
PageTableFlags::PRESENT | PageTableFlags::WRITABLE
};

unsafe {
self.mapper
.map_to_with_table_flags(
page,
frame,
new_flags,
table_flags,
&mut GlobalFrameAllocator,
)
.map_err(|_| "Failed to remap page with new flags")?
.ignore(); // Don't flush - caller will handle TLB
}

Ok(())
}

/// Translate a virtual address to physical address
#[allow(dead_code)]
pub fn translate(&self, addr: VirtAddr) -> Option<PhysAddr> {
Expand Down
1 change: 1 addition & 0 deletions kernel/src/syscall/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ pub fn dispatch_syscall(
SyscallNumber::Brk => super::memory::sys_brk(arg1),
SyscallNumber::Mmap => super::mmap::sys_mmap(arg1, arg2, arg3 as u32, arg4 as u32, arg5 as i64, arg6),
SyscallNumber::Munmap => super::mmap::sys_munmap(arg1, arg2),
SyscallNumber::Mprotect => super::mmap::sys_mprotect(arg1, arg2, arg3 as u32),
}
}
6 changes: 6 additions & 0 deletions kernel/src/syscall/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ pub extern "C" fn rust_syscall_handler(frame: &mut SyscallFrame) {
let offset = args.5;
super::mmap::sys_mmap(addr, length, prot, flags, fd, offset)
}
Some(SyscallNumber::Mprotect) => {
let addr = args.0;
let length = args.1;
let prot = args.2 as u32;
super::mmap::sys_mprotect(addr, length, prot)
}
Some(SyscallNumber::Munmap) => {
let addr = args.0;
let length = args.1;
Expand Down
114 changes: 114 additions & 0 deletions kernel/src/syscall/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,120 @@ pub fn sys_mmap(addr: u64, length: u64, prot: u32, flags: u32, fd: i64, offset:
SyscallResult::Ok(start_addr)
}

/// Syscall 10: mprotect - Change protection of memory region
///
/// Arguments:
/// - addr: Start address (must be page-aligned)
/// - length: Size of region (will be rounded up to page size)
/// - prot: New protection flags (PROT_READ=1, PROT_WRITE=2, PROT_EXEC=4)
///
/// Returns: 0 on success, negative errno on error
pub fn sys_mprotect(addr: u64, length: u64, prot: u32) -> SyscallResult {
let new_prot = Protection::from_bits_truncate(prot);

log::info!(
"sys_mprotect: addr={:#x} length={:#x} prot={:?}",
addr, length, new_prot
);

// Validate addr is page-aligned
if !is_page_aligned(addr) {
log::warn!("sys_mprotect: address not page-aligned");
return SyscallResult::Err(ErrorCode::InvalidArgument as u64);
}

// Validate length
if length == 0 {
log::warn!("sys_mprotect: length is 0");
return SyscallResult::Err(ErrorCode::InvalidArgument as u64);
}

// Round length up to page size
let length = round_up_to_page(length);
let end_addr = match addr.checked_add(length) {
Some(a) => a,
None => {
log::warn!("sys_mprotect: addr + length would overflow");
return SyscallResult::Err(ErrorCode::InvalidArgument as u64);
}
};

// Get current thread and process
let current_thread_id = match crate::per_cpu::current_thread() {
Some(thread) => thread.id,
None => {
log::error!("sys_mprotect: No current thread in per-CPU data!");
return SyscallResult::Err(ErrorCode::NoSuchProcess as u64);
}
};

let mut manager_guard = crate::process::manager();
let manager = match *manager_guard {
Some(ref mut m) => m,
None => {
return SyscallResult::Err(ErrorCode::NoSuchProcess as u64);
}
};

let (_pid, process) = match manager.find_process_by_thread_mut(current_thread_id) {
Some(p) => p,
None => {
log::error!("sys_mprotect: No process found for thread_id={}", current_thread_id);
return SyscallResult::Err(ErrorCode::NoSuchProcess as u64);
}
};

// Find the VMA that contains this address range
// For simplicity, require exact match on start address
let vma_index = process.vmas.iter().position(|vma| {
vma.start.as_u64() == addr && vma.end.as_u64() >= end_addr
});

let vma_index = match vma_index {
Some(idx) => idx,
None => {
log::warn!("sys_mprotect: no VMA found containing {:#x}..{:#x}", addr, end_addr);
return SyscallResult::Err(ErrorCode::InvalidArgument as u64);
}
};

// Get the process page table
let page_table = match process.page_table.as_mut() {
Some(pt) => pt,
None => {
log::error!("sys_mprotect: No page table for process!");
return SyscallResult::Err(ErrorCode::OutOfMemory as u64);
}
};

// Update page table flags for each page in the range
let new_flags = prot_to_page_flags(new_prot);
let start_page = Page::<Size4KiB>::containing_address(VirtAddr::new(addr));
let end_page = Page::<Size4KiB>::containing_address(VirtAddr::new(end_addr - 1));

let mut pages_updated = 0u32;
for page in Page::range_inclusive(start_page, end_page) {
match page_table.update_page_flags(page, new_flags) {
Ok(()) => {
// Flush TLB for this page to ensure new flags take effect
tlb::flush(page.start_address());
pages_updated += 1;
}
Err(e) => {
log::warn!("sys_mprotect: update_page_flags failed for {:#x}: {}", page.start_address().as_u64(), e);
// Continue trying to update other pages
}
}
}

log::info!("sys_mprotect: Successfully updated {} pages", pages_updated);

// Update VMA protection flags
process.vmas[vma_index].prot = new_prot;

SyscallResult::Ok(0)
}

/// Syscall 11: munmap - Unmap memory from process address space
///
/// Arguments:
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/syscall/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum SyscallNumber {
GetTime = 4,
Fork = 5,
Mmap = 9, // Linux syscall number for mmap
Mprotect = 10, // Linux syscall number for mprotect
Munmap = 11, // Linux syscall number for munmap
Brk = 12, // Linux syscall number for brk (heap management)
GetPid = 39, // Linux syscall number for getpid
Expand All @@ -44,6 +45,7 @@ impl SyscallNumber {
4 => Some(Self::GetTime),
5 => Some(Self::Fork),
9 => Some(Self::Mmap),
10 => Some(Self::Mprotect),
11 => Some(Self::Munmap),
12 => Some(Self::Brk),
39 => Some(Self::GetPid),
Expand Down
33 changes: 31 additions & 2 deletions libs/libbreenix/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,34 @@ pub fn munmap(addr: *mut u8, length: usize) -> i32 {
result as i32
}

// Future syscalls (not yet implemented in kernel):
// pub fn mprotect(...) -> i64
/// Change protection of a memory region.
///
/// # Arguments
/// * `addr` - Start address (must be page-aligned)
/// * `length` - Size of region
/// * `prot` - New protection flags (PROT_READ, PROT_WRITE, PROT_EXEC)
///
/// # Returns
/// 0 on success, -1 on error
///
/// # Example
/// ```rust,ignore
/// use libbreenix::memory::{mmap, mprotect, PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANONYMOUS};
/// use core::ptr::null_mut;
///
/// // Map read-write
/// let ptr = mmap(null_mut(), 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
///
/// // Change to read-only
/// let result = mprotect(ptr, 4096, PROT_READ);
/// if result == 0 {
/// // Success - memory is now read-only
/// }
/// ```
#[inline]
pub fn mprotect(addr: *mut u8, length: usize, prot: i32) -> i32 {
let result = unsafe {
raw::syscall3(nr::MPROTECT, addr as u64, length as u64, prot as u64)
};
result as i32
}
1 change: 1 addition & 0 deletions libs/libbreenix/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod nr {
pub const GET_TIME: u64 = 4;
pub const FORK: u64 = 5;
pub const MMAP: u64 = 9; // Linux x86_64 mmap
pub const MPROTECT: u64 = 10; // Linux x86_64 mprotect
pub const MUNMAP: u64 = 11; // Linux x86_64 munmap
pub const BRK: u64 = 12;
pub const EXEC: u64 = 59; // Linux x86_64 execve
Expand Down
65 changes: 64 additions & 1 deletion userspace/tests/test_mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use core::ptr::null_mut;

// Import libbreenix functions
use libbreenix::io::println;
use libbreenix::memory::{mmap, munmap, PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANONYMOUS, MAP_FAILED};
use libbreenix::memory::{mmap, munmap, mprotect, PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANONYMOUS, MAP_FAILED};
use libbreenix::process::exit;

#[no_mangle]
Expand Down Expand Up @@ -67,6 +67,69 @@ pub extern "C" fn _start() -> ! {
exit(1);
}

// Test 3: mprotect
println("Test 3: mprotect...");

// Create a new mmap region for mprotect testing
let ptr2 = mmap(
null_mut(),
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0,
);

if ptr2 == MAP_FAILED {
println(" FAIL: mmap for mprotect test returned MAP_FAILED");
exit(1);
}
println(" mmap for mprotect test succeeded");

// Write a pattern while we have write permission
unsafe {
for i in 0..size {
*ptr2.add(i) = ((i * 2) & 0xFF) as u8;
}
}
println(" Write pattern succeeded");

// Change protection to read-only
let prot_result = mprotect(ptr2, size, PROT_READ);
if prot_result == 0 {
println(" mprotect to PROT_READ succeeded");
} else {
println(" mprotect failed: FAIL");
exit(1);
}

// Verify we can still read the data
let mut read_verified = true;
unsafe {
for i in 0..size {
if *ptr2.add(i) != ((i * 2) & 0xFF) as u8 {
read_verified = false;
break;
}
}
}

if read_verified {
println(" Read after mprotect: PASS");
} else {
println(" Read after mprotect: FAIL");
exit(1);
}

// Clean up
let result2 = munmap(ptr2, size);
if result2 == 0 {
println(" Cleanup munmap: PASS");
} else {
println(" Cleanup munmap: FAIL");
exit(1);
}

println("USERSPACE MMAP: ALL TESTS PASSED");
exit(0);
}
Expand Down
Loading