Skip to content

Commit d950ac1

Browse files
tweidingerchipperslucasfernog
authored
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>
1 parent beda18b commit d950ac1

File tree

9 files changed

+143
-3
lines changed

9 files changed

+143
-3
lines changed

.changes/ipc-only-main-frame.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'tauri': patch:sec
3+
'tauri-runtime-wry': patch:sec
4+
---
5+
6+
Only process IPC commands from the main frame.

core/tauri/scripts/ipc-protocol.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
;(function () {
5+
;(function() {
6+
/**
7+
* A runtime generated key to ensure an IPC call comes from an initialized frame.
8+
*
9+
* This is declared outside the `window.__TAURI_INVOKE__` definition to prevent
10+
* the key from being leaked by `window.__TAURI_INVOKE__.toString()`.
11+
* @var {string} __TEMPLATE_invoke_key__
12+
*/
13+
const __TAURI_INVOKE_KEY__ = __TEMPLATE_invoke_key__
14+
615
const processIpcMessage = __RAW_process_ipc_message_fn__
716
const osName = __TEMPLATE_os_name__
817
const fetchChannelDataCommand = __TEMPLATE_fetch_channel_data_command__
@@ -29,6 +38,7 @@
2938
'Content-Type': contentType,
3039
'Tauri-Callback': callback,
3140
'Tauri-Error': error,
41+
'Tauri-Invoke-Key': __TAURI_INVOKE_KEY__,
3242
...((options && options.headers) || {})
3343
}
3444
})
@@ -66,7 +76,8 @@
6676
callback,
6777
error,
6878
options,
69-
payload
79+
payload,
80+
__TAURI_INVOKE_KEY__
7081
})
7182
window.ipc.postMessage(data)
7283
}

core/tauri/src/app.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,8 @@ pub struct Builder<R: Runtime> {
10971097

10981098
/// The device event filter.
10991099
device_event_filter: DeviceEventFilter,
1100+
1101+
invoke_key: String,
11001102
}
11011103

11021104
#[derive(Template)]
@@ -1108,6 +1110,7 @@ struct InvokeInitializationScript<'a> {
11081110
os_name: &'a str,
11091111
fetch_channel_data_command: &'a str,
11101112
linux_ipc_protocol_enabled: bool,
1113+
invoke_key: &'a str,
11111114
}
11121115

11131116
/// Make `Wry` the default `Runtime` for `Builder`
@@ -1130,6 +1133,8 @@ impl<R: Runtime> Default for Builder<R> {
11301133
impl<R: Runtime> Builder<R> {
11311134
/// Creates a new App builder.
11321135
pub fn new() -> Self {
1136+
let invoke_key = crate::generate_invoke_key().unwrap();
1137+
11331138
Self {
11341139
#[cfg(any(windows, target_os = "linux"))]
11351140
runtime_any_thread: false,
@@ -1141,6 +1146,7 @@ impl<R: Runtime> Builder<R> {
11411146
os_name: std::env::consts::OS,
11421147
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
11431148
linux_ipc_protocol_enabled: cfg!(feature = "linux-ipc-protocol"),
1149+
invoke_key: &invoke_key.clone(),
11441150
}
11451151
.render_default(&Default::default())
11461152
.unwrap()
@@ -1155,6 +1161,7 @@ impl<R: Runtime> Builder<R> {
11551161
window_event_listeners: Vec::new(),
11561162
webview_event_listeners: Vec::new(),
11571163
device_event_filter: Default::default(),
1164+
invoke_key,
11581165
}
11591166
}
11601167
}
@@ -1622,6 +1629,7 @@ tauri::Builder::default()
16221629
#[cfg(desktop)]
16231630
HashMap::new(),
16241631
(self.invoke_responder, self.invoke_initialization_script),
1632+
self.invoke_key,
16251633
));
16261634

