tauri_runtime_cef/environment.rs
1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! The diagnostic environment variables Chromium and CEF read behind the application's
6//! back, and the policy that decides whether they are honoured.
7//!
8//! Chromium is a browser, and a browser is expected to let whoever runs it capture its
9//! own traffic and redirect its own crash reports. An application that embeds Chromium
10//! inherits those hooks without asking for them, and in a shipped application they are
11//! not developer conveniences any more: anyone who can set a variable in the
12//! application's environment can decrypt every TLS session it makes, or point its
13//! minidumps — which carry process memory — at a server of their choosing.
14//!
15//! [`DebugEnvironment`] is the switch. Neither variable group is reachable through a CEF
16//! setting, so each is answered where Chromium reads it.
17//!
18//! # TLS key logging is answered on the command line
19//!
20//! `content/browser/network_service_instance_impl.cc` consults `SSLKEYLOGFILE` only when
21//! `--ssl-key-log-file` is absent, and an empty switch value logs a warning and creates no
22//! key logger. Appending the empty switch is therefore a complete answer that needs no
23//! change to the process environment, and the runtime appends it only when the variable is
24//! actually set — so the warning appears exactly when somebody was trying to log keys.
25//!
26//! # The crash reporter overrides are answered in the environment
27//!
28//! CEF reads its three crash variables from `BasicStartupComplete`, before any hook the
29//! embedder can install, so there is nothing to append and the variables have to go. That
30//! write is [`std::env::remove_var`], which is why it happens once, during runtime
31//! initialization, and only for a variable that is actually set.
32
33/// Whether Chromium and CEF may read their diagnostic environment variables.
34#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum DebugEnvironment {
37 /// Honour them in development builds (`tauri::is_dev()`), refuse them in release
38 /// builds.
39 #[default]
40 Auto,
41 /// Always honour them.
42 ///
43 /// Appropriate for a build whose users are expected to debug it — an internal tool, a
44 /// QA build — and wrong for one handed to the public.
45 Allow,
46 /// Always refuse them, in every build profile.
47 Deny,
48}
49
50/// The environment variable whose answer is the `--ssl-key-log-file` switch.
51///
52/// The network service writes the pre-master secret of every TLS session to the file it
53/// names, which is exactly what a packet capture needs to decrypt the application's
54/// traffic in full.
55const SSL_KEY_LOG_FILE: &str = "SSLKEYLOGFILE";
56
57/// CEF's crash reporter overrides, and what each one does.
58///
59/// * `CEF_CRASH_REPORTER_SERVER_URL` — replaces the crash report upload URL from
60/// `crash_reporter.cfg`. A minidump carries stack and heap memory, so a redirected URL
61/// is a memory exfiltration channel.
62/// * `CEF_CRASH_REPORTER_RATE_LIMIT_ENABLED` — lifts the upload rate limit that keeps a
63/// crash loop from becoming a flood.
64/// * `BREAKPAD_DUMP_LOCATION` — redirects where minidumps are written on Windows.
65///
66/// All three are only consulted when the application ships a `crash_reporter.cfg`, so
67/// removing them costs nothing when it does not.
68const CRASH_REPORTER_VARIABLES: &[(&str, &str)] = &[
69 (
70 "CEF_CRASH_REPORTER_SERVER_URL",
71 "redirecting crash report uploads, which carry process memory",
72 ),
73 (
74 "CEF_CRASH_REPORTER_RATE_LIMIT_ENABLED",
75 "overriding the crash report upload rate limit",
76 ),
77 (
78 "BREAKPAD_DUMP_LOCATION",
79 "redirecting where minidumps are written",
80 ),
81];
82
83/// Whether `policy` refuses the variables in a build where `is_dev` says what it says.
84///
85/// Kept separate from acting on the answer so the decision can be unit tested without
86/// touching the process environment.
87fn refuses_debug_variables(policy: DebugEnvironment, is_dev: bool) -> bool {
88 match policy {
89 DebugEnvironment::Auto => !is_dev,
90 DebugEnvironment::Allow => false,
91 DebugEnvironment::Deny => true,
92 }
93}
94
95/// Whether a variable is set to something Chromium would act on.
96fn is_set(name: &str) -> bool {
97 std::env::var_os(name).is_some_and(|value| !value.is_empty())
98}
99
100/// Whether the runtime should append an empty `--ssl-key-log-file`, which is how
101/// `SSLKEYLOGFILE` is refused.
102///
103/// Answers `false` when the variable is not set, so the switch — and the warning Chromium
104/// logs for it — only appears when something was actually asking for a key log.
105pub(crate) fn neutralizes_tls_key_log(policy: DebugEnvironment, is_dev: bool) -> bool {
106 if !refuses_debug_variables(policy, is_dev) || !is_set(SSL_KEY_LOG_FILE) {
107 return false;
108 }
109
110 log::warn!(
111 "ignoring the {SSL_KEY_LOG_FILE} environment variable: it asks Chromium to log the TLS \
112 session keys that decrypt this application's network traffic. Set \
113 DebugEnvironment::Allow to honour it."
114 );
115 true
116}
117
118/// Removes CEF's crash reporter overrides from the process environment when `policy`
119/// refuses them.
120///
121/// Must run before the first CEF call: CEF reads these from
122/// `ChromeMainDelegateCef::BasicStartupComplete`, which `cef::initialize` reaches, and
123/// child processes inherit the environment of the browser process that spawned them, so a
124/// variable removed here is gone from the whole process tree.
125///
126/// # Safety
127///
128/// Calls [`std::env::remove_var`], which is unsound while another thread reads or writes
129/// the environment concurrently. The caller must be the runtime's own initialization,
130/// which runs on the main thread before CEF exists and before this runtime starts a
131/// thread of its own.
132pub(crate) fn remove_crash_reporter_overrides(policy: DebugEnvironment, is_dev: bool) {
133 if !refuses_debug_variables(policy, is_dev) {
134 return;
135 }
136
137 for (name, effect) in CRASH_REPORTER_VARIABLES {
138 if !is_set(name) {
139 continue;
140 }
141
142 log::warn!(
143 "ignoring the {name} environment variable: it asks CEF for {effect}. Set \
144 DebugEnvironment::Allow to honour it."
145 );
146 // SAFETY: documented on this function; the runtime applies the policy from its own
147 // initialization, before CEF exists and before it starts a thread of its own.
148 unsafe { std::env::remove_var(name) };
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn auto_follows_the_build_profile() {
158 assert!(
159 !refuses_debug_variables(DebugEnvironment::Auto, true),
160 "a development build keeps its debugging hooks"
161 );
162 assert!(
163 refuses_debug_variables(DebugEnvironment::Auto, false),
164 "a shipped build must not hand its TLS keys to whoever sets a variable"
165 );
166 }
167
168 #[test]
169 fn explicit_policies_ignore_the_build_profile() {
170 for is_dev in [false, true] {
171 assert!(!refuses_debug_variables(DebugEnvironment::Allow, is_dev));
172 assert!(refuses_debug_variables(DebugEnvironment::Deny, is_dev));
173 }
174 }
175
176 #[test]
177 fn a_permissive_policy_never_touches_the_command_line() {
178 // Asserted without depending on the ambient environment: `Allow` short-circuits
179 // before the variable is even read.
180 assert!(!neutralizes_tls_key_log(DebugEnvironment::Allow, false));
181 assert!(!neutralizes_tls_key_log(DebugEnvironment::Auto, true));
182 }
183
184 #[test]
185 fn every_crash_variable_is_described() {
186 // The name and the effect both go into a warning the user reads.
187 for (name, effect) in CRASH_REPORTER_VARIABLES {
188 assert!(!name.is_empty());
189 assert!(!effect.is_empty());
190 }
191 }
192
193 #[test]
194 fn the_tls_variable_is_not_also_removed_from_the_environment() {
195 // It is answered on the command line instead, which needs no environment write.
196 assert!(
197 !CRASH_REPORTER_VARIABLES
198 .iter()
199 .any(|(name, _)| *name == SSL_KEY_LOG_FILE)
200 );
201 }
202}