Skip to content

Commit 9776f41

Browse files
committed
syscalls: mprotect: implement
Implement the `mprotect` syscall. Including a bunch of tests for ensuring proper VMA functionality regarding splitting and merging via `mprotect`.
1 parent 6d5bfac commit 9776f41

File tree

6 files changed

+290
-6
lines changed

6 files changed

+290
-6
lines changed

etc/syscalls_linux_aarch64.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@
226226
| 0xdf (223) | fadvise64_64 | (int fd, loff_t offset, loff_t len, int advice) | __arm64_sys_fadvise64_64 | false |
227227
| 0xe0 (224) | swapon | (const char *specialfile, int swap_flags) | __arm64_sys_swapon | false |
228228
| 0xe1 (225) | swapoff | (const char *specialfile) | __arm64_sys_swapoff | false |
229-
| 0xe2 (226) | mprotect | (unsigned long start, size_t len, unsigned long prot) | __arm64_sys_mprotect | false |
229+
| 0xe2 (226) | mprotect | (unsigned long start, size_t len, unsigned long prot) | __arm64_sys_mprotect | true |
230230
| 0xe3 (227) | msync | (unsigned long start, size_t len, int flags) | __arm64_sys_msync | false |
231231
| 0xe4 (228) | mlock | (unsigned long start, size_t len) | __arm64_sys_mlock | false |
232232
| 0xe5 (229) | munlock | (unsigned long start, size_t len) | __arm64_sys_munlock | false |

libkernel/src/error/syscall_error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn kern_err_to_syscall(err: KernelError) -> isize {
4949
KernelError::NotATty => ENOTTY,
5050
KernelError::SeekPipe => ESPIPE,
5151
KernelError::NotSupported => ENOSYS,
52+
KernelError::NoMemory => ENOMEM,
5253
_ => todo!(),
5354
}
5455
}

libkernel/src/memory/proc_vm/memory_map/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,67 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
165165
self.unmap_region(range.align_to_page_boundary(), None)
166166
}
167167

