Skip to content

Commit

Permalink
feat(core): block remote URLs from accessing the IPC
Browse files Browse the repository at this point in the history
This was cherry picked from ee71c31, keeping only the logic to block remote URLs from using the IPC.
PR: #5918
  • Loading branch information
lucasfernog committed Apr 12, 2023
1 parent e4159d4 commit 58ea0b4
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 57 deletions.
7 changes: 7 additions & 0 deletions .changes/remote-urls.md
@@ -0,0 +1,7 @@
---
"tauri": patch
"tauri-runtime": patch
"tauri-runtime-wry": patch
---

Block remote URLs from accessing the IPC.
2 changes: 1 addition & 1 deletion core/tauri-build/src/static_vcruntime.rs
Expand Up @@ -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}");
}
1 change: 1 addition & 0 deletions core/tauri-runtime-wry/Cargo.toml
Expand Up @@ -19,6 +19,7 @@ tauri-utils = { version = "1.1.1", path = "../tauri-utils" }
uuid = { version = "1", features = [ "v4" ] }
rand = "0.8"
raw-window-handle = "0.5"
url = "2"

[target."cfg(windows)".dependencies]
webview2-com = "0.19.1"
Expand Down
17 changes: 15 additions & 2 deletions core/tauri-runtime-wry/src/lib.rs
Expand Up @@ -36,6 +36,7 @@ use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
use wry::application::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};

use tauri_utils::{config::WindowConfig, debug_eprintln, Theme};
use url::Url;
use uuid::Uuid;
use wry::{
application::{
Expand Down Expand Up @@ -210,6 +211,7 @@ impl<T: UserEvent> Context<T> {
impl<T: UserEvent> Context<T> {
fn create_webview(&self, pending: PendingWindow<T, Wry<T>>) -> Result<DetachedWindow<T, Wry<T>>> {
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();
Expand All @@ -231,6 +233,7 @@ impl<T: UserEvent> Context<T> {
};
Ok(DetachedWindow {
label,
current_url,
dispatcher,
menu_ids,
js_event_listeners,
Expand Down Expand Up @@ -1864,6 +1867,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {

fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>> {
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();
Expand All @@ -1890,6 +1894,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {

Ok(DetachedWindow {
label,
current_url,
dispatcher,
menu_ids,
js_event_listeners,
Expand Down Expand Up @@ -2848,7 +2853,7 @@ fn create_webview<T: UserEvent>(
mut window_builder,
ipc_handler,
label,
url,
current_url,
menu_ids,
js_event_listeners,
..
Expand Down Expand Up @@ -2888,17 +2893,23 @@ fn create_webview<T: UserEvent>(
}
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(window_event_listeners.clone()));
}
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,
Expand Down Expand Up @@ -3002,6 +3013,7 @@ fn create_webview<T: UserEvent>(
fn create_ipc_handler<T: UserEvent>(
context: Context<T>,
label: String,
current_url: Arc<Mutex<Url>>,
menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
handler: WebviewIpcHandler<T, Wry<T>>,
Expand All @@ -3010,6 +3022,7 @@ fn create_ipc_handler<T: UserEvent>(
let window_id = context.webview_id_map.get(&window.id()).unwrap();
handler(
DetachedWindow {
current_url: current_url.clone(),
dispatcher: WryDispatcher {
window_id,
context: context.clone(),
Expand Down
1 change: 1 addition & 0 deletions core/tauri-runtime/Cargo.toml
Expand Up @@ -33,6 +33,7 @@ http-range = "0.1.4"
infer = "0.7"
raw-window-handle = "0.5"
rand = "0.8"
url = "2"

[target."cfg(windows)".dependencies]
webview2-com = "0.19.1"
Expand Down
20 changes: 15 additions & 5 deletions core/tauri-runtime/src/window.rs
Expand Up @@ -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},
Expand Down Expand Up @@ -224,14 +225,17 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
/// How to handle IPC calls on the webview window.
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,

/// The resolved URL to load on the webview.
pub url: String,

/// Maps runtime id to a string menu id.
pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,

/// A HashMap mapping JS event names with associated listener ids.
pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,

/// A handler to decide if incoming url is allowed to navigate.
pub navigation_handler: Option<Box<dyn Fn(Url) -> bool + Send>>,

/// The current webview URL.
pub current_url: Arc<Mutex<Url>>,
}

pub fn is_label_valid(label: &str) -> bool {
Expand Down Expand Up @@ -268,9 +272,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
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())),
})
}
}
Expand All @@ -297,9 +302,10 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
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())),
})
}
}
Expand Down Expand Up @@ -340,6 +346,9 @@ pub struct JsEventListenerKey {
/// A webview window that is not yet managed by Tauri.
#[derive(Debug)]
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
/// The current webview URL.
pub current_url: Arc<Mutex<Url>>,

/// Name of the window
pub label: String,

Expand All @@ -356,6 +365,7 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
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(),
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/src/app.rs
Expand Up @@ -948,7 +948,7 @@ impl<R: Runtime> Builder<R> {
#[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(),
Expand Down
89 changes: 52 additions & 37 deletions core/tauri/src/manager.rs
Expand Up @@ -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},
Expand All @@ -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};
Expand Down Expand Up @@ -139,7 +139,7 @@ fn set_csp<R: Runtime>(
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()
Expand Down Expand Up @@ -231,7 +231,7 @@ pub struct InnerWindowManager<R: Runtime> {
/// The script that initializes the invoke system.
invoke_initialization_script: String,
/// Application pattern.
pattern: Pattern,
pub(crate) pattern: Pattern,
}

impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
Expand Down Expand Up @@ -367,9 +367,12 @@ impl<R: Runtime> WindowManager<R> {
/// 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()),
}
}
Expand Down Expand Up @@ -477,7 +480,7 @@ impl<R: Runtime> WindowManager<R> {
});
}

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())
Expand Down Expand Up @@ -1011,7 +1014,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/");
Expand Down Expand Up @@ -1062,27 +1074,21 @@ impl<R: Runtime> WindowManager<R> {
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!(),
};

Expand All @@ -1109,7 +1115,7 @@ impl<R: Runtime> WindowManager<R> {
}
}

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() {
Expand All @@ -1125,17 +1131,15 @@ impl<R: Runtime> WindowManager<R> {
}
}

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
Expand All @@ -1160,6 +1164,17 @@ impl<R: Runtime> WindowManager<R> {
}
}

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)
}

Expand Down
7 changes: 5 additions & 2 deletions core/tauri/src/pattern.rs
Expand Up @@ -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<A: Assets = EmbeddedAssets> {
Expand Down Expand Up @@ -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}")
}
}

0 comments on commit 58ea0b4

Please sign in to comment.