Skip to content

Commit 7a5cda7

Browse files
authored
Merge pull request #111 from Lind-Project/feat-mprotec-syscall
Add mprotect syscall
2 parents 27f0328 + 3b2184a commit 7a5cda7

File tree

7 files changed

+279
-8
lines changed

7 files changed

+279
-8
lines changed

src/RawPOSIX/src/interface/mem.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,80 @@ pub fn mmap_handler(
305305
useraddr as u32
306306
}
307307

308+
/// Handles the `mprotect_syscall`, interacting with the `vmmap` structure.
309+
///
310+
/// This function processes the `mprotect_syscall` by updating the `vmmap` entries and performing
311+
/// the necessary protection changes. The handling logic is as follows:
312+
/// 1. Validate protection flags - specifically disallow `PROT_EXEC`
313+
/// 2. Verify address alignment and round to page boundaries
314+
/// 3. Check if the memory region is mapped in vmmap
315+
/// 4. Perform the actual mprotect syscall
316+
/// 5. Update the protection flags in vmmap entries, splitting entries if necessary
317+
///
318+
/// # Arguments
319+
/// * `cageid` - Identifier of the cage that initiated the `mprotect` syscall
320+
/// * `addr` - Starting address of the region to change protection
321+
/// * `len` - Length of the region to change protection
322+
/// * `prot` - New protection flags (e.g., `PROT_READ`, `PROT_WRITE`)
323+
///
324+
/// # Returns
325+
/// * `i32` - Returns 0 on success, -1 on failure
326+
pub fn mprotect_handler(cageid: u64, addr: *mut u8, len: usize, prot: i32) -> i32 {
327+
let cage = cagetable_getref(cageid);
328+
329+
// PROT_EXEC is not allowed in WASM
330+
// TODO: Remove this panic when we support PROT_EXEC for real user code
331+
if prot & PROT_EXEC > 0 {
332+
// Log the attempt through syscall_error's verbose logging
333+
let _ = syscall_error(Errno::EINVAL, "mprotect", "PROT_EXEC attempt detected - this will panic in development");
334+
// Panic during development for early detection of unsupported operations
335+
panic!("PROT_EXEC is not currently supported in WASM");
336+
}
337+
338+
// Validate length
339+
if len == 0 {
340+
return syscall_error(Errno::EINVAL, "mprotect", "length cannot be zero");
341+
}
342+
343+
// check if the provided address is multiple of pages
344+
let rounded_addr = round_up_page(addr as u64);
345+
if rounded_addr != addr as u64 {
346+
return syscall_error(Errno::EINVAL, "mprotect", "address is not aligned");
347+
}
348+
349+
// round up length to be multiple of pages
350+
let rounded_length = round_up_page(len as u64);
351+
352+
let mut vmmap = cage.vmmap.write();
353+
354+
// Convert to page numbers for vmmap checking
355+
let start_page = (addr as u32) >> PAGESHIFT;
356+
let npages = (rounded_length >> PAGESHIFT) as u32;
357+
358+
// Check if the region is mapped
359+
if !vmmap.check_existing_mapping(start_page, npages, 0) {
360+
return syscall_error(Errno::ENOMEM, "mprotect", "Address range not mapped");
361+
}
362+
363+
// Get system address for the actual mprotect call
364+
let sysaddr = vmmap.user_to_sys(addr as u32);
365+
366+
drop(vmmap);
367+
368+
// Perform mprotect through cage implementation
369+
let result = cage.mprotect_syscall(sysaddr as *mut u8, rounded_length as usize, prot);
370+
371+
if result < 0 {
372+
return result;
373+
}
374+
375+
// Update vmmap entries with new protection
376+
let mut vmmap = cage.vmmap.write();
377+
vmmap.update_protections(start_page, npages, prot);
378+
379+
0
380+
}
381+
308382
/// Handles the `sbrk_syscall`, interacting with the `vmmap` structure.
309383
///
310384
/// This function processes the `sbrk_syscall` by updating the `vmmap` entries and managing

src/RawPOSIX/src/safeposix/dispatcher.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const WAIT_SYSCALL: i32 = 172;
107107
const WAITPID_SYSCALL: i32 = 173;
108108
const BRK_SYSCALL: i32 = 175;
109109
const SBRK_SYSCALL: i32 = 176;
110+
const MPROTECT_SYSCALL: i32 = 177;
110111