168+
pub fn mprotect(
169+
&mut self,
170+
protect_region: VirtMemoryRegion,
171+
new_perms: VMAPermissions,
172+
) -> Result<()> {
173+
if !protect_region.is_page_aligned() {
174+
return Err(KernelError::InvalidValue);
175+
}
176+
177+
if protect_region.size() == 0 {
178+
return Err(KernelError::InvalidValue);
179+
}
180+
181+
let affected_vma_addr = self
182+
.find_vma(protect_region.start_address())
183+
.map(|x| x.region.start_address())
184+
.ok_or(KernelError::NoMemory)?;
185+
186+
let affected_vma = self
187+
.vmas
188+
.remove(&affected_vma_addr)
189+
.expect("Should have the same key as the start address");
190+
191+
// Easy case, the entire VMA is changing.
192+
if affected_vma.region == protect_region {
193+
let old_vma = affected_vma.clone();
194+
let mut new_vma = old_vma.clone();
195+
new_vma.permissions = new_perms;
196+
197+
self.insert_and_merge(new_vma.clone());
198+
self.address_space
199+
.protect_range(protect_region, new_perms.into())?;
200+
201+
return Ok(());
202+
}
203+
204+
// Next case, a sub-region of a VMA is changing, requring a split.
205+
if affected_vma.region.contains(protect_region) {
206+
let (left, right) = affected_vma.region.punch_hole(protect_region);
207+
let mut new_vma = affected_vma.clone().shrink_to(protect_region);
208+
new_vma.permissions = new_perms;
209+
210+
if let Some(left) = left {
211+
self.insert_and_merge(affected_vma.shrink_to(left));
212+
}
213+
214+
self.address_space
215+
.protect_range(protect_region, new_perms.into())?;
216+
self.insert_and_merge(new_vma);
217+
218+
if let Some(right) = right {
219+
self.insert_and_merge(affected_vma.shrink_to(right));
220+
}
221+
222+
return Ok(());
223+
}
224+
225+
// TODO: protecting over contiguous VMAreas.
226+
return Err(KernelError::NoMemory);
227+
}
228+
168229
/// Checks if a given virtual memory region is completely free.
169230
fn is_region_free(&self, region: VirtMemoryRegion) -> bool {
170231
// Find the VMA that might overlap with the start of our desired region.

libkernel/src/memory/proc_vm/memory_map/tests.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,37 @@ fn assert_vma_exists(pvm: &MemoryMap<MockAddressSpace>, start: usize, size: usiz
153153
assert_eq!(vma.region.size(), size, "VMA size mismatch");
154154
}
155155

156+
fn assert_vma_perms(pvm: &MemoryMap<MockAddressSpace>, start: usize, perms: VMAPermissions) {
157+
let vma = pvm
158+
.find_vma(VA::from_value(start))
159+
.expect("VMA not found for permission check");
160+
assert_eq!(
161+
vma.permissions(),
162+
perms,
163+
"VMA permissions mismatch at {:#x}",
164+
start
165+
);
166+
}
167+
168+
fn assert_ops_log_protect(
169+
pvm: &MemoryMap<MockAddressSpace>,
170+
expected_region: VirtMemoryRegion,
171+
expected_perms: VMAPermissions,
172+
) {
173+
let log = pvm.address_space.ops_log.lock().unwrap();
174+
let found = log.iter().any(|op| match op {
175+
MockPageTableOp::ProtectRange { region, perms } => {
176+
*region == expected_region && *perms == expected_perms.into()
177+
}
178+
_ => false,
179+
});
180+
assert!(
181+
found,
182+
"Did not find ProtectRange op for {:?} with {:?}",
183+
expected_region, expected_perms
184+
);
185+
}
186+
156187
#[test]
157188
fn test_mmap_any_empty() {
158189
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
@@ -685,3 +716,179 @@ fn test_munmap_over_multiple_vmas() {
685716
]
686717
);
687718
}
719+
720+
#[test]
721+
fn mprotect_full_vma() {
722+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
723+
let start = MMAP_BASE - 4 * PAGE_SIZE;
724+
let size = 4 * PAGE_SIZE;
725+
726+
pvm.insert_and_merge(create_anon_vma(start, size, VMAPermissions::rw()));
727+
728+
// Protect entire region to RO
729+
let region = VirtMemoryRegion::new(VA::from_value(start), size);
730+
pvm.mprotect(region, VMAPermissions::ro()).unwrap();
731+
732+
assert_eq!(pvm.vmas.len(), 1); // Should still be 1 VMA
733+
assert_vma_exists(&pvm, start, size);
734+
assert_vma_perms(&pvm, start, VMAPermissions::ro());
735+
assert_ops_log_protect(&pvm, region, VMAPermissions::ro());
736+
}
737+
738+
#[test]
739+
fn test_mprotect_split_middle() {
740+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
741+
let start = 0x10000;
742+
let size = 3 * PAGE_SIZE; // [0x10000, 0x11000, 0x12000]
743+
744+
pvm.insert_and_merge(create_anon_vma(start, size, VMAPermissions::rw()));
745+
746+
let protect_start = start + PAGE_SIZE;
747+
let protect_len = PAGE_SIZE;
748+
let region = VirtMemoryRegion::new(VA::from_value(protect_start), protect_len);
749+
750+
pvm.mprotect(region, VMAPermissions::ro()).unwrap();
751+
752+
// Should now be 3 VMAs: RW - RO - RW
753+
assert_eq!(pvm.vmas.len(), 3);
754+
755+
// Left
756+
assert_vma_exists(&pvm, start, PAGE_SIZE);
757+
assert_vma_perms(&pvm, start, VMAPermissions::rw());
758+
759+
// Middle
760+
assert_vma_exists(&pvm, protect_start, PAGE_SIZE);
761+
assert_vma_perms(&pvm, protect_start, VMAPermissions::ro());
762+
763+
// Right
764+
assert_vma_exists(&pvm, start + 2 * PAGE_SIZE, PAGE_SIZE);
765+
assert_vma_perms(&pvm, start + 2 * PAGE_SIZE, VMAPermissions::rw());
766+
767+
assert_ops_log_protect(&pvm, region, VMAPermissions::ro());
768+
}
769+
770+
#[test]
771+
fn test_mprotect_split_start() {
772+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
773+
let start = 0x20000;
774+
let size = 2 * PAGE_SIZE;
775+
776+
pvm.insert_and_merge(create_anon_vma(start, size, VMAPermissions::rw()));
777+
778+
let region = VirtMemoryRegion::new(VA::from_value(start), PAGE_SIZE);
779+
pvm.mprotect(region, VMAPermissions::ro()).unwrap();
780+
781+
// Should be 2 VMAs: RO - RW
782+
assert_eq!(pvm.vmas.len(), 2);
783+
784+
assert_vma_exists(&pvm, start, PAGE_SIZE);
785+
assert_vma_perms(&pvm, start, VMAPermissions::ro());
786+
787+
assert_vma_exists(&pvm, start + PAGE_SIZE, PAGE_SIZE);
788+
assert_vma_perms(&pvm, start + PAGE_SIZE, VMAPermissions::rw());
789+
790+
assert_ops_log_protect(&pvm, region, VMAPermissions::ro());
791+
}
792+
793+
#[test]
794+
fn test_mprotect_split_end() {
795+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
796+
let start = 0x30000;
797+
let size = 2 * PAGE_SIZE;
798+
799+
pvm.insert_and_merge(create_anon_vma(start, size, VMAPermissions::rw()));
800+
801+
let region = VirtMemoryRegion::new(VA::from_value(start + PAGE_SIZE), PAGE_SIZE);
802+
pvm.mprotect(region, VMAPermissions::ro()).unwrap();
803+
804+
// Should be 2 VMAs: RW - RO
805+
assert_eq!(pvm.vmas.len(), 2);
806+
807+
assert_vma_exists(&pvm, start, PAGE_SIZE);
808+
assert_vma_perms(&pvm, start, VMAPermissions::rw());
809+
810+
assert_vma_exists(&pvm, start + PAGE_SIZE, PAGE_SIZE);
811+
assert_vma_perms(&pvm, start + PAGE_SIZE, VMAPermissions::ro());
812+
813+
assert_ops_log_protect(&pvm, region, VMAPermissions::ro());
814+
}
815+
816+
#[test]
817+
fn test_mprotect_file_backed_split() {
818+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
819+
let start = 0x40000;
820+
let size = 3 * PAGE_SIZE;
821+
let file_offset = 0x1000;
822+
let inode = Arc::new(DummyTestInode);
823+
824+
// VMA: [0x40000 - 0x43000), File Offset: 0x1000
825+
pvm.insert_and_merge(create_file_vma(
826+
start,
827+
size,
828+
VMAPermissions::rw(),
829+
file_offset,
830+
inode.clone(),
831+
));
832+
833+
// Protect Middle Page [0x41000 - 0x42000)
834+
let region = VirtMemoryRegion::new(VA::from_value(start + PAGE_SIZE), PAGE_SIZE);
835+
pvm.mprotect(region, VMAPermissions::ro()).unwrap();
836+
837+
// Left VMA: 0x40000, Len 0x1000, Offset 0x1000
838+
let left = pvm.find_vma(VA::from_value(start)).unwrap();
839+
if let VMAreaKind::File(f) = &left.kind {
840+
assert_eq!(f.offset, 0x1000);
841+
assert_eq!(f.len, PAGE_SIZE as u64);
842+
} else {
843+
panic!("Left VMA lost file backing");
844+
}
845+
846+
// Middle VMA: 0x41000, Len 0x1000, Offset 0x2000 (0x1000 + 0x1000)
847+
let middle = pvm.find_vma(VA::from_value(start + PAGE_SIZE)).unwrap();
848+
assert_eq!(middle.permissions(), VMAPermissions::ro());
849+
if let VMAreaKind::File(f) = &middle.kind {
850+
assert_eq!(f.offset, 0x2000);
851+
assert_eq!(f.len, PAGE_SIZE as u64);
852+
} else {
853+
panic!("Middle VMA lost file backing");
854+
}
855+
856+
// Right VMA: 0x42000, Len 0x1000, Offset 0x3000 (0x1000 + 0x2000)
857+
let right = pvm.find_vma(VA::from_value(start + 2 * PAGE_SIZE)).unwrap();
858+
if let VMAreaKind::File(f) = &right.kind {
859+
assert_eq!(f.offset, 0x3000);
860+
assert_eq!(f.len, PAGE_SIZE as u64);
861+
} else {
862+
panic!("Right VMA lost file backing");
863+
}
864+
865+
assert_ops_log_protect(&pvm, region, VMAPermissions::ro());
866+
}
867+
868+
#[test]
869+
fn test_mprotect_merge_restoration() {
870+
// Ensures that if we split permissions, then restore them, the VMAs
871+
// merge back together.
872+
let mut pvm: MemoryMap<MockAddressSpace> = MemoryMap::new().unwrap();
873+
let start = 0x50000;
874+
let size = 2 * PAGE_SIZE;
875+
876+
pvm.insert_and_merge(create_anon_vma(start, size, VMAPermissions::rw()));
877+
878+
// Split.
879+
let region1 = VirtMemoryRegion::new(VA::from_value(start), PAGE_SIZE);
880+
pvm.mprotect(region1, VMAPermissions::ro()).unwrap();
881+
assert_eq!(pvm.vmas.len(), 2);
882+
883+
// Restore back to RW
884+
pvm.mprotect(region1, VMAPermissions::rw()).unwrap();
885+
886+
// 3. Should merge back to 1 VMA
887+
assert_eq!(
888+
pvm.vmas.len(),
889+
1,
890+
"VMAs failed to merge back after permissions restored"
891+
);
892+
assert_vma_exists(&pvm, start, size);
893+
assert_vma_perms(&pvm, start, VMAPermissions::rw());
894+
}

