mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-17 11:50:26 +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.
|
@ -3,6 +3,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
;(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 osName = __TEMPLATE_os_name__
|
||||
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
|
||||
@ -29,6 +38,7 @@
|
||||
'Content-Type': contentType,
|
||||
'Tauri-Callback': callback,
|
||||
'Tauri-Error': error,
|
||||
'Tauri-Invoke-Key': __TAURI_INVOKE_KEY__,
|
||||
...((options && options.headers) || {})
|
||||
}
|
||||
})
|
||||
@ -66,7 +76,8 @@
|
||||
callback,
|
||||
error,
|
||||
options,
|
||||
payload
|
||||
payload,
|
||||
__TAURI_INVOKE_KEY__
|
||||
})
|
||||
window.ipc.postMessage(data)
|
||||
}
|
||||
|
@ -1097,6 +1097,8 @@ pub struct Builder<R: Runtime> {
|
||||
|
||||
/// The device event filter.
|
||||
device_event_filter: DeviceEventFilter,
|
||||
|
||||
invoke_key: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
@ -1108,6 +1110,7 @@ struct InvokeInitializationScript<'a> {
|
||||
os_name: &'a str,
|
||||
fetch_channel_data_command: &'a str,
|
||||
linux_ipc_protocol_enabled: bool,
|
||||
invoke_key: &'a str,
|
||||
}
|
||||
|
||||
/// Make `Wry` the default `Runtime` for `Builder`
|
||||
@ -1130,6 +1133,8 @@ impl<R: Runtime> Default for Builder<R> {
|
||||
impl<R: Runtime> Builder<R> {
|
||||
/// Creates a new App builder.
|
||||
pub fn new() -> Self {
|
||||
let invoke_key = crate::generate_invoke_key().unwrap();
|
||||
|
||||
Self {
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
runtime_any_thread: false,
|
||||
@ -1141,6 +1146,7 @@ impl<R: Runtime> Builder<R> {
|
||||
os_name: std::env::consts::OS,
|
||||
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
|
||||
linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"),
|
||||
invoke_key: &invoke_key.clone(),
|
||||
}
|
||||
.render_default(&Default::default())
|
||||
.unwrap()
|
||||
@ -1155,6 +1161,7 @@ impl<R: Runtime> Builder<R> {
|
||||
window_event_listeners: Vec::new(),
|
||||
webview_event_listeners: Vec::new(),
|
||||
device_event_filter: Default::default(),
|
||||
invoke_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1622,6 +1629,7 @@ tauri::Builder::default()
|
||||
#[cfg(desktop)]
|
||||
HashMap::new(),
|
||||
(self.invoke_responder, self.invoke_initialization_script),
|
||||
self.invoke_key,
|
||||
));
|
||||
|
||||
let runtime_args = RuntimeInitArgs {
|
||||
|
@ -151,10 +151,21 @@ pub enum Error {
|
||||
/// Failed to deserialize scope object.
|
||||
#[error("error deserializing scope: {0}")]
|
||||
CannotDeserializeScope(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
/// Failed to get a raw handle.
|
||||
#[error(transparent)]
|
||||
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>`
|
||||
|
@ -19,6 +19,7 @@ use super::{CallbackFn, InvokeBody, InvokeResponse};
|
||||
|
||||
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
|
||||
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
|
||||
const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key";
|
||||
|
||||
pub fn message_handler<R: Runtime>(
|
||||
manager: Arc<AppManager<R>>,
|
||||
@ -210,6 +211,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
||||
error: CallbackFn,
|
||||
payload: serde_json::Value,
|
||||
options: Option<RequestOptions>,
|
||||
#[serde(rename = "__TAURI_INVOKE_KEY__")]
|
||||
invoke_key: String,
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
@ -224,6 +227,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
|
||||
error: CallbackFn,
|
||||
payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
|
||||
options: Option<RequestOptions>,
|
||||
#[serde(rename = "__TAURI_INVOKE_KEY__")]
|
||||
invoke_key: String,
|
||||
}
|
||||
|
||||
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,
|
||||
payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
|
||||
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"),
|
||||
body: message.payload.into(),
|
||||
headers: message.options.map(|o| o.headers.0).unwrap_or_default(),
|
||||
invoke_key: message.invoke_key,
|
||||
};
|
||||
|
||||
#[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(
|
||||
parts
|
||||
.headers
|
||||
@ -461,6 +476,7 @@ fn parse_invoke_request<R: Runtime>(
|
||||
url,
|
||||
body,
|
||||
headers: parts.headers,
|
||||
invoke_key,
|
||||
};
|
||||
|
||||
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
|
||||
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
|
||||
|
||||
/// Runtime-generated invoke key.
|
||||
pub(crate) invoke_key: String,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for AppManager<R> {
|
||||
@ -232,6 +235,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
crate::app::GlobalMenuEventListener<Window<R>>,
|
||||
>,
|
||||
(invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
|
||||
invoke_key: String,
|
||||
) -> Self {
|
||||
// generate a random isolation key at runtime
|
||||
#[cfg(feature = "isolation")]
|
||||
@ -254,6 +258,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
event_listeners: Arc::new(webiew_event_listeners),
|
||||
invoke_responder,
|
||||
invoke_initialization_script,
|
||||
invoke_key: invoke_key.clone(),
|
||||
},
|
||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||
tray: tray::TrayManager {
|
||||
@ -279,6 +284,7 @@ impl<R: Runtime> AppManager<R> {
|
||||
pattern: Arc::new(context.pattern),
|
||||
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
|
||||
resources_table: Arc::default(),
|
||||
invoke_key,
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,6 +576,10 @@ impl<R: Runtime> AppManager<R> {
|
||||
.lock()
|
||||
.expect("poisoned window manager")
|
||||
}
|
||||
|
||||
pub(crate) fn invoke_key(&self) -> &str {
|
||||
&self.invoke_key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
|
@ -89,6 +89,9 @@ pub struct WebviewManager<R: Runtime> {
|
||||
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
|
||||
/// The script that initializes the invoke system.
|
||||
pub invoke_initialization_script: String,
|
||||
|
||||
/// A runtime generated invoke key.
|
||||
pub(crate) invoke_key: String,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
||||
@ -98,6 +101,7 @@ impl<R: Runtime> fmt::Debug for WebviewManager<R> {
|
||||
"invoke_initialization_script",
|
||||
&self.invoke_initialization_script,
|
||||
)
|
||||
.field("invoke_key", &self.invoke_key)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -371,6 +375,7 @@ impl<R: Runtime> WebviewManager<R> {
|
||||
#[default_template("../../scripts/core.js")]
|
||||
struct CoreJavascript<'a> {
|
||||
os_name: &'a str,
|
||||
invoke_key: &'a str,
|
||||
}
|
||||
|
||||
let bundle_script = if with_global_tauri {
|
||||
@ -391,6 +396,7 @@ impl<R: Runtime> WebviewManager<R> {
|
||||
bundle_script,
|
||||
core_script: &CoreJavascript {
|
||||
os_name: std::env::consts::OS,
|
||||
invoke_key: self.invoke_key(),
|
||||
}
|
||||
.render_default(&Default::default())?
|
||||
.into_string(),
|
||||
@ -660,6 +666,10 @@ impl<R: Runtime> WebviewManager<R> {
|
||||
pub fn labels(&self) -> HashSet<String> {
|
||||
self.webviews_lock().keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn invoke_key(&self) -> &str {
|
||||
&self.invoke_key
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Webview<R> {
|
||||
|
@ -124,6 +124,7 @@ pub struct InvokeRequest {
|
||||
pub body: InvokeBody,
|
||||
/// The request headers.
|
||||
pub headers: HeaderMap,
|
||||
pub(crate) invoke_key: String,
|
||||
}
|
||||
|
||||
/// The platform webview handle. Accessed with [`Webview#method.with_webview`];
|
||||
@ -1132,6 +1133,24 @@ fn main() {
|
||||
let manager = self.manager_owned();
|
||||
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 resolver = InvokeResolver::new(
|
||||
|
Loading…
Reference in New Issue
Block a user