Skip to content

Commit b847101

Browse files
authored
Merge pull request #1222 from cgwalters/lsm-fix
lsm, imgstorage: Rework relabeling
2 parents da468f5 + 2da6f88 commit b847101

File tree

5 files changed

+141
-39
lines changed

5 files changed

+141
-39
lines changed

lib/src/cli.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,14 @@ pub(crate) enum InternalsOpts {
436436
Fsck,
437437
/// Perform cleanup actions
438438
Cleanup,
439+
Relabel {
440+
#[clap(long)]
441+
/// Relabel using this path as root
442+
as_path: Option<Utf8PathBuf>,
443+
444+
/// Relabel this path
445+
path: Utf8PathBuf,
446+
},
439447
/// Proxy frontend for the `ostree-ext` CLI.
440448
OstreeExt {
441449
#[clap(allow_hyphen_values = true)]
@@ -1221,6 +1229,14 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
12211229
let sysroot = get_storage().await?;
12221230
crate::deploy::cleanup(&sysroot).await
12231231
}
1232+
InternalsOpts::Relabel { as_path, path } => {
1233+
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
1234+
let path = path.strip_prefix("/")?;
1235+
let sepolicy =
1236+
&ostree::SePolicy::new(&gio::File::for_path("/"), gio::Cancellable::NONE)?;
1237+
crate::lsm::relabel_recurse(root, path, as_path.as_deref(), sepolicy)?;
1238+
Ok(())
1239+
}
12241240
InternalsOpts::BootcInstallCompletion { sysroot, stateroot } => {
12251241
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
12261242
crate::install::completion::run_from_ostree(rootfs, &sysroot, &stateroot).await

lib/src/imgstorage.rs

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,11 @@ impl Storage {
164164
Ok(())
165165
}
166166

167+
/// Ensure that the LSM (SELinux) labels are set on the bootc-owned
168+
/// containers-storage: instance. We use a `LABELED` stamp file for
169+
/// idempotence.
167170
#[context("Labeling imgstorage dirs")]
168-
fn label_dirs(root: &Dir, sepolicy: Option<&ostree::SePolicy>) -> Result<()> {
171+
fn ensure_labeled(root: &Dir, sepolicy: Option<&ostree::SePolicy>) -> Result<()> {
169172
if root.try_exists(LABELED)? {
170173
return Ok(());
171174
}
@@ -175,28 +178,14 @@ impl Storage {
175178

176179
// recursively set the labels because they were previously set to usr_t,
177180
// and there is no policy defined to set them to the c/storage labels
178-
crate::lsm::ensure_dir_labeled_recurse_policy(
181+
crate::lsm::relabel_recurse(
179182
&root,
180183
".",
181184
Some(Utf8Path::new("/var/lib/containers/storage")),
182-
0o755.into(),
183-
Some(sepolicy),
185+
sepolicy,
184186
)
185187
.context("labeling storage root")?;
186188

187-
let paths = ["overlay", "overlay-images", "overlay-layers", "volumes"];
188-
for p in paths {
189-
let full_label_path = format!("/var/lib/containers/storage/{}", p);
190-
crate::lsm::ensure_dir_labeled_recurse_policy(
191-
&root,
192-
p,
193-
Some(Utf8Path::new(full_label_path.as_str())),
194-
0o755.into(),
195-
Some(sepolicy),
196-
)
197-
.context(format!("labeling storage subpath: {}", p))?;
198-
}
199-
200189
root.create(LABELED)?;
201190

202191
Ok(())
@@ -224,7 +213,6 @@ impl Storage {
224213
.with_context(|| format!("Creating {parent}"))?;
225214
sysroot.create_dir_all(&tmp).context("Creating tmpdir")?;
226215
let storage_root = sysroot.open_dir(&tmp).context("Open tmp")?;
227-
Self::label_dirs(&storage_root, sepolicy)?;
228216

229217
// There's no explicit API to initialize a containers-storage:
230218
// root, simply passing a path will attempt to auto-create it.
@@ -234,6 +222,7 @@ impl Storage {
234222
.arg("images")
235223
.run()
236224
.context("Initializing images")?;
225+
Self::ensure_labeled(&storage_root, sepolicy)?;
237226
drop(storage_root);
238227
sysroot
239228
.rename(&tmp, sysroot, subpath)
@@ -242,7 +231,7 @@ impl Storage {
242231
} else {
243232
// the storage already exists, make sure it has selinux labels
244233
let storage_root = sysroot.open_dir(subpath).context("opening storage dir")?;
245-
Self::label_dirs(&storage_root, sepolicy)?;
234+
Self::ensure_labeled(&storage_root, sepolicy)?;
246235
}
247236

248237
Self::open(sysroot, run)

lib/src/lsm.rs

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::io::Write;
23
use std::os::fd::AsRawFd;
34
use std::os::unix::process::CommandExt;
@@ -230,8 +231,11 @@ pub(crate) fn has_security_selinux(root: &Dir, path: &Utf8Path) -> Result<SELinu
230231
}
231232
}
232233

234+
/// Directly set the `security.selinux` extended atttribute on the target
235+
/// path. Symbolic links are not followed for the target.
236+
///
237+
/// Note that this API will work even if SELinux is disabled.
233238
pub(crate) fn set_security_selinux_path(root: &Dir, path: &Utf8Path, label: &[u8]) -> Result<()> {
234-
// TODO: avoid hardcoding a max size here
235239
let fdpath = format!("/proc/self/fd/{}/", root.as_raw_fd());
236240
let fdpath = &Path::new(&fdpath).join(path);
237241
rustix::fs::lsetxattr(
@@ -243,6 +247,9 @@ pub(crate) fn set_security_selinux_path(root: &Dir, path: &Utf8Path, label: &[u8
243247
Ok(())
244248
}
245249

250+
/// Given a policy, ensure the target file path has a security.selinux label.
251+
/// If the path already is labeled, this function is a no-op, even if
252+
/// the policy would default to a different label.
246253
pub(crate) fn ensure_labeled(
247254
root: &Dir,
248255
path: &Utf8Path,
@@ -251,44 +258,97 @@ pub(crate) fn ensure_labeled(
251258
) -> Result<SELinuxLabelState> {
252259
let r = has_security_selinux(root, path)?;
253260
if matches!(r, SELinuxLabelState::Unlabeled) {
254-
let abspath = Utf8Path::new("/").join(&path);
255-
let label = require_label(policy, &abspath, metadata.mode())?;
256-
tracing::trace!("Setting label for {path} to {label}");
257-
set_security_selinux_path(root, &path, label.as_bytes())?;
261+
relabel(root, metadata, path, None, policy)?;
258262
}
259263
Ok(r)
260264
}
261265

262-
pub(crate) fn ensure_dir_labeled_recurse_policy(
266+
/// Given the policy, relabel the target file or directory.
267+
/// Optionally, an override for the path can be provided
268+
/// to set the label as if the target has that filename.
269+
pub(crate) fn relabel(
263270
root: &Dir,
264-
destname: impl AsRef<Utf8Path>,
271+
metadata: &Metadata,
272+
path: &Utf8Path,
265273
as_path: Option<&Utf8Path>,
266-
mode: rustix::fs::Mode,
267-
policy: Option<&ostree::SePolicy>,
274+
policy: &ostree::SePolicy,
275+
) -> Result<()> {
276+
assert!(!path.starts_with("/"));
277+
let as_path = as_path
278+
.map(Cow::Borrowed)
279+
.unwrap_or_else(|| Utf8Path::new("/").join(path).into());
280+
let label = require_label(policy, &as_path, metadata.mode())?;
281+
tracing::trace!("Setting label for {path} to {label}");
282+
set_security_selinux_path(root, &path, label.as_bytes())
283+
}
284+
285+
pub(crate) fn relabel_recurse_inner(
286+
root: &Dir,
287+
path: &mut Utf8PathBuf,
288+
mut as_path: Option<&mut Utf8PathBuf>,
289+
policy: &ostree::SePolicy,
268290
) -> Result<()> {
269-
ensure_dir_labeled(root, &destname, as_path, mode, policy)?;
270-
let mut dest_path = destname.as_ref().to_path_buf();
291+
// Relabel this directory
292+
let self_meta = root.dir_metadata()?;
293+
relabel(
294+
root,
295+
&self_meta,
296+
path,
297+
as_path.as_ref().map(|p| p.as_path()),
298+
policy,
299+
)?;
271300

272-
for ent in root.read_dir(destname.as_ref())? {
301+
// Relabel all children
302+
for ent in root.read_dir(&path)? {
273303
let ent = ent?;
274304
let metadata = ent.metadata()?;
275305
let name = ent.file_name();
276306
let name = name
277307
.to_str()
278308
.ok_or_else(|| anyhow::anyhow!("Invalid non-UTF-8 filename: {name:?}"))?;
279-
dest_path.push(name);
309+
// Extend both copies of the path
310+
path.push(name);
311+
if let Some(p) = as_path.as_mut() {
312+
p.push(name);
313+
}
280314

281315
if metadata.is_dir() {
282-
ensure_dir_labeled_recurse_policy(root, &dest_path, as_path, mode, policy)?;
316+
let as_path = as_path.as_deref_mut();
317+
relabel_recurse_inner(root, path, as_path, policy)?;
283318
} else {
284-
ensure_dir_labeled(root, &dest_path, as_path, mode, policy)?
319+
let as_path = as_path.as_ref().map(|p| p.as_path());
320+
relabel(root, &metadata, &path, as_path, policy)?
321+
}
322+
// Trim what we added to the path
323+
let r = path.pop();
324+
assert!(r);
325+
if let Some(p) = as_path.as_mut() {
326+
let r = p.pop();
327+
assert!(r);
285328
}
286-
dest_path.pop();
287329
}
288330

289331
Ok(())
290332
}
291333

334+
/// Recursively relabel the target directory.
335+
pub(crate) fn relabel_recurse(
336+
root: &Dir,
337+
path: impl AsRef<Utf8Path>,
338+
as_path: Option<&Utf8Path>,
339+
policy: &ostree::SePolicy,
340+
) -> Result<()> {
341+
let mut path = path.as_ref().to_owned();
342+
// This path must be relative, as we access via cap-std
343+
assert!(!path.starts_with("/"));
344+
let mut as_path = as_path.map(|v| v.to_owned());
345+
// But the as_path must be absolute, if provided
346+
if let Some(as_path) = as_path.as_deref() {
347+
assert!(as_path.starts_with("/"));
348+
}
349+
relabel_recurse_inner(root, &mut path, as_path.as_mut(), policy)
350+
}
351+
292352
/// A wrapper for creating a directory, also optionally setting a SELinux label.
293353
/// The provided `skip` parameter is a device/inode that we will ignore (and not traverse).
294354
pub(crate) fn ensure_dir_labeled_recurse(
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std assert
2+
use tap.nu
3+
4+
tap begin "Relabel"
5+
6+
let td = mktemp -d -p /var/tmp
7+
cd $td
8+
9+
mkdir etc/ssh
10+
touch etc/shadow etc/ssh/ssh_config
11+
bootc internals relabel --as-path /etc $"(pwd)/etc"
12+
13+
def assert_labels_equal [p] {
14+
let base = (getfattr --only-values -n security.selinux $"/($p)")
15+
let target = (getfattr --only-values -n security.selinux $p)
16+
assert equal $base $target
17+
}
18+
19+
for path in ["etc", "etc/shadow", "etc/ssh/ssh_config"] {
20+
assert_labels_equal $path
21+
}
22+
23+
cd /
24+
rm -rf $td
25+
26+
tap ok

tests/booted/test-logically-bound-install.nu

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,22 @@ def test_bootc_image_list [] {
3535
validate_images $images
3636
}
3737

38+
# Get just the type (foo_t) from a security context
39+
def get_file_selinux_type [p] {
40+
getfattr --only-values -n security.selinux $p | split row ':' | get 2
41+
}
42+
43+
# Verify that the SELinux labels on the main "containers-storage:" instance match ours.
44+
# See the relabeling we do in imgstorage.rs. We only verify types, because the role
45+
# may depend on the creating user.
3846
def test_storage_labels [] {
39-
let root_labeled = getfattr -n security.selinux /var/lib/containers/storage | grep container_var_lib_t | complete
40-
assert equal $root_labeled.exit_code 0
41-
let overlay_labeled = getfattr -n security.selinux /usr/lib/bootc/storage/overlay | grep container_ro_file_t | complete
42-
assert equal $overlay_labeled.exit_code 0
47+
for v in [".", "overlay-images", "defaultNetworkBackend"] {
48+
let base = (get_file_selinux_type $"/var/lib/containers/storage/($v)")
49+
let target = (get_file_selinux_type $"/usr/lib/bootc/storage/($v)")
50+
assert equal $base $target
51+
}
52+
# Verify the stamp file exists
53+
test -f /usr/lib/bootc/storage/.bootc_labeled
4354
}
4455

4556
test_logically_bound_images_in_storage

0 commit comments

Comments
 (0)