src/arch/arm64/exceptions/syscall.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::kernel::power::sys_reboot;
22
use crate::kernel::rand::sys_getrandom;
3+
use crate::memory::mmap::sys_mprotect;
34
use crate::{
45
arch::{Arch, ArchImpl},
56
clock::{gettime::sys_clock_gettime, timeofday::sys_gettimeofday},
@@ -248,6 +249,7 @@ pub async fn handle_syscall() {
248249
.await
249250
}
250251
0xde => sys_mmap(arg1, arg2, arg3, arg4, arg5.into(), arg6).await,
252+
0xe2 => sys_mprotect(VA::from_value(arg1 as _), arg2 as _, arg3 as _),
251253
0x104 => {
252254
sys_wait4(
253255
arg1.cast_signed() as _,

src/memory/mmap.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ const MAP_ANONYMOUS: u64 = 0x0020;
2929
/// MAP_FIXED{,_NOREPLACE}.
3030
static MMAP_MIN_ADDR: AtomicUsize = AtomicUsize::new(0x1000);
3131

32+
fn prot_to_perms(prot: u64) -> VMAPermissions {
33+
VMAPermissions {
34+
read: (prot & PROT_READ) != 0,
35+
write: (prot & PROT_WRITE) != 0,
36+
execute: (prot & PROT_EXEC) != 0,
37+
}
38+
}
39+
3240
/// Handles the `mmap` system call.
3341
///
3442
/// # Arguments
@@ -74,11 +82,7 @@ pub async fn sys_mmap(
7482
return Err(KernelError::InvalidValue);
7583
}
7684

77-
let permissions = VMAPermissions {
78-
read: (prot & PROT_READ) != 0,
79-
write: (prot & PROT_WRITE) != 0,
80-
execute: (prot & PROT_EXEC) != 0,
81-
};
85+
let permissions = prot_to_perms(prot);
8286

8387
let requested_len = len as usize;
8488

@@ -137,3 +141,12 @@ pub async fn sys_munmap(addr: VA, len: usize) -> Result<usize> {
137141

138142
Ok(0)
139143
}
144+
145+
pub fn sys_mprotect(addr: VA, len: usize, prot: u64) -> Result<usize> {
146+
let perms = prot_to_perms(prot);
147+
let region = VirtMemoryRegion::new(addr, len);
148+
149+
current_task().vm.lock_save_irq().mm_mut().mprotect(region, perms)?;
150+
151+
Ok(0)
152+
}

0 commit comments

Comments
 (0)