Introduce directory traversal

This commit is contained in:
topjohnwu 2023-06-09 02:00:37 -07:00
parent e13775ec2c
commit 40f25f4d56
9 changed files with 412 additions and 134 deletions

1
native/src/Cargo.lock generated
View File

@ -27,6 +27,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
name = "base"
version = "0.0.0"
dependencies = [
"anyhow",
"cfg-if",
"cxx",
"cxx-gen",

View File

@ -14,3 +14,4 @@ cxx = { workspace = true }
libc = { workspace = true }
cfg-if = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }

View File

@ -0,0 +1,39 @@
// Functions listed here are just to export to C++
use crate::{fd_path, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt};
use anyhow::Context;
use cxx::private::c_char;
use libc::mode_t;
use std::ffi::CStr;
use std::io;
use std::os::fd::{OwnedFd, RawFd};
pub fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
fd_path(fd, buf)
.context("fd_path failed")
.log()
.map_or(-1_isize, |v| v as isize)
}
#[no_mangle]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz)).map_or(-1, |v| v as isize)
}
#[export_name = "mkdirs"]
unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
mkdirs(CStr::from_ptr(path), mode).map_or(-1, |_| 0)
}
#[export_name = "rm_rf"]
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
rm_rf(CStr::from_ptr(path)).map_or(false, |_| true)
}
#[no_mangle]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> io::Result<()> {
Directory::try_from(fd)?.remove_all()
}
inner(fd).map_or(false, |_| true)
}

View File