16271635
let runtime_args = RuntimeInitArgs {

core/tauri/src/error.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,21 @@ pub enum Error {
151151
/// Failed to deserialize scope object.
152152
#[error("error deserializing scope: {0}")]
153153
CannotDeserializeScope(Box<dyn std::error::Error + Send + Sync>),
154-
155154
/// Failed to get a raw handle.
156155
#[error(transparent)]
157156
RawHandleError(#[from] raw_window_handle::HandleError),
157+
/// Something went wrong with the CSPRNG.
158+
#[error("unable to generate random bytes from the operating system: {0}")]
159+
Csprng(getrandom::Error),
160+
/// Bad `__TAURI_INVOKE_KEY__` value received in ipc message.
161+
#[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")]
162+
InvokeKey,
163+
}
164+
165+
impl From<getrandom::Error> for Error {
166+
fn from(value: getrandom::Error) -> Self {
167+
Self::Csprng(value)
168+
}
158169
}
159170

160171
/// `Result<T, ::tauri::Error>`

core/tauri/src/ipc/protocol.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use super::{CallbackFn, InvokeBody, InvokeResponse};
1919

2020
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
2121
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
22+
const TAURI_INVOKE_KEY_HEADER_NAME: &str = "Tauri-Invoke-Key";
2223

2324
pub fn message_handler<R: Runtime>(
2425
manager: Arc<AppManager<R>>,
@@ -210,6 +211,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
210211
error: CallbackFn,
211212
payload: serde_json::Value,
212213
options: Option<RequestOptions>,
214+
#[serde(rename = "__TAURI_INVOKE_KEY__")]
215+
invoke_key: String,
213216
}
214217

215218
#[allow(unused_mut)]
@@ -224,6 +227,8 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
224227
error: CallbackFn,
225228
payload: crate::utils::pattern::isolation::RawIsolationPayload<'a>,
226229
options: Option<RequestOptions>,
230+
#[serde(rename = "__TAURI_INVOKE_KEY__")]
231+
invoke_key: String,
227232
}
228233

229234
if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern {
@@ -240,6 +245,7 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
240245
error: message.error,
241246
payload: serde_json::from_slice(&crypto_keys.decrypt(message.payload)?)?,
242247
options: message.options,
248+
invoke_key: message.invoke_key,
243249
})
244250
}),
245251
);
@@ -261,6 +267,7 @@ fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager
261267
url: Url::parse(&request.uri().to_string()).expect("invalid IPC request URL"),
262268
body: message.payload.into(),
263269
headers: message.options.map(|o| o.headers.0).unwrap_or_default(),
270+
invoke_key: message.invoke_key,
264271
};
265272

266273
#[cfg(feature = "tracing")]
@@ -394,6 +401,14 @@ fn parse_invoke_request<R: Runtime>(
394401
}
395402
}
396403

404+
let invoke_key = parts
405+
.headers
406+
.get(TAURI_INVOKE_KEY_HEADER_NAME)
407+
.ok_or("missing Tauri-Invoke-Key header")?
408+
.to_str()
409+
.map_err(|_| "Tauri invoke key header value must be a string")?
410+
.to_owned();
411+
397412
let url = Url::parse(
398413
parts
399414
.headers
@@ -461,6 +476,7 @@ fn parse_invoke_request<R: Runtime>(
461476
url,
462477
body,
463478
headers: parts.headers,
479+
invoke_key,
464480
};
465481

466482
Ok(payload)

core/tauri/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,3 +1092,52 @@ mod test_utils {
10921092
}
10931093
}
10941094
}
1095+
1096+
/// Simple dependency-free string encoder using [Z85].
1097+
mod z85 {
1098+
const TABLE: &[u8; 85] =
1099+
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
1100+
1101+
/// Encode bytes with [Z85].
1102+
///
1103+
/// # Panics
1104+
///
1105+
/// Will panic if the input bytes are not a multiple of 4.
1106+
pub fn encode(bytes: &[u8]) -> String {
1107+
assert_eq!(bytes.len() % 4, 0);
1108+
1109+
let mut buf = String::with_capacity(bytes.len() * 5 / 4);
1110+
for chunk in bytes.chunks_exact(4) {
1111+
let mut chars = [0u8; 5];
1112+
let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;
1113+
for byte in chars.iter_mut().rev() {
1114+
*byte = TABLE[chunk % 85];
1115+
chunk /= 85;
1116+
}
1117+
1118+
buf.push_str(std::str::from_utf8(&chars).unwrap());
1119+
}
1120+
1121+
buf
1122+
}
1123+
1124+
#[cfg(test)]
1125+
mod tests {
1126+
#[test]
1127+
fn encode() {
1128+
assert_eq!(
1129+
super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),
1130+
"HelloWorld"
1131+
);
1132+
}
1133+
}
1134+
}
1135+
1136+
/// Generate a random 128-bit [Z85] encoded [`String`].
1137+
///
1138+
/// [Z85]: https://rfc.zeromq.org/spec/32/
1139+
pub(crate) fn generate_invoke_key() -> Result<String> {
1140+
let mut bytes = [0u8; 16];
1141+
getrandom::getrandom(&mut bytes)?;
1142+
Ok(z85::encode(&bytes))
1143+
}

core/tauri/src/manager/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ pub struct AppManager<R: Runtime> {
193193

194194
/// Application Resources Table
195195
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
196+
197+
/// Runtime-generated invoke key.
198+
pub(crate) invoke_key: String,
196199
}
197200

