mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-17 03:40:23 +03:00
Merge pull request from GHSA-57fm-592m-34r7
* only allow tauri-initialized frames to call the ipc * dont regenerate the invoke key * return early if the invoke key does not match * add change file * wry 0.40 * update change file * trigger ci --------- Co-authored-by: Chip Reed <chip@chip.sh> Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
parent
beda18bce9
commit
d950ac1239
6
.changes/ipc-only-main-frame.md
Normal file
6
.changes/ipc-only-main-frame.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'tauri': patch:sec
|
||||||
|
'tauri-runtime-wry': patch:sec
|
||||||
|
---
|
||||||
|
|
||||||
|
Only process IPC commands from the main frame.
|
@ -2,7 +2,16 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
;(function () {
|
;(function() {
|
||||||
|
/**
|
||||||
|
* A runtime generated key to ensure an IPC call comes from an initialized frame.
|
||||||
|
*
|
||||||
|
* This is declared outside the `window.__TAURI_INVOKE__` definition to prevent
|
||||||
|
* the key from being leaked by `window.__TAURI_INVOKE__.toString()`.
|
||||||
|
* @var {string} __TEMPLATE_invoke_key__
|
||||||
|
*/
|
||||||
|
const __TAURI_INVOKE_KEY__ = __TEMPLATE_invoke_key__
|
||||||
|
|
||||||
const processIpcMessage = __RAW_process_ipc_message_fn__
|
const processIpcMessage = __RAW_process_ipc_message_fn__
|
||||||
const osName = __TEMPLATE_os_name__
|
const osName = __TEMPLATE_os_name__
|
||||||
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
|
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
|
||||||
@ -29,6 +38,7 @@
|
|||||||
'Content-Type': contentType,
|
'Content-Type': contentType,
|
||||||
'Tauri-Callback': callback,
|
'Tauri-Callback': callback,
|
||||||
'Tauri-Error': error,
|
'Tauri-Error': error,
|
||||||
|
'Tauri-Invoke-Key': __TAURI_INVOKE_KEY__,
|
||||||
...((options && options.headers) || {})
|
...((options && options.headers) || {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -66,7 +76,8 @@
|
|||||||
callback,
|
callback,
|
||||||
error,
|
error,
|
||||||
options,
|
options,
|
||||||
payload
|
payload,
|
||||||
|
__TAURI_INVOKE_KEY__
|
||||||
})
|
})
|
||||||
window.ipc.postMessage(data)
|
window.ipc.postMessage(data)
|
||||||
}
|
}
|
||||||
|
@ -1097,6 +1097,8 @@ pub struct Builder<R: Runtime> {
|
|||||||
|
|
||||||
/// The device event filter.
|
/// The device event filter.
|
||||||
device_event_filter: DeviceEventFilter,
|
device_event_filter: DeviceEventFilter,
|
||||||
|
|
||||||
|
invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@ -1108,6 +1110,7 @@ struct InvokeInitializationScript<'a> {
|
|||||||
os_name: &'a str,
|
os_name: &'a str,
|
||||||
fetch_channel_data_command: &'a str,
|
fetch_channel_data_command: &'a str,
|
||||||
linux_ipc_protocol_enabled: bool,
|
linux_ipc_protocol_enabled: bool,
|
||||||
|
invoke_key: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make `Wry` the default `Runtime` for `Builder`
|
/// Make `Wry` the default `Runtime` for `Builder`
|
||||||
@ -1130,6 +1133,8 @@ impl<R: Runtime> Default for Builder<R> {
|
|||||||
impl<R: Runtime> Builder<R> {
|
impl<R: Runtime> Builder<R> {
|
||||||
/// Creates a new App builder.
|
/// Creates a new App builder.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let invoke_key = crate::generate_invoke_key().unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
#[cfg(any(windows, target_os = "linux"))]
|
#[cfg(any(windows, target_os = "linux"))]
|
||||||
runtime_any_thread: false,
|
runtime_any_thread: false,
|
||||||
@ -1141,6 +1146,7 @@ impl<R: Runtime> Builder<R> {
|
|||||||
os_name: std::env::consts::OS,
|
os_name: std::env::consts::OS,
|
||||||
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
|
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
|
||||||
linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"),
|
linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"),
|
||||||
|
invoke_key: &invoke_key.clone(),
|
||||||
}
|
}
|
||||||
.render_default(&Default::default())
|
.render_default(&Default::default())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -1155,6 +1161,7 @@ impl<R: Runtime> Builder<R> {
|
|||||||
window_event_listeners: Vec::new(),
|
window_event_listeners: Vec::new(),
|
||||||
webview_event_listeners: Vec::new(),
|
webview_event_listeners: Vec::new(),
|
||||||
device_event_filter: Default::default(),
|
device_event_filter: Default::default(),
|
||||||
|
invoke_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1622,6 +1629,7 @@ tauri::Builder::default()
|
|||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
(self.invoke_responder, self.invoke_initialization_script),
|
(self.invoke_responder, self.invoke_initialization_script),
|
||||||
|
self.invoke_key,
|
||||||
));
|
));
|
||||||
|
|
||||||
let runtime_args = RuntimeInitArgs {
|
let runtime_args = RuntimeInitArgs {
|
||||||
|
@ -151,10 +151,21 @@ pub enum Error {
|
|||||||
/// Failed to deserialize scope object.
|
/// Failed to deserialize scope object.
|
||||||
#[error("error deserializing scope: {0}")]
|
#[error("error deserializing scope: {0}")]
|
||||||
CannotDeserializeScope(Box<dyn std::error::Error + Send + Sync>),
|
CannotDeserializeScope(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
|
||||||
/// Failed to get a raw handle.
|
/// Failed to get a raw handle.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RawHandleError(#[from] raw_window_handle::HandleError),
|
RawHandleError(#[from] raw_window_handle::HandleError),
|
||||||
|
/// Something went wrong with the CSPRNG.
|
||||||
|
#[error("unable to generate random bytes from the operating system: {0}")]
|
||||||
|
Csprng(getrandom::Error),
|
||||||
|
/// Bad `__TAURI_INVOKE_KEY__` value received in ipc message.
|
||||||
|
#[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")]
|
||||||
|
InvokeKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<getrandom::Error> for Error {
|
||||||
|
fn from(value: getrandom::Error) -> Self {
|
||||||
|
Self::Csprng(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Result<T, ::tauri::Error>`
|
/// `Result<T, ::tauri::Error>`
|
||||||
|
@ -19,6 +19,7 @@ use super::{CallbackFn, InvokeBody, InvokeResponse};
|
|||||||
|
|
||||||
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
|
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
|
||||||
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
|
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
|
||||||
|
const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key";
|
||||||
|
|
||||||
pub fn message_handler<R: Runtime>(
|
pub fn message_handler<R: Runtime>(
|
||||||
manager: Arc<AppManager<R>>,
|
manager: Arc<AppManager<R>>,
|
||||||
@ -210,6 +211,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
|||||||
error: CallbackFn,
|
error: CallbackFn,
|
||||||
payload: serde_json::Value,
|
payload: serde_json::Value,
|
||||||
options: Option<RequestOptions>,
|
options: Option<RequestOptions>,
|
||||||
|
#[serde(rename = "__TAURI_INVOKE_KEY__")]
|
||||||
|
invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
@ -224,6 +227,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
|||||||
error: CallbackFn,
|
error: CallbackFn,
|
||||||
payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
|
payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
|
||||||
options: Option<RequestOptions>,
|
options: Option<RequestOptions>,
|
||||||
|
#[serde(rename = "__TAURI_INVOKE_KEY__")]
|
||||||
|
invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
|
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
|
||||||
@ -240,6 +245,7 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
|||||||
error: message.error,
|
error: message.error,
|
||||||
payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
|
payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
|
||||||
options: message.options,
|
options: message.options,
|
||||||
|
invoke_key: message.invoke_key,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -261,6 +267,7 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
|||||||
url: Url::parse(&request.uri().to_string()).expect("invalid IPC request URL"),
|
url: Url::parse(&request.uri().to_string()).expect("invalid IPC request URL"),
|
||||||
body: message.payload.into(),
|
body: message.payload.into(),
|
||||||
headers: message.options.map(|o| o.headers.0).unwrap_or_default(),
|
headers: message.options.map(|o| o.headers.0).unwrap_or_default(),
|
||||||
|
invoke_key: message.invoke_key,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
@ -394,6 +401,14 @@ fn parse_invoke_request<R: Runtime>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invoke_key = parts
|
||||||
|
.headers
|
||||||
|
.get(TAURI_INVOKE_KEY_HEADER_NAME)
|
||||||
|
.ok_or("missing Tauri-Invoke-Key header")?
|
||||||
|
.to_str()
|
||||||
|
.map_err(|_| "Tauri invoke key header value must be a string")?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
let url = Url::parse(
|
let url = Url::parse(
|
||||||
parts
|
parts
|
||||||
.headers
|
.headers
|
||||||
@ -461,6 +476,7 @@ fn parse_invoke_request<R: Runtime>(
|
|||||||
url,
|
url,
|
||||||
body,
|
body,
|
||||||
headers: parts.headers,
|
headers: parts.headers,
|
||||||
|
invoke_key,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(payload)
|
Ok(payload)
|
||||||
|
@ -1092,3 +1092,52 @@ mod test_utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple dependency-free string encoder using [Z85].
|
||||||
|
mod z85 {
|
||||||
|
const TABLE: &[u8; 85] =
|
||||||
|
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
|
||||||
|
|
||||||
|
/// Encode bytes with [Z85].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if the input bytes are not a multiple of 4.
|
||||||
|
pub fn encode(bytes: &[u8]) -> String {
|
||||||
|
assert_eq!(bytes.len() % 4, 0);
|
||||||
|
|
||||||
|
let mut buf = String::with_capacity(bytes.len() * 5 / 4);
|
||||||
|
for chunk in bytes.chunks_exact(4) {
|
||||||
|
let mut chars = [0u8; 5];
|
||||||
|
let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;
|
||||||
|
for byte in chars.iter_mut().rev() {
|
||||||
|
*byte = TABLE[chunk % 85];
|
||||||
|
chunk /= 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(std::str::from_utf8(&chars).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn encode() {
|
||||||
|
assert_eq!(
|
||||||
|
super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),
|
||||||
|
"HelloWorld"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a random 128-bit [Z85] encoded [`String`].
|
||||||
|
///
|
||||||
|
/// [Z85]: https://rfc.zeromq.org/spec/32/
|
||||||
|
pub(crate) fn generate_invoke_key() -> Result<String> {
|
||||||
|
let mut bytes = [0u8; 16];
|
||||||
|
getrandom::getrandom(&mut bytes)?;
|
||||||
|
Ok(z85::encode(&bytes))
|
||||||
|
}
|
||||||
|
@ -193,6 +193,9 @@ pub struct AppManager<R: Runtime> {
|
|||||||
|
|
||||||
/// Application Resources Table
|
/// Application Resources Table
|
||||||
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
|
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
|
||||||
|
|
||||||
|
/// Runtime-generated invoke key.
|
||||||
|
pub(crate) invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> fmt::Debug for AppManager<R> {
|
impl<R: Runtime> fmt::Debug for AppManager<R> {
|
||||||
@ -232,6 +235,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
crate::app::GlobalMenuEventListener<Window<R>>,
|
crate::app::GlobalMenuEventListener<Window<R>>,
|
||||||
>,
|
>,
|
||||||
(invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
|
(invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
|
||||||
|
invoke_key: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// generate a random isolation key at runtime
|
// generate a random isolation key at runtime
|
||||||
#[cfg(feature = "isolation")]
|
#[cfg(feature = "isolation")]
|
||||||
@ -254,6 +258,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
event_listeners: Arc::new(webiew_event_listeners),
|
event_listeners: Arc::new(webiew_event_listeners),
|
||||||
invoke_responder,
|
invoke_responder,
|
||||||
invoke_initialization_script,
|
invoke_initialization_script,
|
||||||
|
invoke_key: invoke_key.clone(),
|
||||||
},
|
},
|
||||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||||
tray: tray::TrayManager {
|
tray: tray::TrayManager {
|
||||||
@ -279,6 +284,7 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
pattern: Arc::new(context.pattern),
|
pattern: Arc::new(context.pattern),
|
||||||
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
|
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
|
||||||
resources_table: Arc::default(),
|
resources_table: Arc::default(),
|
||||||
|
invoke_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,6 +576,10 @@ impl<R: Runtime> AppManager<R> {
|
|||||||
.lock()
|
.lock()
|
||||||
.expect("poisoned window manager")
|
.expect("poisoned window manager")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invoke_key(&self) -> &str {
|
||||||
|
&self.invoke_key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
|
@ -89,6 +89,9 @@ pub struct WebviewManager<R: Runtime> {
|
|||||||
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||||
/// The script that initializes the invoke system.
|
/// The script that initializes the invoke system.
|
||||||
pub invoke_initialization_script: String,
|
pub invoke_initialization_script: String,
|
||||||
|
|
||||||
|
/// A runtime generated invoke key.
|
||||||
|
pub(crate) invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
||||||
@ -98,6 +101,7 @@ impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
|||||||
"invoke_initialization_script",
|
"invoke_initialization_script",
|
||||||
&self.invoke_initialization_script,
|
&self.invoke_initialization_script,
|
||||||
)
|
)
|
||||||
|
.field("invoke_key", &self.invoke_key)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,6 +375,7 @@ impl<R: Runtime> WebviewManager<R> {
|
|||||||
#[default_template("../../scripts/core.js")]
|
#[default_template("../../scripts/core.js")]
|
||||||
struct CoreJavascript<'a> {
|
struct CoreJavascript<'a> {
|
||||||
os_name: &'a str,
|
os_name: &'a str,
|
||||||
|
invoke_key: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
let bundle_script = if with_global_tauri {
|
let bundle_script = if with_global_tauri {
|
||||||
@ -391,6 +396,7 @@ impl<R: Runtime> WebviewManager<R> {
|
|||||||
bundle_script,
|
bundle_script,
|
||||||
core_script: &CoreJavascript {
|
core_script: &CoreJavascript {
|
||||||
os_name: std::env::consts::OS,
|
os_name: std::env::consts::OS,
|
||||||
|
invoke_key: self.invoke_key(),
|
||||||
}
|
}
|
||||||
.render_default(&Default::default())?
|
.render_default(&Default::default())?
|
||||||
.into_string(),
|
.into_string(),
|
||||||
@ -660,6 +666,10 @@ impl<R: Runtime> WebviewManager<R> {
|
|||||||
pub fn labels(&self) -> HashSet<String> {
|
pub fn labels(&self) -> HashSet<String> {
|
||||||
self.webviews_lock().keys().cloned().collect()
|
self.webviews_lock().keys().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invoke_key(&self) -> &str {
|
||||||
|
&self.invoke_key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> Webview<R> {
|
impl<R: Runtime> Webview<R> {
|
||||||
|
@ -124,6 +124,7 @@ pub struct InvokeRequest {
|
|||||||
pub body: InvokeBody,
|
pub body: InvokeBody,
|
||||||
/// The request headers.
|
/// The request headers.
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
|
pub(crate) invoke_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The platform webview handle. Accessed with [`Webview#method.with_webview`];
|
/// The platform webview handle. Accessed with [`Webview#method.with_webview`];
|
||||||
@ -1132,6 +1133,24 @@ fn main() {
|
|||||||
let manager = self.manager_owned();
|
let manager = self.manager_owned();
|
||||||
let is_local = self.is_local_url(&request.url);
|
let is_local = self.is_local_url(&request.url);
|
||||||
|
|
||||||
|
// ensure the passed key matches what our manager should have injected
|
||||||
|
let expected = manager.invoke_key();
|
||||||
|
if request.invoke_key != expected {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::error!(
|
||||||
|
"__TAURI_INVOKE_KEY__ expected {expected} but received {}",
|
||||||
|
request.invoke_key
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
eprintln!(
|
||||||
|
"__TAURI_INVOKE_KEY__ expected {expected} but received {}",
|
||||||
|
request.invoke_key
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let custom_responder = self.manager().webview.invoke_responder.clone();
|
let custom_responder = self.manager().webview.invoke_responder.clone();
|
||||||
|
|
||||||
let resolver = InvokeResolver::new(
|
let resolver = InvokeResolver::new(
|
||||||
|
Loading…
Reference in New Issue
Block a user