Skip to content

Commit 1acbfa7

Browse files
WhatAmISupposedToPutHereslp
authored andcommitted
Use the same server instance for privileged launches too
We can keep the root uid in our saved set user id, and copy it to ruid/euid after fork, if launching as root is requested. Writing to drop_caches becomes a bit tricker when running unprivileged, but we can acquire the privileges in a forked process for it to work and not affect other launches. Signed-off-by: Sasha Finkelstein <[email protected]>
1 parent f123d70 commit 1acbfa7

10 files changed

Lines changed: 143 additions & 68 deletions

File tree

crates/muvm/src/bin/muvm.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ fn main() -> Result<ExitCode> {
7777
options.env,
7878
options.interactive,
7979
options.tty,
80+
options.privileged,
8081
)? {
8182
LaunchResult::LaunchRequested(code) => {
8283
// There was a muvm instance already running and we've requested it
@@ -264,7 +265,7 @@ fn main() -> Result<ExitCode> {
264265
.context("Failed to connect to `passt`")?
265266
.into()
266267
} else {
267-
start_passt(options.server_port, options.root_server_port)
268+
start_passt(options.server_port)
268269
.context("Failed to start `passt`")?
269270
.into()
270271
};
@@ -410,10 +411,6 @@ fn main() -> Result<ExitCode> {
410411
"MUVM_SERVER_PORT".to_owned(),
411412
options.server_port.to_string(),
412413
);
413-
env.insert(
414-
"MUVM_ROOT_SERVER_PORT".to_owned(),
415-
options.root_server_port.to_string(),
416-
);
417414
env.insert("MUVM_SERVER_COOKIE".to_owned(), cookie.to_string());
418415

419416
let display = env::var("DISPLAY").context("X11 forwarding requested but DISPLAY is unset")?;
@@ -467,7 +464,7 @@ fn main() -> Result<ExitCode> {
467464
}
468465
}
469466

470-
spawn_monitor(options.root_server_port, cookie);
467+
spawn_monitor(options.server_port, cookie);
471468

472469
{
473470
// Start and enter the microVM. Unless there is some error while creating the

crates/muvm/src/cli_options.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ pub struct Options {
1313
pub mem: Option<MiB>,
1414
pub vram: Option<MiB>,
1515
pub passt_socket: Option<PathBuf>,
16-
pub root_server_port: u32,
1716
pub server_port: u32,
1817
pub fex_images: Vec<String>,
1918
pub merged_rootfs: bool,
2019
pub interactive: bool,
2120
pub tty: bool,
21+
pub privileged: bool,
2222
pub command: PathBuf,
2323
pub command_args: Vec<String>,
2424
}
@@ -98,12 +98,6 @@ pub fn options() -> OptionParser<Options> {
9898
.help("Instead of starting passt, connect to passt socket at PATH")
9999
.argument("PATH")
100100
.optional();
101-
let root_server_port = long("root-server-port")
102-
.short('r')
103-
.help("Set the port to be used in root server mode")
104-
.argument("ROOT_SERVER_PORT")
105-
.fallback(3335)
106-
.display_fallback();
107101
let server_port = long("server-port")
108102
.short('p')
109103
.help("Set the port to be used in server mode")
@@ -118,6 +112,12 @@ pub fn options() -> OptionParser<Options> {
118112
.short('t')
119113
.help("Allocate a tty for the command")
120114
.switch();
115+
let privileged = long("privileged")
116+
.help(
117+
"Run the command as root inside the vm.
118+
This notably does not allow root access to the host fs.",
119+
)
120+
.switch();
121121
let command = positional("COMMAND").help("the command you want to execute in the vm");
122122
let command_args = any::<String, _, _>("COMMAND_ARGS", |arg| {
123123
(!["--help", "-h"].contains(&&*arg)).then_some(arg)
@@ -131,12 +131,12 @@ pub fn options() -> OptionParser<Options> {
131131
mem,
132132
vram,
133133
passt_socket,
134-
root_server_port,
135134
server_port,
136135
fex_images,
137136
merged_rootfs,
138137
interactive,
139138
tty,
139+
privileged,
140140
// positionals
141141
command,
142142
command_args,

crates/muvm/src/guest/bin/muvm-guest.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use muvm::guest::net::configure_network;
1313
use muvm::guest::socket::setup_socket_proxy;
1414
use muvm::guest::user::setup_user;
1515
use muvm::guest::x11::setup_x11_forwarding;
16+
use nix::unistd::{setresgid, setresuid, Gid, Uid};
1617
use rustix::process::{getrlimit, setrlimit, Resource};
1718

1819
fn main() -> Result<()> {
@@ -61,13 +62,6 @@ fn main() -> Result<()> {
6162
.spawn()
6263
.context("Failed to execute `muvm-hidpipe` as child process")?;
6364

64-
// Before switching to the user, start another instance of muvm-server to serve
65-
// launch requests as root.
66-
let muvm_server_path = find_muvm_exec("muvm-server")?;
67-
Command::new(muvm_server_path)
68-
.spawn()
69-
.context("Failed to execute `muvm-server` as child process")?;
70-
7165
let run_path = match setup_user(options.username, options.uid, options.gid) {
7266
Ok(p) => p,
7367
Err(err) => return Err(err).context("Failed to set up user, bailing out"),
@@ -81,6 +75,8 @@ fn main() -> Result<()> {
8175

8276
setup_x11_forwarding(run_path)?;
8377

78+
setresuid(options.uid, Uid::from(0), Uid::from(0))?;
79+
setresgid(options.gid, Gid::from(0), Gid::from(0))?;
8480
debug!(command:? = options.command, command_args:? = options.command_args; "exec");
8581
let err = Command::new(&options.command)
8682
.args(options.command_args)

crates/muvm/src/guest/user.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use std::os::unix::fs::{chown, PermissionsExt as _};
44
use std::path::{Path, PathBuf};
55

66
use anyhow::{anyhow, Context, Result};
7-
use nix::unistd::{setgid, setuid, Gid, Uid, User};
7+
use nix::unistd::{setresgid, setresuid, Gid, Uid, User};
88

99
pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result<PathBuf> {
1010
setup_directories(uid, gid)?;
1111

12-
setgid(gid).context("Failed to setgid")?;
13-
setuid(uid).context("Failed to setuid")?;
12+
setresgid(gid, gid, Gid::from(0)).context("Failed to setgid")?;
13+
setresuid(uid, uid, Uid::from(0)).context("Failed to setuid")?;
1414

1515
let path = tempfile::Builder::new()
1616
.prefix(&format!("muvm-run-{uid}-"))

crates/muvm/src/launch.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ fn acquire_socket_lock() -> Result<(File, u32)> {
8686
Err(anyhow!("Ran out of ports."))
8787
}
8888

89+
#[allow(clippy::too_many_arguments)]
8990
fn wrapped_launch(
9091
server_port: u32,
9192
cookie: Uuid,
@@ -94,9 +95,19 @@ fn wrapped_launch(
9495
env: HashMap<String, String>,
9596
interactive: bool,
9697
tty: bool,
98+
privileged: bool,
9799
) -> Result<ExitCode> {
98100
if !interactive {
99-
request_launch(server_port, cookie, command, command_args, env, 0, false)?;
101+
request_launch(
102+
server_port,
103+
cookie,
104+
command,
105+
command_args,
106+
env,
107+
0,
108+
false,
109+
privileged,
110+
)?;
100111
return Ok(ExitCode::from(0));
101112
}
102113
let run_path = env::var("XDG_RUNTIME_DIR")
@@ -122,6 +133,7 @@ fn wrapped_launch(
122133
env,
123134
vsock_port,
124135
tty,
136+
privileged,
125137
)?;
126138
let code = run_io_host(listener, tty)?;
127139
drop(raw_tty);
@@ -135,13 +147,23 @@ pub fn launch_or_lock(
135147
env: Vec<(String, Option<String>)>,
136148
interactive: bool,
137149
tty: bool,
150+
privileged: bool,
138151
) -> Result<LaunchResult> {
139152
let running_server_port = env::var("MUVM_SERVER_PORT").ok();
140153
if let Some(port) = running_server_port {
141154
let port: u32 = port.parse()?;
142155
let env = prepare_env_vars(env)?;
143156
let cookie = read_cookie()?;
144-
return match wrapped_launch(port, cookie, command, command_args, env, interactive, tty) {
157+
return match wrapped_launch(
158+
port,
159+
cookie,
160+
command,
161+
command_args,
162+
env,
163+
interactive,
164+
tty,
165+
privileged,
166+
) {
145167
Err(err) => Err(anyhow!("could not request launch to server: {err}")),
146168
Ok(code) => Ok(LaunchResult::LaunchRequested(code)),
147169
};
@@ -168,6 +190,7 @@ pub fn launch_or_lock(
168190
env.clone(),
169191
interactive,
170192
tty,
193+
privileged,
171194
) {
172195
Err(err) => match err.downcast_ref::<LaunchError>() {
173196
Some(&LaunchError::Connection(_)) => {
@@ -230,6 +253,7 @@ fn lock_file() -> Result<(Option<File>, Uuid)> {
230253
Ok((Some(lock_file), cookie))
231254
}
232255

256+
#[allow(clippy::too_many_arguments)]
233257
pub fn request_launch(
234258
server_port: u32,
235259
cookie: Uuid,
@@ -238,6 +262,7 @@ pub fn request_launch(
238262
env: HashMap<String, String>,
239263
vsock_port: u32,
240264
tty: bool,
265+
privileged: bool,
241266
) -> Result<()> {
242267
let mut stream =
243268
TcpStream::connect(format!("127.0.0.1:{server_port}")).map_err(LaunchError::Connection)?;
@@ -249,6 +274,7 @@ pub fn request_launch(
249274
env,
250275
vsock_port,
251276
tty,
277+
privileged,
252278
};
253279

254280
stream

crates/muvm/src/monitor.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,16 @@ fn set_guest_pressure(server_port: u32, cookie: Uuid, pressure: GuestPressure) -
4444
let command = PathBuf::from("/muvmdropcaches");
4545
let command_args = vec![];
4646
let env = HashMap::new();
47-
request_launch(server_port, cookie, command, command_args, env, 0, false)?;
47+
request_launch(
48+
server_port,
49+
cookie,
50+
command,
51+
command_args,
52+
env,
53+
0,
54+
false,
55+
true,
56+
)?;
4857
}
4958

5059
let wsf: u32 = pressure.into();
@@ -53,7 +62,16 @@ fn set_guest_pressure(server_port: u32, cookie: Uuid, pressure: GuestPressure) -
5362
let command = PathBuf::from("/sbin/sysctl");
5463
let command_args = vec![format!("vm.watermark_scale_factor={}", wsf)];
5564
let env = HashMap::new();
56-
request_launch(server_port, cookie, command, command_args, env, 0, false)
65+
request_launch(
66+
server_port,
67+
cookie,
68+
command,
69+
command_args,
70+
env,
71+
0,
72+
false,
73+
true,
74+
)
5775
}
5876

5977
fn run(server_port: u32, cookie: Uuid) {

crates/muvm/src/net.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ where
1414
Ok(UnixStream::connect(passt_socket_path)?)
1515
}
1616

17-
pub fn start_passt(server_port: u32, root_server_port: u32) -> Result<UnixStream> {
17+
pub fn start_passt(server_port: u32) -> Result<UnixStream> {
1818
// SAFETY: The child process should not inherit the file descriptor of
1919
// `parent_socket`. There is no documented guarantee of this, but the
2020
// implementation as of writing atomically sets `SOCK_CLOEXEC`.
@@ -40,9 +40,7 @@ pub fn start_passt(server_port: u32, root_server_port: u32) -> Result<UnixStream
4040
// See https://doc.rust-lang.org/std/io/index.html#io-safety
4141
let child = Command::new("passt")
4242
.args(["-q", "-f", "-t"])
43-
.arg(format!(
44-
"{server_port}:{server_port},{root_server_port}:{root_server_port}"
45-
))
43+
.arg(format!("{server_port}:{server_port}"))
4644
.arg("--fd")
4745
.arg(format!("{}", child_fd.into_raw_fd()))
4846
.spawn();

crates/muvm/src/server/bin/muvm-server.rs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use std::env;
22
use std::os::unix::process::ExitStatusExt as _;
3-
use std::path::PathBuf;
43

54
use anyhow::{Context, Result};
65
use log::error;
76
use muvm::server::cli_options::options;
87
use muvm::server::worker::{State, Worker};
9-
use nix::unistd::geteuid;
8+
use nix::unistd::{getgid, getuid, setresgid, setresuid, Gid, Uid};
109
use tokio::net::TcpListener;
1110
use tokio::process::Command;
1211
use tokio::sync::watch;
@@ -26,32 +25,23 @@ async fn tokio_main(cookie: String) -> Result<()> {
2625
env_logger::init();
2726

2827
let cookie = Uuid::try_parse(&cookie).context("Couldn't parse cookie as UUID v7")?;
29-
let uid: u32 = geteuid().into();
28+
let uid = getuid();
29+
let gid = getgid();
30+
setresuid(uid, uid, Uid::from(0))?;
31+
setresgid(gid, gid, Gid::from(0))?;
3032

31-
let (server_port, command, command_args) = if uid == 0 {
32-
let server_port = if let Ok(server_port) = env::var("MUVM_ROOT_SERVER_PORT") {
33-
server_port.parse()?
34-
} else {
35-
3335
36-
};
37-
(
38-
server_port,
39-
PathBuf::from("/bin/sleep"),
40-
vec!["inf".to_string()],
41-
)
42-
} else {
43-
let options = options().run();
44-
(options.server_port, options.command, options.command_args)
45-
};
33+
let options = options().run();
4634

47-
let listener = TcpListener::bind(format!("0.0.0.0:{}", server_port)).await?;
35+
let listener = TcpListener::bind(format!("0.0.0.0:{}", options.server_port)).await?;
4836
let (state_tx, state_rx) = watch::channel(State::new());
4937

5038
let mut worker_handle = tokio::spawn(async move {
5139
let mut worker = Worker::new(cookie, listener, state_tx);
5240
worker.run().await;
5341
});
54-
let command_status = Command::new(&command).args(command_args).status();
42+
let command_status = Command::new(&options.command)
43+
.args(options.command_args)
44+
.status();
5545
tokio::pin!(command_status);
5646
let mut state_rx = WatchStream::new(state_rx);
5747

@@ -79,12 +69,12 @@ async fn tokio_main(cookie: String) -> Result<()> {
7969
if let Some(code) = status.code() {
8070
eprintln!(
8171
"{:?} process exited with status code: {code}",
82-
command
72+
options.command
8373
);
8474
} else {
8575
eprintln!(
8676
"{:?} process terminated by signal: {}",
87-
command,
77+
options.command,
8878
status
8979
.signal()
9080
.expect("either one of status code or signal should be set")
@@ -95,7 +85,7 @@ async fn tokio_main(cookie: String) -> Result<()> {
9585
Err(err) => {
9686
eprintln!(
9787
"Failed to execute {:?} as child process: {err}",
98-
command
88+
options.command
9989
);
10090
},
10191
}

0 commit comments

Comments
 (0)