Skip to content

Commit

Permalink
Merge pull request from GHSA-57fm-592m-34r7
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored May 22, 2024
1 parent beda18b commit d950ac1
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .changes/ipc-only-main-frame.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'tauri': patch:sec
'tauri-runtime-wry': patch:sec
---

Only process IPC commands from the main frame.
15 changes: 13 additions & 2 deletions core/tauri/scripts/ipc-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
// 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 osName = __TEMPLATE_os_name__
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
Expand All @@ -29,6 +38,7 @@
'Content-Type': contentType,
'Tauri-Callback': callback,
'Tauri-Error': error,
'Tauri-Invoke-Key': __TAURI_INVOKE_KEY__,
...((options && options.headers) || {})
}
})
Expand Down Expand Up @@ -66,7 +76,8 @@
callback,
error,
options,
payload
payload,
__TAURI_INVOKE_KEY__
})
window.ipc.postMessage(data)
}
Expand Down
8 changes: 8 additions & 0 deletions core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,8 @@ pub struct Builder<R: Runtime> {

/// The device event filter.
device_event_filter: DeviceEventFilter,

invoke_key: String,
}

#[derive(Template)]
Expand All @@ -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`
Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 12 additions & 1 deletion core/tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>`
Expand Down
16 changes: 16 additions & 0 deletions core/tauri/src/ipc/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand Down Expand Up @@ -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)]
Expand All @@ -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 {
Expand All @@ -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,
})
}),
);
Expand All @@ -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")]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -461,6 +476,7 @@ fn parse_invoke_request<R: Runtime>(
url,
body,
headers: parts.headers,
invoke_key,
};

Ok(payload)
Expand Down
49 changes: 49 additions & 0 deletions core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
10 changes: 10 additions & 0 deletions core/tauri/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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")]
Expand All @@ -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 {
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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)]
Expand Down
10 changes: 10 additions & 0 deletions core/tauri/src/manager/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
Expand Down Expand Up @@ -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> {
Expand Down
19 changes: 19 additions & 0 deletions core/tauri/src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`];
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit d950ac1

Please sign in to comment.