Skip to content

Set mmapped files as readonly to prevent other processes from modifying it by accident #137025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions compiler/rustc_codegen_gcc/src/back/lto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// /usr/bin/ld: warning: type of symbol `_RNvNvNvNvNtNtNtCsAj5i4SGTR7_3std4sync4mpmc5waker17current_thread_id5DUMMY7___getit5___KEY' changed from 1 to 6 in /tmp/ccKeUSiR.ltrans0.ltrans.o
// /usr/bin/ld: warning: incremental linking of LTO and non-LTO objects; using -flinker-output=nolto-rel which will bypass whole program optimization
use std::ffi::{CStr, CString};
use std::fs::{self, File};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;

Expand Down Expand Up @@ -126,9 +126,7 @@ fn prepare_lto(
.extend(exported_symbols[&cnum].iter().filter_map(symbol_filter));
}

let archive_data = unsafe {
Mmap::map(File::open(path).expect("couldn't open rlib")).expect("couldn't map rlib")
};
let archive_data = unsafe { Mmap::map(path).expect("couldn't map rlib") };
let archive = ArchiveFile::parse(&*archive_data).expect("wanted an rlib");
let obj_files = archive
.members()
Expand Down
5 changes: 1 addition & 4 deletions compiler/rustc_codegen_llvm/src/back/lto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,7 @@ fn prepare_lto(
.extend(exported_symbols[&cnum].iter().filter_map(symbol_filter));
}

let archive_data = unsafe {
Mmap::map(std::fs::File::open(&path).expect("couldn't open rlib"))
.expect("couldn't map rlib")
};
let archive_data = Mmap::map(&path).expect("couldn't map rlib");
let archive = ArchiveFile::parse(&*archive_data).expect("wanted an rlib");
let obj_files = archive
.members()
Expand Down
46 changes: 18 additions & 28 deletions compiler/rustc_codegen_ssa/src/back/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,8 @@ pub trait ArchiveBuilderBuilder {
outdir: &Path,
bundled_lib_file_names: &FxIndexSet<Symbol>,
) -> Result<(), ExtractBundledLibsError<'a>> {
let archive_map = unsafe {
Mmap::map(
File::open(rlib)
.map_err(|e| ExtractBundledLibsError::OpenFile { rlib, error: Box::new(e) })?,
)
.map_err(|e| ExtractBundledLibsError::MmapFile { rlib, error: Box::new(e) })?
};
let archive_map = Mmap::map(rlib)
.map_err(|e| ExtractBundledLibsError::MmapFile { rlib, error: Box::new(e) })?;
let archive = ArchiveFile::parse(&*archive_map)
.map_err(|e| ExtractBundledLibsError::ParseArchive { rlib, error: Box::new(e) })?;

