mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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 yazi_binding::{SER_OPT, cached_field};
|
||||||
|
|
||||||
use super::{Lives, PtrCell};
|
use super::{Lives, PtrCell};
|
||||||
use crate::lives::TaskSnap;
|
use crate::lives::{Behavior, TaskSnap};
|
||||||
|
|
||||||
pub(super) struct Tasks {
|
pub(super) struct Tasks {
|
||||||
inner: PtrCell<yazi_core::tasks::Tasks>,
|
inner: PtrCell<yazi_core::tasks::Tasks>,
|
||||||
|
|
||||||
v_snaps: Option<Value>,
|
v_behavior: Option<Value>,
|
||||||
v_summary: Option<Value>,
|
v_snaps: Option<Value>,
|
||||||
|
v_summary: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Tasks {
|
impl Deref for Tasks {
|
||||||
|
|
@ -24,8 +25,9 @@ impl Tasks {
|
||||||
Lives::scoped_userdata(Self {
|
Lives::scoped_userdata(Self {
|
||||||
inner: inner.into(),
|
inner: inner.into(),
|
||||||
|
|
||||||
v_snaps: None,
|
v_behavior: None,
|
||||||
v_summary: None,
|
v_snaps: None,
|
||||||
|
v_summary: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +36,8 @@ impl UserData for Tasks {
|
||||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
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| {
|
cached_field!(fields, snaps, |lua, me| {
|
||||||
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
|
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
|
||||||
for snap in &me.snaps {
|
for snap in &me.snaps {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ impl Actor for Spawn {
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
succ!(match form.opt {
|
succ!(match form.opt {
|
||||||
TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in),
|
TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in),
|
||||||
|
|
||||||
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
|
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
#[cfg(unix)]
|
use std::path::PathBuf;
|
||||||
use std::os::unix::fs::DirBuilderExt;
|
|
||||||
use std::{fs::DirBuilder, path::PathBuf};
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use yazi_codegen::DeserializeOver2;
|
use yazi_codegen::DeserializeOver2;
|
||||||
use yazi_fs::Xdg;
|
use yazi_fs::{Xdg, create_owned_dir_blocking};
|
||||||
use yazi_shared::{SStr, timestamp_us};
|
use yazi_shared::{SStr, timestamp_us};
|
||||||
use yazi_shim::toml::DeserializeOverHook;
|
use yazi_shim::toml::DeserializeOverHook;
|
||||||
|
|
||||||
|
|
@ -51,12 +49,7 @@ impl Preview {
|
||||||
|
|
||||||
impl DeserializeOverHook for Preview {
|
impl DeserializeOverHook for Preview {
|
||||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||||
#[cfg(unix)]
|
create_owned_dir_blocking(&self.cache_dir)
|
||||||
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
|
|
||||||
.context(format!("Failed to create cache directory: {}", self.cache_dir.display()))
|
.context(format!("Failed to create cache directory: {}", self.cache_dir.display()))
|
||||||
.map_err(serde::de::Error::custom)?;
|
.map_err(serde::de::Error::custom)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,18 +53,14 @@ impl Stream {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
async fn socket_file() -> io::Result<&'static std::path::PathBuf> {
|
async fn socket_file() -> io::Result<&'static std::path::PathBuf> {
|
||||||
use tokio::{fs::DirBuilder, sync::OnceCell};
|
use tokio::sync::OnceCell;
|
||||||
use yazi_fs::Xdg;
|
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
|
ONCE
|
||||||
.get_or_try_init(|| async move {
|
.get_or_try_init(|| async move {
|
||||||
let p = Xdg::runtime_dir();
|
let p = Xdg::runtime_dir();
|
||||||
|
create_owned_dir(p).await?;
|
||||||
#[cfg(unix)]
|
|
||||||
DirBuilder::new().mode(0o700).recursive(true).create(p).await?;
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
DirBuilder::new().recursive(true).create(p).await?;
|
|
||||||
|
|
||||||
Ok(p.join(".dds.sock"))
|
Ok(p.join(".dds.sock"))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use tokio::io;
|
use std::{io, path::Path};
|
||||||
|
|
||||||
use yazi_shared::url::{Component, UrlBuf, UrlLike};
|
use yazi_shared::url::{Component, UrlBuf, UrlLike};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -74,3 +75,47 @@ fn test_max_common_root() {
|
||||||
assert(&["/aa/bb/cc", "/aa/dd/ee"], "/aa");
|
assert(&["/aa/bb/cc", "/aa/dd/ee"], "/aa");
|
||||||
assert(&["/aa/bb/cc", "/aa/bb/cc/dd/ee", "/aa/bb/cc/ff"], "/aa/bb");
|
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)
|
:direction(ui.Layout.VERTICAL)
|
||||||
:constraints({
|
:constraints({
|
||||||
ui.Constraint.Fill(1),
|
ui.Constraint.Fill(1),
|
||||||
ui.Constraint.Length(1),
|
ui.Constraint.Length(3),
|
||||||
ui.Constraint.Fill(1),
|
ui.Constraint.Fill(1),
|
||||||
})
|
})
|
||||||
:split(self._area)
|
:split(self._area)
|
||||||
|
|
@ -24,6 +24,7 @@ function Tip:reflow() return {} end
|
||||||
function Tip:redraw()
|
function Tip:redraw()
|
||||||
return {
|
return {
|
||||||
ui.Clear(self._chunks[2]),
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ function M.selected_uri_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.cut_uri_list(list)
|
function M.cut_uri_list(list)
|
||||||
|
cx.tasks.behavior:reset()
|
||||||
for line in list:gmatch("[^\r\n]+") do
|
for line in list:gmatch("[^\r\n]+") do
|
||||||
if line:sub(1, 7) ~= "file://" then
|
if line:sub(1, 7) ~= "file://" then
|
||||||
goto continue
|
goto continue
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue