mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
fix: a TOCTOU race condition while creating temporary directories (#4008)
This commit is contained in:
parent
7a51626505
commit
6c30430666
9 changed files with 97 additions and 27 deletions
29
yazi-actor/src/lives/behavior.rs
Normal file
29
yazi-actor/src/lives/behavior.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{AnyUserData, UserData, UserDataMethods};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
|
||||
pub(super) struct Behavior {
|
||||
inner: PtrCell<yazi_scheduler::Behavior>,
|
||||
}
|
||||
|
||||
impl Deref for Behavior {
|
||||
type Target = yazi_scheduler::Behavior;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl Behavior {
|
||||
pub(super) fn make(inner: &yazi_scheduler::Behavior) -> mlua::Result<AnyUserData> {
|
||||
let inner = PtrCell::from(inner);
|
||||
|
||||
Lives::scoped_userdata(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Behavior {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("reset", |_, me, ()| Ok(me.reset()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked);
|
||||
yazi_macro::mod_flat!(behavior core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
|
|||
use yazi_binding::{SER_OPT, cached_field};
|
||||
|
||||
use super::{Lives, PtrCell};
|
||||
use crate::lives::TaskSnap;
|
||||
use crate::lives::{Behavior, TaskSnap};
|
||||
|
||||
pub(super) struct Tasks {
|
||||
inner: PtrCell<yazi_core::tasks::Tasks>,
|
||||
|
||||
v_snaps: Option<Value>,
|
||||
v_summary: Option<Value>,
|
||||
v_behavior: Option<Value>,
|
||||
v_snaps: Option<Value>,
|
||||
v_summary: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for Tasks {
|
||||
|
|
@ -24,8 +25,9 @@ impl Tasks {
|
|||
Lives::scoped_userdata(Self {
|
||||
inner: inner.into(),
|
||||
|
||||
v_snaps: None,
|
||||
v_summary: None,
|
||||
v_behavior: None,
|
||||
v_snaps: None,
|
||||
v_summary: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +36,8 @@ impl UserData for Tasks {
|
|||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
||||
|
||||
cached_field!(fields, behavior, |_, me| Behavior::make(&me.scheduler.behavior));
|
||||
|
||||
cached_field!(fields, snaps, |lua, me| {
|
||||
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
|
||||
for snap in &me.snaps {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ impl Actor for Spawn {
|
|||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
succ!(match form.opt {
|
||||
TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in),
|
||||
|
||||
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
#[cfg(unix)]
|
||||
use std::os::unix::fs::DirBuilderExt;
|
||||
use std::{fs::DirBuilder, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_fs::{Xdg, create_owned_dir_blocking};
|
||||
use yazi_shared::{SStr, timestamp_us};
|
||||
use yazi_shim::toml::DeserializeOverHook;
|
||||
|
||||
|
|
@ -51,12 +49,7 @@ impl Preview {
|
|||
|
||||
impl DeserializeOverHook for Preview {
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
#[cfg(unix)]
|
||||
let result = DirBuilder::new().mode(0o700).recursive(true).create(&self.cache_dir);
|
||||
#[cfg(not(unix))]
|
||||
let result = DirBuilder::new().recursive(true).create(&self.cache_dir);
|
||||
|
||||
result
|
||||
create_owned_dir_blocking(&self.cache_dir)
|
||||
.context(format!("Failed to create cache directory: {}", self.cache_dir.display()))
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -53,18 +53,14 @@ impl Stream {
|
|||
|
||||
#[cfg(unix)]
|
||||
async fn socket_file() -> io::Result<&'static std::path::PathBuf> {
|
||||
use tokio::{fs::DirBuilder, sync::OnceCell};
|
||||
use yazi_fs::Xdg;
|
||||
use tokio::sync::OnceCell;
|
||||
use yazi_fs::{Xdg, create_owned_dir};
|
||||
|
||||
static ONCE: tokio::sync::OnceCell<std::path::PathBuf> = OnceCell::const_new();
|
||||
static ONCE: OnceCell<std::path::PathBuf> = OnceCell::const_new();
|
||||
ONCE
|
||||
.get_or_try_init(|| async move {
|
||||
let p = Xdg::runtime_dir();
|
||||
|
||||
#[cfg(unix)]
|
||||
DirBuilder::new().mode(0o700).recursive(true).create(p).await?;
|
||||
#[cfg(not(unix))]
|
||||
DirBuilder::new().recursive(true).create(p).await?;
|
||||
create_owned_dir(p).await?;
|
||||
|
||||
Ok(p.join(".dds.sock"))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use tokio::io;
|
||||
use std::{io, path::Path};
|
||||
|
||||
use yazi_shared::url::{Component, UrlBuf, UrlLike};
|
||||
|
||||
#[inline]
|
||||
|
|
@ -74,3 +75,47 @@ fn test_max_common_root() {
|
|||
assert(&["/aa/bb/cc", "/aa/dd/ee"], "/aa");
|
||||
assert(&["/aa/bb/cc", "/aa/bb/cc/dd/ee", "/aa/bb/cc/ff"], "/aa/bb");
|
||||
}
|
||||
|
||||
pub async fn create_owned_dir(p: &Path) -> io::Result<()> {
|
||||
let p = p.to_owned();
|
||||
tokio::task::spawn_blocking(move || create_owned_dir_blocking(&p)).await?
|
||||
}
|
||||
|
||||
pub fn create_owned_dir_blocking(p: &Path) -> io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::{fs::{DirBuilder, OpenOptions}, mem, os::unix::{fs::{DirBuilderExt, OpenOptionsExt}, io::AsRawFd}};
|
||||
|
||||
use libc::{O_DIRECTORY, O_NOFOLLOW};
|
||||
use uzers::Users;
|
||||
use yazi_shared::USERS_CACHE;
|
||||
|
||||
DirBuilder::new().mode(0o700).recursive(true).create(p)?;
|
||||
let dir = OpenOptions::new().read(true).custom_flags(O_DIRECTORY | O_NOFOLLOW).open(p)?;
|
||||
|
||||
let mut stat: libc::stat = unsafe { mem::zeroed() };
|
||||
if unsafe { libc::fstat(dir.as_raw_fd(), &mut stat) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
// Reject directories not owned by the current user.
|
||||
let uid = USERS_CACHE.get_current_uid();
|
||||
if stat.st_uid != uid {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
format!("directory {:?} is owned by uid {} but current uid is {}", p, stat.st_uid, uid),
|
||||
));
|
||||
}
|
||||
|
||||
// Enforce mode 0o700 via the fd.
|
||||
if unsafe { libc::fchmod(dir.as_raw_fd(), 0o700) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::fs::DirBuilder::new().recursive(true).create(p)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ function Tip:layout()
|
|||
:direction(ui.Layout.VERTICAL)
|
||||
:constraints({
|
||||
ui.Constraint.Fill(1),
|
||||
ui.Constraint.Length(1),
|
||||
ui.Constraint.Length(3),
|
||||
ui.Constraint.Fill(1),
|
||||
})
|
||||
:split(self._area)
|
||||
|
|
@ -24,6 +24,7 @@ function Tip:reflow() return {} end
|
|||
function Tip:redraw()
|
||||
return {
|
||||
ui.Clear(self._chunks[2]),
|
||||
ui.Text(self._text):area(self._chunks[2]):align(ui.Align.CENTER):fg("black"):bg("yellow"):bold(),
|
||||
ui.Text(""):area(self._chunks[2]):align(ui.Align.CENTER):bg("yellow"),
|
||||
ui.Text(self._text):area(self._chunks[2]:pad(ui.Pad.xy(1, 1))):align(ui.Align.CENTER):fg("black"):bold(),
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ function M.selected_uri_list()
|
|||
end
|
||||
|
||||
function M.cut_uri_list(list)
|
||||
cx.tasks.behavior:reset()
|
||||
for line in list:gmatch("[^\r\n]+") do
|
||||
if line:sub(1, 7) ~= "file://" then
|
||||
goto continue
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue