Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions litebox_runner_linux_userland/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use anyhow::{Result, anyhow};
use anyhow::{Context, Result, anyhow};
use clap::Parser;
use litebox::fs::{FileSystem as _, Mode};
use litebox_platform_multiplex::Platform;
Expand Down Expand Up @@ -92,11 +92,15 @@ fn mmapped_file_data(path: impl AsRef<Path>) -> Result<&'static [u8]> {

/// Run Linux programs with LiteBox on unmodified Linux
///
/// # Errors
///
/// Returns an error if the program path cannot be resolved, directories along the path are
/// inaccessible, or the initial filesystem cannot be set up.
///
/// # Panics
///
/// Can panic if any particulars of the environment are not set up as expected. Ideally, would not
/// panic. If it does actually panic, then ping the authors of LiteBox, and likely a better error
/// message could be thrown instead.
/// May still panic if internal filesystem operations (e.g., in-memory mkdir/chown/open) or
/// syscall rewriting fail unexpectedly.
pub fn run(cli_args: CliArgs) -> Result<()> {
if !cli_args.insert_files.is_empty() {
unimplemented!(
Expand All @@ -108,20 +112,21 @@ pub fn run(cli_args: CliArgs) -> Result<()> {
Vec<(litebox::fs::Mode, u32)>,
alloc::borrow::Cow<'static, [u8]>,
) = {
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0])).unwrap();
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0]))
.with_context(|| format!("Could not resolve absolute path for program '{}'", &cli_args.program_and_arguments[0]))?;
let ancestors: Vec<_> = prog.ancestors().collect();
Comment on lines +115 to 117
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a regression test covering the reported scenario (missing/unresolvable program path) to ensure run() returns a contextual Err instead of panicking. This crate already has an integration test suite under litebox_runner_linux_userland/tests, and a test here would prevent #650 from regressing.

Copilot generated this review using guidance from repository custom instructions.
let modes: Vec<_> = ancestors
let modes: Result<Vec<_>> = ancestors
.into_iter()
.rev()
.skip(1)
.map(|path| {
let metadata = path.metadata().unwrap();
(
litebox::fs::Mode::from_bits(metadata.st_mode()).unwrap(),
metadata.st_uid(),
)
let metadata = path.metadata()
.with_context(|| format!("Could not read metadata for '{}'. Ensure the path exists and is accessible.", path.display()))?;
let mode = litebox::fs::Mode::from_bits_truncate(metadata.st_mode());
Ok((mode, metadata.st_uid()))
})
.collect();
let modes = modes?;
let data = mmapped_file_data(prog)?;
let data = if cli_args.rewrite_syscalls {
litebox_syscall_rewriter::hook_syscalls_in_elf(data, None)
Expand Down Expand Up @@ -152,7 +157,8 @@ pub fn run(cli_args: CliArgs) -> Result<()> {
let litebox = shim_builder.litebox();
let initial_file_system = {
let mut in_mem = litebox::fs::in_mem::FileSystem::new(litebox);
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0])).unwrap();
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0]))
.with_context(|| format!("Could not resolve absolute path for program '{}'", &cli_args.program_and_arguments[0]))?;
Comment on lines +160 to +161
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still a host-OS-facing std::path::absolute(...).unwrap() later in run() (around the "We need to get the file path before enabling seccomp" block). This means the runner can still panic in the same class of environment issues this PR is addressing, and it also contradicts the PR description claiming all relevant absolute() unwraps were replaced. Consider applying the same with_context + ? handling there too (or reusing the already-resolved prog).

Copilot uses AI. Check for mistakes.
let ancestors: Vec<_> = prog.ancestors().collect();
let mut prev_user = 0;
for (path, &mode_and_user) in ancestors
Expand Down Expand Up @@ -254,7 +260,8 @@ pub fn run(cli_args: CliArgs) -> Result<()> {
};

// We need to get the file path before enabling seccomp
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0])).unwrap();
let prog = std::path::absolute(Path::new(&cli_args.program_and_arguments[0]))
.with_context(|| format!("Could not resolve absolute path for program '{}'", &cli_args.program_and_arguments[0]))?;
let prog_path = prog.to_str().ok_or_else(|| {
anyhow!(
"Could not convert program path {:?} to a string",
Expand Down
47 changes: 47 additions & 0 deletions litebox_runner_linux_userland/tests/error_handling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

//! Regression tests for error handling in the runner (see #650).
//!
//! These tests verify that the runner produces clear error messages instead
//! of panicking when given invalid inputs.

use std::process::Command;

/// Get the path to the litebox_runner_linux_userland binary.
fn runner_binary() -> String {
std::env::var("NEXTEST_BIN_EXE_litebox_runner_linux_userland")
.unwrap_or_else(|_| env!("CARGO_BIN_EXE_litebox_runner_linux_userland").to_string())
}

/// Regression test for #650: running with a nonexistent program path should
/// produce a clear, contextual error message instead of panicking.
#[test]
fn test_nonexistent_program_returns_error_not_panic() {
let output = Command::new(runner_binary())
.arg("/nonexistent/path/to/program")
.output()
.expect("Failed to execute runner binary");

// The process should fail (non-zero exit code)
assert!(
!output.status.success(),
"Expected runner to fail for nonexistent program, but it succeeded"
);

let stderr = String::from_utf8_lossy(&output.stderr);

// It should NOT contain a panic message
assert!(
!stderr.contains("panicked at"),
"Runner panicked instead of returning an error.\nStderr: {stderr}"
);

// It SHOULD contain a contextual error message about the path
assert!(
stderr.contains("Could not resolve absolute path")
|| stderr.contains("Could not read metadata"),
"Expected a contextual error message about path resolution or metadata, \
but got:\nStderr: {stderr}"
);
}