Expand Down Expand Up @@ -363,7 +358,7 @@ pub fn try_extract_macho_fat_archive(
sess: &Session,
archive_path: &Path,
) -> io::Result<Option<PathBuf>> {
let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
let archive_map = Mmap::map(&archive_path)?;
let target_arch = match sess.target.arch.as_ref() {
"aarch64" => object::Architecture::Aarch64,
"x86_64" => object::Architecture::X86_64,
Expand Down Expand Up @@ -400,7 +395,7 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
return Ok(());
}

let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
let archive_map = Mmap::map(&archive_path)?;
let archive = ArchiveFile::parse(&*archive_map)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let archive_index = self.src_archives.len();
Expand Down Expand Up @@ -463,25 +458,20 @@ impl<'a> ArArchiveBuilder<'a> {
let mut entries = Vec::new();

for (entry_name, entry) in self.entries {
let data =
match entry {
ArchiveEntry::FromArchive { archive_index, file_range } => {
let src_archive = &self.src_archives[archive_index];

let data = &src_archive.1
[file_range.0 as usize..file_range.0 as usize + file_range.1 as usize];

Box::new(data) as Box<dyn AsRef<[u8]>>
}
ArchiveEntry::File(file) => unsafe {
Box::new(
Mmap::map(File::open(file).map_err(|err| {
io_error_context("failed to open object file", err)
})?)
.map_err(|err| io_error_context("failed to map object file", err))?,
) as Box<dyn AsRef<[u8]>>
},
};
let data = match entry {
ArchiveEntry::FromArchive { archive_index, file_range } => {
let src_archive = &self.src_archives[archive_index];

let data = &src_archive.1
[file_range.0 as usize..file_range.0 as usize + file_range.1 as usize];

Box::new(data) as Box<dyn AsRef<[u8]>>
}
ArchiveEntry::File(file) => Box::new(
Mmap::map(file)
.map_err(|err| io_error_context("failed to map object file", err))?,
) as Box<dyn AsRef<[u8]>>,
};

entries.push(NewArchiveMember {
buf: data,
Expand Down
5 changes: 2 additions & 3 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ mod raw_dylib;

use std::collections::BTreeSet;
use std::ffi::OsString;
use std::fs::{File, OpenOptions, read};
use std::fs::{OpenOptions, read};
use std::io::{BufWriter, Write};
use std::ops::{ControlFlow, Deref};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -578,8 +578,7 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
}

fn read_input(&self, path: &Path) -> std::io::Result<&[u8]> {
let file = File::open(&path)?;
let mmap = (unsafe { Mmap::map(file) })?;
let mmap = Mmap::map(&path)?;
Ok(self.alloc_mmap(mmap))
}
}
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_codegen_ssa/src/back/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Reading of the rustc metadata for rlibs and dylibs

use std::borrow::Cow;
use std::fs::File;
use std::io::Write;
use std::path::Path;

Expand Down Expand Up @@ -44,10 +43,7 @@ fn load_metadata_with(
path: &Path,
f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>,
) -> Result<OwnedSlice, String> {
let file =
File::open(path).map_err(|e| format!("failed to open file '{}': {}", path.display(), e))?;

unsafe { Mmap::map(file) }
Mmap::map(&path)
.map_err(|e| format!("failed to mmap file '{}': {}", path.display(), e))
.and_then(|mmap| try_slice_owned(mmap, |mmap| f(mmap)))
}
Expand Down
9 changes: 2 additions & 7 deletions compiler/rustc_codegen_ssa/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2151,14 +2151,9 @@ pub(crate) fn submit_pre_lto_module_to_llvm<B: ExtraBackendMethods>(
) {
let filename = pre_lto_bitcode_filename(&module.name);
let bc_path = in_incr_comp_dir_sess(tcx.sess, &filename);
let file = fs::File::open(&bc_path)
.unwrap_or_else(|e| panic!("failed to open bitcode file `{}`: {}", bc_path.display(), e));

let mmap = unsafe {
Mmap::map(file).unwrap_or_else(|e| {
panic!("failed to mmap bitcode file `{}`: {}", bc_path.display(), e)
})
};
let mmap = Mmap::map(&bc_path)
.unwrap_or_else(|e| panic!("failed to mmap bitcode file `{}`: {}", bc_path.display(), e));
// Schedule the module to be loaded
drop(tx_to_llvm_workers.send(Box::new(Message::AddImportOnlyModule::<B> {
module_data: SerializedModule::FromUncompressedFile(mmap),
Expand Down
14 changes: 6 additions & 8 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,22 @@ impl CodegenResults {
) -> Result<(Self, OutputFilenames), CodegenErrors> {
// The Decodable machinery is not used here because it panics if the input data is invalid
// and because its internal representation may change.
if !data.starts_with(RLINK_MAGIC) {
let Some(data) = data.strip_prefix(RLINK_MAGIC) else {
return Err(CodegenErrors::WrongFileType);
}
let data = &data[RLINK_MAGIC.len()..];
if data.len() < 4 {
};

let Some((&version_array, data)) = data.split_first_chunk() else {
return Err(CodegenErrors::EmptyVersionNumber);
}
};

let mut version_array: [u8; 4] = Default::default();
version_array.copy_from_slice(&data[..4]);
if u32::from_be_bytes(version_array) != RLINK_VERSION {
return Err(CodegenErrors::EncodingVersionMismatch {
version_array: String::from_utf8_lossy(&version_array).to_string(),
rlink_version: RLINK_VERSION,
});
}

let Ok(mut decoder) = MemDecoder::new(&data[4..], 0) else {
let Ok(mut decoder) = MemDecoder::new(data, 0) else {
return Err(CodegenErrors::CorruptFile);
};
let rustc_version = decoder.read_str();
Expand Down
38 changes: 27 additions & 11 deletions compiler/rustc_data_structures/src/memmap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fs::File;
use std::io;
use std::ops::{Deref, DerefMut};
use std::path::Path;

/// A trivial wrapper for [`memmap2::Mmap`] (or `Vec<u8>` on WASM).
#[cfg(not(any(miri, target_arch = "wasm32")))]
Expand All @@ -11,33 +12,48 @@ pub struct Mmap(Vec<u8>);

#[cfg(not(any(miri, target_arch = "wasm32")))]
impl Mmap {
/// # Safety
///
/// The given file must not be mutated (i.e., not written, not truncated, ...) until the mapping is closed.
///
/// However in practice most callers do not ensure this, so uses of this function are likely unsound.
/// This process must not modify nor remove the backing file while the memory map lives.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would removing the file be a problem? Mappings should say valid for unlinked files... unless network filesystems are involved I guess...

/// For the dep-graph and the work product index, it is as soon as the decoding is done.
/// For the query result cache, the memory map is dropped in save_dep_graph before calling
/// save_in and trying to remove the backing file.
///
/// There is no way to prevent another process from modifying this file.
///
/// This means in practice all uses of this function are theoretically unsound, but also
/// the way rustc uses `Mmap` (reading bytes, validating them afterwards *anyway* to detect
/// corrupted files) avoids the actual issues this could cause.
///
/// Someone may truncate our file, but then we'll SIGBUS, which is not great, but at least
/// we won't succeed with corrupted data.
///
/// To get a bit more hardening out of this we will set the file as readonly before opening it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we immediately set it to readonly after initial creation? The writing process can have an FD with write permission and then make the file readonly that other processes can't open it for writing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. That disconnects the read only setting from the mmap. I guess I could check I'd it's already read only

#[inline]
pub unsafe fn map(file: File) -> io::Result<Self> {
pub fn map(path: impl AsRef<Path>) -> io::Result<Self> {
let path = path.as_ref();
let mut perms = std::fs::metadata(path)?.permissions();
perms.set_readonly(true);
std::fs::set_permissions(path, perms)?;

let file = File::open(path)?;

// By default, memmap2 creates shared mappings, implying that we could see updates to the
// file through the mapping. That would violate our precondition; so by requesting a
// map_copy_read_only we do not lose anything.
// This mapping mode also improves our support for filesystems such as cacheless virtiofs.
// For more details see https://github.com/rust-lang/rust/issues/122262
//
// SAFETY: The caller must ensure that this is safe.
unsafe { memmap2::MmapOptions::new().map_copy_read_only(&file).map(Mmap) }
unsafe { Ok(Self(memmap2::MmapOptions::new().map_copy_read_only(&file)?)) }
}
}

#[cfg(any(miri, target_arch = "wasm32"))]
impl Mmap {
#[inline]
pub unsafe fn map(mut file: File) -> io::Result<Self> {
use std::io::Read;

let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(Mmap(data))
pub fn map(path: impl AsRef<Path>) -> io::Result<Self> {
Ok(Mmap(std::fs::read(path)?))
}
}

Expand Down
9 changes: 1 addition & 8 deletions compiler/rustc_incremental/src/persist/file_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,11 @@ pub(crate) fn read_file(
is_nightly_build: bool,
cfg_version: &'static str,
) -> io::Result<Option<(Mmap, usize)>> {
let file = match fs::File::open(path) {
let mmap = match Mmap::map(path) {
Ok(file) => file,
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(err),
};
// SAFETY: This process must not modify nor remove the backing file while the memory map lives.
// For the dep-graph and the work product index, it is as soon as the decoding is done.
// For the query result cache, the memory map is dropped in save_dep_graph before calling
// save_in and trying to remove the backing file.
//
// There is no way to prevent another process from modifying this file.
let mmap = unsafe { Mmap::map(file) }?;

let mut file = io::Cursor::new(&*mmap);

Expand Down
8 changes: 1 addition & 7 deletions compiler/rustc_metadata/src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,13 +822,7 @@ fn get_metadata_section<'p>(
}
CrateFlavor::Rmeta => {
// mmap the file, because only a small fraction of it is read.
let file = std::fs::File::open(filename).map_err(|_| {
MetadataError::LoadFailure(format!(
"failed to open rmeta metadata: '{}'",
filename.display()
))
})?;
let mmap = unsafe { Mmap::map(file) };
let mmap = unsafe { Mmap::map(filename) };
let mmap = mmap.map_err(|_| {
MetadataError::LoadFailure(format!(
"failed to mmap rmeta metadata: '{}'",
Expand Down
6 changes: 2 additions & 4 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2245,12 +2245,10 @@ pub struct EncodedMetadata {
impl EncodedMetadata {
#[inline]
pub fn from_path(path: PathBuf, temp_dir: Option<MaybeTempDir>) -> std::io::Result<Self> {
let file = std::fs::File::open(&path)?;
let file_metadata = file.metadata()?;
if file_metadata.len() == 0 {
if std::fs::metadata(&path)?.len() == 0 {
return Ok(Self { mmap: None, _temp_dir: None });
}
let mmap = unsafe { Some(Mmap::map(file)?) };
let mmap = unsafe { Some(Mmap::map(path)?) };
Ok(Self { mmap, _temp_dir: temp_dir })
}

Expand Down
Loading