From ebb2eb2fe2ebfbb70530d16a983d396aa5829aa1 Mon Sep 17 00:00:00 2001 From: Fabian-Lars Date: Wed, 26 Apr 2023 12:37:23 +0200 Subject: [PATCH] fix(persisted-scope): Prevent out-of-memory issues, fixes #274 (#328) * fix(persisted-scope): Unescape paths before saving to disk. * fix cfg flags * dedupe code * unescape when reading to try to fix existing files * add more patterns. only fix pattern on app start. don't check if pattern is allowed already. * remove dbg log * typo * remove unused imports * clippy * fix compilation with asset-protocol feature flag enabled * update patterns * manually re-save state once * add changefile * remove dbg print --- .changes/persisted-scope-fix-oom.md | 5 ++ Cargo.lock | 1 + plugins/persisted-scope/Cargo.toml | 1 + plugins/persisted-scope/src/lib.rs | 97 ++++++++++++++++++++--------- 4 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 .changes/persisted-scope-fix-oom.md diff --git a/.changes/persisted-scope-fix-oom.md b/.changes/persisted-scope-fix-oom.md new file mode 100644 index 000000000..6e6520aff --- /dev/null +++ b/.changes/persisted-scope-fix-oom.md @@ -0,0 +1,5 @@ +--- +persisted-scope: patch +--- + +Recursively unescape saved patterns before allowing/forbidding them. This effectively prevents `.persisted-scope` files from blowing up, which caused Out-Of-Memory issues, while automatically fixing existing broken files seamlessly. diff --git a/Cargo.lock b/Cargo.lock index 05dc5a93b..fe5c76bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4368,6 +4368,7 @@ dependencies = [ name = "tauri-plugin-persisted-scope" version = "0.1.0" dependencies = [ + "aho-corasick", "bincode", "log", "serde", diff --git a/plugins/persisted-scope/Cargo.toml b/plugins/persisted-scope/Cargo.toml index 7bc30228d..fa6784c20 100644 --- a/plugins/persisted-scope/Cargo.toml +++ b/plugins/persisted-scope/Cargo.toml @@ -15,6 +15,7 @@ serde_json.workspace = true tauri.workspace = true log.workspace = true thiserror.workspace = true +aho-corasick = "0.7" bincode = "1" [features] diff --git a/plugins/persisted-scope/src/lib.rs b/plugins/persisted-scope/src/lib.rs index ef0625bab..b7ac5dde8 100644 --- a/plugins/persisted-scope/src/lib.rs +++ b/plugins/persisted-scope/src/lib.rs @@ -2,19 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use aho_corasick::AhoCorasick; use serde::{Deserialize, Serialize}; use tauri::{ plugin::{Builder, TauriPlugin}, - FsScopeEvent, Manager, Runtime, + AppHandle, FsScopeEvent, Manager, Runtime, }; use std::{ fs::{create_dir_all, File}, io::Write, + path::Path, }; const SCOPE_STATE_FILENAME: &str = ".persisted-scope"; +// Most of these patterns are just added to try to fix broken files in the wild. +// After a while we can hopefully reduce it to something like [r"[?]", r"[*]", r"\\?\\\?\"] +const PATTERNS: &[&str] = &[ + r"[[]", + r"[]]", + r"[?]", + r"[*]", + r"\?\?", + r"\\?\\?\", + r"\\?\\\?\", +]; +const REPLACE_WITH: &[&str] = &[r"[", r"]", r"?", r"*", r"\?", r"\\?\", r"\\?\"]; + #[derive(Debug, thiserror::Error)] enum Error { #[error(transparent)] @@ -33,6 +48,41 @@ struct Scope { forbidden_patterns: Vec, } +fn fix_pattern(ac: &AhoCorasick, s: &str) -> String { + let s = ac.replace_all(s, REPLACE_WITH); + + if ac.find(&s).is_some() { + return fix_pattern(ac, &s); + } + + s +} + +fn save_scopes(app: &AppHandle, app_dir: &Path, scope_state_path: &Path) { + let fs_scope = app.fs_scope(); + + let scope = Scope { + allowed_paths: fs_scope + .allowed_patterns() + .into_iter() + .map(|p| p.to_string()) + .collect(), + forbidden_patterns: fs_scope + .forbidden_patterns() + .into_iter() + .map(|p| p.to_string()) + .collect(), + }; + + let _ = create_dir_all(app_dir) + .and_then(|_| File::create(scope_state_path)) + .map_err(Error::Io) + .and_then(|mut f| { + f.write_all(&bincode::serialize(&scope).map_err(Error::from)?) + .map_err(Into::into) + }); +} + pub fn init() -> TauriPlugin { Builder::new("persisted-scope") .setup(|app| { @@ -49,49 +99,38 @@ pub fn init() -> TauriPlugin { #[cfg(feature = "protocol-asset")] let _ = asset_protocol_scope.forbid_file(&scope_state_path); + // We're trying to fix broken .persisted-scope files seamlessly, so we'll be running this on the values read on the saved file. + // We will still save some semi-broken values because the scope events are quite spammy and we don't want to reduce runtime performance any further. + let ac = AhoCorasick::new_auto_configured(PATTERNS); + if scope_state_path.exists() { let scope: Scope = tauri::api::file::read_binary(&scope_state_path) .map_err(Error::from) .and_then(|scope| bincode::deserialize(&scope).map_err(Into::into)) .unwrap_or_default(); for allowed in &scope.allowed_paths { - // allows the path as is - let _ = fs_scope.allow_file(allowed); + let allowed = fix_pattern(&ac, allowed); + + let _ = fs_scope.allow_file(&allowed); #[cfg(feature = "protocol-asset")] - let _ = asset_protocol_scope.allow_file(allowed); + let _ = asset_protocol_scope.allow_file(&allowed); } for forbidden in &scope.forbidden_patterns { - // forbid the path as is - let _ = fs_scope.forbid_file(forbidden); + let forbidden = fix_pattern(&ac, forbidden); + + let _ = fs_scope.forbid_file(&forbidden); #[cfg(feature = "protocol-asset")] - let _ = asset_protocol_scope.forbid_file(forbidden); + let _ = asset_protocol_scope.forbid_file(&forbidden); } + + // Manually save the fixed scopes to disk once. + // This is needed to fix broken .peristed-scope files in case the app doesn't update the scope itself. + save_scopes(&app, &app_dir, &scope_state_path); } fs_scope.listen(move |event| { - let fs_scope = app.fs_scope(); if let FsScopeEvent::PathAllowed(_) = event { - let scope = Scope { - allowed_paths: fs_scope - .allowed_patterns() - .into_iter() - .map(|p| p.to_string()) - .collect(), - forbidden_patterns: fs_scope - .forbidden_patterns() - .into_iter() - .map(|p| p.to_string()) - .collect(), - }; - let scope_state_path = scope_state_path.clone(); - - let _ = create_dir_all(&app_dir) - .and_then(|_| File::create(scope_state_path)) - .map_err(Error::Io) - .and_then(|mut f| { - f.write_all(&bincode::serialize(&scope).map_err(Error::from)?) - .map_err(Into::into) - }); + save_scopes(&app, &app_dir, &scope_state_path); } }); }