diff --git a/.github/workflows/portage-stable-packages-list b/.github/workflows/portage-stable-packages-list index 0949ca56b16..585579004ba 100644 --- a/.github/workflows/portage-stable-packages-list +++ b/.github/workflows/portage-stable-packages-list @@ -592,6 +592,7 @@ sec-policy/selinux-base sec-policy/selinux-base-policy sec-policy/selinux-container sec-policy/selinux-dbus +sec-policy/selinux-docker sec-policy/selinux-policykit sec-policy/selinux-sssd sec-policy/selinux-unconfined diff --git a/build_library/build_image_util.sh b/build_library/build_image_util.sh index b7795ade839..2eae88fa615 100755 --- a/build_library/build_image_util.sh +++ b/build_library/build_image_util.sh @@ -518,6 +518,18 @@ start_image() { --board="${BOARD}" } +# Actions common to the dev container and the image +# but which should be run before the image branches off +# the base squashfs (used for systemd-sysext image building) +finish_image_common() { + local root_fs_dir="$1" + + # Build the selinux policy + if pkg_use_enabled coreos-base/coreos selinux; then + sudo semodule -n -s mcs -p "${root_fs_dir}" -i "${root_fs_dir}"/usr/share/selinux/mcs/*.pp + fi +} + finish_image() { local image_name="$1" local disk_layout="$2" @@ -634,11 +646,6 @@ EOF "${BUILD_DIR}/${image_kconfig}" fi - # Build the selinux policy - if pkg_use_enabled coreos-base/coreos selinux; then - sudo semodule -n -s mcs -p "${root_fs_dir}" -i "${root_fs_dir}"/usr/share/selinux/mcs/*.pp - fi - # Run tmpfiles once to make sure that /etc has everything in place before # we freeze it in /usr/share/flatcar/etc as lowerdir in the overlayfs. @@ -674,10 +681,8 @@ EOF # setfiles from version 3.11 requires the passed path to have no symlinks local real_root_fs_dir real_root_fs_dir=$(realpath "${root_fs_dir}") - # TODO: Breaks the system: - # sudo setfiles -Dv -r "${real_root_fs_dir}" "${real_root_fs_dir}"/etc/selinux/mcs/contexts/files/file_contexts "${real_root_fs_dir}" - # sudo setfiles -Dv -r "${real_root_fs_dir}" "${real_root_fs_dir}"/etc/selinux/mcs/contexts/files/file_contexts "${real_root_fs_dir}"/usr - # For now we only try it with /etc + sudo setfiles -Dv -r "${real_root_fs_dir}" "${real_root_fs_dir}"/etc/selinux/mcs/contexts/files/file_contexts "${real_root_fs_dir}" + sudo setfiles -Dv -r "${real_root_fs_dir}" "${real_root_fs_dir}"/etc/selinux/mcs/contexts/files/file_contexts "${real_root_fs_dir}"/usr sudo setfiles -Dv -r "${real_root_fs_dir}" "${real_root_fs_dir}"/etc/selinux/mcs/contexts/files/file_contexts "${real_root_fs_dir}"/etc fi diff --git a/build_library/dev_container_util.sh b/build_library/dev_container_util.sh index 5bee2889e55..c885e269572 100755 --- a/build_library/dev_container_util.sh +++ b/build_library/dev_container_util.sh @@ -99,6 +99,9 @@ create_dev_container() { # The remount services are provided by coreos-base/coreos-init systemd_enable "${root_fs_dir}" "multi-user.target" "remount-usr.service" + finish_image_common \ + "${root_fs_dir}" + finish_image "${image_name}" "${disk_layout}" "${root_fs_dir}" "${image_contents}" "${image_contents_wtd}" declare -a files_to_evaluate diff --git a/build_library/prod_image_util.sh b/build_library/prod_image_util.sh index 0ea886b9a00..0a392fd94c2 100755 --- a/build_library/prod_image_util.sh +++ b/build_library/prod_image_util.sh @@ -101,6 +101,9 @@ create_prod_image() { ROOT="${root_fs_dir}" PORTAGE_CONFIGROOT="${BUILD_DIR}"/configroot \ test_image_content "${root_fs_dir}" + finish_image_common \ + "${root_fs_dir}" + local root_with_everything="${root_fs_dir}" # Call helper script for adding sysexts to the base OS. diff --git a/build_library/sysext_mangle_flatcar-zfs b/build_library/sysext_mangle_flatcar-zfs index e9ffdb11861..0bf2b8b1a27 100755 --- a/build_library/sysext_mangle_flatcar-zfs +++ b/build_library/sysext_mangle_flatcar-zfs @@ -35,13 +35,8 @@ d /etc/zfs 0755 root root - L /etc/zfs/zed.d - - - - /usr/share/zfs/etc/zfs/zed.d L /etc/zfs/zfs-functions - - - - /usr/share/zfs/etc/zfs/zfs-functions L /etc/zfs/zpool.d - - - - /usr/share/zfs/etc/zfs/zpool.d -C /etc/systemd/system/systemd-udevd.service.d/10-zfs.conf - - - - /usr/lib/systemd/system/systemd-udevd.service.d/10-zfs.conf +r /etc/systemd/system/systemd-udevd.service.d/10-zfs.conf - - - - - EOF -mkdir -p ./usr/lib/systemd/system/systemd-udevd.service.d -cat <./usr/lib/systemd/system/systemd-udevd.service.d/10-zfs.conf -[Unit] -After=systemd-sysext.service -EOF configure_modprobe flatcar-zfs popd diff --git a/build_sysext b/build_sysext index 6ecbdb5f500..e0f6115805d 100755 --- a/build_sysext +++ b/build_sysext @@ -244,7 +244,6 @@ export SOURCE_DATE_EPOCH=$(stat -c '%Y' "${BUILD_DIR}/fs-root/usr/lib/os-release # Unmount in order to get rid of the overlay umount "${BUILD_DIR}/${FLAGS_install_root_basename}" -umount "${BUILD_DIR}/fs-root" if [[ "$FLAGS_generate_pkginfo" = "${FLAGS_TRUE}" ]] ; then info " Creating pkginfo squashfs '${BUILD_DIR}/${SYSEXT_ID}_pkginfo.raw'" @@ -290,11 +289,19 @@ for entry in "${BUILD_DIR}/${FLAGS_install_root_basename}"/*; do rm -rf "${entry}" done +EXTENSION_RELOAD_MANAGER=0 +[[ -d ${BUILD_DIR}/${FLAGS_install_root_basename}/usr/lib/systemd ]] && EXTENSION_RELOAD_MANAGER=1 + +EXTENSION_RESTART_UNITS=() +[[ -d ${BUILD_DIR}/${FLAGS_install_root_basename}/usr/lib/tmpfiles.d ]] && EXTENSION_RESTART_UNITS+=( systemd-tmpfiles-setup-again.service ) +[[ -d ${BUILD_DIR}/${FLAGS_install_root_basename}/usr/lib/udev/rules.d ]] && EXTENSION_RESTART_UNITS+=( systemd-udev-trigger.service ) + grep . </dev/null)" = y ]; then attr -R -r overlay.opaque {}; fi' \; +if pkg_use_enabled coreos-base/coreos selinux; then + # setfiles from version 3.11 requires the passed path to have no symlinks + real_install_root=$(realpath "${BUILD_DIR}/${FLAGS_install_root_basename}") + real_fs_root=$(realpath "${BUILD_DIR}/fs-root") + sudo setfiles -Dv -r "${real_install_root}" "${real_fs_root}/etc/selinux/mcs/contexts/files/file_contexts" "${real_install_root}/usr" +fi + +umount "${BUILD_DIR}/fs-root" + info "Checking for invalid file ownership" invalid_files=$(find "${BUILD_DIR}/${FLAGS_install_root_basename}" -user sdk -or -group sdk) if [[ -n "${invalid_files}" ]]; then diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1-r321.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1-r322.ebuild similarity index 100% rename from sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1-r321.ebuild rename to sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1-r322.ebuild diff --git a/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild b/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild index b6e64960492..e58e45a82d0 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild @@ -148,6 +148,7 @@ RDEPEND="${RDEPEND} sec-policy/selinux-base-policy sec-policy/selinux-container sec-policy/selinux-dbus + sec-policy/selinux-docker sec-policy/selinux-policykit sec-policy/selinux-unconfined sys-apps/acl diff --git a/sdk_container/src/third_party/coreos-overlay/coreos/config/env/sys-apps/systemd b/sdk_container/src/third_party/coreos-overlay/coreos/config/env/sys-apps/systemd index cbee5f0c78a..c9c4cac9461 100644 --- a/sdk_container/src/third_party/coreos-overlay/coreos/config/env/sys-apps/systemd +++ b/sdk_container/src/third_party/coreos-overlay/coreos/config/env/sys-apps/systemd @@ -168,6 +168,36 @@ EOF [Service] SystemCallFilter=@mount +EOF + ) + + ( + insinto "$(systemd_get_systemunitdir)/systemd-udev-trigger.service.d" + newins - flatcar.conf <<'EOF' +# Ensure udev rules are reloaded before triggering events with them because +# udevd caches its rules for 3 seconds. System extensions that include udev +# rules need them immediately loaded before triggering. + +[Service] +ExecStartPre=-udevadm control --reload +EOF + + insinto "$(systemd_get_systemunitdir)" + newins - systemd-tmpfiles-setup-again.service <<'EOF' +# System extensions that include tmpfiles.d configuration can restart this +# service to have it processed when merging. + +[Unit] +Description=Create System Files and Directories After Booting +Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8) + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=systemd-tmpfiles --create --remove --exclude-prefix=/dev +SuccessExitStatus=DATAERR CANTCREAT +ImportCredential=tmpfiles.* +RestrictSUIDSGID=no EOF ) } diff --git a/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-apps/systemd/0009-sysext-Allow-to-re-start-units-from-extension-releas.patch b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-apps/systemd/0009-sysext-Allow-to-re-start-units-from-extension-releas.patch new file mode 100644 index 00000000000..f4cef59863b --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/coreos/user-patches/sys-apps/systemd/0009-sysext-Allow-to-re-start-units-from-extension-releas.patch @@ -0,0 +1,337 @@ +From 347f35cf42013acf2d324ac1299907d19051beed Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Kai=20L=C3=BCke?= +Date: Thu, 28 May 2026 22:07:16 +0900 +Subject: [PATCH] sysext: Allow to (re)start units from extension-release + metadata file + +Up to now we recommended to use TARGET.upholds/ symlinks to start units +when an extension is loaded. However, this has some drawbacks. First, +for services that should not be tried to be started all the time we have +to resort to hiding them through a target that gets uphold and then +uses regular .wants/ for the actual service. Second, we actually leak +services on extension unload even if the unit has disappeared with the +extension. Third, to affect a service through a drop-in or a config +change from a confext/sysext and that service is already running, we +need a way to restart/reload it instead of just starting it. + +Similar to EXTENSION_RELOAD_MANAGER=1, add a EXTENSION_RESTART_UNITS= +and a EXTENSION_RELOAD_OR_RESTART_UNITS= setting to the +extension-release metadata file, carrying a whitespace-separated list +of units to restart/reload on merge/refresh/unmerge after the daemon +reload. Also detect when the unit has vanished which is normally the +case when the unit was part of the unmerged extension, and stop it +explicitly to prevent it leaking. When the extension itself ships the +binary it should use EXTENSION_RESTART_UNITS= to make sure the new +binary is picked up. Since starting through this setting does not work +when the extension is mounted from the initrd, extensions should still +ship at least a .wants/ symlink to start at boot but can also continue +to ship a .upholds/ symlink for backwards compatibility without any +drawback and still benefit from the unit stopping triggered by the new +setting. While there are cases where one could want to set +EXTENSION_RESTART_UNITS= without requiring a daemon reload (e.g., an +env var file change instead of a unit drop-in), we now do an implicit +daemon reload when we have to restart units so that we know we work on +the right state and we spare users remembering to set this setting in +addition to prevent running into this issue. +--- a/src/sysext/sysext.c ++++ b/src/sysext/sysext.c +@@ -15,9 +15,12 @@ + #include "blkid-util.h" + #include "blockdev-util.h" + #include "build.h" ++#include "bus-common-errors.h" ++#include "bus-locator.h" + #include "bus-polkit.h" + #include "bus-unit-util.h" + #include "bus-util.h" ++#include "bus-wait-for-jobs.h" + #include "capability-util.h" + #include "chase.h" + #include "conf-parser.h" +@@ -57,12 +60,14 @@ + #include "rm-rf.h" + #include "runtime-scope.h" + #include "selinux-util.h" ++#include "set.h" + #include "sort-util.h" + #include "stat-util.h" + #include "string-table.h" + #include "string-util.h" + #include "strv.h" + #include "time-util.h" ++#include "unit-name.h" + #include "varlink-io.systemd.sysext.h" + #include "varlink-util.h" + #include "verbs.h" +@@ -289,16 +294,60 @@ static int is_our_mount_point( + return true; + } + +-static int need_reload( ++static int split_unit_string(const char *s, const char *field, const char *extension, Set **units) { ++ _cleanup_strv_free_ char **split = NULL; ++ int r; ++ ++ assert(field); ++ assert(extension); ++ assert(units); ++ ++ if (isempty(s)) ++ return 0; ++ ++ split = strv_split(s, /* separators= whitespace */ NULL); ++ if (!split) ++ return log_oom(); ++ ++ STRV_FOREACH(u, split) { ++ if (!unit_name_is_valid(*u, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { ++ log_warning("Invalid unit name '%s' in %s= of %s, ignoring.", *u, field, extension); ++ continue; ++ } ++ ++ r = set_put_strdup(units, *u); ++ if (r < 0) ++ return log_oom(); ++ } ++ ++ return 0; ++} ++ ++static int get_extension_release_metadata( + ImageClass image_class, + char **hierarchies, +- bool no_reload) { +- +- /* Parse the mounted images to find out if we need to reload the daemon. */ ++ bool no_reload, ++ bool *ret_need_reload, ++ Set **ret_restart_units, ++ Set **ret_reload_or_restart_units) { ++ ++ /* Parse the mounted images to find out if we need to reload the daemon, and which units to ++ * restart or reload-or-restart afterwards. */ ++ _cleanup_set_free_ Set *restart_units = NULL, *reload_or_restart_units = NULL; ++ bool need_to_reload = false; + int r; + +- if (no_reload) +- return false; ++ if (no_reload || !isempty(arg_root)) { ++ /* With --root= we'd be talking to the host service manager about extensions merged ++ * into a foreign root, which is never what we want. */ ++ if (ret_need_reload) ++ *ret_need_reload = false; ++ if (ret_restart_units) ++ *ret_restart_units = NULL; ++ if (ret_reload_or_restart_units) ++ *ret_reload_or_restart_units = NULL; ++ return 0; ++ } + + STRV_FOREACH(p, hierarchies) { + _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL; +@@ -334,8 +383,7 @@ static int need_reload( + + STRV_FOREACH(extension, mounted_extensions) { + _cleanup_strv_free_ char **extension_release = NULL; +- const char *extension_reload_manager = NULL; +- int b; ++ const char *value; + + r = load_extension_release_pairs(arg_root, image_class, *extension, /* relax_extension_release_check= */ true, &extension_release); + if (r < 0) { +@@ -343,23 +391,50 @@ static int need_reload( + continue; + } + +- extension_reload_manager = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER"); +- if (isempty(extension_reload_manager)) +- continue; ++ value = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER"); ++ if (!isempty(value)) { ++ int b = parse_boolean(value); ++ if (b < 0) ++ log_warning_errno(b, "Failed to parse EXTENSION_RELOAD_MANAGER= of %s, ignoring: %m", *extension); ++ else if (b) ++ need_to_reload = true; ++ } + +- b = parse_boolean(extension_reload_manager); +- if (b < 0) { +- log_warning_errno(b, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m"); +- continue; ++ if (ret_restart_units) { ++ value = strv_env_pairs_get(extension_release, "EXTENSION_RESTART_UNITS"); ++ r = split_unit_string(value, "EXTENSION_RESTART_UNITS", *extension, &restart_units); ++ if (r < 0) ++ return r; ++ if (!isempty(value)) ++ /* Listing units to restart implies a manager reload because a unit ++ * shipped (and later removed) by the extension would otherwise not ++ * be visible to the manager when RestartUnit/StopUnit is issued. */ ++ need_to_reload = true; + } + +- if (b) +- /* If at least one extension wants a reload, we reload. */ +- return true; ++ if (ret_reload_or_restart_units) { ++ value = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_OR_RESTART_UNITS"); ++ r = split_unit_string(value, "EXTENSION_RELOAD_OR_RESTART_UNITS", *extension, &reload_or_restart_units); ++ if (r < 0) ++ return r; ++ if (!isempty(value)) ++ need_to_reload = true; ++ } + } + } + +- return false; ++ /* If a unit is listed in both, restart takes precedence over reload-or-restart. */ ++ const char *u; ++ SET_FOREACH(u, restart_units) ++ free(set_remove(reload_or_restart_units, u)); ++ ++ if (ret_need_reload) ++ *ret_need_reload = need_to_reload; ++ if (ret_restart_units) ++ *ret_restart_units = TAKE_PTR(restart_units); ++ if (ret_reload_or_restart_units) ++ *ret_reload_or_restart_units = TAKE_PTR(reload_or_restart_units); ++ return 0; + } + + static int move_submounts(const char *src, const char *dst) { +@@ -425,6 +500,77 @@ static int daemon_reload(void) { + return bus_service_manager_reload(bus); + } + ++static int dispatch_unit_method(sd_bus *bus, BusWaitForJobs *w, Set *units, const char *method) { ++ const char *unit; ++ int r; ++ ++ assert(bus); ++ assert(w); ++ assert(method); ++ ++ /* Issue the given method for each unit and wait for the jobs to complete. Fall back to StopUnit ++ * when the call reports that the unit's file is gone, e.g., on unmerge when the unit was part of ++ * the extension, because otherwise the running service is leaked. */ ++ ++ SET_FOREACH(unit, units) { ++ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; ++ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; ++ const char *m = method, *job; ++ ++ r = bus_call_method(bus, bus_systemd_mgr, m, &error, &reply, "ss", unit, "replace"); ++ if (r < 0 && sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT, BUS_ERROR_LOAD_FAILED)) { ++ sd_bus_error_free(&error); ++ m = "StopUnit"; ++ r = bus_call_method(bus, bus_systemd_mgr, m, &error, &reply, "ss", unit, "replace"); ++ if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) { ++ log_debug("Unit '%s' is already gone, nothing to stop.", unit); ++ continue; ++ } ++ } ++ if (r < 0) { ++ log_warning("Failed to %s unit '%s': %s", m, unit, bus_error_message(&error, r)); ++ continue; ++ } ++ ++ r = sd_bus_message_read(reply, "o", &job); ++ if (r < 0) ++ return bus_log_parse_error(r); ++ ++ r = bus_wait_for_jobs_one(w, job, BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL); ++ if (r < 0) ++ log_warning_errno(r, "Failed to wait for %s job of unit '%s', ignoring: %m", m, unit); ++ } ++ ++ return 0; ++} ++ ++static int restart_units(Set *restart_units, Set *reload_or_restart_units) { ++ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; ++ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; ++ int r; ++ ++ if (set_isempty(restart_units) && set_isempty(reload_or_restart_units)) ++ return 0; ++ ++ r = bus_connect_system_systemd(&bus); ++ if (r < 0) ++ return log_error_errno(r, "Failed to get D-Bus connection: %m"); ++ ++ r = bus_wait_for_jobs_new(bus, &w); ++ if (r < 0) ++ return log_error_errno(r, "Failed to set up job watcher: %m"); ++ ++ r = dispatch_unit_method(bus, w, restart_units, "RestartUnit"); ++ if (r < 0) ++ return r; ++ ++ r = dispatch_unit_method(bus, w, reload_or_restart_units, "ReloadOrRestartUnit"); ++ if (r < 0) ++ return r; ++ ++ return 0; ++} ++ + static int append_overlayfs_path_option( + char **options, + const char *separator, +@@ -1673,15 +1819,15 @@ static int unmerge( + char **hierarchies, + bool no_reload) { + ++ _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; + bool need_to_reload; + int r; + + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + +- r = need_reload(image_class, hierarchies, no_reload); ++ r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + if (r < 0) + return r; +- need_to_reload = r > 0; + + r = pidref_safe_fork( + "(sd-unmerge)", +@@ -1706,6 +1852,11 @@ static int unmerge( + return r; + } + ++ /* The sets will be empty when opted out */ ++ r = restart_units(units_to_restart, units_to_reload_or_restart); ++ if (r < 0) ++ return r; ++ + return 0; + } + +@@ -2293,15 +2444,22 @@ static int merge(ImageClass image_class, + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies"); + +- r = need_reload(image_class, hierarchies, no_reload); ++ _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; ++ bool need_to_reload; ++ r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + if (r < 0) + return r; +- if (r > 0) { ++ if (need_to_reload) { + r = daemon_reload(); + if (r < 0) + return r; + } + ++ /* The sets will be empty when opted out */ ++ r = restart_units(units_to_restart, units_to_reload_or_restart); ++ if (r < 0) ++ return r; ++ + return 1; + } +