@@ -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]
157188fn 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+ }
0 commit comments