33// Use of this source code is governed by an MIT-style license that can be found
44// in the LICENSE file or at https://opensource.org/licenses/MIT.
55use std:: collections:: VecDeque ;
6- use std:: fs:: File ;
7- use std:: io:: { Read as _, Seek as _, SeekFrom } ;
86use std:: path:: PathBuf ;
97
108/// A memory mapping in a running process' virtual address space.
@@ -18,6 +16,7 @@ pub struct MappedRegion {
1816 /// Permissions associated with this mapping.
1917 pub permissions : Permissions ,
2018 /// If this mapping is backed by a file, this field will contain said file's inode.
19+ /// Only valid on Linux.
2120 pub inode : Option < u64 > ,
2221 /// If this mapping is backed by a file, this field will contain the path to said file.
2322 /// It can also contain a pseudo-path that indicates the type of mapping, otherwise.
@@ -71,7 +70,8 @@ pub use windows::*;
7170#[ cfg( target_os = "linux" ) ]
7271mod linux {
7372 use super :: * ;
74- use std:: io:: BufRead ;
73+ use std:: fs:: File ;
74+ use std:: io:: { BufRead , Read as _, Seek as _, SeekFrom } ;
7575
7676 /// An error that occurred during parsing of a process' memory mappings file.
7777 #[ derive( Debug ) ]
@@ -332,7 +332,7 @@ mod windows {
332332 use std:: mem:: MaybeUninit ;
333333 use windows_sys:: Win32 :: Foundation :: { CloseHandle , HANDLE } ;
334334 use windows_sys:: Win32 :: System :: Diagnostics :: Debug :: ReadProcessMemory ;
335- use windows_sys:: Win32 :: System :: Memory :: { MEMORY_BASIC_INFORMATION , VirtualQueryEx } ;
335+ use windows_sys:: Win32 :: System :: Memory :: { MEM_FREE , MEMORY_BASIC_INFORMATION , VirtualQueryEx } ;
336336 use windows_sys:: Win32 :: System :: Threading :: { OpenProcess , PROCESS_ALL_ACCESS } ;
337337
338338 struct ProcessHandle ( HANDLE ) ;
@@ -355,33 +355,9 @@ mod windows {
355355 }
356356 }
357357
358- impl MappedRegion {
359- fn query ( handle : & ProcessHandle , address_start : u64 ) -> std:: io:: Result < Self > {
360- let mut mem: std:: mem:: MaybeUninit < MEMORY_BASIC_INFORMATION > = MaybeUninit :: zeroed ( ) ;
361- if unsafe {
362- VirtualQueryEx (
363- handle. 0 ,
364- address_start as * const c_void ,
365- mem. as_mut_ptr ( ) ,
366- std:: mem:: size_of :: < MEMORY_BASIC_INFORMATION > ( ) ,
367- ) == 0
368- } {
369- return Err ( std:: io:: Error :: last_os_error ( ) ) ;
370- }
371-
372- let mem = unsafe { mem. assume_init ( ) } ;
373- // let permissions = ...;
374- Ok ( Self {
375- address_start,
376- size : mem. RegionSize as u64 ,
377- ..Default :: default ( )
378- } )
379- }
380- }
381-
382358 pub struct MappedRegionIter {
383359 process : ProcessHandle ,
384- cur_addr : u64 ,
360+ cur_addr : * mut c_void ,
385361 }
386362
387363 impl MappedRegionIter {
@@ -390,25 +366,106 @@ mod windows {
390366 let process = ProcessHandle :: open ( pid) ?;
391367 Ok ( Self {
392368 process,
393- cur_addr : 0 ,
369+ cur_addr : std :: ptr :: null_mut ( ) ,
394370 } )
395371 }
396372 }
397373
374+ fn parse_permissions ( mem_info : & MEMORY_BASIC_INFORMATION ) -> Permissions {
375+ use windows_sys:: Win32 :: System :: Memory :: * ;
376+
377+ // Info obtained from https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
378+ // and https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
379+
380+ const EXECUTE_FLAGS : u32 =
381+ PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY ;
382+ const WRITE_FLAGS : u32 =
383+ PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READWRITE | PAGE_WRITECOPY ;
384+
385+ // Use the protection information of the actual committed memory if available,
386+ // otherwise the flags with which the memory mapping was created.
387+ let flags = if mem_info. State == MEM_COMMIT {
388+ mem_info. Protect
389+ } else {
390+ mem_info. AllocationProtect
391+ } ;
392+
393+ let execute = flags & EXECUTE_FLAGS != 0 ;
394+ let write = flags & WRITE_FLAGS != 0 ;
395+ // The only flag which disables write access is PAGE_NOACCESS
396+ let read = flags & PAGE_NOACCESS == 0 ;
397+ let private = mem_info. Type == MEM_PRIVATE ;
398+ Permissions {
399+ read,
400+ write,
401+ execute,
402+ private,
403+ shared : false ,
404+ }
405+ }
406+
407+ fn get_mapped_filename ( addr : * const c_void , process : & ProcessHandle ) -> Option < PathBuf > {
408+ use windows_sys:: Win32 :: Foundation :: MAX_PATH ;
409+ use windows_sys:: Win32 :: System :: ProcessStatus :: GetMappedFileNameW ;
410+
411+ use std:: ffi:: OsString ;
412+ use std:: os:: windows:: ffi:: OsStringExt ;
413+
414+ let mut buf = [ 0u16 ; ( MAX_PATH + 1 ) as usize ] ;
415+ let len = unsafe { GetMappedFileNameW ( process. 0 , addr, buf. as_mut_ptr ( ) , buf. len ( ) as u32 ) }
416+ as usize ;
417+ // A return value of 0 indicates an error, and nSize indicates that the path was
418+ // truncated. We treat both cases as errors and just return None.
419+ if len == 0 || len == buf. len ( ) {
420+ return None ;
421+ }
422+ Some ( PathBuf :: from ( OsString :: from_wide ( & buf[ ..len] ) ) )
423+ }
424+
398425 impl Iterator for MappedRegionIter {
399- type Item = Result < MappedRegion , crate :: session :: Error > ;
426+ type Item = std :: io :: Result < MappedRegion > ;
400427 fn next ( & mut self ) -> Option < Self :: Item > {
401- let result = MappedRegion :: query ( & self . process , self . cur_addr ) ;
402- match & result {
403- Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: InvalidInput => {
404- // Invalid input is returned when the address falls outside of the accessible
405- // memory bounds of the process, so we can stop iteration here
406- return None ;
428+ loop {
429+ let mem_info: MEMORY_BASIC_INFORMATION = {
430+ let mut mem: std:: mem:: MaybeUninit < MEMORY_BASIC_INFORMATION > =
431+ MaybeUninit :: zeroed ( ) ;
432+ if unsafe {
433+ VirtualQueryEx (
434+ self . process . 0 ,
435+ self . cur_addr ,
436+ mem. as_mut_ptr ( ) ,
437+ std:: mem:: size_of :: < MEMORY_BASIC_INFORMATION > ( ) ,
438+ ) == 0
439+ } {
440+ let err = std:: io:: Error :: last_os_error ( ) ;
441+ // InvalidInput is returned when the given address
442+ // falls beyond the last page accessible by the process,
443+ // so we know we are done iterating when we receive that.
444+ if err. kind ( ) == std:: io:: ErrorKind :: InvalidInput {
445+ return None ;
446+ }
447+ break Some ( Err ( err) ) ;
448+ }
449+ unsafe { mem. assume_init ( ) }
450+ } ;
451+
452+ let address_start = mem_info. BaseAddress ;
453+ let address_end = address_start. wrapping_byte_add ( mem_info. RegionSize ) ;
454+ self . cur_addr = address_end;
455+
456+ if mem_info. State == MEM_FREE {
457+ // Skip over chunks of free memory
458+ continue ;
407459 }
408- Ok ( region) => self . cur_addr = region. end_address ( ) ,
409- _ => ( ) ,
460+
461+ break Some ( Ok ( MappedRegion {
462+ address_start : address_start as u64 ,
463+ size : mem_info. RegionSize as u64 ,
464+ permissions : parse_permissions ( & mem_info) ,
465+ inode : None ,
466+ path : get_mapped_filename ( address_start, & self . process ) ,
467+ } ) ) ;
410468 }
411- Some ( result. map_err ( crate :: session:: Error :: action) )
412469 }
413470 }
414471
@@ -450,6 +507,88 @@ mod windows {
450507 Ok ( buf)
451508 }
452509 }
510+
511+ #[ cfg( test) ]
512+ mod test {
513+ use super :: * ;
514+
515+ #[ test]
516+ fn region_iter_detects_mmap ( ) {
517+ // `mmap` a file and check that the mapping is detected among those returned by
518+ // `MappedRegionIter`.
519+ use std:: io:: Write as _;
520+ use std:: os:: windows:: io:: { AsRawHandle , FromRawHandle , OwnedHandle } ;
521+ use windows_sys:: Win32 :: System :: Memory :: {
522+ CreateFileMappingW , FILE_MAP_ALL_ACCESS , MEMORY_MAPPED_VIEW_ADDRESS , MapViewOfFile ,
523+ PAGE_READWRITE , UnmapViewOfFile ,
524+ } ;
525+
526+ let mut file = tempfile:: tempfile ( ) . unwrap ( ) ;
527+ writeln ! ( file, "hello there" ) . unwrap ( ) ;
528+
529+ let meta = file. metadata ( ) . unwrap ( ) ;
530+ let length = meta. len ( ) as usize ;
531+
532+ // Create a file mapping object for the file
533+ let mapping = unsafe {
534+ CreateFileMappingW (
535+ file. as_raw_handle ( ) ,
536+ std:: ptr:: null ( ) , // default security
537+ PAGE_READWRITE , // read/write permission
538+ 0 , // size of mapping object, high
539+ length as u32 , // size of mapping object, low
540+ std:: ptr:: null ( ) ,
541+ )
542+ } ;
543+
544+ if mapping. is_null ( ) {
545+ panic ! ( "Could not create file mapping" ) ;
546+ }
547+
548+ let mapping = unsafe { OwnedHandle :: from_raw_handle ( mapping) } ;
549+
550+ /// RAII wrapper around a `mmap`ed pointer that will `munmap` on drop.
551+ struct MappedView {
552+ addr : MEMORY_MAPPED_VIEW_ADDRESS ,
553+ }
554+
555+ impl Drop for MappedView {
556+ fn drop ( & mut self ) {
557+ unsafe {
558+ UnmapViewOfFile ( self . addr ) ;
559+ }
560+ }
561+ }
562+
563+ // Map the view and test the results.
564+
565+ let view = unsafe {
566+ let addr = MapViewOfFile (
567+ mapping. as_raw_handle ( ) , // handle to mapping object
568+ FILE_MAP_ALL_ACCESS , // read/write
569+ 0 , // high-order 32 bits of file offset
570+ 0 , // low-order 32 bits of file offset
571+ length,
572+ ) ;
573+ assert ! ( !addr. Value . is_null( ) ) ;
574+ MappedView { addr }
575+ } ;
576+
577+ let regions: Vec < MappedRegion > = MappedRegionIter :: from_pid ( std:: process:: id ( ) )
578+ . expect ( "Could not read memory regions of current process" )
579+ . collect :: < Result < _ , _ > > ( )
580+ . expect ( "Reading maps" ) ;
581+
582+ assert ! ( regions. into_iter( ) . any( |r| {
583+ r. address_start == view. addr. Value as u64
584+ && r. permissions. read
585+ && r. permissions. write
586+ && r. path. is_some( )
587+ } ) ) ;
588+
589+ drop ( view) ;
590+ }
591+ }
453592}
454593
455594/// A set of permissions associated with a [`MappedRegion`].
@@ -464,10 +603,11 @@ pub struct Permissions {
464603 /// Whether this mapping was created as `shared`.
465604 /// Writes to a shared mapping (usually backed by a file)
466605 /// can be observed by other processes which map the same file.
606+ /// Currently only supported on Linux.
467607 shared : bool ,
468608 /// Whether this mapping was created as 'private'.
469609 /// Writes to a private mapping (usually backed by a file)
470- /// are private to a proess and cannot be observed by other processes which map the same file.
610+ /// are private to a process and cannot be observed by other processes which map the same file.
471611 private : bool ,
472612}
473613
@@ -560,7 +700,7 @@ impl Args {
560700 if self . skip_executable_regions && region. permissions . execute {
561701 return false ;
562702 }
563- if self . skip_mapped_files && region. inode . is_some ( ) {
703+ if self . skip_mapped_files && region. inode . is_some ( ) || region . path . is_some ( ) {
564704 return false ;
565705 }
566706 if self . skip_readonly_regions
0 commit comments