@ -62,23 +62,6 @@ static walk_result pre_order_walk(int dirfd, const Func &fn) {
return CONTINUE;
}
static void remove_at(int dirfd, struct dirent *entry) {
unlinkat(dirfd, entry->d_name, entry->d_type == DT_DIR ? AT_REMOVEDIR : 0);
}
void rm_rf(const char *path) {
struct stat st;
if (lstat(path, &st) < 0)
return;
if (S_ISDIR(st.st_mode))
frm_rf(xopen(path, O_RDONLY | O_CLOEXEC));
remove(path);
}
void frm_rf(int dirfd) {
post_order_walk(dirfd, remove_at);
}
void mv_path(const char *src, const char *dest) {
file_attr attr;
getattr(src, &attr);

View File

@ -55,12 +55,13 @@ extern "C" {
int mkdirs(const char *path, mode_t mode);
ssize_t canonical_path(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz);
bool rm_rf(const char *path);
bool frm_rf(int dirfd);
} // extern "C"
using rust::fd_path;
int fd_pathat(int dirfd, const char *name, char *path, size_t size);
void rm_rf(const char *path);
void mv_path(const char *src, const char *dest);
void mv_dir(int src, int dest);
void cp_afc(const char *src, const char *dest);
@ -89,7 +90,6 @@ void file_readline(const char *file, const std::function<bool(std::string_view)>
void parse_prop_file(FILE *fp, const std::function<bool(std::string_view, std::string_view)> &fn);
void parse_prop_file(const char *file,
const std::function<bool(std::string_view, std::string_view)> &fn);
void frm_rf(int dirfd);
void clone_dir(int src, int dest);
std::vector<mount_info> parse_mount_info(const char *pid);
std::string find_apk_path(const char *pkg);

View File

@ -1,53 +1,25 @@
use mem::MaybeUninit;
use std::cmp::min;
use std::ffi::CStr;
use std::fs::File;
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::{io, mem};
use std::{io, mem, slice};
use libc::{c_char, c_uint, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH};
use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY};
use crate::{bfmt_cstr, errno, error, xopen};
use crate::{bfmt_cstr, copy_cstr, cstr, errno, error};
pub mod unsafe_impl {
use std::ffi::CStr;
use libc::c_char;
use crate::slice_from_ptr_mut;
pub unsafe fn readlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
let r = libc::readlink(path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
r
}
#[no_mangle]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
super::realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz))
}
}
pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint);
if fd >= 0 {
Some(OwnedFd::from_raw_fd(fd))
Ok(OwnedFd::from_raw_fd(fd))
} else {
None
}
}
}
pub fn __xopen_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option<OwnedFd> {
unsafe {
let fd = xopen(path.as_ptr(), flags, mode);
if fd >= 0 {
Some(OwnedFd::from_raw_fd(fd))
} else {
None
Err(io::Error::last_os_error())
}
}
}
@ -62,29 +34,31 @@ macro_rules! open_fd {
};
}
#[macro_export]
macro_rules! xopen_fd {
($path:expr, $flags:expr) => {
$crate::__xopen_fd_impl($path, $flags, 0)
};
($path:expr, $flags:expr, $mode:expr) => {
$crate::__xopen_fd_impl($path, $flags, $mode)
};
pub unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
let r = libc::readlink(path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
r
}
pub fn readlink(path: &CStr, data: &mut [u8]) -> isize {
unsafe { unsafe_impl::readlink(path.as_ptr(), data.as_mut_ptr(), data.len()) }
pub fn readlink(path: &CStr, data: &mut [u8]) -> io::Result<usize> {
let r = unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) };
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(r as usize)
}
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> isize {
let mut fd_buf: [u8; 40] = [0; 40];
pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
let mut fd_buf = [0_u8; 40];
let fd_path = bfmt_cstr!(&mut fd_buf, "/proc/self/fd/{}", fd);
readlink(fd_path, buf)
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(path: &CStr, buf: &mut [u8]) -> isize {
if let Some(fd) = open_fd!(path, O_PATH | O_CLOEXEC) {
pub fn realpath(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
let fd = open_fd!(path, O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat;
let mut st2: libc::stat;
let mut skip_check = false;
@ -95,45 +69,39 @@ pub fn realpath(path: &CStr, buf: &mut [u8]) -> isize {
skip_check = true;
}
}
let len = fd_path(fd.as_raw_fd(), buf);
let len = fd_path(fd.as_raw_fd(), buf)?;
unsafe {
st2 = mem::zeroed();
if libc::stat(buf.as_ptr().cast(), &mut st2) < 0
|| (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino))
{
*errno() = ENOENT;
return -1;
return Err(io::Error::last_os_error());
}
}
len
} else {
*errno() = ENOENT;
-1
}
Ok(len)
}
extern "C" {
fn strscpy(dst: *mut c_char, src: *const c_char, size: usize) -> usize;
}
#[no_mangle]
pub unsafe extern "C" fn mkdirs(path: *const c_char, mode: mode_t) -> i32 {
pub fn mkdirs(path: &CStr, mode: mode_t) -> io::Result<()> {
let mut buf = [0_u8; 4096];
let ptr: *mut c_char = buf.as_mut_ptr().cast();
let len = strscpy(ptr, path, buf.len());
let mut curr = &mut buf[1..len];
while let Some(p) = curr.iter().position(|c| *c == b'/') {
curr[p] = b'\0';
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
return -1;
let len = copy_cstr(&mut buf, path);
let buf = &mut buf[..len];
let mut off = 1;
unsafe {
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') {
buf[off + p] = b'\0';
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
}
curr[p] = b'/';
curr = &mut curr[(p + 1)..];
buf[off + p] = b'/';
off += p + 1;
}
if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST {
return -1;
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
}
0
}
*errno() = 0;
Ok(())
}
pub trait ReadExt {
@ -221,3 +189,276 @@ impl<T: Write> WriteExt for T {
Ok(())
}
}
pub struct DirEntry<'a> {
dir: &'a Directory<'a>,
entry: &'a dirent,
name_len: usize,
}
impl DirEntry<'_> {
pub fn d_name(&self) -> &CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.name_len,
))
}
}
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut len = self.dir.path(buf)?;
buf[len] = b'/';
len += 1;
len += copy_cstr(&mut buf[len..], self.d_name());
Ok(len)
}
pub fn is_dir(&self) -> bool {
self.d_type == libc::DT_DIR
}
pub fn is_file(&self) -> bool {
self.d_type == libc::DT_REG
}
pub fn is_lnk(&self) -> bool {
self.d_type == libc::DT_LNK
}
pub fn unlink(&self) -> io::Result<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
if libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory));
}
unsafe {
let fd = libc::openat(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
O_RDONLY | O_CLOEXEC,
);
if fd < 0 {
return Err(io::Error::last_os_error());
}
Directory::try_from(OwnedFd::from_raw_fd(fd))
}
}
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory));
}
unsafe {
let fd = libc::openat(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
flags | O_CLOEXEC,
);
if fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(File::from_raw_fd(fd))
}
}
}
impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
self.entry
}
}
pub struct Directory<'a> {
dirp: *mut libc::DIR,
_phantom: PhantomData<&'a libc::DIR>,
}
pub enum WalkResult {
Continue,
Abort,
Skip,
}
impl<'a> Directory<'a> {
pub fn open(path: &CStr) -> io::Result<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) };
if dirp.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Directory {
dirp,
_phantom: PhantomData,
})
}
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.dirp) };
if e.is_null() {
return if *errno() != 0 {
Err(io::Error::last_os_error())
} else {
Ok(None)
};
}
// Skip both "." and ".."
unsafe {
let entry = &*e;
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
return if d_name == cstr!(".") || d_name == cstr!("..") {
self.read()
} else {
let e = DirEntry {
dir: self,
entry,
name_len: d_name.to_bytes().len(),
};
Ok(Some(e))
};
}
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.dirp) }
}
pub fn path(&self, buf: &mut [u8]) -> io::Result<usize> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> io::Result<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
})?;
Ok(())
}
}
impl Directory<'_> {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.post_order_walk_impl(f)? {
return Ok(Abort);
}
}
match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {}
}
}
}
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.pre_order_walk_impl(f)? {
return Ok(Abort);
}
}
}
},
}
}
}
}
impl TryFrom<OwnedFd> for Directory<'_> {
type Error = io::Error;
fn try_from(fd: OwnedFd) -> io::Result<Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
if dirp.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Directory {
dirp,
_phantom: PhantomData,
})
}
}
impl AsRawFd for Directory<'_> {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dirp) }
}
}
impl<'a> AsFd for Directory<'a> {
fn as_fd(&self) -> BorrowedFd<'a> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Directory<'_> {
fn drop(&mut self) {
unsafe {
libc::closedir(self.dirp);
}
}
}
pub fn rm_rf(path: &CStr) -> io::Result<()> {
unsafe {
let mut stat: libc::stat = mem::zeroed();
if libc::lstat(path.as_ptr(), &mut stat) < 0 {
return Err(io::Error::last_os_error());
}
if (stat.st_mode & libc::S_IFMT as u32) == libc::S_IFDIR as u32 {
let mut dir = Directory::open(path)?;
dir.remove_all()?;
}
if libc::remove(path.as_ptr()) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}

View File

@ -4,11 +4,13 @@
pub use libc;
use cxx_extern::*;
pub use files::*;
pub use logging::*;
pub use misc::*;
pub use xwrap::*;
mod cxx_extern;
mod files;
mod logging;
mod misc;
@ -34,6 +36,7 @@ pub mod ffi {
#[namespace = "rust"]
extern "Rust" {
fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32;
#[rust_name = "fd_path_for_cxx"]
fn fd_path(fd: i32, buf: &mut [u8]) -> isize;
}
}

View File

@ -6,13 +6,21 @@ use std::{fmt, slice};
use thiserror::Error;
pub fn copy_str(dest: &mut [u8], src: &[u8]) -> usize {
pub fn copy_str<T: AsRef<[u8]>>(dest: &mut [u8], src: T) -> usize {
let src = src.as_ref();
let len = min(src.len(), dest.len() - 1);
dest[..len].copy_from_slice(&src[..len]);
dest[len] = b'\0';
len
}
pub fn copy_cstr(dest: &mut [u8], src: &CStr) -> usize {
let src = src.to_bytes_with_nul();
let len = min(src.len(), dest.len());
dest[..len].copy_from_slice(&src[..len]);
len
}
struct BufFmtWriter<'a> {
buf: &'a mut [u8],
used: usize,
@ -31,7 +39,7 @@ impl<'a> fmt::Write for BufFmtWriter<'a> {
// Silent truncate
return Ok(());
}
self.used += copy_str(&mut self.buf[self.used..], s.as_bytes());
self.used += copy_str(&mut self.buf[self.used..], s);
// Silent truncate
Ok(())
}
@ -57,6 +65,7 @@ macro_rules! bfmt {
macro_rules! bfmt_cstr {
($buf:expr, $($args:tt)*) => {{
let len = $crate::fmt_to_buf($buf, format_args!($($args)*));
#[allow(unused_unsafe)]
unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&$buf[..(len + 1)]) }
}};
}
@ -70,7 +79,10 @@ macro_rules! cstr {
!$str.bytes().any(|b| b == b'\0'),
"cstr argument contains embedded NUL bytes",
);
unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes()) }
#[allow(unused_unsafe)]
unsafe {
std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes())
}
}};
}

