i am making it worse

pull/2070/head
FabianLars 8 months ago
parent caac44d6ee
commit f323eb8f56
No known key found for this signature in database

@ -14,7 +14,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "No write access to `$RESOURCES` folder with MSI installer and NSIS installers in `perMachine` or `both` mode" } windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write acces in `$RESOURCES` folder" }
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" } linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
macos = { level = "full", notes = "No write access to `$RESOURCES` folder" } macos = { level = "full", notes = "No write access to `$RESOURCES` folder" }
android = { level = "partial", notes = "Access is restricted to Application folder by default" } android = { level = "partial", notes = "Access is restricted to Application folder by default" }

@ -7,7 +7,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
#[path = "src/scope.rs"] #[path = "src/entryraw.rs"]
#[allow(dead_code)] #[allow(dead_code)]
mod scope; mod scope;

@ -16,7 +16,7 @@ use std::{
borrow::Cow, borrow::Cow,
fs::File, fs::File,
io::{BufReader, Lines, Read, Write}, io::{BufReader, Lines, Read, Write},
path::PathBuf, path::{Path, PathBuf},
str::FromStr, str::FromStr,
sync::Mutex, sync::Mutex,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
@ -1002,37 +1002,88 @@ pub fn resolve_path<R: Runtime>(
let scope = tauri::scope::fs::Scope::new( let scope = tauri::scope::fs::Scope::new(
webview, webview,
&FsScope::Scope { &FsScope::Scope {
allow: webview allow: global_scope
.fs_scope() .allows()
.allowed .iter()
.lock() .filter_map(|e| e.path.clone())
.unwrap()
.clone()
.into_iter()
.chain(global_scope.allows().iter().filter_map(|e| e.path.clone()))
.chain(command_scope.allows().iter().filter_map(|e| e.path.clone())) .chain(command_scope.allows().iter().filter_map(|e| e.path.clone()))
.collect(), .collect(),
deny: webview deny: global_scope
.fs_scope() .denies()
.denied .iter()
.lock() .filter_map(|e| e.path.clone())
.unwrap()
.clone()
.into_iter()
.chain(global_scope.denies().iter().filter_map(|e| e.path.clone()))
.chain(command_scope.denies().iter().filter_map(|e| e.path.clone())) .chain(command_scope.denies().iter().filter_map(|e| e.path.clone()))
.collect(), .collect(),
require_literal_leading_dot: webview.fs_scope().require_literal_leading_dot, require_literal_leading_dot: webview.fs_scope().require_literal_leading_dot,
}, },
)?; )?;
if scope.is_allowed(&path) { let fs_scope = webview.fs_scope();
let require_literal_leading_dot = fs_scope.require_literal_leading_dot.unwrap_or(cfg!(unix));
if fs_scope
.scope
.as_ref()
.map(|s| is_forbidden(s, &path, require_literal_leading_dot))
.unwrap_or(false)
|| is_forbidden(&scope, &path, require_literal_leading_dot)
{
return Err(CommandError::Plugin(Error::PathForbidden(path)));
}
if fs_scope
.scope
.as_ref()
.map(|s| s.is_allowed(&path))
.unwrap_or(false)
|| scope.is_allowed(&path)
{
Ok(path) Ok(path)
} else { } else {
Err(CommandError::Plugin(Error::PathForbidden(path))) Err(CommandError::Plugin(Error::PathForbidden(path)))
} }
} }
fn is_forbidden<P: AsRef<Path>>(
scope: &tauri::fs::Scope,
path: P,
require_literal_leading_dot: bool,
) -> bool {
let path = path.as_ref();
let path = if path.is_symlink() {
match std::fs::read_link(path) {
Ok(p) => p,
Err(_) => return false,
}
} else {
path.to_path_buf()
};
let path = if !path.exists() {
crate::Result::Ok(path)
} else {
std::fs::canonicalize(path).map_err(Into::into)
};
if let Ok(path) = path {
let path: PathBuf = path.components().collect();
scope.forbidden_patterns().iter().any(|p| {
p.matches_path_with(
&path,
glob::MatchOptions {
// this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt`
// see: <https://github.com/tauri-apps/tauri/security/advisories/GHSA-6mv3-wm7j-h4w5>
require_literal_separator: true,
require_literal_leading_dot,
..Default::default()
},
)
})
} else {
false
}
}
struct StdFileResource(Mutex<File>); struct StdFileResource(Mutex<File>);
impl StdFileResource { impl StdFileResource {

@ -0,0 +1,14 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::PathBuf;
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(PathBuf),
Object { path: PathBuf },
}

@ -15,7 +15,7 @@ use serde::Deserialize;
use tauri::{ use tauri::{
ipc::ScopeObject, ipc::ScopeObject,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
utils::acl::Value, utils::{acl::Value, config::FsScope},
AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent, AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent,
}; };
@ -23,6 +23,7 @@ mod commands;
mod config; mod config;
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
mod desktop; mod desktop;
mod entryraw;
mod error; mod error;
mod file_path; mod file_path;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -352,8 +353,8 @@ impl ScopeObject for scope::Entry {
raw: Value, raw: Value,
) -> std::result::Result<Self, Self::Error> { ) -> std::result::Result<Self, Self::Error> {
let path = serde_json::from_value(raw.into()).map(|raw| match raw { let path = serde_json::from_value(raw.into()).map(|raw| match raw {
scope::EntryRaw::Value(path) => path, entryraw::EntryRaw::Value(path) => path,
scope::EntryRaw::Object { path } => path, entryraw::EntryRaw::Object { path } => path,
})?; })?;
match app.path().parse(path) { match app.path().parse(path) {
@ -419,11 +420,13 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
watcher::unwatch watcher::unwatch
]) ])
.setup(|app, api| { .setup(|app, api| {
let mut scope = Scope::default(); let scope = Scope {
scope.require_literal_leading_dot = api require_literal_leading_dot: api
.config() .config()
.as_ref() .as_ref()
.and_then(|c| c.require_literal_leading_dot); .and_then(|c| c.require_literal_leading_dot),
scope: Some(tauri::fs::Scope::new(app, &FsScope::default())?),
};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {

@ -3,30 +3,16 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::{ use std::{
collections::HashMap, collections::HashSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{
atomic::{AtomicU32, Ordering},
Mutex,
},
}; };
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum EntryRaw {
Value(PathBuf),
Object { path: PathBuf },
}
#[derive(Debug)] #[derive(Debug)]
pub struct Entry { pub struct Entry {
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
} }
pub type EventId = u32; pub type EventId = u32;
type EventListener = Box<dyn Fn(&Event) + Send>;
/// Scope change event. /// Scope change event.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -39,10 +25,8 @@ pub enum Event {
#[derive(Default)] #[derive(Default)]
pub struct Scope { pub struct Scope {
pub(crate) allowed: Mutex<Vec<PathBuf>>, // TODO: Remove Option in v2, just used to keep Default
pub(crate) denied: Mutex<Vec<PathBuf>>, pub(crate) scope: Option<tauri::fs::Scope>,
event_listeners: Mutex<HashMap<EventId, EventListener>>,
next_event_id: AtomicU32,
pub(crate) require_literal_leading_dot: Option<bool>, pub(crate) require_literal_leading_dot: Option<bool>,
} }
@ -51,113 +35,93 @@ impl Scope {
/// ///
/// After this function has been called, the frontend will be able to use the Tauri API to read /// After this function has been called, the frontend will be able to use the Tauri API to read
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too. /// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
// TODO: Return Result
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) { pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref(); if let Some(scope) = &self.scope {
let _ = scope.allow_directory(path, recursive);
{
let mut allowed = self.allowed.lock().unwrap();
let p = path.to_string_lossy();
allowed.push(escape(&p));
allowed.push(PathBuf::from(if recursive { "**" } else { "*" }));
} }
self.emit(Event::PathAllowed(path.to_path_buf()));
} }
/// Extend the allowed patterns with the given file path. /// Extend the allowed patterns with the given file path.
/// ///
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file. /// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
// TODO: Return Result
pub fn allow_file<P: AsRef<Path>>(&self, path: P) { pub fn allow_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref(); if let Some(scope) = &self.scope {
let _ = scope.allow_file(path);
self.allowed }
.lock()
.unwrap()
.push(escape(&path.to_string_lossy()));
self.emit(Event::PathAllowed(path.to_path_buf()));
} }
/// Set the given directory path to be forbidden by this scope. /// Set the given directory path to be forbidden by this scope.
/// ///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**. /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
// TODO: Return Result
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) { pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
let path = path.as_ref(); if let Some(scope) = &self.scope {
let _ = scope.forbid_directory(path, recursive);
{
let mut denied = self.denied.lock().unwrap();
let p = path.to_string_lossy();
denied.push(escape(&p));
denied.push(PathBuf::from(if recursive { "**" } else { "*" }));
} }
self.emit(Event::PathForbidden(path.to_path_buf()));
} }
/// Set the given file path to be forbidden by this scope. /// Set the given file path to be forbidden by this scope.
/// ///
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**. /// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
// TODO: Return Result
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) { pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
let path = path.as_ref(); if let Some(scope) = &self.scope {
let _ = scope.forbid_file(path);
self.denied }
.lock()
.unwrap()
.push(escape(&path.to_string_lossy()));
self.emit(Event::PathForbidden(path.to_path_buf()));
} }
/// List of allowed paths. /// List of allowed paths.
#[deprecated(since = "2.1.0", note = "use `allowed_patterns` instead")]
pub fn allowed(&self) -> Vec<PathBuf> { pub fn allowed(&self) -> Vec<PathBuf> {
self.allowed.lock().unwrap().clone() self.scope
.as_ref()
.map(|s| s.allowed_patterns().clone())
.unwrap_or_default()
.iter()
.map(|p| PathBuf::from(p.as_str()))
.collect()
} }
/// List of forbidden paths. /// List of allowed patterns. Note that this does not include paths defined in capabilites.
pub fn forbidden(&self) -> Vec<PathBuf> { pub fn allowed_patterns(&self) -> HashSet<tauri::fs::Pattern> {
self.denied.lock().unwrap().clone() self.scope
.as_ref()
.map(|s| s.allowed_patterns().clone())
.unwrap_or_default()
} }
fn next_event_id(&self) -> u32 { /// List of forbidden paths.
self.next_event_id.fetch_add(1, Ordering::Relaxed) #[deprecated(since = "2.1.0", note = "use `forbidden_patterns` instead")]
pub fn forbidden(&self) -> Vec<PathBuf> {
self.scope
.as_ref()
.map(|s| s.forbidden_patterns().clone())
.unwrap_or_default()
.iter()
.map(|p| PathBuf::from(p.as_str()))
.collect()
} }
fn emit(&self, event: Event) { /// List of forbidden patterns. Note that this does not include paths defined in capabilites.
let listeners = self.event_listeners.lock().unwrap(); pub fn forbidden_patterns(&self) -> HashSet<tauri::fs::Pattern> {
let handlers = listeners.values(); self.scope
for listener in handlers { .as_ref()
listener(&event); .map(|s| s.forbidden_patterns())
} .unwrap_or_default()
} }
/// Listen to an event on this scope. /// Listen to an event on this scope.
/// Silently fails and returns `0` until v3 if `Scope` was constructed manually instead of getting it via `app.fs_scope()`.
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId { pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId {
let id = self.next_event_id(); if let Some(scope) = &self.scope {
self.event_listeners.lock().unwrap().insert(id, Box::new(f)); scope.listen(move |e| match e {
id tauri::fs::Event::PathAllowed(p) => f(&Event::PathAllowed(p.to_owned())),
} tauri::fs::Event::PathForbidden(p) => f(&Event::PathForbidden(p.to_owned())),
} })
} else {
// taken from https://github.com/rust-lang/glob/blob/master/src/lib.rs#L717C5-L737C6 0
/// Escape metacharacters within the given string by surrounding them in
/// brackets. The resulting string will, when compiled into a `Pattern`,
/// match the input string and nothing else.
pub fn escape(s: &str) -> PathBuf {
let mut escaped = String::new();
for c in s.chars() {
match c {
// note that ! does not need escaping because it is only special
// inside brackets
/* disabled to not break paths '?' | */
'*' | '[' | ']' => {
escaped.push('[');
escaped.push(c);
escaped.push(']');
}
c => {
escaped.push(c);
}
} }
} }
PathBuf::from(escaped)
} }

Loading…
Cancel
Save