You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
11 KiB
315 lines
11 KiB
From 1ec5618144e2d5e76caedba9cdcddb2d7ca1d8f7 Mon Sep 17 00:00:00 2001
|
|
From: Colin Walters <walters@verbum.org>
|
|
Date: Fri, 12 Apr 2024 12:59:54 -0400
|
|
Subject: [PATCH 3/3] shadow: Adjust all deployments
|
|
|
|
It was pointed out that in the previous change here we missed
|
|
the fact that the previous deployments were accessible.
|
|
|
|
- Move the logic into Rust, adding unit tests
|
|
- Change the code to iterate over all deployments
|
|
- Add an integration test too
|
|
|
|
Note: A likely future enhancement here will be to finally
|
|
deny unprivileged access to non-default roots; cc
|
|
https://github.com/ostreedev/ostree/issues/3211
|
|
---
|
|
rust/src/lib.rs | 2 +-
|
|
rust/src/main.rs | 1 +
|
|
rust/src/passwd.rs | 124 ++++++++++++++++++
|
|
src/daemon/rpm-ostree-fix-shadow-mode.service | 12 +-
|
|
tests/kolainst/destructive/shadow | 80 +++++++++++
|
|
5 files changed, 214 insertions(+), 5 deletions(-)
|
|
create mode 100755 tests/kolainst/destructive/shadow
|
|
|
|
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
|
|
index e244158b..a65e669b 100644
|
|
--- a/rust/src/lib.rs
|
|
+++ b/rust/src/lib.rs
|
|
@@ -979,7 +979,7 @@ mod normalization;
|
|
mod origin;
|
|
mod ostree_prepareroot;
|
|
pub(crate) use self::origin::*;
|
|
-mod passwd;
|
|
+pub mod passwd;
|
|
use passwd::*;
|
|
mod console_progress;
|
|
pub(crate) use self::console_progress::*;
|
|
diff --git a/rust/src/main.rs b/rust/src/main.rs
|
|
index 5a3c04d0..bf10d45d 100644
|
|
--- a/rust/src/main.rs
|
|
+++ b/rust/src/main.rs
|
|
@@ -28,6 +28,7 @@ async fn inner_async_main(args: Vec<String>) -> Result<i32> {
|
|
match *arg {
|
|
// Add custom Rust commands here, and also in `libmain.cxx` if user-visible.
|
|
"countme" => rpmostree_rust::countme::entrypoint(args).map(|_| 0),
|
|
+ "fix-shadow-perms" => rpmostree_rust::passwd::fix_shadow_perms_entrypoint(args).map(|_| 0),
|
|
"cliwrap" => rpmostree_rust::cliwrap::entrypoint(args).map(|_| 0),
|
|
// A hidden wrapper to intercept some binaries in RPM scriptlets.
|
|
"scriptlet-intercept" => builtins::scriptlet_intercept::entrypoint(args).map(|_| 0),
|
|
diff --git a/rust/src/passwd.rs b/rust/src/passwd.rs
|
|
index a64f6468..f0a6da31 100644
|
|
--- a/rust/src/passwd.rs
|
|
+++ b/rust/src/passwd.rs
|
|
@@ -30,6 +30,10 @@ const DEFAULT_MODE: u32 = 0o644;
|
|
static DEFAULT_PERMS: Lazy<Permissions> = Lazy::new(|| Permissions::from_mode(DEFAULT_MODE));
|
|
static PWGRP_SHADOW_FILES: &[&str] = &["shadow", "gshadow", "subuid", "subgid"];
|
|
static USRLIB_PWGRP_FILES: &[&str] = &["passwd", "group"];
|
|
+// This stamp file signals the original fix which only changed the booted deployment
|
|
+const SHADOW_MODE_FIXED_STAMP_OLD: &str = "etc/.rpm-ostree-shadow-mode-fixed.stamp";
|
|
+// And this one is written by the newer logic that changes all deployments
|
|
+const SHADOW_MODE_FIXED_STAMP: &str = "etc/.rpm-ostree-shadow-mode-fixed2.stamp";
|
|
|
|
// Lock/backup files that should not be in the base commit (TODO fix).
|
|
static PWGRP_LOCK_AND_BACKUP_FILES: &[&str] = &[
|
|
@@ -363,6 +367,86 @@ impl PasswdKind {
|
|
}
|
|
}
|
|
|
|
+/// Due to a prior bug, the build system had some deployments with a world-readable
|
|
+/// shadow file. This fixes a given deployment.
|
|
+#[context("Fixing shadow permissions")]
|
|
+pub(crate) fn fix_shadow_perms_in_root(root: &Dir) -> Result<bool> {
|
|
+ let zero_perms = Permissions::from_mode(0);
|
|
+ let mut changed = false;
|
|
+ for path in ["etc/shadow", "etc/shadow-", "etc/gshadow", "etc/gshadow-"] {
|
|
+ let metadata = if let Some(meta) = root
|
|
+ .symlink_metadata_optional(path)
|
|
+ .context("Querying metadata")?
|
|
+ {
|
|
+ meta
|
|
+ } else {
|
|
+ tracing::debug!("No path {path}");
|
|
+ continue;
|
|
+ };
|
|
+ let mode = metadata.mode() & !libc::S_IFMT;
|
|
+ // Don't touch the file if it's already correct
|
|
+ if mode == 0 {
|
|
+ continue;
|
|
+ }
|
|
+ let f = root.open(path).with_context(|| format!("Opening {path}"))?;
|
|
+ f.set_permissions(zero_perms.clone())
|
|
+ .with_context(|| format!("chmod: {path}"))?;
|
|
+ println!("Adjusted mode for {path}");
|
|
+ changed = true;
|
|
+ }
|
|
+ // Write our stamp file
|
|
+ root.write(SHADOW_MODE_FIXED_STAMP, "")
|
|
+ .context(SHADOW_MODE_FIXED_STAMP)?;
|
|
+ // And clean up the old one
|
|
+ root.remove_file_optional(SHADOW_MODE_FIXED_STAMP_OLD)
|
|
+ .with_context(|| format!("Removing old {SHADOW_MODE_FIXED_STAMP_OLD}"))?;
|
|
+ Ok(changed)
|
|
+}
|
|
+
|
|
+/// Due to a prior bug, the build system had some deployments with a world-readable
|
|
+/// shadow file. This fixes all deployments.
|
|
+pub(crate) fn fix_shadow_perms_in_sysroot(sysroot: &ostree::Sysroot) -> Result<bool> {
|
|
+ let deployments = sysroot.deployments();
|
|
+ // TODO add a nicer api for this to ostree-rs
|
|
+ let sysroot_fd =
|
|
+ Dir::reopen_dir(unsafe { &std::os::fd::BorrowedFd::borrow_raw(sysroot.fd()) })?;
|
|
+ let mut changed = false;
|
|
+ for deployment in deployments {
|
|
+ let path = sysroot.deployment_dirpath(&deployment);
|
|
+ let dir = sysroot_fd.open_dir(&path)?;
|
|
+ if fix_shadow_perms_in_root(&dir)
|
|
+ .with_context(|| format!("Deployment index={}", deployment.index()))?
|
|
+ {
|
|
+ println!(
|
|
+ "Adjusted shadow files in deployment index={} {}.{}",
|
|
+ deployment.index(),
|
|
+ deployment.csum(),
|
|
+ deployment.bootserial()
|
|
+ );
|
|
+ changed = true;
|
|
+ }
|
|
+ }
|
|
+ Ok(changed)
|
|
+}
|
|
+
|
|
+/// The main entrypoint for updating /etc/{,g}shadow permissions across
|
|
+/// all deployments.
|
|
+pub fn fix_shadow_perms_entrypoint(_args: &[&str]) -> Result<()> {
|
|
+ let cancellable = gio::Cancellable::NONE;
|
|
+ let sysroot = ostree::Sysroot::new_default();
|
|
+ sysroot.set_mount_namespace_in_use();
|
|
+ sysroot.lock()?;
|
|
+ sysroot.load(cancellable)?;
|
|
+ let changed = fix_shadow_perms_in_sysroot(&sysroot)?;
|
|
+ if changed {
|
|
+ // We already printed per deployment, so this one is just
|
|
+ // a debug-level log.
|
|
+ tracing::debug!("Updated shadow/gshadow permissions");
|
|
+ }
|
|
+ sysroot.unlock();
|
|
+ Ok(())
|
|
+}
|
|
+
|
|
// This function writes the static passwd/group data from the treefile to the
|
|
// target root filesystem.
|
|
fn write_data_from_treefile(
|
|
@@ -1070,3 +1154,43 @@ impl PasswdEntries {
|
|
Ok(())
|
|
}
|
|
}
|
|
+
|
|
+#[test]
|
|
+fn test_shadow_perms() -> Result<()> {
|
|
+ let root = &cap_tempfile::tempdir(cap_std::ambient_authority())?;
|
|
+ root.create_dir("etc")?;
|
|
+ root.write("etc/shadow", "some shadow")?;
|
|
+ root.write("etc/gshadow", "some gshadow")?;
|
|
+ root.set_permissions("etc/gshadow", Permissions::from_mode(0))?;
|
|
+
|
|
+ assert!(fix_shadow_perms_in_root(root)?);
|
|
+ assert!(!root.try_exists(SHADOW_MODE_FIXED_STAMP_OLD)?);
|
|
+ assert!(root.try_exists(SHADOW_MODE_FIXED_STAMP)?);
|
|
+ // Verify idempotence
|
|
+ assert!(!fix_shadow_perms_in_root(root)?);
|
|
+ assert!(!root.try_exists(SHADOW_MODE_FIXED_STAMP_OLD)?);
|
|
+ assert!(root.try_exists(SHADOW_MODE_FIXED_STAMP)?);
|
|
+
|
|
+ Ok(())
|
|
+}
|
|
+
|
|
+#[test]
|
|
+/// Verify the scenario of updating from a previously fixed root
|
|
+fn test_shadow_perms_from_orig_fix() -> Result<()> {
|
|
+ let root = &cap_tempfile::tempdir(cap_std::ambient_authority())?;
|
|
+ root.create_dir("etc")?;
|
|
+ root.write("etc/shadow", "some shadow")?;
|
|
+ root.set_permissions("etc/shadow", Permissions::from_mode(0))?;
|
|
+ root.write("etc/gshadow", "some gshadow")?;
|
|
+ root.set_permissions("etc/gshadow", Permissions::from_mode(0))?;
|
|
+ // Write the original stamp file
|
|
+ root.write(SHADOW_MODE_FIXED_STAMP_OLD, "")?;
|
|
+
|
|
+ // No changes
|
|
+ assert!(!fix_shadow_perms_in_root(root)?);
|
|
+ // Except we should have updated to the new stamp file
|
|
+ assert!(!root.try_exists(SHADOW_MODE_FIXED_STAMP_OLD)?);
|
|
+ assert!(root.try_exists(SHADOW_MODE_FIXED_STAMP)?);
|
|
+
|
|
+ Ok(())
|
|
+}
|
|
diff --git a/src/daemon/rpm-ostree-fix-shadow-mode.service b/src/daemon/rpm-ostree-fix-shadow-mode.service
|
|
index 4aea7462..121bc74e 100644
|
|
--- a/src/daemon/rpm-ostree-fix-shadow-mode.service
|
|
+++ b/src/daemon/rpm-ostree-fix-shadow-mode.service
|
|
@@ -3,17 +3,21 @@
|
|
# This makes sure to fix permissions on systems that were deployed with the wrong permissions.
|
|
Description=Update permissions for /etc/shadow
|
|
Documentation=https://github.com/coreos/rpm-ostree-ghsa-2m76-cwhg-7wv6
|
|
-ConditionPathExists=!/etc/.rpm-ostree-shadow-mode-fixed.stamp
|
|
+# This new stamp file is written by the Rust code, and obsoletes
|
|
+# the old /etc/.rpm-ostree-shadow-mode-fixed.stamp
|
|
+ConditionPathExists=!/etc/.rpm-ostree-shadow-mode-fixed2.stamp
|
|
ConditionPathExists=/run/ostree-booted
|
|
+# Because we read the sysroot
|
|
+RequiresMountsFor=/boot
|
|
# Make sure this is started before any unprivileged (interactive) user has access to the system.
|
|
Before=systemd-user-sessions.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
-ExecStart=chmod --verbose 0000 /etc/shadow /etc/gshadow
|
|
-ExecStart=-chmod --verbose 0000 /etc/shadow- /etc/gshadow-
|
|
-ExecStart=touch /etc/.rpm-ostree-shadow-mode-fixed.stamp
|
|
+ExecStart=rpm-ostree fix-shadow-perms
|
|
RemainAfterExit=yes
|
|
+# So we can remount /sysroot writable in our own namespace
|
|
+MountFlags=slave
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
diff --git a/tests/kolainst/destructive/shadow b/tests/kolainst/destructive/shadow
|
|
new file mode 100755
|
|
index 00000000..7caf84c0
|
|
--- /dev/null
|
|
+++ b/tests/kolainst/destructive/shadow
|
|
@@ -0,0 +1,80 @@
|
|
+#!/bin/bash
|
|
+#
|
|
+# Copyright (C) 2024 Red Hat Inc.
|
|
+#
|
|
+# This library is free software; you can redistribute it and/or
|
|
+# modify it under the terms of the GNU Lesser General Public
|
|
+# License as published by the Free Software Foundation; either
|
|
+# version 2 of the License, or (at your option) any later version.
|
|
+#
|
|
+# This library is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+# Lesser General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU Lesser General Public
|
|
+# License along with this library; if not, write to the
|
|
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
+# Boston, MA 02111-1307, USA.
|
|
+
|
|
+set -euo pipefail
|
|
+
|
|
+. ${KOLA_EXT_DATA}/libtest.sh
|
|
+
|
|
+set -x
|
|
+
|
|
+cd $(mktemp -d)
|
|
+
|
|
+service=rpm-ostree-fix-shadow-mode.service
|
|
+stamp=/etc/.rpm-ostree-shadow-mode-fixed2.stamp
|
|
+
|
|
+case "${AUTOPKGTEST_REBOOT_MARK:-}" in
|
|
+"")
|
|
+
|
|
+libtest_prepare_fully_offline
|
|
+libtest_enable_repover 0
|
|
+
|
|
+systemctl status ${service} || true
|
|
+rm -vf /etc/.rpm-ostree-shadow-mode*
|
|
+chmod 0644 /etc/gshadow
|
|
+
|
|
+# Verify running the service once fixes things
|
|
+systemctl restart $service
|
|
+assert_has_file "${stamp}"
|
|
+assert_streq "$(stat -c '%f' /etc/gshadow)" 8000
|
|
+
|
|
+# Now *undo* the fix, so that the current (then old) deployment
|
|
+# is broken still, and ensure after reboot that it's fixed
|
|
+# in both.
|
|
+
|
|
+chmod 0644 /etc/gshadow
|
|
+rm -vf /etc/.rpm-ostree*
|
|
+
|
|
+booted_commit=$(rpm-ostree status --json | jq -r '.deployments[0].checksum')
|
|
+ostree refs ${booted_commit} --create vmcheck2
|
|
+rpm-ostree rebase :vmcheck2
|
|
+
|
|
+/tmp/autopkgtest-reboot "1"
|
|
+;;
|
|
+"1")
|
|
+
|
|
+systemctl status $service
|
|
+assert_has_file "${stamp}"
|
|
+
|
|
+verified=0
|
|
+for f in $(ls /ostree/deploy/*/deploy/*/etc/{,g}shadow{,-}); do
|
|
+ verified=$(($verified + 1))
|
|
+ assert_streq "$(stat -c '%f' $f)" 8000
|
|
+ echo "ok ${f}"
|
|
+done
|
|
+assert_streq "$verified" 8
|
|
+
|
|
+journalctl -b -u $service --grep="Adjusted shadow files in deployment" | tee out.txt
|
|
+assert_streq "$(wc -l < out.txt)" 2
|
|
+
|
|
+echo "ok shadow"
|
|
+
|
|
+;;
|
|
+*) echo "unexpected mark: ${AUTOPKGTEST_REBOOT_MARK}"; exit 1;;
|
|
+
|
|
+esac
|
|
--
|
|
2.44.0
|
|
|