From fa90214b052b1a5d38d54fbf1ca422b4c37cfd1f Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 12 Apr 2023 11:43:57 -0300 Subject: [PATCH] feat(core): block remote URLs from accessing the IPC This was cherry picked from ee71c31fd09cc5427da6d29d37c003a914547696, keeping only the logic to block remote URLs from using the IPC. PR: #5918 --- .changes/remote-urls.md | 7 ++ core/tauri-build/src/static_vcruntime.rs | 2 +- core/tauri-runtime-wry/Cargo.toml | 1 + core/tauri-runtime-wry/src/lib.rs | 17 ++++- core/tauri-runtime/Cargo.toml | 1 + core/tauri-runtime/src/window.rs | 20 ++++-- core/tauri/src/app.rs | 2 +- core/tauri/src/manager.rs | 89 ++++++++++++++---------- core/tauri/src/pattern.rs | 7 +- core/tauri/src/test/mock_runtime.rs | 2 + core/tauri/src/window.rs | 21 +++++- examples/api/src-tauri/Cargo.lock | 16 +++-- 12 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 .changes/remote-urls.md diff --git a/.changes/remote-urls.md b/.changes/remote-urls.md new file mode 100644 index 00000000000..71d094cd97a --- /dev/null +++ b/.changes/remote-urls.md @@ -0,0 +1,7 @@ +--- +"tauri": patch +"tauri-runtime": patch +"tauri-runtime-wry": patch +--- + +Block remote URLs from accessing the IPC. diff --git a/core/tauri-build/src/static_vcruntime.rs b/core/tauri-build/src/static_vcruntime.rs index 11d2752b90a..39159ad3a66 100644 --- a/core/tauri-build/src/static_vcruntime.rs +++ b/core/tauri-build/src/static_vcruntime.rs @@ -54,5 +54,5 @@ fn override_msvcrt_lib() { f.write_all(bytes).unwrap(); } // Add the output directory to the native library path. - println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-search=native={out_dir}"); } diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 59c90d68825..c335695cf6a 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -19,6 +19,7 @@ tauri-utils = { version = "1.0.3", path = "../tauri-utils" } uuid = { version = "1", features = [ "v4" ] } rand = "0.8" raw-window-handle = "0.4.3" +url = "2" [target."cfg(windows)".dependencies] webview2-com = "0.16.0" diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 13186edbeab..8950547f514 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -41,6 +41,7 @@ use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWind use wry::application::system_tray::{SystemTray as WrySystemTray, SystemTrayBuilder}; use tauri_utils::{config::WindowConfig, debug_eprintln, Theme}; +use url::Url; use uuid::Uuid; use wry::{ application::{ @@ -216,6 +217,7 @@ impl Context { impl Context { fn create_webview(&self, pending: PendingWindow>) -> Result>> { let label = pending.label.clone(); + let current_url = pending.current_url.clone(); let menu_ids = pending.menu_ids.clone(); let js_event_listeners = pending.js_event_listeners.clone(); let context = self.clone(); @@ -237,6 +239,7 @@ impl Context { }; Ok(DetachedWindow { label, + current_url, dispatcher, menu_ids, js_event_listeners, @@ -1862,6 +1865,7 @@ impl Runtime for Wry { fn create_window(&self, pending: PendingWindow) -> Result> { let label = pending.label.clone(); + let current_url = pending.current_url.clone(); let menu_ids = pending.menu_ids.clone(); let js_event_listeners = pending.js_event_listeners.clone(); let window_id = rand::random(); @@ -1889,6 +1893,7 @@ impl Runtime for Wry { Ok(DetachedWindow { label, + current_url, dispatcher, menu_ids, js_event_listeners, @@ -2833,7 +2838,7 @@ fn create_webview( mut window_builder, ipc_handler, label, - url, + current_url, menu_ids, js_event_listeners, .. @@ -2871,16 +2876,22 @@ fn create_webview( } let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| Error::CreateWebview(Box::new(e)))? - .with_url(&url) + .with_url(current_url.lock().unwrap().as_str()) .unwrap() // safe to unwrap because we validate the URL beforehand .with_transparent(is_window_transparent); if webview_attributes.file_drop_handler_enabled { webview_builder = webview_builder.with_file_drop_handler(create_file_drop_handler(&context)); } + if let Some(navigation_handler) = pending.navigation_handler { + webview_builder = webview_builder.with_navigation_handler(move |url| { + Url::parse(&url).map(&navigation_handler).unwrap_or(true) + }); + } if let Some(handler) = ipc_handler { webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( context, label.clone(), + current_url, menu_ids, js_event_listeners, handler, @@ -2984,6 +2995,7 @@ fn create_webview( fn create_ipc_handler( context: Context, label: String, + current_url: Arc>, menu_ids: Arc>>, js_event_listeners: Arc>>>, handler: WebviewIpcHandler>, @@ -2992,6 +3004,7 @@ fn create_ipc_handler( let window_id = context.webview_id_map.get(&window.id()); handler( DetachedWindow { + current_url: current_url.clone(), dispatcher: WryDispatcher { window_id, context: context.clone(), diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index 72d9d8a94a3..145002557a5 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -32,6 +32,7 @@ http = "0.2.4" http-range = "0.1.4" infer = "0.7" raw-window-handle = "0.4.3" +url = "2" [target."cfg(windows)".dependencies] webview2-com = "0.16.0" diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 3a6becd0c0c..6c7528fbe9a 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -12,6 +12,7 @@ use crate::{ }; use serde::{Deserialize, Deserializer, Serialize}; use tauri_utils::{config::WindowConfig, Theme}; +use url::Url; use std::{ collections::{HashMap, HashSet}, @@ -226,14 +227,17 @@ pub struct PendingWindow> { /// How to handle IPC calls on the webview window. pub ipc_handler: Option>, - /// The resolved URL to load on the webview. - pub url: String, - /// Maps runtime id to a string menu id. pub menu_ids: Arc>>, /// A HashMap mapping JS event names with associated listener ids. pub js_event_listeners: Arc>>>, + + /// A handler to decide if incoming url is allowed to navigate. + pub navigation_handler: Option bool + Send>>, + + /// The current webview URL. + pub current_url: Arc>, } pub fn is_label_valid(label: &str) -> bool { @@ -270,9 +274,10 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - url: "tauri://localhost".to_string(), menu_ids: Arc::new(Mutex::new(menu_ids)), js_event_listeners: Default::default(), + navigation_handler: Default::default(), + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), }) } } @@ -299,9 +304,10 @@ impl> PendingWindow { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - url: "tauri://localhost".to_string(), menu_ids: Arc::new(Mutex::new(menu_ids)), js_event_listeners: Default::default(), + navigation_handler: Default::default(), + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), }) } } @@ -342,6 +348,9 @@ pub struct JsEventListenerKey { /// A webview window that is not yet managed by Tauri. #[derive(Debug)] pub struct DetachedWindow> { + /// The current webview URL. + pub current_url: Arc>, + /// Name of the window pub label: String, @@ -358,6 +367,7 @@ pub struct DetachedWindow> { impl> Clone for DetachedWindow { fn clone(&self) -> Self { Self { + current_url: self.current_url.clone(), label: self.label.clone(), dispatcher: self.dispatcher.clone(), menu_ids: self.menu_ids.clone(), diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 1f02fd9e617..f6ab57bc253 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -902,7 +902,7 @@ impl Builder { #[cfg(any(windows, target_os = "linux"))] runtime_any_thread: false, setup: Box::new(|_| Ok(())), - invoke_handler: Box::new(|_| ()), + invoke_handler: Box::new(|invoke| invoke.resolver.reject("not implemented")), invoke_responder: Arc::new(window_invoke_responder), invoke_initialization_script: "Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(), diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 34846714bf2..15521061fc5 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -25,10 +25,9 @@ use tauri_utils::{ html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; -use crate::hooks::IpcJavascript; #[cfg(feature = "isolation")] use crate::hooks::IsolationJavascript; -use crate::pattern::{format_real_schema, PatternJavascript}; +use crate::pattern::PatternJavascript; use crate::{ app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener}, event::{assert_event_name_is_valid, Event, EventHandler, Listeners}, @@ -54,6 +53,7 @@ use crate::{ app::{GlobalMenuEventListener, WindowMenuEvent}, window::WebResourceRequestHandler, }; +use crate::{hooks::IpcJavascript, pattern::format_real_schema}; #[cfg(any(target_os = "linux", target_os = "windows"))] use crate::api::path::{resolve_path, BaseDirectory}; @@ -136,7 +136,7 @@ fn set_csp( let default_src = csp .entry("default-src".into()) .or_insert_with(Default::default); - default_src.push(format_real_schema(schema)); + default_src.push(crate::pattern::format_real_schema(schema)); } Csp::DirectiveMap(csp).to_string() @@ -225,7 +225,7 @@ pub struct InnerWindowManager { /// The script that initializes the invoke system. invoke_initialization_script: String, /// Application pattern. - pattern: Pattern, + pub(crate) pattern: Pattern, } impl fmt::Debug for InnerWindowManager { @@ -357,9 +357,12 @@ impl WindowManager { /// Get the base URL to use for webview requests. /// /// In dev mode, this will be based on the `devPath` configuration value. - fn get_url(&self) -> Cow<'_, Url> { + pub(crate) fn get_url(&self) -> Cow<'_, Url> { match self.base_path() { AppUrl::Url(WindowUrl::External(url)) => Cow::Borrowed(url), + #[cfg(windows)] + _ => Cow::Owned(Url::parse("https://tauri.localhost").unwrap()), + #[cfg(not(windows))] _ => Cow::Owned(Url::parse("tauri://localhost").unwrap()), } } @@ -467,7 +470,7 @@ impl WindowManager { }); } - let window_url = Url::parse(&pending.url).unwrap(); + let window_url = pending.current_url.lock().unwrap().clone(); let window_origin = if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" { format!("https://{}.localhost", window_url.scheme()) @@ -1001,7 +1004,16 @@ mod test { ); #[cfg(custom_protocol)] - assert_eq!(manager.get_url().to_string(), "tauri://localhost"); + { + assert_eq!( + manager.get_url().to_string(), + if cfg!(windows) { + "https://tauri.localhost/" + } else { + "tauri://localhost" + } + ); + } #[cfg(dev)] assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); @@ -1052,27 +1064,21 @@ impl WindowManager { return Err(crate::Error::WindowLabelAlreadyExists(pending.label)); } #[allow(unused_mut)] // mut url only for the data-url parsing - let (is_local, mut url) = match &pending.webview_attributes.url { + let mut url = match &pending.webview_attributes.url { WindowUrl::App(path) => { let url = self.get_url(); - ( - true, - // ignore "index.html" just to simplify the url - if path.to_str() != Some("index.html") { - url - .join(&*path.to_string_lossy()) - .map_err(crate::Error::InvalidUrl) - // this will never fail - .unwrap() - } else { - url.into_owned() - }, - ) - } - WindowUrl::External(url) => { - let config_url = self.get_url(); - (config_url.make_relative(url).is_some(), url.clone()) + // ignore "index.html" just to simplify the url + if path.to_str() != Some("index.html") { + url + .join(&*path.to_string_lossy()) + .map_err(crate::Error::InvalidUrl) + // this will never fail + .unwrap() + } else { + url.into_owned() + } } + WindowUrl::External(url) => url.clone(), _ => unimplemented!(), }; @@ -1099,7 +1105,7 @@ impl WindowManager { } } - pending.url = url.to_string(); + *pending.current_url.lock().unwrap() = url; if !pending.window_builder.has_icon() { if let Some(default_window_icon) = self.inner.default_window_icon.clone() { @@ -1115,17 +1121,15 @@ impl WindowManager { } } - if is_local { - let label = pending.label.clone(); - pending = self.prepare_pending_window( - pending, - &label, - window_labels, - app_handle.clone(), - web_resource_request_handler, - )?; - pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle)); - } + let label = pending.label.clone(); + pending = self.prepare_pending_window( + pending, + &label, + window_labels, + app_handle.clone(), + web_resource_request_handler, + )?; + pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle)); // in `Windows`, we need to force a data_directory // but we do respect user-specification @@ -1150,6 +1154,17 @@ impl WindowManager { } } + let current_url_ = pending.current_url.clone(); + let navigation_handler = pending.navigation_handler.take(); + pending.navigation_handler = Some(Box::new(move |url| { + *current_url_.lock().unwrap() = url.clone(); + if let Some(handler) = &navigation_handler { + handler(url) + } else { + true + } + })); + Ok(pending) } diff --git a/core/tauri/src/pattern.rs b/core/tauri/src/pattern.rs index 19b3713033e..e78bc2b8b66 100644 --- a/core/tauri/src/pattern.rs +++ b/core/tauri/src/pattern.rs @@ -11,6 +11,9 @@ use serialize_to_javascript::{default_template, Template}; use tauri_utils::assets::{Assets, EmbeddedAssets}; +/// The domain of the isolation iframe source. +pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost"; + /// An application pattern. #[derive(Debug, Clone)] pub enum Pattern { @@ -87,8 +90,8 @@ pub(crate) struct PatternJavascript { #[allow(dead_code)] pub(crate) fn format_real_schema(schema: &str) -> String { if cfg!(windows) { - format!("https://{}.localhost", schema) + format!("https://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") } else { - format!("{}://localhost", schema) + format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}") } } diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index f51f957a516..4fc072a1a37 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -67,6 +67,7 @@ impl RuntimeHandle for MockRuntimeHandle { ) -> Result> { Ok(DetachedWindow { label: pending.label, + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), dispatcher: MockDispatcher { context: self.context.clone(), }, @@ -616,6 +617,7 @@ impl Runtime for MockRuntime { fn create_window(&self, pending: PendingWindow) -> Result> { Ok(DetachedWindow { label: pending.label, + current_url: Arc::new(Mutex::new("tauri://localhost".parse().unwrap())), dispatcher: MockDispatcher { context: self.context.clone(), }, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 23d9144bdef..d5d9f75eaaa 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -33,6 +33,7 @@ use crate::{ }; use serde::Serialize; +use url::Url; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -483,7 +484,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { #[derive(Debug)] pub struct Window { /// The webview window created by the runtime. - window: DetachedWindow, + pub(crate) window: DetachedWindow, /// The manager to associate this webview window with. manager: WindowManager, pub(crate) app_handle: AppHandle, @@ -1157,9 +1158,18 @@ impl Window { /// Webview APIs. impl Window { + /// Returns the current url of the webview. + pub fn url(&self) -> Url { + self.window.current_url.lock().unwrap().clone() + } + /// Handles this window receiving an [`InvokeMessage`]. pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> { let manager = self.manager.clone(); + let current_url = self.url(); + let config_url = manager.get_url(); + let is_local = config_url.make_relative(¤t_url).is_some(); + match payload.cmd.as_str() { "__initialized" => { let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; @@ -1173,8 +1183,15 @@ impl Window { payload.inner, ); let resolver = InvokeResolver::new(self, payload.callback, payload.error); - let invoke = Invoke { message, resolver }; + + if !is_local { + invoke + .resolver + .reject("Remote URLs are not allowed to access the IPC"); + return Ok(()); + } + if let Some(module) = &payload.tauri_module { crate::endpoints::handle( module.to_string(), diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 2afcba228ac..f1ebd586bd4 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3132,7 +3132,7 @@ dependencies = [ [[package]] name = "tauri" -version = "1.0.2" +version = "1.0.8" dependencies = [ "anyhow", "attohttpc", @@ -3194,7 +3194,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "1.0.2" +version = "1.0.4" dependencies = [ "anyhow", "cargo_toml", @@ -3209,7 +3209,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.0.2" +version = "1.0.4" dependencies = [ "base64", "brotli", @@ -3233,7 +3233,7 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "1.0.2" +version = "1.0.4" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -3245,7 +3245,7 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.10.1" +version = "0.10.2" dependencies = [ "gtk", "http", @@ -3256,6 +3256,7 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", + "url", "uuid 1.1.2", "webview2-com", "windows 0.37.0", @@ -3263,7 +3264,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "0.10.1" +version = "0.10.2" dependencies = [ "cocoa", "gtk", @@ -3272,6 +3273,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", + "url", "uuid 1.1.2", "webkit2gtk", "webview2-com", @@ -3281,7 +3283,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.0.2" +version = "1.0.3" dependencies = [ "aes-gcm", "brotli",