View File

@ -2,22 +2,26 @@ use std::ffi::CStr;
use std::os::unix::io::RawFd;
use std::ptr;
use anyhow::Context;
use libc::{
c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t,
ssize_t, SYS_dup3,
};
use crate::{cstr, errno, error, mkdirs, perror, ptr_to_str, raw_cstr, realpath};
use crate::{cstr, errno, error, mkdirs, perror, ptr_to_str, raw_cstr, ResultExt};
mod unsafe_impl {
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use anyhow::Context;
use cfg_if::cfg_if;
use libc::{c_char, nfds_t, off_t, pollfd};
use crate::unsafe_impl::readlink;
use crate::{perror, ptr_to_str, slice_from_ptr, slice_from_ptr_mut};
use crate::{
perror, ptr_to_str, readlink, readlink_unsafe, slice_from_ptr, slice_from_ptr_mut,
ResultExt,
};
#[no_mangle]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
@ -36,12 +40,15 @@ mod unsafe_impl {
#[no_mangle]
unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
super::xrealpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz))
readlink(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz))
.with_context(|| format!("realpath {} failed", ptr_to_str(path)))
.log()
.map_or(-1, |v| v as isize)
}
#[no_mangle]
pub unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
let r = readlink(path, buf, bufsz);
let r = readlink_unsafe(path, buf, bufsz);
if r < 0 {
perror!("readlink");
}
@ -572,11 +579,10 @@ pub unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
#[no_mangle]
pub unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {
let r = mkdirs(path, mode);
if r < 0 {
perror!("mkdirs {}", ptr_to_str(path));
}
r
mkdirs(CStr::from_ptr(path), mode)
.with_context(|| format!("mkdirs {}", ptr_to_str(path)))
.log()
.map_or(-1, |_| 0)
}
#[no_mangle]
@ -629,14 +635,6 @@ pub fn xpoll(fds: &mut [pollfd], timeout: i32) -> i32 {
unsafe { unsafe_impl::xpoll(fds.as_mut_ptr(), fds.len() as nfds_t, timeout) }
}
pub fn xrealpath(path: &CStr, buf: &mut [u8]) -> isize {
let r = realpath(path, buf);
if r < 0 {
perror!("realpath {}", path.to_str().unwrap_or(""))
}
r
}
#[no_mangle]
pub unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {
let r = libc::mknod(pathname, mode, dev);