diff --git a/src/safeposix/syscalls/fs_calls.rs b/src/safeposix/syscalls/fs_calls.rs index 7be78607a..9f8d1c02b 100644 --- a/src/safeposix/syscalls/fs_calls.rs +++ b/src/safeposix/syscalls/fs_calls.rs @@ -3504,7 +3504,70 @@ impl Cage { } } - //------------------------------------MMAP SYSCALL------------------------------------ + /// ### Description + /// + /// The `mmap_syscall()` function creates a new mapping in the + /// virtual address space of the calling process. + /// + /// ### Arguments + /// + /// The `mmap_syscall()` accepts six arguments: + /// * `addr` - the starting address for the new mapping. If `addr` + /// is NULL, then the kernel chooses the (page-aligned) address at + /// which to create the mapping. If addr is not NULL, then the + /// kernel takes it as a hint about where to place the mapping. + /// * `len` - specifies the length of the mapping (which must be + /// greater than 0). + /// * `prot` - describes the desired memory protection of the + /// mapping, which must not conflict with the open mode of the file. + /// It is either `PROT_NONE` or the bitwise OR of one or more of the + /// following flags: `PROT_EXEC` (Pages may be executed), `PROT_READ` + /// (Pages may be read), `PROT_WRITE` (Pages may be written), `PROT_NONE` + /// (Pages may not be accessed). + /// * `flags` - determines whether updates to the mapping are visible + /// to other processes mapping the same region, and whether updates are + /// carried through to the underlying file. This behavior is determined + /// by including exactly one of the following values in flags: `MAP_SHARED` + /// (Share this mapping. Updates to the mapping are visible to other + /// processes mapping the same region, and in the case of file-backed + /// mappings are carried through to the underlying file) or `MAP_PRIVATE` + /// (Create a private copy-on-write mapping. Updates to the mapping are + /// not visible to other processes mapping the same file, and are not + /// carried through to the underlying file). `MAP_SHARED_VALIDATE` and + /// other flags are not validated in the current implementation but + /// are supported by the underlying `libc_mmap()` syscall. + /// * `filedes` - a file descriptor specifying the file that shall be + /// mapped. + /// * `off` - designates the offset in the file from which the mapping + /// should start. + /// + /// ### Returns + /// + /// On success, `mmap_syscall()` returns a pointer to the mapped area. + /// In case of a failure, an error is returned, and `errno` is set depending + /// on the error, e.g. EINVAL, ERANGE, etc. + /// + /// ### Errors + /// + /// * `EINVAL` - the value of len is 0 or `flags` contained neither + /// `MAP_PRIVATE` nor `MAP_SHARED` or `flags` contained both + /// `MAP_PRIVATE` and `MAP_SHARED`. + /// * `EACCES` - `fildes` is not open for reading or `MAP_SHARED` + /// was requested and PROT_WRITE is set, but fd is not open in + /// read/write (`O_RDWR`) mode or `fildes` refers to a non-regular file. + /// * `ENXIO` - addresses in the range [`off`, `off`+`len`) are invalid + /// for the object specified by `fildes`. + /// * `EOPNOTSUPP` - Lind currently does not support mapping character + /// files. + /// * `EBADF` - invalid file descriptor. + /// Other errors, like `ENOMEM`, `EOVERFLOW`, etc. are not supported. + /// + /// ### Panics + /// + /// A panic occurs when a provided file descriptor is out of bounds. + /// + /// To learn more about the syscall, flags, possible error values, etc., see + /// [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html) pub fn mmap_syscall( &self, @@ -3516,57 +3579,78 @@ impl Cage { off: i64, ) -> i32 { if len == 0 { - syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); + return syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); } - - if 0 == flags & (MAP_PRIVATE | MAP_SHARED) { - syscall_error( + //Exactly one of the two flags (either `MAP_PRIVATE` or `MAP_SHARED`) must be + // set + if 0 == (flags & (MAP_PRIVATE | MAP_SHARED)) { + return syscall_error( Errno::EINVAL, "mmap", "The value of flags is invalid (neither MAP_PRIVATE nor MAP_SHARED is set)", ); } - - if 0 != flags & MAP_ANONYMOUS { + if ((flags & MAP_PRIVATE) != 0) && ((flags & MAP_SHARED) != 0) { + return syscall_error( + Errno::EINVAL, + "mmap", + "The value of flags is invalid (MAP_PRIVATE and MAP_SHARED cannot be both set)", + ); + } + //The `MAP_ANONYMOUS` flag specifies that the mapping + //is not backed by any file, so the `fildes` and `off` + //arguments should be ignored; however, some implementations + //require `fildes` to be -1, which we follow for the + //sake of portability. + if 0 != (flags & MAP_ANONYMOUS) { return interface::libc_mmap(addr, len, prot, flags, -1, 0); } - + //BUG + //If the provided file descriptor is out of bounds, get_filedescriptor returns + //Err(), unwrapping on which produces a 'panic!' + //otherwise, file descriptor table entry is stored in 'checkedfd' let checkedfd = self.get_filedescriptor(fildes).unwrap(); let mut unlocked_fd = checkedfd.write(); if let Some(filedesc_enum) = &mut *unlocked_fd { - //confirm fd type is mappable + //The current implementation supports only regular files match filedesc_enum { File(ref mut normalfile_filedesc_obj) => { let inodeobj = FS_METADATA .inodetable .get(&normalfile_filedesc_obj.inode) .unwrap(); - - //confirm inode type is mappable + //Confirm inode type is mappable match &*inodeobj { + Inode::CharDev(_chardev_inode_obj) => { + syscall_error(Errno::EOPNOTSUPP, "mmap", "lind currently does not support mapping character files") + } Inode::File(normalfile_inode_obj) => { - //if we want to write our changes back to the file the file needs to be open for reading and writing - if (flags & MAP_SHARED != 0) && (flags & PROT_WRITE != 0) && (normalfile_filedesc_obj.flags & O_RDWR != 0) { + //For any kind of memory mapping, the file should be + //opened for reading, so if it was opened for write + //only, the mapping should be denied + if (normalfile_filedesc_obj.flags & O_WRONLY) != 0 { + return syscall_error(Errno::EACCES, "mmap", "file descriptor is not open for reading"); + } + //If we want to write our changes back to the file the file needs to be open for reading and writing + if (flags & MAP_SHARED) != 0 && (prot & PROT_WRITE) != 0 && (normalfile_filedesc_obj.flags & O_RDWR) != O_RDWR { return syscall_error(Errno::EACCES, "mmap", "file descriptor is not open RDWR, but MAP_SHARED and PROT_WRITE are set"); } let filesize = normalfile_inode_obj.size; + //The offset cannot be negative, and we cannot read past the end of the file if off < 0 || off > filesize as i64 { return syscall_error(Errno::ENXIO, "mmap", "Addresses in the range [off,off+len) are invalid for the object specified by fildes."); } - //because of NaCl's internal workings we must allow mappings to extend past the end of a file + //Because of NaCl's internal workings we must allow mappings to extend past the end of the file let fobj = FILEOBJECTTABLE.get(&normalfile_filedesc_obj.inode).unwrap(); - //we cannot mmap a rust file in quite the right way so we retrieve the fd number from it - //this is the system fd number--the number of the lind. file in our host system + //The actual memory mapping is not emulated inside Lind, so the call to the kernel + //is required. To perform this call, the file descriptor of the actual file + //stored on the host machine is needed. Since Lind's emulated filesystem + //does not match the underlying host's filesystem, the file descriptor + //provided to the `mmap_syscall()` cannot be used and must be converted + //to the actual file descriptor stored in the host's filesystem. let fobjfdno = fobj.as_fd_handle_raw_int(); - - interface::libc_mmap(addr, len, prot, flags, fobjfdno, off) } - - Inode::CharDev(_chardev_inode_obj) => { - syscall_error(Errno::EOPNOTSUPP, "mmap", "lind currently does not support mapping character files") - } - _ => {syscall_error(Errno::EACCES, "mmap", "the fildes argument refers to a file whose type is not supported by mmap")} } } @@ -3581,15 +3665,61 @@ impl Cage { } } - //------------------------------------MUNMAP SYSCALL------------------------------------ + /// ### Description + /// + /// The `munmap_syscall()` function shall remove any mappings + /// containing any part of the address space of the process + /// starting at `addr` and continuing for `len` bytes. + /// Further references to these pages shall result in the + /// generation of a `SIGSEGV` signal to the process. If there + /// are no mappings in the specified address range, then + /// `munmap_syscall()` has no effect. + /// The current implementation of the syscall solely relies + /// on the inner implementation of NaCl (except for a simple + /// `len` argument check) by creating a new mapping in the + /// specified memory region by using `MAP_FIXED` with + /// `PROT_NONE` flag to deny any access to the unmapped + /// memory region. + /// + /// ### Arguments + /// + /// The `munmap_syscall()` accepts two arguments: + /// * `addr` - the address starting from which the mapping + /// shall be removed + /// * `len` - specifies the length of the mapping that + /// shall be removed. + /// + /// ### Returns + /// + /// On success, `munmap_syscall()` returns 0. + /// In case of a failure, an error is returned, and `errno` + /// is set to `EINVAL`. + /// + /// ### Errors + /// + /// * `EINVAL` - the value of len is 0 + /// Other `EINVAL` errors are returned directly from the call to + /// `libc_mmap` + /// + /// ### Panics + /// + /// There are no cases where this function panics. + /// + /// To learn more about the syscall, flags, possible error values, etc., see + /// [munmap(2)](https://linux.die.net/man/2/munmap) pub fn munmap_syscall(&self, addr: *mut u8, len: usize) -> i32 { if len == 0 { - syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); + return syscall_error(Errno::EINVAL, "mmap", "the value of len is 0"); } - //NaCl's munmap implementation actually just writes over the previously mapped - // data with PROT_NONE This frees all of the resources except page table - // space, and is put inside safeposix for consistency + //NaCl's munmap implementation actually just writes + //over the previously mapped data with PROT_NONE. + //This frees all of the resources except page table + //space, and is put inside safeposix for consistency. + //`MAP_FIXED` is used to precisely unmap the specified + //memory region, and `MAP_ANONYMOUS` is used to deny + //any further access to the unmapped memory region + //thereby emulating the unmapping process. interface::libc_mmap( addr, len, diff --git a/src/tests/fs_tests.rs b/src/tests/fs_tests.rs index a42de9e1e..60e5a4f8d 100644 --- a/src/tests/fs_tests.rs +++ b/src/tests/fs_tests.rs @@ -342,6 +342,314 @@ pub mod fs_tests { lindrustfinalize(); } + #[test] + pub fn ut_lind_fs_mmap_zerolen() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if passing 0 as `len` to `mmap_syscall()` + //correctly results in 'The value of len is 0` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_invalid_flags_none() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if not passing any of the two `MAP_PRIVATE` + //or `MAP_SHARED` flags correctly results in `The value + //of flags is invalid (neither `MAP_PRIVATE` nor + //`MAP_SHARED` is set)` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, 0, fd, 0), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_invalid_flags_both() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if passing both `MAP_PRIVATE` + //and `MAP_SHARED` flags correctly results in `The value + //of flags is invalid (`MAP_PRIVATE` and `MAP_SHARED` + //cannot be both set)` error. + assert_eq!( + cage.mmap_syscall( + 0 as *mut u8, + 5, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_SHARED, + fd, + 0 + ), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_no_read() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file without a reading flag + //making it invalid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_WRONLY; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if trying to map a file that does not + //allow reading correctly results in `File descriptor + //is not open for reading` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + -(Errno::EACCES as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_no_write() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with flags for + //reading and writing + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Opening a file descriptor for the same file + //but now with a read flag and without a write + //flag making it invalid for shared mapping with + //write protection flag. + let testflags: i32 = O_RDONLY; + let testfd = cage.open_syscall(filepath, testflags, 0); + + //Checking if trying to map a file that does not + //allow writing for shared mapping with writing + //protection flag set correctly results in + //``MAP_SHARED` was requested and PROT_WRITE is + //set, but fd is not open in read/write (`O_RDWR`) + //mode` error. + assert_eq!( + cage.mmap_syscall( + 0 as *mut u8, + 5, + PROT_READ | PROT_WRITE, + MAP_SHARED, + testfd, + 0 + ), + -(Errno::EACCES as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_invalid_offset_len() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if passing a negative offset correctly + //results in `Addresses in the range [off,off+len) + //are invalid for the object specified by `fildes`` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, -10), + -(Errno::ENXIO as i32) + ); + + //Checking if passing an offset that seeks beyond the end + //of the file correctly results in `Addresses in the + //range [off,off+len) are invalid for the object specified + //by `fildes`` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 25), + -(Errno::ENXIO as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_chardev() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Opening a character device file `/dev/zero`. + let fd = cage.open_syscall("/dev/zero", O_RDWR, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if calling `mmap_syscall()` on the character device + //file correctly results in `Lind currently does not support + //mapping character files` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + -(Errno::EOPNOTSUPP as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_unsupported_file() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a directory. + assert_eq!(cage.mkdir_syscall("/testdir", S_IRWXA), 0); + let fd = cage.open_syscall("/testdir", O_RDWR, S_IRWXA); + + //Checking if passing the created directory to + //`mmap_syscall()` correctly results in `The `fildes` + //argument refers to a file whose type is not + //supported by mmap` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0), + -(Errno::EACCES as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_mmap_invalid_fildes() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping and then + //closing it, thereby making the obtained + //filede scriptor invalid because no other + //file is opened after it. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + assert_eq!(cage.close_syscall(fd), 0); + + //Checking if passing the invalid file descriptor + //correctly results in `Invalid file descriptor` error. + assert_eq!( + cage.mmap_syscall(0 as *mut u8, 5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + -(Errno::EBADF as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] + pub fn ut_lind_fs_munmap_zerolen() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + //Creating a regular file with `O_RDWR` flag + //making it valid for any mapping. + let flags: i32 = O_TRUNC | O_CREAT | O_RDWR; + let filepath = "/mmapTestFile1"; + let fd = cage.open_syscall(filepath, flags, S_IRWXA); + //Writing into that file's first 9 bytes. + assert_eq!(cage.write_syscall(fd, str2cbuf("Test text"), 9), 9); + + //Checking if passing 0 as `len` to `munmap_syscall()` + //correctly results in 'The value of len is 0` error. + assert_eq!( + cage.munmap_syscall(0 as *mut u8, 0), + -(Errno::EINVAL as i32) + ); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + #[test] pub fn ut_lind_fs_chdir_valid_args() { //acquiring a lock on TESTMUTEX prevents other tests from running concurrently,