use std::path::Path;
#[cfg(unix)]
pub fn open_readonly_nofollow(path: &Path) -> std::io::Result<std::fs::File> {
use std::os::unix::fs::OpenOptionsExt;
std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(path)
}
#[cfg(not(unix))]
pub fn open_readonly_nofollow(path: &std::path::Path) -> std::io::Result<std::fs::File> {
std::fs::File::open(path)
}
#[cfg(unix)]
pub fn verify_fd_matches_stat(file: &std::fs::File, pre_open_meta: &std::fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
match file.metadata() {
Ok(fd_meta) => fd_meta.dev() == pre_open_meta.dev() && fd_meta.ino() == pre_open_meta.ino(),
Err(_) => false,
}
}
#[cfg(windows)]
pub fn verify_fd_matches_stat(file: &std::fs::File, pre_open_meta: &std::fs::Metadata) -> bool {
use std::os::windows::fs::MetadataExt;
use std::os::windows::io::AsRawHandle;
let handle = file.as_raw_handle();
if handle.is_null() || handle as isize == -1 {
return false;
}
let mut info = unsafe {
std::mem::zeroed::<windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION>()
};
let ok = unsafe {
windows_sys::Win32::Storage::FileSystem::GetFileInformationByHandle(handle as _, &mut info)
};
if ok == 0 {
return false;
}
let fd_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64);
let fd_attrs = info.dwFileAttributes;
let fd_write_time = ((info.ftLastWriteTime.dwHighDateTime as u64) << 32)
| (info.ftLastWriteTime.dwLowDateTime as u64);
fd_size == pre_open_meta.file_size()
&& fd_attrs == pre_open_meta.file_attributes()
&& fd_write_time == pre_open_meta.last_write_time()
}
#[cfg(not(any(unix, windows)))]
#[allow(dead_code)]
pub fn verify_fd_matches_stat(_file: &std::fs::File, _pre_open_meta: &std::fs::Metadata) -> bool {
true
}
#[cfg(target_os = "linux")]
pub(crate) fn open_root_dirfd(canonical_root: &Path) -> Option<std::fs::File> {
linux_openat2::open_root_dirfd(canonical_root)
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn open_root_dirfd(_canonical_root: &Path) -> Option<std::fs::File> {
None
}
pub(crate) fn open_beneath(
root_fd: Option<&std::fs::File>,
canonical_root: &Path,
rel: &Path,
) -> Option<std::fs::File> {
#[cfg(target_os = "linux")]
if let Some(dirfd) = root_fd {
match linux_openat2::try_open_beneath(dirfd, rel) {
linux_openat2::Outcome::Opened(file) => return Some(file),
linux_openat2::Outcome::Unsupported | linux_openat2::Outcome::CandidateRejected => {}
}
}
#[cfg(not(target_os = "linux"))]
let _ = root_fd;
legacy_open_checked(canonical_root, rel)
}
pub(crate) fn open_beneath_fresh(canonical_root: &Path, rel: &Path) -> Option<std::fs::File> {
let root_fd = open_root_dirfd(canonical_root);
open_beneath(root_fd.as_ref(), canonical_root, rel)
}
fn legacy_open_checked(canonical_root: &Path, rel: &Path) -> Option<std::fs::File> {
let abs_path = canonical_root.join(rel);
let canonical = std::fs::canonicalize(&abs_path).ok()?;
if !canonical.starts_with(canonical_root) {
return None;
}
#[cfg(any(unix, windows))]
{
let pre_meta = std::fs::metadata(&canonical).ok()?;
let file = open_readonly_nofollow(&canonical).ok()?;
if !verify_fd_matches_stat(&file, &pre_meta) {
return None;
}
Some(file)
}
#[cfg(not(any(unix, windows)))]
{
open_readonly_nofollow(&canonical).ok()
}
}
#[cfg(target_os = "linux")]
mod linux_openat2 {
use std::path::Path;
use std::sync::atomic::{AtomicU8, Ordering};
static STATE: AtomicU8 = AtomicU8::new(UNPROBED);
const UNPROBED: u8 = 0;
const AVAILABLE: u8 = 1;
const UNAVAILABLE: u8 = 2;
pub(super) enum Outcome {
Opened(std::fs::File),
Unsupported,
CandidateRejected,
}
pub(super) fn open_root_dirfd(canonical_root: &Path) -> Option<std::fs::File> {
use std::os::unix::fs::OpenOptionsExt;
if std::env::var_os("SYNTEXT_NO_OPENAT2").is_some() {
return None;
}
if STATE.load(Ordering::Relaxed) == UNAVAILABLE {
return None;
}
std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_PATH | libc::O_DIRECTORY | libc::O_CLOEXEC)
.open(canonical_root)
.ok()
}
pub(super) fn try_open_beneath(dirfd: &std::fs::File, rel: &Path) -> Outcome {
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
if STATE.load(Ordering::Relaxed) == UNAVAILABLE {
return Outcome::Unsupported;
}
let Ok(c_path) = std::ffi::CString::new(rel.as_os_str().as_bytes()) else {
return Outcome::CandidateRejected;
};
let mut how: libc::open_how = unsafe { std::mem::zeroed() };
how.flags = (libc::O_RDONLY | libc::O_CLOEXEC) as u64;
how.resolve = libc::RESOLVE_BENEATH | libc::RESOLVE_NO_MAGICLINKS;
for _ in 0..2 {
let ret = unsafe {
libc::syscall(
libc::SYS_openat2,
dirfd.as_raw_fd(),
c_path.as_ptr(),
&how as *const libc::open_how,
std::mem::size_of::<libc::open_how>(),
)
};
if ret >= 0 {
STATE.store(AVAILABLE, Ordering::Relaxed);
return Outcome::Opened(unsafe { std::fs::File::from_raw_fd(ret as RawFd) });
}
match std::io::Error::last_os_error().raw_os_error().unwrap_or(0) {
libc::ENOSYS | libc::EINVAL => {
STATE.store(UNAVAILABLE, Ordering::Relaxed);
return Outcome::Unsupported;
}
libc::EAGAIN => continue,
_ => return Outcome::CandidateRejected,
}
}
Outcome::CandidateRejected }
}