111112
const NANOSLEEP_TIME64_SYSCALL: i32 = 181;
112113
const CLOCK_GETTIME_SYSCALL: i32 = 191;
@@ -237,6 +238,15 @@ pub fn lind_syscall_api(
237238
interface::mmap_handler(cageid, addr, len, prot, flags, fd, off) as i32
238239
}
239240

241+
MPROTECT_SYSCALL => {
242+
let cage = interface::cagetable_getref(cageid);
243+
let addr = arg1 as *mut u8;
244+
let len = arg2 as usize;
245+
let prot = arg3 as i32;
246+
247+
interface::mprotect_handler(cageid, addr, len, prot)
248+
}
249+
240250
PREAD_SYSCALL => {
241251
let fd = arg1 as i32;
242252
let count = arg3 as usize;

src/RawPOSIX/src/safeposix/syscalls/fs_calls.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,23 @@ impl Cage {
10401040
ret
10411041
}
10421042

1043+
//------------------------------------MPROTECT SYSCALL------------------------------------
1044+
/*
1045+
* mprotect() changes protection for memory pages
1046+
* Returns 0 on success, -1 on failure
1047+
* Manual page: https://man7.org/linux/man-pages/man2/mprotect.2.html
1048+
*/
1049+
pub fn mprotect_syscall(&self, addr: *mut u8, len: usize, prot: i32) -> i32 {
1050+
let ret = unsafe {
1051+
libc::mprotect(addr as *mut c_void, len, prot)
1052+
};
1053+
if ret < 0 {
1054+
let errno = get_errno();
1055+
return handle_errno(errno, "mprotect");
1056+
}
1057+
ret
1058+
}
1059+
10431060
//------------------------------------FLOCK SYSCALL------------------------------------
10441061
/*
10451062
* Get the kernel fd with provided virtual fd first

src/RawPOSIX/src/safeposix/vmmap.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,19 @@ pub trait VmmapOps {
226226
pages_per_map: u32,
227227
hint: u32,
228228
) -> Option<Interval<u32>>;
229+
230+
/// Updates protection flags for a range of pages, handling mprotect operations
231+
///
232+
/// Arguments:
233+
/// - start_page: Starting page number to update
234+
/// - npages: Number of pages to update
235+
/// - new_prot: New protection flags to apply
236+
///
237+
/// This function:
238+
/// - Updates protection flags for the specified range
239+
/// - Handles splitting of regions if necessary
240+
/// - Maintains vmmap consistency
241+
fn update_protections(&mut self, start_page: u32, npages: u32, new_prot: i32);
229242
}
230243

231244
/// Represents a virtual memory map that manages memory regions and their attributes
@@ -951,4 +964,58 @@ impl VmmapOps for Vmmap {
951964

952965
None
953966
}
967+
968+
/// Updates protection flags for a range of pages, handling mprotect operations
969+
///
970+
/// Arguments:
971+
/// - start_page: Starting page number to update
972+
/// - npages: Number of pages to update
973+
/// - new_prot: New protection flags to apply
974+
///
975+
/// This function:
976+
/// - Updates protection flags for the specified range
977+
/// - Handles splitting of regions if necessary
978+
/// - Maintains vmmap consistency
979+
fn update_protections(&mut self, start_page: u32, npages: u32, new_prot: i32) {
980+
// Calculate page range
981+
let region_end_page = start_page + npages;
982+
let region_interval = ie(start_page, region_end_page);
983+
984+
// Store intervals that need to be inserted after iteration
985+
let mut to_insert = Vec::new();
986+
987+
// Iterate over overlapping entries
988+
for (interval, entry) in self.entries.overlapping_mut(region_interval) {
989+
let entry_start = interval.start();
990+
let entry_end = interval.end();
991+
992+
// Case 1: Entry starts before region
993+
if entry_start < start_page {
994+
// Split entry and keep original protection for first part
995+
let mut first_part = entry.clone();
996+
first_part.page_num = entry_start;
997+
first_part.npages = start_page - entry_start;
998+
to_insert.push((ie(entry_start, start_page), first_part));
999+
}
1000+
1001+
// Case 2: Entry extends beyond region
1002+
if entry_end > region_end_page {
1003+
// Split entry and keep original protection for last part
1004+
let mut last_part = entry.clone();
1005+
last_part.page_num = region_end_page;
1006+
last_part.npages = entry_end - region_end_page;
1007+
to_insert.push((ie(region_end_page, entry_end), last_part));
1008+
}
1009+
1010+
// Update protection for the overlapping part
1011+
entry.prot = new_prot;
1012+
entry.page_num = start_page.max(entry_start);
1013+
entry.npages = region_end_page.min(entry_end) - entry.page_num;
1014+
}
1015+
1016+
// Insert the split regions
1017+
for (interval, entry) in to_insert {
1018+
let _ = self.entries.insert_overwrite(interval, entry);
1019+
}
1020+
}
9541021
}

src/RawPOSIX/src/tests/fs_tests.rs

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub mod fs_tests {
66

77
use fdtables::{translate_virtual_fd, FDTABLE};
88
use sysdefs::constants::err_const::get_errno;
9-
use sysdefs::constants::fs_const::{SHMMAX, S_IRWXA};
9+
use sysdefs::constants::fs_const::{SHMMAX, S_IRWXA, PAGESIZE};
1010
use sysdefs::constants::sys_const::{DEFAULT_GID, DEFAULT_UID};
1111
use sysdefs::data::fs_struct::{FSData, ShmidsStruct, StatData};
1212

@@ -772,6 +772,107 @@ pub mod fs_tests {
772772
lindrustfinalize();
773773
}
774774

775+
#[test]
776+
pub fn ut_lind_fs_mprotect_readwrite_test() {
777+
// Acquire test lock and initialize environment
778+
let _thelock = setup::lock_and_init();
779+
let cage = interface::cagetable_getref(1);
780+
781+
782+
// Create an anonymous memory mapping with read-only permissions
783+
// This simulates the C program's mmap() call to allocate a read-only page
784+
let readonlydata = cage.mmap_syscall(
785+
std::ptr::null_mut(), // Let kernel choose address
786+
PAGESIZE as usize, // Map one page
787+
PROT_READ, // Initially read-only
788+
(MAP_ANONYMOUS | MAP_PRIVATE) as i32, // Private anonymous mapping
789+
-1, // No file descriptor for anonymous mapping
790+
0 // Offset is ignored for anonymous mappings
791+
);
792+
assert!(readonlydata >= 0, "mmap should succeed");
793+
794+
// Change the protection to allow writing
795+
// This simulates the C program's mprotect() call to make the page writable
796+
let result = cage.mprotect_syscall(
797+
readonlydata as *mut u8,
798+
PAGESIZE as usize,
799+
PROT_READ | PROT_WRITE // Add write permission
800+
);
801+
assert_eq!(result, 0, "mprotect should succeed");
802+
803+
// Test string to write to the now-writable memory
804+
let text = b"Mprotect write test text\0";
805+
unsafe {
806+
// Copy test string into the mapped memory
807+
// This simulates the C program's memcpy() call
808+
let result = libc::memcpy(
809+
readonlydata as *mut libc::c_void,
810+
text.as_ptr() as *const libc::c_void,
811+
text.len()
812+
);
813+
814+
// Verify that the write operation succeeded by comparing memory contents
815+
let written = std::slice::from_raw_parts(readonlydata as *const u8, text.len());
816+
assert_eq!(written, text, "Written data should match test string");
817+
818+
// Print the written text to verify it's readable
819+
// This simulates the C program's puts() call
820+
}
821+
822+
// Clean up by unmapping the memory
823+
// This simulates the C program's munmap() call
824+
let result = cage.munmap_syscall(readonlydata as *mut u8, PAGESIZE as usize);
825+
assert_eq!(result, 0, "munmap should succeed");
826+
827+
// Clean up and exit
828+
assert_eq!(cage.exit_syscall(libc::EXIT_SUCCESS), libc::EXIT_SUCCESS);
829+
lindrustfinalize();
830+
}
831+
832+
#[test]
833+
pub fn ut_lind_fs_mprotect_unmapped_addr() {
834+
let _thelock = setup::lock_and_init();
835+
let cage = interface::cagetable_getref(1);
836+
837+
// Try to protect an unmapped address
838+
let unmapped_addr = 0x1000 as *mut u8; // Some arbitrary address
839+
let result = cage.mprotect_syscall(unmapped_addr, 4096, PROT_READ);
840+
assert_eq!(result, -(Errno::ENOMEM as i32), "mprotect should fail with ENOMEM for unmapped address");
841+
842+
assert_eq!(cage.exit_syscall(libc::EXIT_SUCCESS), libc::EXIT_SUCCESS);
843+
lindrustfinalize();
844+
}
845+
846+
#[test]
847+
pub fn ut_lind_fs_mprotect_split_region() {
848+
let _thelock = setup::lock_and_init();
849+
let cage = interface::cagetable_getref(1);
850+
851+
// Map 4 pages with anonymous mapping
852+
let addr = cage.mmap_syscall(
853+
std::ptr::null_mut(),
854+
PAGESIZE as usize * 4,
855+
PROT_READ | PROT_WRITE,
856+
(MAP_PRIVATE | MAP_ANONYMOUS) as i32,
857+
-1,
858+
0
859+
);
860+
861+
assert!(addr >= 0, "mmap failed with error: {}", addr);
862+
863+
// Change protection for middle two pages
864+
let middle_addr = (addr as usize + PAGESIZE as usize) as *mut u8;
865+
let result = cage.mprotect_syscall(
866+
middle_addr,
867+
PAGESIZE as usize * 2,
868+
PROT_READ
869+
);
870+
assert_eq!(result, 0, "mprotect failed");
871+
872+
// Clean up
873+
assert_eq!(cage.munmap_syscall(addr as *mut u8, PAGESIZE as usize * 4), 0);
874+
}
875+
775876
#[test]
776877
pub fn ut_lind_fs_munmap_zerolen() {
777878
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently,
@@ -2895,7 +2996,7 @@ pub mod fs_tests {
28952996
assert_eq!(cage.fstat_syscall(fd1, &mut uselessstatdata), 0);
28962997
assert_eq!(cage.fstat_syscall(fd2, &mut uselessstatdata), 0);
28972998

2898-
assert_eq!(cage.exec_syscall(2), 0);
2999+
assert_eq!(cage.exec_syscall(), 0);
28993000

29003001
let execcage = interface::cagetable_getref(2);
29013002
assert_eq!(
@@ -3101,7 +3202,6 @@ pub mod fs_tests {
31013202
// Now try to create a subdirectory under the parent directory
31023203
let c_subdir_path = std::ffi::CString::new(subdir_path).unwrap();
31033204
let result = unsafe { libc::mkdir(c_subdir_path.as_ptr(), invalid_mode) };
3104-
println!("mkdir returned for subdir: {}", result);
31053205

31063206
// Check if mkdir failed
31073207
if result != 0 {

src/RawPOSIX/src/tests/sys_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub mod sys_tests {
128128
let _thelock = setup::lock_and_init();
129129
let cage1 = interface::cagetable_getref(1);
130130
// Exec a new child
131-
assert_eq!(cage1.exec_syscall(2), 0);
131+
assert_eq!(cage1.exec_syscall(), 0);
132132
// Assert that the fork was correct
133133
let child_cage = interface::cagetable_getref(2);
134134
assert_eq!(child_cage.getuid_syscall(), -1);
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#include <unistd.h>
22
#include <sysdep-cancel.h>
3+
#include <stdint.h>
4+
#include <fcntl.h>
5+
#include <syscall-template.h>
36

47
int
5-
__GI___mprotect (int fd, const void *buf, size_t nbytes)
8+
__GI___mprotect (void *addr, size_t len, int prot)
69
{
7-
8-
return 0;
10+
return MAKE_SYSCALL(177, "syscall|mprotect", (uint64_t)(uintptr_t) addr, (uint64_t) len, (uint64_t) prot, NOTUSED, NOTUSED, NOTUSED);
911
}
1012

11-
weak_alias(__GI___mprotect, __mprotect)
13+
weak_alias(__GI___mprotect, __mprotect)
14+
strong_alias(__GI___mprotect, mprotect)

0 commit comments

Comments
 (0)