1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
use std::fs;
use std::os::unix::fs::symlink;
use std::path::Path;
use alt_controller_image::rootfs::{CopyTree, InitrdOem, RootfsFinalization, ServiceName};
use tempfile::tempdir;
#[test]
fn finalization_copies_trees_generates_oem_recipe_and_enables_services() {
let fixture = tempdir().expect("fixture directory");
let source = fixture.path().join("overlay");
fs::create_dir_all(source.join("nested")).expect("create source tree");
fs::write(source.join("nested/controller.conf"), "mode = controller\n").expect("write source file");
symlink("nested/controller.conf", source.join("controller.conf"))
.expect("create relative source symlink");
let rootfs = tempdir().expect("rootfs directory");
let finalization = RootfsFinalization::new(
vec![CopyTree::new(&source, "etc/controller").expect("valid copy destination")],
InitrdOem::new(["rootfs", "qemu"], ["ext4", "virtio_blk.ko"]),
vec![ServiceName::new("chronyd.service").expect("valid service")],
);
let manifest = finalization.apply(rootfs.path()).expect("finalize rootfs");
assert_eq!(
fs::read_to_string(rootfs.path().join("etc/controller/nested/controller.conf"))
.expect("copied file"),
"mode = controller\n"
);
assert_eq!(
fs::read_link(rootfs.path().join("etc/controller/controller.conf")).expect("copied symlink"),
Path::new("nested/controller.conf")
);
assert_eq!(
fs::read_to_string(rootfs.path().join("etc/initrd.mk.oem")).expect("OEM recipe"),
"FEATURES += qemu rootfs\nMODULES += ext4 virtio_blk.ko\n"
);
assert_eq!(
fs::read_link(
rootfs
.path()
.join("etc/systemd/system/multi-user.target.wants/chronyd.service")
)
.expect("enabled service"),
Path::new("/usr/lib/systemd/system/chronyd.service")
);
assert_eq!(
manifest.created_paths(),
[
"etc/controller",
"etc/controller/controller.conf",
"etc/controller/nested",
"etc/controller/nested/controller.conf",
"etc/initrd.mk.oem",
"etc/systemd/system/multi-user.target.wants/chronyd.service",
]
);
}
#[test]
fn finalization_rejects_rootfs_escaping_destinations_and_symlinks() {
assert!(CopyTree::new("fixtures/overlay", "/etc/controller").is_err());
assert!(CopyTree::new("fixtures/overlay", "../etc/controller").is_err());
let fixture = tempdir().expect("fixture directory");
let source = fixture.path().join("overlay");
fs::create_dir_all(&source).expect("create source tree");
symlink("../../outside", source.join("unsafe-link")).expect("create unsafe symlink");
let rootfs = tempdir().expect("rootfs directory");
let finalization = RootfsFinalization::new(
vec![CopyTree::new(&source, "etc/controller").expect("valid destination")],
InitrdOem::new(std::iter::empty::<&str>(), std::iter::empty::<&str>()),
vec![],
);
let error = finalization
.apply(rootfs.path())
.expect_err("unsafe symlink must be rejected");
assert!(error.to_string().contains("escapes copied tree"));
}
#[test]
fn finalization_is_idempotent_for_matching_symlinks() {
let fixture = tempdir().expect("fixture directory");
let source = fixture.path().join("overlay");
fs::create_dir_all(&source).expect("create source tree");
symlink("controller.conf", source.join("controller-link")).expect("create source symlink");
fs::write(source.join("controller.conf"), "controller\n").expect("write source file");
let rootfs = tempdir().expect("rootfs directory");
let finalization = RootfsFinalization::new(
vec![CopyTree::new(&source, "etc/controller").expect("valid copy destination")],
InitrdOem::new(std::iter::empty::<&str>(), std::iter::empty::<&str>()),
vec![ServiceName::new("chronyd.service").expect("valid service")],
);
finalization.apply(rootfs.path()).expect("first finalization succeeds");
let second = finalization.apply(rootfs.path()).expect("repeat finalization succeeds");
assert_eq!(
fs::read_link(rootfs.path().join("etc/controller/controller-link")).expect("copied symlink"),
Path::new("controller.conf")
);
assert_eq!(
fs::read_link(
rootfs
.path()
.join("etc/systemd/system/multi-user.target.wants/chronyd.service")
)
.expect("enabled service"),
Path::new("/usr/lib/systemd/system/chronyd.service")
);
assert!(second
.created_paths()
.iter()
.any(|path| path == "etc/controller/controller-link"));
}
|