198201
impl<R: Runtime> fmt::Debug for AppManager<R> {
@@ -232,6 +235,7 @@ impl<R: Runtime> AppManager<R> {
232235
crate::app::GlobalMenuEventListener<Window<R>>,
233236
>,
234237
(invoke_responder, invoke_initialization_script): (Option<Arc<InvokeResponder<R>>>, String),
238+
invoke_key: String,
235239
) -> Self {
236240
// generate a random isolation key at runtime
237241
#[cfg(feature = "isolation")]
@@ -254,6 +258,7 @@ impl<R: Runtime> AppManager<R> {
254258
event_listeners: Arc::new(webiew_event_listeners),
255259
invoke_responder,
256260
invoke_initialization_script,
261+
invoke_key: invoke_key.clone(),
257262
},
258263
#[cfg(all(desktop, feature = "tray-icon"))]
259264
tray: tray::TrayManager {
@@ -279,6 +284,7 @@ impl<R: Runtime> AppManager<R> {
279284
pattern: Arc::new(context.pattern),
280285
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
281286
resources_table: Arc::default(),
287+
invoke_key,
282288
}
283289
}
284290

@@ -570,6 +576,10 @@ impl<R: Runtime> AppManager<R> {
570576
.lock()
571577
.expect("poisoned window manager")
572578
}
579+
580+
pub(crate) fn invoke_key(&self) -> &str {
581+
&self.invoke_key
582+
}
573583
}
574584

575585
#[cfg(desktop)]

core/tauri/src/manager/webview.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ pub struct WebviewManager<R: Runtime> {
8989
pub invoke_responder: Option<Arc<InvokeResponder<R>>>,
9090
/// The script that initializes the invoke system.
9191
pub invoke_initialization_script: String,
92+
93+
/// A runtime generated invoke key.
94+
pub(crate) invoke_key: String,
9295
}
9396

9497
impl<R: Runtime> fmt::Debug for WebviewManager<R> {
@@ -98,6 +101,7 @@ impl<R: Runtime> fmt::Debug for WebviewManager<R> {
98101
"invoke_initialization_script",
99102
&self.invoke_initialization_script,
100103
)
104+
.field("invoke_key", &self.invoke_key)
101105
.finish()
102106
}
103107
}
@@ -371,6 +375,7 @@ impl<R: Runtime> WebviewManager<R> {
371375
#[default_template("../../scripts/core.js")]
372376
struct CoreJavascript<'a> {
373377
os_name: &'a str,
378+
invoke_key: &'a str,
374379
}
375380

376381
let bundle_script = if with_global_tauri {
@@ -391,6 +396,7 @@ impl<R: Runtime> WebviewManager<R> {
391396
bundle_script,
392397
core_script: &CoreJavascript {
393398
os_name: std::env::consts::OS,
399+
invoke_key: self.invoke_key(),
394400
}
395401
.render_default(&Default::default())?
396402
.into_string(),
@@ -660,6 +666,10 @@ impl<R: Runtime> WebviewManager<R> {
660666
pub fn labels(&self) -> HashSet<String> {
661667
self.webviews_lock().keys().cloned().collect()
662668
}
669+
670+
pub(crate) fn invoke_key(&self) -> &str {
671+
&self.invoke_key
672+
}
663673
}
664674

665675
impl<R: Runtime> Webview<R> {

core/tauri/src/webview/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub struct InvokeRequest {
124124
pub body: InvokeBody,
125125
/// The request headers.
126126
pub headers: HeaderMap,
127+
pub(crate) invoke_key: String,
127128
}
128129

129130
/// The platform webview handle. Accessed with [`Webview#method.with_webview`];
@@ -1132,6 +1133,24 @@ fn main() {
11321133
let manager = self.manager_owned();
11331134
let is_local = self.is_local_url(&request.url);
11341135

1136+
// ensure the passed key matches what our manager should have injected
1137+
let expected = manager.invoke_key();
1138+
if request.invoke_key != expected {
1139+
#[cfg(feature = "tracing")]
1140+
tracing::error!(
1141+
"__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1142+
request.invoke_key
1143+
);
1144+
1145+
#[cfg(not(feature = "tracing"))]
1146+
eprintln!(
1147+
"__TAURI_INVOKE_KEY__ expected {expected} but received {}",
1148+
request.invoke_key
1149+
);
1150+
1151+
return;
1152+
}
1153+
11351154
let custom_responder = self.manager().webview.invoke_responder.clone();
11361155

11371156
let resolver = InvokeResolver::new(

0 commit comments

Comments
 (0)