Skip to content

Commit 96eae42

Browse files
committed
/proc/cmdline is more robust to pass WSL envs than using the container interface
1 parent 95faf81 commit 96eae42

File tree

1 file changed

+79
-17
lines changed

1 file changed

+79
-17
lines changed

distrod/libs/src/distro.rs

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ffi::{OsStr, OsString};
55
use std::fs::{self, File};
66
use std::io::{BufReader, BufWriter, Write};
77
use std::os::linux::fs::MetadataExt;
8-
use std::os::unix::prelude::CommandExt;
8+
use std::os::unix::prelude::{CommandExt, OsStrExt};
99
use std::path::{Path, PathBuf};
1010
use std::process::Command;
1111

@@ -44,6 +44,8 @@ impl DistroLauncher {
4444
};
4545
set_wsl_interop_envs_in_system_envs(&mut distro_launcher)
4646
.with_context(|| "failed to set up WSL interop env vars")?;
47+
mount_kernelcmdline_with_wsl_interop_envs_for_systemd(&mut distro_launcher)
48+
.with_context(|| "Failed to mount the custom /proc/cmdline")?;
4749
set_per_user_wsl_envs(&mut distro_launcher)
4850
.with_context(|| "failed to mount WSL environment variables init script.")?;
4951
mount_slash_run_static_files(&mut distro_launcher)
@@ -211,39 +213,92 @@ impl DistroLauncher {
211213
}
212214

