Skip to content

Commit 8937da5

Browse files
committed
feat(jailer): check if executable has exec permissions
The executable passed to the jailer must have exec permissions set. Update the jailer executable check unit tests with `match!`. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent d0247a3 commit 8937da5

File tree

3 files changed

+39
-24
lines changed

3 files changed

+39
-24
lines changed

src/jailer/src/env.rs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,14 @@ impl Env {
289289
let exec_file_path = canonicalize(exec_file)
290290
.map_err(|err| JailerError::Canonicalize(PathBuf::from(exec_file), err))?;
291291

292-
if !exec_file_path.is_file() {
292+
// Safety: the `canonicalize` call above ensured the existance of the path.
293+
let metadata = std::fs::metadata(&exec_file_path).unwrap();
294+
295+
// Check that it is executable
296+
if metadata.permissions().mode() & 0o111 == 0 {
297+
return Err(JailerError::NotExecutable(exec_file_path));
298+
}
299+
if !metadata.is_file() {
293300
return Err(JailerError::NotAFile(exec_file_path));
294301
}
295302

@@ -782,7 +789,8 @@ mod tests {
782789
pub fn new(pseudo_exec_file_path: &'a str) -> ArgVals<'a> {
783790
let pseudo_exec_file_dir = Path::new(&pseudo_exec_file_path).parent().unwrap();
784791
fs::create_dir_all(pseudo_exec_file_dir).unwrap();
785-
File::create(pseudo_exec_file_path).unwrap();
792+
create_exec_file(pseudo_exec_file_path);
793+
786794
ArgVals {
787795
id: "bd65600d-8669-4903-8a14-af88203add38",
788796
exec_file: pseudo_exec_file_path,
@@ -799,6 +807,13 @@ mod tests {
799807
}
800808
}
801809

810+
fn create_exec_file(pseudo_exec_file_path: &str) {
811+
let exec = File::create(pseudo_exec_file_path).unwrap();
812+
let mut perms = exec.metadata().unwrap().permissions();
813+
perms.set_mode(0o111);
814+
exec.set_permissions(perms).unwrap();
815+
}
816+
802817
fn make_args(arg_vals: &ArgVals) -> Vec<String> {
803818
let mut arg_vec = vec![
804819
"--binary-name",
@@ -1018,31 +1033,30 @@ mod tests {
10181033
let pseudo_exec_file_path = get_pseudo_exec_file_path();
10191034
let pseudo_exec_file_dir = Path::new(&pseudo_exec_file_path).parent().unwrap();
10201035
create_dir_all(pseudo_exec_file_dir).unwrap();
1021-
File::create(&pseudo_exec_file_path).unwrap();
1036+
create_exec_file(&pseudo_exec_file_path);
10221037
Env::validate_exec_file(&pseudo_exec_file_path).unwrap();
10231038

10241039
// Error case 1: No such file exists
10251040
std::fs::remove_file(&pseudo_exec_file_path).unwrap();
1026-
assert_eq!(
1027-
format!(
1028-
"{}",
1029-
Env::validate_exec_file(&pseudo_exec_file_path).unwrap_err()
1030-
),
1031-
format!(
1032-
"Failed to canonicalize path {}: No such file or directory (os error 2)",
1033-
pseudo_exec_file_path
1034-
)
1035-
);
1041+
assert!(matches!(
1042+
Env::validate_exec_file("/tmp/firecracker_test_dir/foobarbaz"),
1043+
Err(JailerError::Canonicalize(_, _))
1044+
));
10361045

10371046
// Error case 2: Not a file
10381047
std::fs::create_dir_all("/tmp/firecracker_test_dir").unwrap();
1039-
assert_eq!(
1040-
format!(
1041-
"{}",
1042-
Env::validate_exec_file("/tmp/firecracker_test_dir").unwrap_err()
1043-
),
1044-
"/tmp/firecracker_test_dir is not a file"
1045-
);
1048+
assert!(matches!(
1049+
Env::validate_exec_file("/tmp/firecracker_test_dir"),
1050+
Err(JailerError::NotAFile(_))
1051+
));
1052+
1053+
// Error case 3: Not an executable
1054+
File::create("/tmp/firecracker_test_dir/foobarbaz").unwrap();
1055+
assert!(matches!(
1056+
Env::validate_exec_file("/tmp/firecracker_test_dir/foobarbaz"),
1057+
Err(JailerError::NotExecutable(_))
1058+
));
1059+
std::fs::remove_file("/tmp/firecracker_test_dir/foobarbaz").unwrap();
10461060

10471061
std::fs::remove_dir_all("/tmp/firecracker_test_dir").unwrap();
10481062
}
@@ -1181,7 +1195,7 @@ mod tests {
11811195
let exec_file_path = get_pseudo_exec_file_path();
11821196
let exec_file_dir = Path::new(&exec_file_path).parent().unwrap();
11831197
fs::create_dir_all(exec_file_dir).unwrap();
1184-
File::create(&exec_file_path).unwrap();
1198+
create_exec_file(&exec_file_path);
11851199
let some_dir = TempDir::new().unwrap();
11861200
let some_dir_path = some_dir.as_path().to_str().unwrap();
11871201

src/jailer/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ pub enum JailerError {
9999
MountBind(io::Error),
100100
#[error("Failed to change the propagation type to slave: {0}")]
101101
MountPropagationSlave(io::Error),
102+
#[error("{}", format!("{:?} is not executable", .0).replace('\"', ""))]
103+
NotExecutable(PathBuf),
102104
#[error("{}", format!("{:?} is not a file", .0).replace('\"', ""))]
103105
NotAFile(PathBuf),
104106
#[error("{}", format!("{:?} is not a directory", .0).replace('\"', ""))]

tests/integration_tests/security/test_jail.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test_exec_file_not_exist(uvm_plain, tmp_path):
106106
):
107107
test_microvm.spawn()
108108

109-
# Error case 3: Filename without "firecracker"
109+
# Error case 3: Not an executable
110110
pseudo_exec_file_path = tmp_path / "foobarbaz"
111111
pseudo_exec_file_path.touch()
112112
fc_dir = Path("/srv/jailer") / pseudo_exec_file_path.name / test_microvm.id
@@ -115,8 +115,7 @@ def test_exec_file_not_exist(uvm_plain, tmp_path):
115115

116116
with pytest.raises(
117117
Exception,
118-
match=r"Jailer error: Invalid filename. The filename of `--exec-file` option"
119-
r' must contain "firecracker": foobarbaz',
118+
match=rf"Jailer error: {pseudo_exec_file_path} is not executable",
120119
):
121120
test_microvm.spawn()
122121

0 commit comments

Comments
 (0)