213215
fn set_wsl_interop_envs_in_system_envs(distro_launcher: &mut DistroLauncher) -> Result<()> {
214-
// Be careful to collect only harmless environment variables.
216+
for (key, value) in collect_safe_wsl_interop_envs()
217+
.with_context(|| "Failed to collect safe WSL interop envs")?
218+
{
219+
distro_launcher.with_system_env(
220+
key.to_string_lossy().to_string(),
221+
value.to_string_lossy().to_string(),
222+
);
223+
distro_launcher
224+
.container_launcher
225+
.with_init_arg(&env_to_systemd_setenv_arg(key, value));
226+
}
227+
Ok(())
228+
}
229+
230+
fn mount_kernelcmdline_with_wsl_interop_envs_for_systemd(
231+
distro_launcher: &mut DistroLauncher,
232+
) -> Result<()> {
233+
let cmdline_overwrite_path = get_cmdline_overwrite_path()
234+
.with_context(|| "Failed to get the /proc/cmdline overwrite path.")?;
235+
std::fs::write(
236+
cmdline_overwrite_path.as_path(),
237+
get_cmdline_with_wsl_interop_envs_for_systemd("/proc/cmdline")
238+
.with_context(|| "Failed to generate the contents of new /proc/cmdline")?,
239+
)
240+
.with_context(|| format!("Failed to write to {:?}", &cmdline_overwrite_path))?;
241+
242+
distro_launcher.with_mount(
243+
Some(HostPath::new(cmdline_overwrite_path)?), // /run is bind-mounted
244+
ContainerPath::new("/proc/cmdline")?,
245+
None,
246+
nix::mount::MsFlags::MS_BIND,
247+
None,
248+
true,
249+
);
250+
Ok(())
251+
}
252+
253+
fn get_cmdline_with_wsl_interop_envs_for_systemd<P: AsRef<Path>>(
254+
cmdline_path: P,
255+
) -> Result<Vec<u8>> {
256+
let mut cmdline = std::fs::read(cmdline_path.as_ref())
257+
.with_context(|| format!("Failed to read {:?}.", cmdline_path.as_ref()))?;
258+
if cmdline.ends_with("\n".as_bytes()) {
259+
cmdline.truncate(cmdline.len() - 1);
260+
}
261+
262+
// Set default environment vairables for the systemd services.
263+
for (key, value) in
264+
collect_safe_wsl_interop_envs().with_context(|| "Failed to collect WSL envs.")?
265+
{
266+
cmdline.extend(" ".as_bytes());
267+
cmdline.extend(env_to_systemd_setenv_arg(&key, &value).as_bytes());
268+
}
269+
cmdline.extend("\n".as_bytes());
270+
271+
Ok(cmdline)
272+
}
273+
274+
fn collect_safe_wsl_interop_envs() -> Result<Vec<(OsString, OsString)>> {
275+
// Collect only harmless environment variables.
215276
// Distrod can be running as setuid program. So, non-root user can set arbitrary values.
216-
// mount_per_user_wsl_envs_script and its family should source those arbitrary values instead.
277+
// Thus, mount_per_user_wsl_envs_script or similar functions should collect only restricted value.
217278
// Actually, this doesn't matter for now because WSL allows a non-root user to be root by `wsl.exe -u root`,
218279
// but be prepared for this WSL spec to be improved in a safer direction someday.
219-
let wsl_envs = collect_wsl_env_vars().with_context(|| "Failed to collect WSL envs.")?;
280+
let mut envs = vec![];
220281
let envs_to_set = [
221282
OsStr::new("WSL_INTEROP"),
222283
OsStr::new("WSLENV"),
223284
OsStr::new("WSL_DISTRO_NAME"),
224285
];
225-
for (key, value) in &wsl_envs {
286+
for (key, value) in collect_wsl_env_vars().with_context(|| "Failed to collect WSL envs.")? {
226287
if !envs_to_set.contains(&key.as_os_str()) {
227288
continue;
228289
}
229-
if !sanity_check_wsl_env(key, value) {
290+
if !sanity_check_wsl_env(&key, &value) {
230291
log::warn!("sanity check of {:?} failed.", &key);
231292
// stop handling this and further envs
232-
break;
293+
continue;
233294
}
234-
distro_launcher.with_system_env(
235-
key.to_string_lossy().to_string(),
236-
value.to_string_lossy().to_string(),
237-
);
238-
distro_launcher
239-
.container_launcher
240-
.with_init_arg(&env_to_systemd_setenv_arg(key, value));
295+
envs.push((key, value));
241296
}
242-
Ok(())
297+
Ok(envs)
243298
}
244299

245300
/// Make sure that the values of WSL_INTEROP, WSLENV, and WSL_DISTRO_NAME are harmless values that can be
246-
/// written to /etc/environment and passed to Systemd service processes. These values may be polluted
301+
/// written to /etc/environment and passed to Systemd via /proc/cmdline. These values may be polluted
247302
/// because distrod-exec can be launched by any user.
248303
fn sanity_check_wsl_env(key: &OsStr, value: &OsStr) -> bool {
249304
if key == OsStr::new("WSL_INTEROP") {
@@ -267,13 +322,20 @@ fn sanity_check_wsl_interop(value: &OsStr) -> bool {
267322
fn sanity_check_general_wsl_envs(value: &OsStr) -> bool {
268323
// sanity check for WSLENV and WSL_INTEROP
269324
let inner = || -> Result<bool> {
270-
let harmless_pattern = regex::Regex::new("^[a-zA-Z0-9_\\-./:]*$")?;
325+
let harmless_pattern = regex::Regex::new(r#"^([a-zA-Z0-9_./:]|-)*$"#)?;
271326
let str = value.to_str().ok_or_else(|| anyhow!("non-UTF8 value."))?;
272327
Ok(harmless_pattern.is_match(str))
273328
};
274329
inner().unwrap_or(false)
275330
}
276331

332+
fn get_cmdline_overwrite_path() -> Result<HostPath> {
333+
get_distrod_runtime_files_dir_path().map(|mut path| {
334+
path.push("cmdline");
335+
path
336+
})
337+
}
338+
277339
fn env_to_systemd_setenv_arg<K, V>(key: K, value: V) -> OsString
278340
where
279341
K: AsRef<OsStr>,

0 commit comments

Comments
 (0)