From b7ebf973897f73a9ef999c708edd2aa8d217d2ae Mon Sep 17 00:00:00 2001 From: Jan Macku Date: Thu, 16 Apr 2026 15:01:05 +0200 Subject: [PATCH] systemd-257-24 Resolves: RHEL-155454, RHEL-155805, RHEL-155396, RHEL-158303, RHEL-158354, RHEL-143728, RHEL-168098, RHEL-143028 --- ...-framework-option-for-build-and-unit.patch | 89 ++++ ...bpftool-workaround-to-codeql-job-too.patch | 32 ++ ...-workaround-about-bpftool-for-codeql.patch | 27 ++ ...d-bpftool-workaround-to-coverity-too.patch | 31 ++ ...on-RHEL-documentation-in-systemctl-s.patch | 36 ++ ...avour-of-path_startswith-that-leaves.patch | 162 +++++++ ...me-code-over-to-path_startswith_full.patch | 45 ++ ...-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch | 95 ++++ ...by-one-issue-when-updating-parent-fo.patch | 28 ++ ...group-avoid-one-unnecessary-strjoina.patch | 96 ++++ ...ate-input-cgroup-path-more-prudently.patch | 29 ++ ...dUser-Ephemeral-from-settings-file-o.patch | 55 +++ 0622-nspawn-normalize-pivot_root-paths.patch | 32 ++ ...nvalid-chars-in-various-fields-recei.patch | 124 +++++ ...e-is-space-for-trailing-NUL-before-c.patch | 45 ++ ...sure-tag-parsing-stays-within-bounds.patch | 32 ++ 0626-udev-fix-review-mixup.patch | 32 ++ ...ck-for-invalid-chars-in-various-fiel.patch | 52 +++ ...net-id-print-cescaped-bad-attributes.patch | 88 ++++ ...te-transaction-that-contain-useful-j.patch | 34 ++ ...ald-extend-STDOUT_STREAMS_MAX-to-64k.patch | 27 ++ ...ing-util-introduce-strprepend-helper.patch | 76 +++ 0632-missing-add-quotactl_fd-wrapper.patch | 139 ++++++ ...ays-use-quotactl_fd-if-its-available.patch | 413 ++++++++++++++++ ...cefulOptions-setting-to-.mount-units.patch | 400 ++++++++++++++++ ...1-enable-usrquota-support-on-dev-shm.patch | 75 +++ ...units-enable-usrquota-support-on-tmp.patch | 24 + ...-usrquota-support-on-tmp-and-dev-shm.patch | 71 +++ ...-log-message-about-usrquota-to-debug.patch | 31 ++ 0639-mount-setup-drop-outdated-comment.patch | 24 + ...-down-log-level-if-usrquota-is-not-s.patch | 40 ++ ...macros-to-safely-convert-dev_t-to-po.patch | 89 ++++ ...fields-for-setting-limits-on-tmp-and.patch | 284 +++++++++++ ...-some-smaller-modernizations-refacto.patch | 108 +++++ ...me-dir-enforce-tmp-and-dev-shm-quota.patch | 304 ++++++++++++ ...support-for-configuring-tmpfs-limits.patch | 129 +++++ ...se-for-tmpfs-quota-logic-PAMName-ask.patch | 40 ++ 0647-update-TODO.patch | 52 +++ 0648-core-dbus-service-fix-alignment.patch | 31 ++ ...mount-filter-out-fail-option-as-well.patch | 25 + ...-parameters_fragment-first-in-mount_.patch | 155 +++++++ ...rt-accurate-can_start-and-can_reload.patch | 49 ++ ...-mount-trivial-coding-style-cleanups.patch | 67 +++ ...add-missing-ReloadResult-and-CleanRe.patch | 73 +++ ...bus-unit-util-add-missing-assertions.patch | 49 ++ ...k-GracefulOptions-to-be-just-x-syste.patch | 439 ++++++++++++++++++ ...assume-fsopen-works-in-mount_option_.patch | 48 ++ ...nly-once-about-fs-not-supporting-new.patch | 30 ++ ...e-dir-correct-quota-size-calculation.patch | 29 ++ ...ta-add-a-little-helper-binary-to-sho.patch | 135 ++++++ ...eck-for-support-on-dev-shm-and-tmp-s.patch | 61 +++ ...ED-conditionally-skip-usrquota-tests.patch | 46 ++ systemd.spec | 108 ++++- 53 files changed, 4834 insertions(+), 1 deletion(-) create mode 100644 0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch create mode 100644 0611-ci-add-bpftool-workaround-to-codeql-job-too.patch create mode 100644 0612-ci-fix-workaround-about-bpftool-for-codeql.patch create mode 100644 0613-ci-add-bpftool-workaround-to-coverity-too.patch create mode 100644 0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch create mode 100644 0615-path-util-add-flavour-of-path_startswith-that-leaves.patch create mode 100644 0616-cgroup-port-some-code-over-to-path_startswith_full.patch create mode 100644 0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch create mode 100644 0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch create mode 100644 0619-core-cgroup-avoid-one-unnecessary-strjoina.patch create mode 100644 0620-core-validate-input-cgroup-path-more-prudently.patch create mode 100644 0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch create mode 100644 0622-nspawn-normalize-pivot_root-paths.patch create mode 100644 0623-udev-check-for-invalid-chars-in-various-fields-recei.patch create mode 100644 0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch create mode 100644 0625-udev-ensure-tag-parsing-stays-within-bounds.patch create mode 100644 0626-udev-fix-review-mixup.patch create mode 100644 0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch create mode 100644 0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch create mode 100644 0629-core-only-activate-transaction-that-contain-useful-j.patch create mode 100644 0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch create mode 100644 0631-string-util-introduce-strprepend-helper.patch create mode 100644 0632-missing-add-quotactl_fd-wrapper.patch create mode 100644 0633-homed-always-use-quotactl_fd-if-its-available.patch create mode 100644 0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch create mode 100644 0635-pid1-enable-usrquota-support-on-dev-shm.patch create mode 100644 0636-units-enable-usrquota-support-on-tmp.patch create mode 100644 0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch create mode 100644 0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch create mode 100644 0639-mount-setup-drop-outdated-comment.patch create mode 100644 0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch create mode 100644 0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch create mode 100644 0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch create mode 100644 0643-user-runtime-dir-some-smaller-modernizations-refacto.patch create mode 100644 0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch create mode 100644 0645-homectl-add-support-for-configuring-tmpfs-limits.patch create mode 100644 0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch create mode 100644 0647-update-TODO.patch create mode 100644 0648-core-dbus-service-fix-alignment.patch create mode 100644 0649-core-mount-filter-out-fail-option-as-well.patch create mode 100644 0650-core-mount-check-parameters_fragment-first-in-mount_.patch create mode 100644 0651-core-mount-report-accurate-can_start-and-can_reload.patch create mode 100644 0652-core-mount-trivial-coding-style-cleanups.patch create mode 100644 0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch create mode 100644 0654-bus-unit-util-add-missing-assertions.patch create mode 100644 0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch create mode 100644 0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch create mode 100644 0657-core-mount-log-only-once-about-fs-not-supporting-new.patch create mode 100644 0658-user-runtime-dir-correct-quota-size-calculation.patch create mode 100644 0659-test-display-quota-add-a-little-helper-binary-to-sho.patch create mode 100644 0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch create mode 100644 0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch diff --git a/0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch b/0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch new file mode 100644 index 0000000..44d5745 --- /dev/null +++ b/0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch @@ -0,0 +1,89 @@ +From 3061980e575f36129fe1880118ae757e6069eaa0 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 17 Oct 2025 14:00:23 +0100 +Subject: [PATCH] ci: re-enable bpf-framework option for build and unit test + jobs + +Use the same trickery we do in the package build and search for +the actual bpftool binary. For the CI job any one we find is +good enough. +When we switch all jobs to 26.04 we can drop all of this. + +This reverts commit cc814110af7a453db898ea2990a0281616d5ceff. + +(cherry picked from commit 3b11139c0db9dd0a37b0493a8d2ad5f531a92344) + +Related: RHEL-155454 +--- + .github/workflows/build_test.sh | 12 ++++++++++++ + .github/workflows/unit_tests.sh | 13 +++++++++++++ + 2 files changed, 25 insertions(+) + +diff --git a/.github/workflows/build_test.sh b/.github/workflows/build_test.sh +index 113af704a8..462203be9a 100755 +--- a/.github/workflows/build_test.sh ++++ b/.github/workflows/build_test.sh +@@ -47,6 +47,7 @@ PACKAGES=( + libxkbcommon-dev + libxtables-dev + libzstd-dev ++ linux-tools-generic + mold + mount + net-tools +@@ -131,6 +132,17 @@ sudo apt-get -y install "${PACKAGES[@]}" + pip3 install --user -r .github/workflows/requirements.txt --require-hashes --break-system-packages + export PATH="$HOME/.local/bin:$PATH" + ++# TODO: drop after we switch to ubuntu 26.04 ++bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)") ++if [ -n "$bpftool_dir" ]; then ++ export PATH="$bpftool_dir:$PATH" ++fi ++ ++if [[ -n "$CUSTOM_PYTHON" ]]; then ++ # If CUSTOM_PYTHON is set we need to pull jinja2 from pip, as a local interpreter is used ++ pip3 install --user --break-system-packages jinja2 ++fi ++ + $CC --version + meson --version + ninja --version +diff --git a/.github/workflows/unit_tests.sh b/.github/workflows/unit_tests.sh +index 168bcc55c3..3acaf75417 100755 +--- a/.github/workflows/unit_tests.sh ++++ b/.github/workflows/unit_tests.sh +@@ -18,6 +18,7 @@ ADDITIONAL_DEPS=( + libtss2-dev + libxkbcommon-dev + libzstd-dev ++ linux-tools-generic + python3-libevdev + python3-pefile + python3-pyelftools +@@ -70,6 +71,12 @@ for phase in "${PHASES[@]}"; do + capsh --drop=all -- -c "stat $PWD/meson.build" + ;; + RUN|RUN_GCC|RUN_CLANG|RUN_CLANG_RELEASE) ++ # TODO: drop after we switch to ubuntu 26.04 ++ bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)") ++ if [ -n "$bpftool_dir" ]; then ++ export PATH="$bpftool_dir:$PATH" ++ fi ++ + if [[ "$phase" =~ ^RUN_CLANG ]]; then + export CC=clang + export CXX=clang++ +@@ -94,6 +101,12 @@ for phase in "${PHASES[@]}"; do + TZ=GMT+12 meson test -C build --print-errorlogs + ;; + RUN_ASAN_UBSAN|RUN_GCC_ASAN_UBSAN|RUN_CLANG_ASAN_UBSAN|RUN_CLANG_ASAN_UBSAN_NO_DEPS) ++ # TODO: drop after we switch to ubuntu 26.04 ++ bpftool_dir=$(dirname "$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1)") ++ if [ -n "$bpftool_dir" ]; then ++ export PATH="$bpftool_dir:$PATH" ++ fi ++ + MESON_ARGS=(--optimization=1) + + if [[ "$phase" =~ ^RUN_CLANG_ASAN_UBSAN ]]; then diff --git a/0611-ci-add-bpftool-workaround-to-codeql-job-too.patch b/0611-ci-add-bpftool-workaround-to-codeql-job-too.patch new file mode 100644 index 0000000..7b76515 --- /dev/null +++ b/0611-ci-add-bpftool-workaround-to-codeql-job-too.patch @@ -0,0 +1,32 @@ +From 5886ebf6256fb0b1873828fa46d918877c18e474 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 17 Oct 2025 15:39:09 +0100 +Subject: [PATCH] ci: add bpftool workaround to codeql job too + +(cherry picked from commit e9fd2bbfffc5c2c7cd1ea0a288d5435fc15e387f) + +Related: RHEL-155454 +--- + .github/workflows/codeql.yml | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml +index 51034783ca..443221fa11 100644 +--- a/.github/workflows/codeql.yml ++++ b/.github/workflows/codeql.yml +@@ -44,7 +44,14 @@ jobs: + languages: ${{ matrix.language }} + config-file: ./.github/codeql-config.yml + +- - run: sudo -E .github/workflows/unit_tests.sh SETUP ++ - run: | ++ sudo -E .github/workflows/unit_tests.sh SETUP ++ # TODO: drop after we switch to ubuntu 26.04 ++ bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1) ++ if [ -n "$bpftool_binary" ]; then ++ sudo rm -f /usr/bin/bpftool ++ sudo ln -s "$bpftool_binary" /usr/bin/ ++ fi + + - name: Autobuild + uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88 diff --git a/0612-ci-fix-workaround-about-bpftool-for-codeql.patch b/0612-ci-fix-workaround-about-bpftool-for-codeql.patch new file mode 100644 index 0000000..01af887 --- /dev/null +++ b/0612-ci-fix-workaround-about-bpftool-for-codeql.patch @@ -0,0 +1,27 @@ +From 9455ccb53a8cf50d9b7026e297f8afd8847a57e2 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sat, 18 Oct 2025 10:39:13 +0900 +Subject: [PATCH] ci: fix workaround about bpftool for codeql + +Follow-up for e9fd2bbfffc5c2c7cd1ea0a288d5435fc15e387f. + +(cherry picked from commit a6836cfa0bdf1bb1fcf05686c5af3f2b5ad97f6b) + +Related: RHEL-155454 +--- + .github/workflows/codeql.yml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml +index 443221fa11..cfe54d59ac 100644 +--- a/.github/workflows/codeql.yml ++++ b/.github/workflows/codeql.yml +@@ -49,7 +49,7 @@ jobs: + # TODO: drop after we switch to ubuntu 26.04 + bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1) + if [ -n "$bpftool_binary" ]; then +- sudo rm -f /usr/bin/bpftool ++ sudo rm -f /usr/{bin,sbin}/bpftool + sudo ln -s "$bpftool_binary" /usr/bin/ + fi + diff --git a/0613-ci-add-bpftool-workaround-to-coverity-too.patch b/0613-ci-add-bpftool-workaround-to-coverity-too.patch new file mode 100644 index 0000000..f2592e4 --- /dev/null +++ b/0613-ci-add-bpftool-workaround-to-coverity-too.patch @@ -0,0 +1,31 @@ +From 2842f1200bcb79f211309270a0afab4ab7a89b20 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Sat, 18 Oct 2025 14:23:59 +0100 +Subject: [PATCH] ci: add bpftool workaround to coverity too + +(cherry picked from commit d29f181cf02100c146fc8691a5515a708d06ddbf) + +Related: RHEL-155454 +--- + .github/workflows/coverity.yml | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml +index 66204069c4..80d25d380a 100644 +--- a/.github/workflows/coverity.yml ++++ b/.github/workflows/coverity.yml +@@ -25,6 +25,13 @@ jobs: + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + # Reuse the setup phase of the unit test script to avoid code duplication + - name: Install build dependencies +- run: sudo -E .github/workflows/unit_tests.sh SETUP ++ run: | ++ sudo -E .github/workflows/unit_tests.sh SETUP ++ # TODO: drop after we switch to ubuntu 26.04 ++ bpftool_binary=$(find /usr/lib/linux-tools/ /usr/lib/linux-tools-* -name 'bpftool' -perm /u=x 2>/dev/null | sort -r | head -n1) ++ if [ -n "$bpftool_binary" ]; then ++ sudo rm -f /usr/{bin,sbin}/bpftool ++ sudo ln -s "$bpftool_binary" /usr/bin/ ++ fi + - name: Build & upload the results + run: tools/coverity.sh diff --git a/0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch b/0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch new file mode 100644 index 0000000..1ebf815 --- /dev/null +++ b/0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch @@ -0,0 +1,36 @@ +From 3e597926e21dfbaf2b4a38aa64a4da954815f2b7 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Mon, 30 Mar 2026 14:48:42 +0200 +Subject: [PATCH] Revert "man: mention RHEL documentation in systemctl's man + page" + +This reverts commit fdebd2be914ce55ec4244c99adc93fc7f046c08f. + +rhel-only: doc + +Resolves: RHEL-155805 +--- + man/systemctl.xml | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/man/systemctl.xml b/man/systemctl.xml +index 322505e615..fe15bd3722 100644 +--- a/man/systemctl.xml ++++ b/man/systemctl.xml +@@ -2978,16 +2978,6 @@ EOF + + + +- +- Examples +- +- For examples how to use systemctl in comparison with old service and chkconfig commands please see: +- +- Managing System Services +- +- +- +- + + See Also + diff --git a/0615-path-util-add-flavour-of-path_startswith-that-leaves.patch b/0615-path-util-add-flavour-of-path_startswith-that-leaves.patch new file mode 100644 index 0000000..8e9f02b --- /dev/null +++ b/0615-path-util-add-flavour-of-path_startswith-that-leaves.patch @@ -0,0 +1,162 @@ +From 655ca1b4d224079a6ce87f705fb0c33ebe69726f Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 19 May 2025 12:58:52 +0200 +Subject: [PATCH] path-util: add flavour of path_startswith() that leaves a + leading slash in place + +(cherry picked from commit ee19edbb9f3455db3f750089082f3e5a925e3a0c) + +Related: RHEL-155396 +--- + src/basic/fs-util.c | 2 +- + src/basic/mkdir.c | 2 +- + src/basic/path-util.c | 39 ++++++++++++++++++++++++++++----------- + src/basic/path-util.h | 10 ++++++++-- + src/test/test-path-util.c | 16 ++++++++++++++++ + 5 files changed, 54 insertions(+), 15 deletions(-) + +diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c +index 4ede324c34..21a670c9e8 100644 +--- a/src/basic/fs-util.c ++++ b/src/basic/fs-util.c +@@ -66,7 +66,7 @@ int rmdir_parents(const char *path, const char *stop) { + assert(*slash == '/'); + *slash = '\0'; + +- if (path_startswith_full(stop, p, /* accept_dot_dot= */ false)) ++ if (path_startswith_full(stop, p, /* flags= */ 0)) + return 0; + + if (rmdir(p) < 0 && errno != ENOENT) +diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c +index 3e1545a58b..6fc2a79944 100644 +--- a/src/basic/mkdir.c ++++ b/src/basic/mkdir.c +@@ -149,7 +149,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui + assert(_mkdirat != mkdirat); + + if (prefix) { +- p = path_startswith_full(path, prefix, /* accept_dot_dot= */ false); ++ p = path_startswith_full(path, prefix, /* flags= */ 0); + if (!p) + return -EINVAL; + +diff --git a/src/basic/path-util.c b/src/basic/path-util.c +index 78ba10ed80..a4709e1b7d 100644 +--- a/src/basic/path-util.c ++++ b/src/basic/path-util.c +@@ -405,8 +405,8 @@ char* path_simplify_full(char *path, PathSimplifyFlags flags) { + return path; + } + +-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) { +- assert(path); ++char* path_startswith_full(const char *original_path, const char *prefix, PathStartWithFlags flags) { ++ assert(original_path); + assert(prefix); + + /* Returns a pointer to the start of the first component after the parts matched by +@@ -419,28 +419,45 @@ char* path_startswith_full(const char *path, const char *prefix, bool accept_dot + * Returns NULL otherwise. + */ + ++ const char *path = original_path; ++ + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + const char *p, *q; +- int r, k; ++ int m, n; + +- r = path_find_first_component(&path, accept_dot_dot, &p); +- if (r < 0) ++ m = path_find_first_component(&path, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &p); ++ if (m < 0) + return NULL; + +- k = path_find_first_component(&prefix, accept_dot_dot, &q); +- if (k < 0) ++ n = path_find_first_component(&prefix, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &q); ++ if (n < 0) + return NULL; + +- if (k == 0) +- return (char*) (p ?: path); ++ if (n == 0) { ++ if (!p) ++ p = path; ++ ++ if (FLAGS_SET(flags, PATH_STARTSWITH_RETURN_LEADING_SLASH)) { ++ ++ if (p <= original_path) ++ return NULL; ++ ++ p--; ++ ++ if (*p != '/') ++ return NULL; ++ } ++ ++ return (char*) p; ++ } + +- if (r != k) ++ if (m != n) + return NULL; + +- if (!strneq(p, q, r)) ++ if (!strneq(p, q, m)) + return NULL; + } + } +diff --git a/src/basic/path-util.h b/src/basic/path-util.h +index dff5a3a549..d1e9f4b785 100644 +--- a/src/basic/path-util.h ++++ b/src/basic/path-util.h +@@ -52,9 +52,15 @@ int safe_getcwd(char **ret); + int path_make_absolute_cwd(const char *p, char **ret); + int path_make_relative(const char *from, const char *to, char **ret); + int path_make_relative_parent(const char *from_child, const char *to, char **ret); +-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_; ++ ++typedef enum PathStartWithFlags { ++ PATH_STARTSWITH_ACCEPT_DOT_DOT = 1U << 0, ++ PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1, ++} PathStartWithFlags; ++ ++char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_; + static inline char* path_startswith(const char *path, const char *prefix) { +- return path_startswith_full(path, prefix, true); ++ return path_startswith_full(path, prefix, PATH_STARTSWITH_ACCEPT_DOT_DOT); + } + + int path_compare(const char *a, const char *b) _pure_; +diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c +index e02bd8c857..8aa477bd69 100644 +--- a/src/test/test-path-util.c ++++ b/src/test/test-path-util.c +@@ -756,6 +756,22 @@ TEST(path_startswith) { + test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL); + } + ++static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) { ++ const char *p; ++ ++ log_debug("/* %s(%s, %s) */", __func__, path, prefix); ++ ++ p = path_startswith_full(path, prefix, PATH_STARTSWITH_RETURN_LEADING_SLASH); ++ ASSERT_STREQ(p, expected); ++} ++ ++TEST(path_startswith_return_leading_slash) { ++ test_path_startswith_return_leading_slash_one("/foo/bar", "/", "/foo/bar"); ++ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo", "/bar"); ++ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo/bar", NULL); ++ test_path_startswith_return_leading_slash_one("/foo/bar/", "/foo/bar", "/"); ++} ++ + static void test_prefix_root_one(const char *r, const char *p, const char *expected) { + _cleanup_free_ char *s = NULL; + const char *t; diff --git a/0616-cgroup-port-some-code-over-to-path_startswith_full.patch b/0616-cgroup-port-some-code-over-to-path_startswith_full.patch new file mode 100644 index 0000000..a2be0db --- /dev/null +++ b/0616-cgroup-port-some-code-over-to-path_startswith_full.patch @@ -0,0 +1,45 @@ +From 6ac39e6caa209898323dda2d907b594322ba3894 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 22 May 2025 18:35:25 +0200 +Subject: [PATCH] cgroup: port some code over to path_startswith_full() + +(cherry picked from commit 482107724f64cb8dd24db3e65b6ea3151a330301) + +Related: RHEL-155396 +--- + src/basic/cgroup-util.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c +index 309dccb45a..b8a17badea 100644 +--- a/src/basic/cgroup-util.c ++++ b/src/basic/cgroup-util.c +@@ -1013,13 +1013,12 @@ int cg_get_root_path(char **ret_path) { + } + + int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted) { +- _cleanup_free_ char *rt = NULL; +- char *p; + int r; + + assert(cgroup); + assert(ret_shifted); + ++ _cleanup_free_ char *rt = NULL; + if (!root) { + /* If the root was specified let's use that, otherwise + * let's determine it from PID 1 */ +@@ -1031,12 +1030,7 @@ int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted + root = rt; + } + +- p = path_startswith(cgroup, root); +- if (p && p > cgroup) +- *ret_shifted = p - 1; +- else +- *ret_shifted = cgroup; +- ++ *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH) ?: cgroup; + return 0; + } + diff --git a/0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch b/0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch new file mode 100644 index 0000000..276e848 --- /dev/null +++ b/0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch @@ -0,0 +1,95 @@ +From 889e708da11d9185abb56d5aa42bb1df747217b5 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 23 May 2025 06:45:40 +0200 +Subject: [PATCH] path-util: invert PATH_STARTSWITH_ACCEPT_DOT_DOT flag + +As requested: https://github.com/systemd/systemd/pull/37572#pullrequestreview-2861928094 + +(cherry picked from commit ceed11e465f1c8efff1931412a85924d9de7c08d) + +Related: RHEL-155396 +--- + src/basic/cgroup-util.c | 2 +- + src/basic/fs-util.c | 2 +- + src/basic/mkdir.c | 2 +- + src/basic/path-util.c | 4 ++-- + src/basic/path-util.h | 4 ++-- + 5 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c +index b8a17badea..925b753c39 100644 +--- a/src/basic/cgroup-util.c ++++ b/src/basic/cgroup-util.c +@@ -1030,7 +1030,7 @@ int cg_shift_path(const char *cgroup, const char *root, const char **ret_shifted + root = rt; + } + +- *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH) ?: cgroup; ++ *ret_shifted = path_startswith_full(cgroup, root, PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT) ?: cgroup; + return 0; + } + +diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c +index 21a670c9e8..69e76653ab 100644 +--- a/src/basic/fs-util.c ++++ b/src/basic/fs-util.c +@@ -66,7 +66,7 @@ int rmdir_parents(const char *path, const char *stop) { + assert(*slash == '/'); + *slash = '\0'; + +- if (path_startswith_full(stop, p, /* flags= */ 0)) ++ if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT)) + return 0; + + if (rmdir(p) < 0 && errno != ENOENT) +diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c +index 6fc2a79944..f1e5f2dc8d 100644 +--- a/src/basic/mkdir.c ++++ b/src/basic/mkdir.c +@@ -149,7 +149,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui + assert(_mkdirat != mkdirat); + + if (prefix) { +- p = path_startswith_full(path, prefix, /* flags= */ 0); ++ p = path_startswith_full(path, prefix, PATH_STARTSWITH_REFUSE_DOT_DOT); + if (!p) + return -EINVAL; + +diff --git a/src/basic/path-util.c b/src/basic/path-util.c +index a4709e1b7d..fda6066bc6 100644 +--- a/src/basic/path-util.c ++++ b/src/basic/path-util.c +@@ -428,11 +428,11 @@ char* path_startswith_full(const char *original_path, const char *prefix, PathSt + const char *p, *q; + int m, n; + +- m = path_find_first_component(&path, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &p); ++ m = path_find_first_component(&path, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &p); + if (m < 0) + return NULL; + +- n = path_find_first_component(&prefix, FLAGS_SET(flags, PATH_STARTSWITH_ACCEPT_DOT_DOT), &q); ++ n = path_find_first_component(&prefix, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &q); + if (n < 0) + return NULL; + +diff --git a/src/basic/path-util.h b/src/basic/path-util.h +index d1e9f4b785..429d7ac507 100644 +--- a/src/basic/path-util.h ++++ b/src/basic/path-util.h +@@ -54,13 +54,13 @@ int path_make_relative(const char *from, const char *to, char **ret); + int path_make_relative_parent(const char *from_child, const char *to, char **ret); + + typedef enum PathStartWithFlags { +- PATH_STARTSWITH_ACCEPT_DOT_DOT = 1U << 0, ++ PATH_STARTSWITH_REFUSE_DOT_DOT = 1U << 0, + PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1, + } PathStartWithFlags; + + char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_; + static inline char* path_startswith(const char *path, const char *prefix) { +- return path_startswith_full(path, prefix, PATH_STARTSWITH_ACCEPT_DOT_DOT); ++ return path_startswith_full(path, prefix, 0); + } + + int path_compare(const char *a, const char *b) _pure_; diff --git a/0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch b/0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch new file mode 100644 index 0000000..95a7c0a --- /dev/null +++ b/0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch @@ -0,0 +1,28 @@ +From 9108a63c9d99242b6a5c96046b1494d3101afa2a Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Thu, 26 Feb 2026 11:07:39 +0100 +Subject: [PATCH] sd-json: fix off-by-one issue when updating parent for array + elements + +Follow-up for 8525bb369a09f488ec77f94e1557ecc2343eb4ab + +(cherry picked from commit 4e6e3b8707c84018051ae1885af20e06b2a5209e) + +Related: RHEL-155396 +--- + src/libsystemd/sd-json/sd-json.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c +index 64b59e0f3f..97897122b8 100644 +--- a/src/libsystemd/sd-json/sd-json.c ++++ b/src/libsystemd/sd-json/sd-json.c +@@ -2243,7 +2243,7 @@ _public_ int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant * + + if (old != *v) + /* Readjust the parent pointers to the new address */ +- for (size_t i = 1; i < size; i++) ++ for (size_t i = 0; i < size; i++) + (*v)[1 + i].parent = *v; + + return json_variant_array_put_element(*v, element); diff --git a/0619-core-cgroup-avoid-one-unnecessary-strjoina.patch b/0619-core-cgroup-avoid-one-unnecessary-strjoina.patch new file mode 100644 index 0000000..154166c --- /dev/null +++ b/0619-core-cgroup-avoid-one-unnecessary-strjoina.patch @@ -0,0 +1,96 @@ +From 3af048c981a816e0dca65597532456fb7630a417 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Thu, 26 Feb 2026 11:06:00 +0100 +Subject: [PATCH] core/cgroup: avoid one unnecessary strjoina() + +(cherry picked from commit 42aee39107fbdd7db1ccd402a2151822b2805e9f) + +Related: RHEL-155396 +--- + src/core/cgroup.c | 28 +++++++++++++--------------- + 1 file changed, 13 insertions(+), 15 deletions(-) + +diff --git a/src/core/cgroup.c b/src/core/cgroup.c +index 22a29666b6..88ba883f77 100644 +--- a/src/core/cgroup.c ++++ b/src/core/cgroup.c +@@ -2961,12 +2961,13 @@ static int unit_update_cgroup( + return 0; + } + +-static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suffix_path) { ++static int unit_attach_pid_to_cgroup_via_bus(Unit *u, const char *cgroup_path, pid_t pid) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +- char *pp; + int r; + + assert(u); ++ assert(cgroup_path); ++ assert(pid_is_valid(pid)); + + if (MANAGER_IS_SYSTEM(u->manager)) + return -EINVAL; +@@ -2974,18 +2975,13 @@ static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suf + if (!u->manager->system_bus) + return -EIO; + +- CGroupRuntime *crt = unit_get_cgroup_runtime(u); +- if (!crt || !crt->cgroup_path) +- return -EOWNERDEAD; +- + /* Determine this unit's cgroup path relative to our cgroup root */ +- pp = path_startswith(crt->cgroup_path, u->manager->cgroup_root); ++ const char *pp = path_startswith_full(cgroup_path, ++ u->manager->cgroup_root, ++ PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT); + if (!pp) + return -EINVAL; + +- pp = strjoina("/", pp, suffix_path); +- path_simplify(pp); +- + r = bus_call_method(u->manager->system_bus, + bus_systemd_mgr, + "AttachProcessesToUnit", +@@ -3026,8 +3022,10 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { + CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + + if (isempty(suffix_path)) +- p = crt->cgroup_path; ++ p = empty_to_root(crt->cgroup_path); + else { ++ assert(path_is_absolute(suffix_path)); ++ + joined = path_join(crt->cgroup_path, suffix_path); + if (!joined) + return -ENOMEM; +@@ -3045,7 +3043,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { + * before we use it */ + r = pidref_verify(pid); + if (r < 0) { +- log_unit_info_errno(u, r, "PID " PID_FMT " vanished before we could move it to target cgroup '%s', skipping: %m", pid->pid, empty_to_root(p)); ++ log_unit_info_errno(u, r, "PID " PID_FMT " vanished before we could move it to target cgroup '%s', skipping: %m", pid->pid, p); + continue; + } + +@@ -3056,7 +3054,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { + + log_unit_full_errno(u, again ? LOG_DEBUG : LOG_INFO, r, + "Couldn't move process "PID_FMT" to%s requested cgroup '%s': %m", +- pid->pid, again ? " directly" : "", empty_to_root(p)); ++ pid->pid, again ? " directly" : "", p); + + if (again) { + int z; +@@ -3066,9 +3064,9 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { + * Since it's more privileged it might be able to move the process across the + * leaves of a subtree whose top node is not owned by us. */ + +- z = unit_attach_pid_to_cgroup_via_bus(u, pid->pid, suffix_path); ++ z = unit_attach_pid_to_cgroup_via_bus(u, p, pid->pid); + if (z < 0) +- log_unit_info_errno(u, z, "Couldn't move process "PID_FMT" to requested cgroup '%s' (directly or via the system bus): %m", pid->pid, empty_to_root(p)); ++ log_unit_info_errno(u, z, "Couldn't move process "PID_FMT" to requested cgroup '%s' (directly or via the system bus): %m", pid->pid, p); + else { + if (ret >= 0) + ret++; /* Count successful additions */ diff --git a/0620-core-validate-input-cgroup-path-more-prudently.patch b/0620-core-validate-input-cgroup-path-more-prudently.patch new file mode 100644 index 0000000..b471737 --- /dev/null +++ b/0620-core-validate-input-cgroup-path-more-prudently.patch @@ -0,0 +1,29 @@ +From e1c092e585f4cada10dbd79d0c31bfb9156edea0 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Thu, 26 Feb 2026 11:06:34 +0100 +Subject: [PATCH] core: validate input cgroup path more prudently + +(cherry picked from commit efa6ba2ab625aaa160ac435a09e6482fc63bdbe8) + +Resolves: RHEL-155396 +--- + src/core/dbus-manager.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c +index 8e39d67a00..d516f30c96 100644 +--- a/src/core/dbus-manager.c ++++ b/src/core/dbus-manager.c +@@ -622,6 +622,12 @@ static int method_get_unit_by_control_group(sd_bus_message *message, void *userd + if (r < 0) + return r; + ++ if (!path_is_absolute(cgroup)) ++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not absolute: %s", cgroup); ++ ++ if (!path_is_normalized(cgroup)) ++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not normalized: %s", cgroup); ++ + u = manager_get_unit_by_cgroup(m, cgroup); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, diff --git a/0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch b/0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch new file mode 100644 index 0000000..2695079 --- /dev/null +++ b/0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch @@ -0,0 +1,55 @@ +From b29c80a11df03e849e4f2a33e8332776c4b0f637 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Wed, 11 Mar 2026 12:15:26 +0000 +Subject: [PATCH] nspawn: apply BindUser/Ephemeral from settings file only if + trusted + +Originally reported on yeswehack.com as: +YWH-PGM9780-116 + +Follow-up for 2f8930449079403b26c9164b8eeac78d5af2c8df +Follow-up for a2f577fca0be79b23f61f033229b64884e7d840a + +(cherry picked from commit 61bceb1bff4b1f9c126b18dc971ca3e6d8c71c40) + +Resolves: RHEL-158303 +--- + src/nspawn/nspawn.c | 18 ++++++++++++++---- + 1 file changed, 14 insertions(+), 4 deletions(-) + +diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c +index 724639df5c..acf579c007 100644 +--- a/src/nspawn/nspawn.c ++++ b/src/nspawn/nspawn.c +@@ -4739,8 +4739,13 @@ static int merge_settings(Settings *settings, const char *path) { + } + + if ((arg_settings_mask & SETTING_EPHEMERAL) == 0 && +- settings->ephemeral >= 0) +- arg_ephemeral = settings->ephemeral; ++ settings->ephemeral >= 0) { ++ ++ if (!arg_settings_trusted) ++ log_warning("Ignoring ephemeral setting, file %s is not trusted.", path); ++ else ++ arg_ephemeral = settings->ephemeral; ++ } + + if ((arg_settings_mask & SETTING_DIRECTORY) == 0 && + settings->root) { +@@ -4908,8 +4913,13 @@ static int merge_settings(Settings *settings, const char *path) { + } + + if ((arg_settings_mask & SETTING_BIND_USER) == 0 && +- !strv_isempty(settings->bind_user)) +- strv_free_and_replace(arg_bind_user, settings->bind_user); ++ !strv_isempty(settings->bind_user)) { ++ ++ if (!arg_settings_trusted) ++ log_warning("Ignoring bind user setting, file %s is not trusted.", path); ++ else ++ strv_free_and_replace(arg_bind_user, settings->bind_user); ++ } + + if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0 && + settings->notify_ready >= 0) diff --git a/0622-nspawn-normalize-pivot_root-paths.patch b/0622-nspawn-normalize-pivot_root-paths.patch new file mode 100644 index 0000000..02e36f3 --- /dev/null +++ b/0622-nspawn-normalize-pivot_root-paths.patch @@ -0,0 +1,32 @@ +From 5f8d0355128c2aaa59ffc56916d1939d6022e6db Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Wed, 11 Mar 2026 13:27:14 +0000 +Subject: [PATCH] nspawn: normalize pivot_root paths + +Originally reported on yeswehack.com as: +YWH-PGM9780-116 + +Follow-up for b53ede699cdc5233041a22591f18863fb3fe2672 + +(cherry picked from commit 7b85f5498a958e5bb660c703b8f4a71cceed3373) + +Resolves: RHEL-158303 +--- + src/nspawn/nspawn-mount.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c +index ddbdba6fb6..c233cdf600 100644 +--- a/src/nspawn/nspawn-mount.c ++++ b/src/nspawn/nspawn-mount.c +@@ -1309,7 +1309,9 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s + + if (!path_is_absolute(root_new)) + return -EINVAL; +- if (root_old && !path_is_absolute(root_old)) ++ if (!path_is_normalized(root_new)) ++ return -EINVAL; ++ if (root_old && (!path_is_absolute(root_old) || !path_is_normalized(root_old))) + return -EINVAL; + + free_and_replace(*pivot_root_new, root_new); diff --git a/0623-udev-check-for-invalid-chars-in-various-fields-recei.patch b/0623-udev-check-for-invalid-chars-in-various-fields-recei.patch new file mode 100644 index 0000000..ff89f93 --- /dev/null +++ b/0623-udev-check-for-invalid-chars-in-various-fields-recei.patch @@ -0,0 +1,124 @@ +From 193648df27a08e5564c09f82115ee0c282c9fe85 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 6 Mar 2026 19:32:35 +0000 +Subject: [PATCH] udev: check for invalid chars in various fields received from + the kernel + +(cherry picked from commit 16325b35fa6ecb25f66534a562583ce3b96d52f3) + +Resolves: RHEL-158354 +--- + src/udev/dmi_memory_id/dmi_memory_id.c | 3 ++- + src/udev/scsi_id/scsi_id.c | 5 +++-- + src/udev/udev-builtin-net_id.c | 9 +++++++++ + src/udev/v4l_id/v4l_id.c | 5 ++++- + 4 files changed, 18 insertions(+), 4 deletions(-) + +diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c +index e62222a307..d8370bbe3f 100644 +--- a/src/udev/dmi_memory_id/dmi_memory_id.c ++++ b/src/udev/dmi_memory_id/dmi_memory_id.c +@@ -51,6 +51,7 @@ + #include "string-util.h" + #include "udev-util.h" + #include "unaligned.h" ++#include "utf8.h" + + #define SUPPORTED_SMBIOS_VER 0x030300 + +@@ -185,7 +186,7 @@ static void dmi_memory_device_string( + + str = strdupa_safe(dmi_string(h, s)); + str = strstrip(str); +- if (!isempty(str)) ++ if (!isempty(str) && utf8_is_valid(str) && !string_has_cc(str, /* ok= */ NULL)) + printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str); + } + +diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c +index b63a46a730..650bf7824f 100644 +--- a/src/udev/scsi_id/scsi_id.c ++++ b/src/udev/scsi_id/scsi_id.c +@@ -27,6 +27,7 @@ + #include "strv.h" + #include "strxcpyx.h" + #include "udev-util.h" ++#include "utf8.h" + + static const struct option options[] = { + { "device", required_argument, NULL, 'd' }, +@@ -450,8 +451,8 @@ static int scsi_id(char *maj_min_dev) { + } + if (dev_scsi.tgpt_group[0] != '\0') + printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); +- if (dev_scsi.unit_serial_number[0] != '\0') +- printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); ++ if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) ++ printf("ID_SCSI_SERIAL=%s\n", serial_str); + goto out; + } + +diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c +index 96e792bcde..0d3c62f4b5 100644 +--- a/src/udev/udev-builtin-net_id.c ++++ b/src/udev/udev-builtin-net_id.c +@@ -40,6 +40,7 @@ + #include "strv.h" + #include "strxcpyx.h" + #include "udev-builtin.h" ++#include "utf8.h" + + #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1) + #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1) +@@ -236,6 +237,9 @@ static int get_port_specifier(sd_device *dev, bool fallback_to_dev_id, char **re + } + } + ++ if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) ++ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); ++ + /* Otherwise, use phys_port_name as is. */ + buf = strjoin("n", phys_port_name); + if (!buf) +@@ -340,6 +344,9 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c + if (r < 0) + return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); + ++ if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL)) ++ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label"); ++ + char str[ALTIFNAMSIZ]; + if (snprintf_ok(str, sizeof str, "%s%s", + naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix, +@@ -1257,6 +1264,8 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { + if (isempty(phys_port_name)) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP), + "The 'phys_port_name' attribute is empty."); ++ if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) ++ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); + + char str[ALTIFNAMSIZ]; + if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) +diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c +index 5c540659f3..8e29f8898e 100644 +--- a/src/udev/v4l_id/v4l_id.c ++++ b/src/udev/v4l_id/v4l_id.c +@@ -19,6 +19,8 @@ + #include "build.h" + #include "fd-util.h" + #include "main-func.h" ++#include "string-util.h" ++#include "utf8.h" + + static const char *arg_device = NULL; + +@@ -72,7 +74,8 @@ static int run(int argc, char *argv[]) { + int capabilities; + + printf("ID_V4L_VERSION=2\n"); +- printf("ID_V4L_PRODUCT=%s\n", v2cap.card); ++ if (utf8_is_valid((char *)v2cap.card) && !string_has_cc((char *)v2cap.card, /* ok= */ NULL)) ++ printf("ID_V4L_PRODUCT=%s\n", v2cap.card); + printf("ID_V4L_CAPABILITIES=:"); + + if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS) diff --git a/0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch b/0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch new file mode 100644 index 0000000..9d8c7ef --- /dev/null +++ b/0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch @@ -0,0 +1,45 @@ +From 611f421f23f2e1a560eb76bb8d922c8a942a9da5 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 6 Mar 2026 19:42:16 +0000 +Subject: [PATCH] udev: ensure there is space for trailing NUL before calling + sprintf + +sprintf will write 5 characters, as it adds a trailing NUL byte. + +Reported on yeswehack.com as: +YWH-PGM9780-62 + +Follow-up for 8cfcf9980a3 + +(cherry picked from commit 69e4ba69d689748d1d515c5a8d063073df3c5821) + +Related: RHEL-158354 +--- + src/shared/device-nodes.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/shared/device-nodes.c b/src/shared/device-nodes.c +index d08c40fe2c..20206ee7b4 100644 +--- a/src/shared/device-nodes.c ++++ b/src/shared/device-nodes.c +@@ -7,6 +7,7 @@ + + #include "device-nodes.h" + #include "path-util.h" ++#include "stdio-util.h" + #include "string-util.h" + #include "utf8.h" + +@@ -39,10 +40,10 @@ int encode_devnode_name(const char *str, char *str_enc, size_t len) { + + } else if (str[i] == '\\' || !allow_listed_char_for_devnode(str[i], NULL)) { + +- if (len-j < 4) ++ if (len-j < 5) + return -EINVAL; + +- sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); ++ assert_se(snprintf_ok(&str_enc[j], 5, "\\x%02x", (unsigned char) str[i])); + j += 4; + + } else { diff --git a/0625-udev-ensure-tag-parsing-stays-within-bounds.patch b/0625-udev-ensure-tag-parsing-stays-within-bounds.patch new file mode 100644 index 0000000..3517cda --- /dev/null +++ b/0625-udev-ensure-tag-parsing-stays-within-bounds.patch @@ -0,0 +1,32 @@ +From 6a324492fc5710816ef376d2e012b1661f50b291 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 6 Mar 2026 20:25:05 +0000 +Subject: [PATCH] udev: ensure tag parsing stays within bounds + +This cannot actually happen, but add a safety check nonetheless. + +Reported on yeswehack.com as: +YWH-PGM9780-43 + +Follow-up for d7867b31836173d1a943ecb1cab6484536126411 + +(cherry picked from commit 45a200cd751fae382f4145760cf84fd181db1319) + +Related: RHEL-158354 +--- + src/udev/udev-builtin-path_id.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c +index d6ea471482..545757dc09 100644 +--- a/src/udev/udev-builtin-path_id.c ++++ b/src/udev/udev-builtin-path_id.c +@@ -667,7 +667,7 @@ static void add_id_tag(UdevEvent *event, const char *path) { + size_t i = 0; + + /* compose valid udev tag name */ +- for (const char *p = path; *p; p++) { ++ for (const char *p = path; *p && i < sizeof(tag) - 1; p++) { + if (ascii_isdigit(*p) || + ascii_isalpha(*p) || + *p == '-') { diff --git a/0626-udev-fix-review-mixup.patch b/0626-udev-fix-review-mixup.patch new file mode 100644 index 0000000..2258034 --- /dev/null +++ b/0626-udev-fix-review-mixup.patch @@ -0,0 +1,32 @@ +From 51ee6986c85c060fbbd644d94553edc6854ee5d6 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 13 Mar 2026 11:10:47 +0000 +Subject: [PATCH] udev: fix review mixup + +The previous version in the PR changed variable and sanitized it +in place. The second version switched to skip if CCs are in the +string instead, but didn't move back to the original variable. +Because it's an existing variable, no CI caught it. + +Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3 + +(cherry picked from commit 54f880b02ecf7362e630ffc885d1466df6ee6820) + +Resolves: RHEL-158354 +--- + src/udev/scsi_id/scsi_id.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c +index 650bf7824f..854f8ffa05 100644 +--- a/src/udev/scsi_id/scsi_id.c ++++ b/src/udev/scsi_id/scsi_id.c +@@ -452,7 +452,7 @@ static int scsi_id(char *maj_min_dev) { + if (dev_scsi.tgpt_group[0] != '\0') + printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); + if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) +- printf("ID_SCSI_SERIAL=%s\n", serial_str); ++ printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); + goto out; + } + diff --git a/0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch b/0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch new file mode 100644 index 0000000..06e30e0 --- /dev/null +++ b/0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch @@ -0,0 +1,52 @@ +From 2ce69dc53f7c6076dcbdf60ef0211f0b8ea3f876 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Fri, 10 Apr 2026 19:04:04 +0100 +Subject: [PATCH] udev/scsi-id: check for invalid chars in various fields + received from the kernel + +Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3 + +(cherry picked from commit 5f700d148c44063c0f0dbb9fc136866339cd3fa7) + +Related: RHEL-158354 +--- + src/udev/scsi_id/scsi_id.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c +index 854f8ffa05..bc350fed47 100644 +--- a/src/udev/scsi_id/scsi_id.c ++++ b/src/udev/scsi_id/scsi_id.c +@@ -398,6 +398,10 @@ static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) { + return 0; + } + ++static bool scsi_string_is_valid(const char *s) { ++ return !isempty(s) && utf8_is_valid(s) && !string_has_cc(s, /* ok= */ NULL); ++} ++ + /* + * scsi_id: try to get an id, if one is found, printf it to stdout. + * returns a value passed to exit() - 0 if printed an id, else 1. +@@ -441,17 +445,17 @@ static int scsi_id(char *maj_min_dev) { + udev_replace_chars(serial_str, NULL); + printf("ID_SERIAL_SHORT=%s\n", serial_str); + } +- if (dev_scsi.wwn[0] != '\0') { ++ if (scsi_string_is_valid(dev_scsi.wwn)) { + printf("ID_WWN=0x%s\n", dev_scsi.wwn); +- if (dev_scsi.wwn_vendor_extension[0] != '\0') { ++ if (scsi_string_is_valid(dev_scsi.wwn_vendor_extension)) { + printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension); + printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension); + } else + printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn); + } +- if (dev_scsi.tgpt_group[0] != '\0') ++ if (scsi_string_is_valid(dev_scsi.tgpt_group)) + printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group); +- if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL)) ++ if (scsi_string_is_valid(dev_scsi.unit_serial_number)) + printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number); + goto out; + } diff --git a/0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch b/0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch new file mode 100644 index 0000000..903fe0a --- /dev/null +++ b/0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch @@ -0,0 +1,88 @@ +From de3d4ab2348dc313a08fa3b667acf42a34d19bb0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 11 Mar 2026 11:27:48 +0100 +Subject: [PATCH] udev-builtin-net-id: print cescaped bad attributes + +Follow-up for 16325b35fa6ecb25f66534a562583ce3b96d52f3. Let's +log those bad value to make it easier to figure out why things +are not working if we reject an attribute. + +(cherry picked from commit 7c4047957ef58744ecfad6d277f7c45d430f6d70) + +Related: RHEL-158354 +--- + src/udev/udev-builtin-net_id.c | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c +index 0d3c62f4b5..fd39a90c87 100644 +--- a/src/udev/udev-builtin-net_id.c ++++ b/src/udev/udev-builtin-net_id.c +@@ -28,6 +28,7 @@ + #include "device-private.h" + #include "device-util.h" + #include "dirent-util.h" ++#include "escape.h" + #include "ether-addr-util.h" + #include "fd-util.h" + #include "fileio.h" +@@ -45,6 +46,12 @@ + #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1) + #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1) + ++static int log_invalid_device_attr(sd_device *dev, const char *attr, const char *value) { ++ _cleanup_free_ char *escaped = cescape(value); ++ return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), ++ "Invalid %s value '%s'.", attr, strnull(escaped)); ++} ++ + /* skip intermediate virtio devices */ + static sd_device *device_skip_virtio(sd_device *dev) { + /* there can only ever be one virtio bus per parent device, so we can +@@ -238,7 +245,7 @@ static int get_port_specifier(sd_device *dev, bool fallback_to_dev_id, char **re + } + + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) +- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); ++ return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); + + /* Otherwise, use phys_port_name as is. */ + buf = strjoin("n", phys_port_name); +@@ -345,7 +352,7 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c + return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); + + if (!utf8_is_valid(label) || string_has_cc(label, /* ok= */ NULL)) +- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid label"); ++ return log_invalid_device_attr(dev, "label", label); + + char str[ALTIFNAMSIZ]; + if (snprintf_ok(str, sizeof str, "%s%s", +@@ -751,8 +758,7 @@ static int names_vio(UdevEvent *event, const char *prefix) { + "VIO bus ID and slot ID have invalid length: %s", s); + + if (!in_charset(s, HEXDIGITS)) +- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), +- "VIO bus ID and slot ID contain invalid characters: %s", s); ++ return log_invalid_device_attr(dev, "VIO bus ID and slot ID", s); + + /* Parse only slot ID (the last 4 hexdigits). */ + r = safe_atou_full(s + 4, 16, &slotid); +@@ -808,8 +814,7 @@ static int names_platform(UdevEvent *event, const char *prefix) { + return -EOPNOTSUPP; + + if (!in_charset(vendor, validchars)) +- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT), +- "Platform vendor contains invalid characters: %s", vendor); ++ return log_invalid_device_attr(dev, "platform vendor", vendor); + + ascii_strlower(vendor); + +@@ -1265,7 +1270,7 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EOPNOTSUPP), + "The 'phys_port_name' attribute is empty."); + if (!utf8_is_valid(phys_port_name) || string_has_cc(phys_port_name, /* ok= */ NULL)) +- return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid phys_port_name"); ++ return log_invalid_device_attr(dev, "phys_port_name", phys_port_name); + + char str[ALTIFNAMSIZ]; + if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) diff --git a/0629-core-only-activate-transaction-that-contain-useful-j.patch b/0629-core-only-activate-transaction-that-contain-useful-j.patch new file mode 100644 index 0000000..dce43db --- /dev/null +++ b/0629-core-only-activate-transaction-that-contain-useful-j.patch @@ -0,0 +1,34 @@ +From e1498685abfebf42a64a5545be6c925d1adefa66 Mon Sep 17 00:00:00 2001 +From: Michal Sekletar +Date: Fri, 9 Jan 2026 17:18:41 +0100 +Subject: [PATCH] core: only activate transaction that contain useful jobs + +If no real jobs were added to the transaction, do not activate it. +The JOB_NOP anchor does not perform any useful work and activating +such transaction only wastes resources. + +Fixes #9751 + +(cherry picked from commit bcbf80c43d107ad233edc990a60bdc40f517085a) + +Resolves: RHEL-143728 +--- + src/core/manager.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/core/manager.c b/src/core/manager.c +index f7f901521b..22efab33df 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -2297,6 +2297,11 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error + tr->anchor_job, + mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0); + ++ /* Only activate the transaction if it contains jobs other than NOP anchor. ++ * Short-circuiting here avoids unnecessary processing, such as emitting D-Bus signals. */ ++ if (hashmap_size(tr->jobs) <= 1) ++ return 0; ++ + r = transaction_activate(tr, m, mode, NULL, e); + if (r < 0) + return r; diff --git a/0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch b/0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch new file mode 100644 index 0000000..fc49df6 --- /dev/null +++ b/0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch @@ -0,0 +1,27 @@ +From 4a438fbccf6b533d4546e2e9b37919684a9a2d90 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 1 Dec 2024 14:46:40 +0900 +Subject: [PATCH] journald: extend STDOUT_STREAMS_MAX to 64k + +Closes #35390. + +(cherry picked from commit c576ba7182f54f352c03f0768c9178b173fb8bcb) + +Resolves: RHEL-168098 +--- + src/journal/journald-stream.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c +index b019eda0d2..f039a33ad2 100644 +--- a/src/journal/journald-stream.c ++++ b/src/journal/journald-stream.c +@@ -40,7 +40,7 @@ + #include "unit-name.h" + #include "user-util.h" + +-#define STDOUT_STREAMS_MAX 4096 ++#define STDOUT_STREAMS_MAX (64*1024) + + /* During the "setup" protocol phase of the stream logic let's define a different maximum line length than + * during the actual operational phase. We want to allow users to specify very short line lengths after all, diff --git a/0631-string-util-introduce-strprepend-helper.patch b/0631-string-util-introduce-strprepend-helper.patch new file mode 100644 index 0000000..8379222 --- /dev/null +++ b/0631-string-util-introduce-strprepend-helper.patch @@ -0,0 +1,76 @@ +From 4b773b91307715d016c84542037cbfbc16dbdcfa Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Mon, 10 Feb 2025 19:03:08 +0100 +Subject: [PATCH] string-util: introduce strprepend() helper + +(cherry picked from commit b40694f5fc21317af617f37bb03304c84ca993c8) + +Related: RHEL-143028 +--- + src/basic/string-util.c | 14 ++++++++++++++ + src/basic/string-util.h | 2 ++ + src/test/test-string-util.c | 13 +++++++++++++ + 3 files changed, 29 insertions(+) + +diff --git a/src/basic/string-util.c b/src/basic/string-util.c +index 7122d5145e..6d490e7444 100644 +--- a/src/basic/string-util.c ++++ b/src/basic/string-util.c +@@ -46,6 +46,20 @@ char* first_word(const char *s, const char *word) { + return (char*) nw; + } + ++char* strprepend(char **x, const char *s) { ++ assert(x); ++ ++ if (isempty(s) && *x) ++ return *x; ++ ++ char *p = strjoin(strempty(s), *x); ++ if (!p) ++ return NULL; ++ ++ free_and_replace(*x, p); ++ return *x; ++} ++ + char* strnappend(const char *s, const char *suffix, size_t b) { + size_t a; + char *r; +diff --git a/src/basic/string-util.h b/src/basic/string-util.h +index a1592b6e6d..7ce26b30f9 100644 +--- a/src/basic/string-util.h ++++ b/src/basic/string-util.h +@@ -106,6 +106,8 @@ static inline const char* empty_or_dash_to_null(const char *p) { + + char* first_word(const char *s, const char *word) _pure_; + ++char* strprepend(char **x, const char *s); ++ + char* strnappend(const char *s, const char *suffix, size_t length); + + char* strjoin_real(const char *x, ...) _sentinel_; +diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c +index b692af6cc0..0474f3a162 100644 +--- a/src/test/test-string-util.c ++++ b/src/test/test-string-util.c +@@ -1335,6 +1335,19 @@ TEST(strextendn) { + x = mfree(x); + } + ++TEST(strprepend) { ++ _cleanup_free_ char *x = NULL; ++ ++ ASSERT_STREQ(strprepend(&x, NULL), ""); ++ x = mfree(x); ++ ++ ASSERT_STREQ(strprepend(&x, ""), ""); ++ ++ ASSERT_STREQ(strprepend(&x, "xxx"), "xxx"); ++ ASSERT_STREQ(strprepend(&x, "bar"), "barxxx"); ++ ASSERT_STREQ(strprepend(&x, "foo"), "foobarxxx"); ++} ++ + TEST(strlevenshtein) { + assert_se(strlevenshtein(NULL, NULL) == 0); + assert_se(strlevenshtein("", "") == 0); diff --git a/0632-missing-add-quotactl_fd-wrapper.patch b/0632-missing-add-quotactl_fd-wrapper.patch new file mode 100644 index 0000000..172441c --- /dev/null +++ b/0632-missing-add-quotactl_fd-wrapper.patch @@ -0,0 +1,139 @@ +From 125c2b786edaed0c11ef923e10cf0448909abf63 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 10 Jan 2025 11:33:03 +0100 +Subject: [PATCH] missing: add quotactl_fd() wrapper + +(cherry picked from commit 7adafb0832a709f4fcf1210602224cb049d02857) + +Related: RHEL-143028 +--- + meson.build | 1 + + src/basic/missing_syscall.h | 16 ++++++++ + src/basic/missing_syscall_def.h | 68 +++++++++++++++++++++++++++++++++ + src/basic/missing_syscalls.py | 1 + + 4 files changed, 86 insertions(+) + +diff --git a/meson.build b/meson.build +index fa39da2d38..8b9818a202 100644 +--- a/meson.build ++++ b/meson.build +@@ -675,6 +675,7 @@ foreach ident : [ + ['fsmount', '''#include '''], + ['getdents64', '''#include '''], + ['pidfd_spawn', '''#include '''], ++ ['quotactl_fd', '''#include '''], + ] + + have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') +diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h +index e2cd8b4e35..8ac4c5288a 100644 +--- a/src/basic/missing_syscall.h ++++ b/src/basic/missing_syscall.h +@@ -695,3 +695,19 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, + * at build time) and just define it. Once the kernel drops ia64 support, we can drop this too. */ + #define HAVE_CLONE 1 + #endif ++ ++/* ======================================================================= */ ++ ++#if !HAVE_QUOTACTL_FD ++ ++static inline int missing_quotactl_fd(int fd, int cmd, int id, void *addr) { ++#if defined __NR_quotactl_fd ++ return syscall(__NR_quotactl_fd, fd, cmd, id, addr); ++#else ++ errno = ENOSYS; ++ return -1; ++#endif ++} ++ ++# define quotactl_fd missing_quotactl_fd ++#endif +diff --git a/src/basic/missing_syscall_def.h b/src/basic/missing_syscall_def.h +index f679422a2e..cd22058cdd 100644 +--- a/src/basic/missing_syscall_def.h ++++ b/src/basic/missing_syscall_def.h +@@ -1197,3 +1197,71 @@ assert_cc(__NR_statx == systemd_NR_statx); + # endif + # endif + #endif ++ ++#ifndef __IGNORE_quotactl_fd ++# if defined(__aarch64__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__alpha__) ++# define systemd_NR_quotactl_fd 553 ++# elif defined(__arc__) || defined(__tilegx__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__arm__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__i386__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__ia64__) ++# define systemd_NR_quotactl_fd 1467 ++# elif defined(__loongarch_lp64) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__m68k__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(_MIPS_SIM) ++# if _MIPS_SIM == _MIPS_SIM_ABI32 ++# define systemd_NR_quotactl_fd 4443 ++# elif _MIPS_SIM == _MIPS_SIM_NABI32 ++# define systemd_NR_quotactl_fd 6443 ++# elif _MIPS_SIM == _MIPS_SIM_ABI64 ++# define systemd_NR_quotactl_fd 5443 ++# else ++# error "Unknown MIPS ABI" ++# endif ++# elif defined(__hppa__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__powerpc__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__riscv) ++# if __riscv_xlen == 32 ++# define systemd_NR_quotactl_fd 443 ++# elif __riscv_xlen == 64 ++# define systemd_NR_quotactl_fd 443 ++# else ++# error "Unknown RISC-V ABI" ++# endif ++# elif defined(__s390__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__sparc__) ++# define systemd_NR_quotactl_fd 443 ++# elif defined(__x86_64__) ++# if defined(__ILP32__) ++# define systemd_NR_quotactl_fd (443 | /* __X32_SYSCALL_BIT */ 0x40000000) ++# else ++# define systemd_NR_quotactl_fd 443 ++# endif ++# elif !defined(missing_arch_template) ++# warning "quotactl_fd() syscall number is unknown for your architecture" ++# endif ++ ++/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ ++# if defined __NR_quotactl_fd && __NR_quotactl_fd >= 0 ++# if defined systemd_NR_quotactl_fd ++assert_cc(__NR_quotactl_fd == systemd_NR_quotactl_fd); ++# endif ++# else ++# if defined __NR_quotactl_fd ++# undef __NR_quotactl_fd ++# endif ++# if defined systemd_NR_quotactl_fd && systemd_NR_quotactl_fd >= 0 ++# define __NR_quotactl_fd systemd_NR_quotactl_fd ++# endif ++# endif ++#endif +diff --git a/src/basic/missing_syscalls.py b/src/basic/missing_syscalls.py +index 3749e89c4e..a15fed0358 100644 +--- a/src/basic/missing_syscalls.py ++++ b/src/basic/missing_syscalls.py +@@ -20,6 +20,7 @@ SYSCALLS = [ + 'pidfd_open', + 'pidfd_send_signal', + 'pkey_mprotect', ++ 'quotactl_fd', + 'renameat2', + 'setns', + 'statx', diff --git a/0633-homed-always-use-quotactl_fd-if-its-available.patch b/0633-homed-always-use-quotactl_fd-if-its-available.patch new file mode 100644 index 0000000..0c6a094 --- /dev/null +++ b/0633-homed-always-use-quotactl_fd-if-its-available.patch @@ -0,0 +1,413 @@ +From 58fc6c67b9723180f1c54e8b72e7f341e77a7591 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 31 Mar 2025 11:47:17 +0200 +Subject: [PATCH] homed: always use quotactl_fd() if its available + +Let's always prefer quotactl_fd() when it's available and use quotactl() +only as as a fallback on old kernels. + +This way we can operate on the fds we typically already have open, or if +needed we can open a new one, and use for multiple fs operation. + +In the long run we should really focus on operating exclusively by fd +instead of by path, by device nor or otherwise. This gets us a step +closer to that. + +(cherry picked from commit 5daca30b0f28ffdcfdd1e6b9df9691b89d1b6b0f) + +Related: RHEL-143028 +--- + src/home/homed-home.c | 16 +++++++--- + src/home/homed-manager.c | 12 ++++--- + src/home/homework-directory.c | 6 ++-- + src/home/homework-fscrypt.c | 2 +- + src/home/homework-quota.c | 57 +++++++++++++++++++++++----------- + src/home/homework-quota.h | 6 ++-- + src/shared/quota-util.c | 34 +++++++++----------- + src/shared/quota-util.h | 3 +- + units/systemd-homed.service.in | 2 +- + 9 files changed, 80 insertions(+), 58 deletions(-) + +diff --git a/src/home/homed-home.c b/src/home/homed-home.c +index 44e3274c42..84b2a4935e 100644 +--- a/src/home/homed-home.c ++++ b/src/home/homed-home.c +@@ -29,7 +29,6 @@ + #include "memfd-util.h" + #include "missing_magic.h" + #include "missing_mman.h" +-#include "missing_syscall.h" + #include "mkdir.h" + #include "path-util.h" + #include "process-util.h" +@@ -2414,6 +2413,7 @@ static int home_get_disk_status_directory( + uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, + disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX; + mode_t access_mode = MODE_INVALID; ++ _cleanup_close_ int fd = -EBADF; + statfs_f_type_t fstype = 0; + struct statfs sfs; + struct dqblk req; +@@ -2435,7 +2435,13 @@ static int home_get_disk_status_directory( + if (!path) + goto finish; + +- if (statfs(path, &sfs) < 0) ++ fd = open(path, O_CLOEXEC|O_RDONLY); ++ if (fd < 0) { ++ log_debug_errno(errno, "Failed to open '%s', ignoring: %m", path); ++ goto finish; ++ } ++ ++ if (fstatfs(fd, &sfs) < 0) + log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", path); + else { + disk_free = sfs.f_bsize * sfs.f_bavail; +@@ -2449,13 +2455,13 @@ static int home_get_disk_status_directory( + + if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) { + +- r = btrfs_is_subvol(path); ++ r = btrfs_is_subvol_fd(fd); + if (r < 0) + log_debug_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path); + else if (r > 0) { + BtrfsQuotaInfo qi; + +- r = btrfs_subvol_get_subtree_quota(path, 0, &qi); ++ r = btrfs_subvol_get_subtree_quota_fd(fd, /* subvol_id= */ 0, &qi); + if (r < 0) + log_debug_errno(r, "Failed to query btrfs subtree quota, ignoring: %m"); + else { +@@ -2488,7 +2494,7 @@ static int home_get_disk_status_directory( + } + + if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_FSCRYPT)) { +- r = quotactl_path(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), path, h->uid, &req); ++ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req); + if (r < 0) { + if (ERRNO_IS_NOT_SUPPORTED(r)) { + log_debug_errno(r, "No UID quota support on %s.", path); +diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c +index 6b9e4fcf11..8d0e8514bf 100644 +--- a/src/home/homed-manager.c ++++ b/src/home/homed-manager.c +@@ -55,6 +55,7 @@ + #include "user-record.h" + #include "user-util.h" + #include "varlink-io.systemd.UserDatabase.h" ++#include "varlink-io.systemd.service.h" + #include "varlink-util.h" + + /* Where to look for private/public keys that are used to sign the user records. We are not using +@@ -513,14 +514,15 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) { + struct dqblk req; + struct stat st; + +- if (stat(where, &st) < 0) { ++ _cleanup_close_ int fd = open(where, O_RDONLY|O_CLOEXEC|O_DIRECTORY); ++ if (fd < 0) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, +- "Failed to stat %s, ignoring: %m", where); ++ "Failed to open '%s', ignoring: %m", where); + continue; + } + +- if (major(st.st_dev) == 0) { +- log_debug("Directory %s is not on a real block device, not checking quota for UID use.", where); ++ if (fstat(fd, &st) < 0) { ++ log_error_errno(errno, "Failed to stat '%s', ignoring: %m", where); + continue; + } + +@@ -539,7 +541,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) { + + previous_devno = st.st_dev; + +- r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), st.st_dev, uid, &req); ++ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req); + if (r < 0) { + if (ERRNO_IS_NOT_SUPPORTED(r)) + log_debug_errno(r, "No UID quota support on %s, ignoring.", where); +diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c +index ff88367e43..37bdba30a3 100644 +--- a/src/home/homework-directory.c ++++ b/src/home/homework-directory.c +@@ -153,7 +153,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco + /* Actually configure the quota. We also ignore errors here, but we do log + * about them loudly, to keep things discoverable even though we don't + * consider lacking quota support in kernel fatal. */ +- (void) home_update_quota_btrfs(h, d); ++ (void) home_update_quota_btrfs(h, /* fd= */ -EBADF, d); + } + + break; +@@ -169,7 +169,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco + if (mkdir(d, 0700) < 0) + return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d); + +- (void) home_update_quota_classic(h, d); ++ (void) home_update_quota_classic(h, /* fd= */ -EBADF, d); + break; + + default: +@@ -285,7 +285,7 @@ int home_resize_directory( + if (r < 0) + return r; + +- r = home_update_quota_auto(h, NULL); ++ r = home_update_quota_auto(h, setup->root_fd, /* path= */ NULL); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return -ESOCKTNOSUPPORT; /* make recognizable */ + if (r < 0) +diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c +index 3a9d4ea891..6e22fbd57c 100644 +--- a/src/home/homework-fscrypt.c ++++ b/src/home/homework-fscrypt.c +@@ -629,7 +629,7 @@ int home_create_fscrypt( + nr++; + } + +- (void) home_update_quota_classic(h, temporary); ++ (void) home_update_quota_classic(h, setup->root_fd, temporary); + + r = home_shift_uid(setup->root_fd, HOME_RUNTIME_WORK_DIR, h->uid, h->uid, &mount_fd); + if (r > 0) +diff --git a/src/home/homework-quota.c b/src/home/homework-quota.c +index c9516829d8..363be87104 100644 +--- a/src/home/homework-quota.c ++++ b/src/home/homework-quota.c +@@ -4,6 +4,7 @@ + #include "blockdev-util.h" + #include "btrfs-util.h" + #include "errno-util.h" ++#include "fd-util.h" + #include "format-util.h" + #include "homework-quota.h" + #include "missing_magic.h" +@@ -11,23 +12,32 @@ + #include "stat-util.h" + #include "user-util.h" + +-int home_update_quota_btrfs(UserRecord *h, const char *path) { ++int home_update_quota_btrfs(UserRecord *h, int fd, const char *path) { + int r; + + assert(h); + assert(path); + ++ _cleanup_close_ int _fd = -EBADF; ++ if (fd < 0) { ++ _fd = open(path, O_CLOEXEC|O_RDONLY); ++ if (_fd < 0) ++ return log_error_errno(errno, "Failed to open '%s': %m", path); ++ ++ fd = _fd; ++ } ++ + if (h->disk_size == UINT64_MAX) + return 0; + + /* If the user wants quota, enable it */ +- r = btrfs_quota_enable(path, true); ++ r = btrfs_quota_enable_fd(fd, true); + if (r == -ENOTTY) + return log_error_errno(r, "No btrfs quota support on subvolume %s.", path); + if (r < 0) + return log_error_errno(r, "Failed to enable btrfs quota support on %s.", path); + +- r = btrfs_qgroup_set_limit(path, 0, h->disk_size); ++ r = btrfs_qgroup_set_limit_fd(fd, 0, h->disk_size); + if (r < 0) + return log_error_errno(r, "Failed to set disk quota on subvolume %s: %m", path); + +@@ -36,25 +46,27 @@ int home_update_quota_btrfs(UserRecord *h, const char *path) { + return 0; + } + +-int home_update_quota_classic(UserRecord *h, const char *path) { ++int home_update_quota_classic(UserRecord *h, int fd, const char *path) { + struct dqblk req; +- dev_t devno; + int r; + + assert(h); + assert(uid_is_valid(h->uid)); + assert(path); + ++ _cleanup_close_ int _fd = -EBADF; ++ if (fd < 0) { ++ _fd = open(path, O_CLOEXEC|O_RDONLY); ++ if (_fd < 0) ++ return log_error_errno(errno, "Failed to open '%s': %m", path); ++ ++ fd = _fd; ++ } ++ + if (h->disk_size == UINT64_MAX) + return 0; + +- r = get_block_device(path, &devno); +- if (r < 0) +- return log_error_errno(r, "Failed to determine block device of %s: %m", path); +- if (devno == 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system %s not backed by a block device.", path); +- +- r = quotactl_devnum(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), devno, h->uid, &req); ++ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), h->uid, &req); + if (r == -ESRCH) + zero(req); + else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) +@@ -70,7 +82,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) { + req.dqb_valid = QIF_BLIMITS; + req.dqb_bsoftlimit = req.dqb_bhardlimit = h->disk_size / QIF_DQBLKSIZE; + +- r = quotactl_devnum(QCMD_FIXED(Q_SETQUOTA, USRQUOTA), devno, h->uid, &req); ++ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), h->uid, &req); + if (r == -ESRCH) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "UID quota not available on %s.", path); + if (r < 0) +@@ -81,7 +93,7 @@ int home_update_quota_classic(UserRecord *h, const char *path) { + return 0; + } + +-int home_update_quota_auto(UserRecord *h, const char *path) { ++int home_update_quota_auto(UserRecord *h, int fd, const char *path) { + struct statfs sfs; + int r; + +@@ -96,22 +108,31 @@ int home_update_quota_auto(UserRecord *h, const char *path) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home record lacks image path."); + } + +- if (statfs(path, &sfs) < 0) ++ _cleanup_close_ int _fd = -EBADF; ++ if (fd < 0) { ++ _fd = open(path, O_CLOEXEC|O_RDONLY); ++ if (_fd < 0) ++ return log_error_errno(errno, "Failed to open '%s': %m", path); ++ ++ fd = _fd; ++ } ++ ++ if (fstatfs(fd, &sfs) < 0) + return log_error_errno(errno, "Failed to statfs() file system: %m"); + + if (is_fs_type(&sfs, XFS_SUPER_MAGIC) || + is_fs_type(&sfs, EXT4_SUPER_MAGIC)) +- return home_update_quota_classic(h, path); ++ return home_update_quota_classic(h, fd, path); + + if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) { + +- r = btrfs_is_subvol(path); ++ r = btrfs_is_subvol_fd(fd); + if (r < 0) + return log_error_errno(r, "Failed to test if %s is a subvolume: %m", path); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Directory %s is not a subvolume, cannot apply quota.", path); + +- return home_update_quota_btrfs(h, path); ++ return home_update_quota_btrfs(h, fd, path); + } + + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Type of directory %s not known, cannot apply quota.", path); +diff --git a/src/home/homework-quota.h b/src/home/homework-quota.h +index a21c9ba8b1..a03510c75e 100644 +--- a/src/home/homework-quota.h ++++ b/src/home/homework-quota.h +@@ -3,6 +3,6 @@ + + #include "user-record.h" + +-int home_update_quota_btrfs(UserRecord *h, const char *path); +-int home_update_quota_classic(UserRecord *h, const char *path); +-int home_update_quota_auto(UserRecord *h, const char *path); ++int home_update_quota_btrfs(UserRecord *h, int fd, const char *path); ++int home_update_quota_classic(UserRecord *h, int fd, const char *path); ++int home_update_quota_auto(UserRecord *h, int fd, const char *path); +diff --git a/src/shared/quota-util.c b/src/shared/quota-util.c +index 4d014f847c..a698129adf 100644 +--- a/src/shared/quota-util.c ++++ b/src/shared/quota-util.c +@@ -1,42 +1,36 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + ++#include + #include + #include + + #include "alloc-util.h" + #include "blockdev-util.h" + #include "device-util.h" ++#include "errno-util.h" ++#include "missing_syscall.h" + #include "quota-util.h" + +-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr) { +- _cleanup_free_ char *devnode = NULL; ++int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr) { + int r; + +- /* Like quotactl() but takes a dev_t instead of a path to a device node, and fixes caddr_t → void*, +- * like we should, today */ ++ /* Emulates quotactl_fd() on older kernels that lack it. (i.e. kernels < 5.14) */ + +- r = devname_from_devnum(S_IFBLK, devnum, &devnode); +- if (r < 0) ++ r = RET_NERRNO(quotactl_fd(fd, cmd, id, addr)); ++ if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + +- if (quotactl(cmd, devnode, id, addr) < 0) +- return -errno; +- +- return 0; +-} +- +-int quotactl_path(int cmd, const char *path, int id, void *addr) { + dev_t devno; +- int r; +- +- /* Like quotactl() but takes a path to some fs object, and changes the backing file system. I.e. the +- * argument shouldn't be a block device but a regular file system object */ +- +- r = get_block_device(path, &devno); ++ r = get_block_device_fd(fd, &devno); + if (r < 0) + return r; + if (devno == 0) /* Doesn't have a block device */ + return -ENODEV; + +- return quotactl_devnum(cmd, devno, id, addr); ++ _cleanup_free_ char *devnode = NULL; ++ r = devname_from_devnum(S_IFBLK, devno, &devnode); ++ if (r < 0) ++ return r; ++ ++ return RET_NERRNO(quotactl(cmd, devnode, id, addr)); + } +diff --git a/src/shared/quota-util.h b/src/shared/quota-util.h +index 14a390ebe4..ad97eede01 100644 +--- a/src/shared/quota-util.h ++++ b/src/shared/quota-util.h +@@ -15,5 +15,4 @@ static inline int QCMD_FIXED(uint32_t cmd, uint32_t type) { + return (int) QCMD(cmd, type); + } + +-int quotactl_devnum(int cmd, dev_t devnum, int id, void *addr); +-int quotactl_path(int cmd, const char *path, int id, void *addr); ++int quotactl_fd_with_fallback(int fd, int cmd, int id, void *addr); +diff --git a/units/systemd-homed.service.in b/units/systemd-homed.service.in +index b54e5d30b2..5687fc0ed4 100644 +--- a/units/systemd-homed.service.in ++++ b/units/systemd-homed.service.in +@@ -33,7 +33,7 @@ StateDirectory=systemd/home + CacheDirectory=systemd/home + SystemCallArchitectures=native + SystemCallErrorNumber=EPERM +-SystemCallFilter=@system-service @mount quotactl ++SystemCallFilter=@system-service @mount quotactl quotactl_fd + TimeoutStopSec=3min + {{SERVICE_WATCHDOG}} + diff --git a/0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch b/0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch new file mode 100644 index 0000000..960dc29 --- /dev/null +++ b/0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch @@ -0,0 +1,400 @@ +From d40f49bc7e9454abdc0fc32936294cd046846c59 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 14 Jan 2025 16:49:52 +0100 +Subject: [PATCH] pid1: add GracefulOptions= setting to .mount units + +This new setting can be used to specify mount options that shall only be +added to the mount option string if the kernel supports them. + +This shall be used for adding "usrquota" to tmp.mount without breaking compat, +but is generally be useful. + +[dtardon: I put the test into TEST-87-AUX-UTILS-VM, because that's where +3f9539a97f3b4747ff22a530bac39dec24ac58af, which we have already +backported, would have moved it.] + +(cherry picked from commit 09fbff57fcde47782a73f23b3d5cfdcd0e8f699b) + +Related: RHEL-143028 +--- + man/org.freedesktop.systemd1.xml | 7 ++++ + man/systemd.mount.xml | 16 +++++++++ + src/core/dbus-mount.c | 25 +++++++++++++ + src/core/load-fragment-gperf.gperf.in | 1 + + src/core/load-fragment.c | 45 ++++++++++++++++++++++++ + src/core/load-fragment.h | 1 + + src/core/mount.c | 44 +++++++++++++++++++++++ + src/core/mount.h | 2 ++ + src/shared/bus-unit-util.c | 19 +++++----- + test/units/TEST-87-AUX-UTILS-VM.mount.sh | 7 ++++ + 10 files changed, 159 insertions(+), 8 deletions(-) + +diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml +index 0c7c52d563..71d2a11d29 100644 +--- a/man/org.freedesktop.systemd1.xml ++++ b/man/org.freedesktop.systemd1.xml +@@ -7004,6 +7004,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + readonly s Result = '...'; + readonly u UID = ...; + readonly u GID = ...; ++ @org.freedesktop.DBus.Property.EmitsChangedSignal("const") ++ readonly as GracefulOptions = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates") + readonly a(sasbttttuii) ExecMount = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates") +@@ -7604,6 +7606,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + ++ ++ + + + +@@ -8150,6 +8154,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + ++ ++ + + + +@@ -12380,6 +12386,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ + ManagedOOMMemoryPressureDurationUSec, + ProtectControlGroupsEx, and + PrivatePIDs were added in version 257. ++ GracefulOptions were added in version 258. + + + Swap Unit Objects +diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml +index 20e724d540..9b394f6079 100644 +--- a/man/systemd.mount.xml ++++ b/man/systemd.mount.xml +@@ -650,6 +650,22 @@ + systemd-system.conf5. + + ++ ++ ++ GracefulOptions= ++ ++ Additional mount options that shall be appended to Options= if ++ supported by the kernel. This may be used to configure mount options that are optional and only ++ enabled on kernels that support them. Note that this is supported only for native kernel mount ++ options (i.e. explicitly not for mount options implemented in userspace, such as those processed by ++ /usr/bin/mount itself, by FUSE or by mount helpers such as ++ mount.nfs). ++ ++ May be specified multiple times. If specified multiple times, all listed, supported mount ++ options are combined. If an empty string is assigned, the list is reset. ++ ++ ++ + + + +diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c +index d59aa06a11..855300d025 100644 +--- a/src/core/dbus-mount.c ++++ b/src/core/dbus-mount.c +@@ -75,6 +75,7 @@ const sd_bus_vtable bus_mount_vtable[] = { + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), ++ SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), +@@ -150,6 +151,30 @@ static int bus_mount_set_transient_property( + if (streq(name, "ReadWriteOnly")) + return bus_set_transient_bool(u, name, &m->read_write_only, message, flags, error); + ++ if (streq(name, "GracefulOptions")) { ++ _cleanup_strv_free_ char **add = NULL; ++ r = sd_bus_message_read_strv(message, &add); ++ if (r < 0) ++ return r; ++ ++ if (!UNIT_WRITE_FLAGS_NOOP(flags)) { ++ ++ if (strv_isempty(add)) { ++ m->graceful_options = strv_free(m->graceful_options); ++ unit_write_settingf(u, flags, name, "GracefulOptions="); ++ } else { ++ r = strv_extend_strv(&m->graceful_options, add, /* filter_duplicates= */ false); ++ if (r < 0) ++ return r; ++ ++ STRV_FOREACH(a, add) ++ unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "GracefulOptions=%s", *a); ++ } ++ } ++ ++ return 1; ++ } ++ + return 0; + } + +diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in +index d7564b3767..5b67d7b554 100644 +--- a/src/core/load-fragment-gperf.gperf.in ++++ b/src/core/load-fragment-gperf.gperf.in +@@ -546,6 +546,7 @@ Mount.SloppyOptions, config_parse_bool, + Mount.LazyUnmount, config_parse_bool, 0, offsetof(Mount, lazy_unmount) + Mount.ForceUnmount, config_parse_bool, 0, offsetof(Mount, force_unmount) + Mount.ReadWriteOnly, config_parse_bool, 0, offsetof(Mount, read_write_only) ++Mount.GracefulOptions, config_parse_mount_graceful_options, 0, offsetof(Mount, graceful_options) + {{ EXEC_CONTEXT_CONFIG_ITEMS('Mount') }} + {{ CGROUP_CONTEXT_CONFIG_ITEMS('Mount') }} + {{ KILL_CONTEXT_CONFIG_ITEMS('Mount') }} +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 90f054ec9e..9007846e64 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -6121,6 +6121,51 @@ int config_parse_mount_node( + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata); + } + ++int config_parse_mount_graceful_options( ++ const char *unit, ++ const char *filename, ++ unsigned line, ++ const char *section, ++ unsigned section_line, ++ const char *lvalue, ++ int ltype, ++ const char *rvalue, ++ void *data, ++ void *userdata) { ++ ++ const Unit *u = ASSERT_PTR(userdata); ++ char ***sv = ASSERT_PTR(data); ++ int r; ++ ++ assert(filename); ++ assert(lvalue); ++ assert(rvalue); ++ ++ if (isempty(rvalue)) { ++ *sv = strv_free(*sv); ++ return 1; ++ } ++ ++ _cleanup_free_ char *resolved = NULL; ++ r = unit_full_printf(u, rvalue, &resolved); ++ if (r < 0) { ++ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue); ++ return 0; ++ } ++ ++ _cleanup_strv_free_ char **strv = NULL; ++ ++ r = strv_split_full(&strv, resolved, ",", EXTRACT_RETAIN_ESCAPE|EXTRACT_UNESCAPE_SEPARATORS); ++ if (r < 0) ++ return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); ++ ++ r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates = */ false); ++ if (r < 0) ++ return log_oom(); ++ ++ return 1; ++} ++ + static int merge_by_names(Unit *u, Set *names, const char *id) { + char *k; + int r; +diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h +index 8ac962a94b..673088a42d 100644 +--- a/src/core/load-fragment.h ++++ b/src/core/load-fragment.h +@@ -164,6 +164,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_open_file); + CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch); + CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); + CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); ++CONFIG_PARSER_PROTOTYPE(config_parse_mount_graceful_options); + + /* gperf prototypes */ + const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); +diff --git a/src/core/mount.c b/src/core/mount.c +index e5d02965be..136c3f1453 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -231,6 +231,8 @@ static void mount_done(Unit *u) { + mount_unwatch_control_pid(m); + + m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source); ++ ++ m->graceful_options = strv_free(m->graceful_options); + } + + static int update_parameters_proc_self_mountinfo( +@@ -1061,6 +1063,44 @@ fail: + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false); + } + ++static int mount_append_graceful_options(Mount *m, const MountParameters *p, char **opts) { ++ int r; ++ ++ assert(m); ++ assert(p); ++ assert(opts); ++ ++ if (strv_isempty(m->graceful_options)) ++ return 0; ++ ++ if (!p->fstype) { ++ log_unit_warning(UNIT(m), "GracefulOptions= used but file system type not known, suppressing all graceful options."); ++ return 0; ++ } ++ ++ STRV_FOREACH(o, m->graceful_options) { ++ _cleanup_free_ char *k = NULL, *v = NULL; ++ ++ r = split_pair(*o, "=", &k, &v); ++ if (r < 0 && r != -EINVAL) /* EINVAL → not a key/value pair */ ++ return r; ++ ++ r = mount_option_supported(p->fstype, k ?: *o, v); ++ if (r < 0) ++ log_unit_warning_errno(UNIT(m), r, "GracefulOptions=%s specified, but cannot determine availability, suppressing.", *o); ++ else if (r == 0) ++ log_unit_info(UNIT(m), "GracefulOptions=%s specified, but option is not available, suppressing.", *o); ++ else { ++ log_unit_debug(UNIT(m), "GracefulOptions=%s specified and supported, appending to mount option string.", *o); ++ ++ if (!strextend_with_separator(opts, ",", *o)) ++ return -ENOMEM; ++ } ++ } ++ ++ return 0; ++} ++ + static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) { + int r; + +@@ -1095,6 +1135,10 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete + if (r < 0) + return r; + ++ r = mount_append_graceful_options(m, p, &opts); ++ if (r < 0) ++ return r; ++ + if (!isempty(opts)) { + r = exec_command_append(c, "-o", opts, NULL); + if (r < 0) +diff --git a/src/core/mount.h b/src/core/mount.h +index 9a8b6bb4c0..28cc7785d8 100644 +--- a/src/core/mount.h ++++ b/src/core/mount.h +@@ -88,6 +88,8 @@ struct Mount { + sd_event_source *timer_event_source; + + unsigned n_retry_umount; ++ ++ char **graceful_options; + }; + + extern const UnitVTable mount_vtable; +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 06bfb90c8f..8667d10077 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -147,7 +147,7 @@ static int bus_append_string(sd_bus_message *m, const char *field, const char *e + return 1; + } + +-static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, ExtractFlags flags) { ++static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, const char *separator, ExtractFlags flags) { + const char *p; + int r; + +@@ -170,7 +170,7 @@ static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, + for (p = eq;;) { + _cleanup_free_ char *word = NULL; + +- r = extract_first_word(&p, &word, NULL, flags); ++ r = extract_first_word(&p, &word, separator, flags); + if (r == 0) + break; + if (r == -ENOMEM) +@@ -607,12 +607,12 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons + return bus_append_cg_blkio_weight_parse(m, field, eq); + + if (streq(field, "DisableControllers")) +- return bus_append_strv(m, "DisableControllers", eq, EXTRACT_UNQUOTE); ++ return bus_append_strv(m, "DisableControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE); + + if (streq(field, "Delegate")) { + r = parse_boolean(eq); + if (r < 0) +- return bus_append_strv(m, "DelegateControllers", eq, EXTRACT_UNQUOTE); ++ return bus_append_strv(m, "DelegateControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE); + + r = sd_bus_message_append(m, "(sv)", "Delegate", "b", r); + if (r < 0) +@@ -1110,7 +1110,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con + "ConfigurationDirectory", + "SupplementaryGroups", + "SystemCallArchitectures")) +- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE); ++ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE); + + if (STR_IN_SET(field, "SyslogLevel", + "LogLevelMax")) +@@ -1169,7 +1169,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con + if (STR_IN_SET(field, "Environment", + "UnsetEnvironment", + "PassEnvironment")) +- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE); ++ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE); + + if (streq(field, "EnvironmentFile")) { + if (isempty(eq)) +@@ -2309,6 +2309,9 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const + "ReadwriteOnly")) + return bus_append_parse_boolean(m, field, eq); + ++ if (streq(field, "GracefulOptions")) ++ return bus_append_strv(m, field, eq, /* separator= */ ",", 0); ++ + return 0; + } + +@@ -2591,7 +2594,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons + return bus_append_string(m, field, eq); + + if (streq(field, "Symlinks")) +- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE); ++ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE); + + if (streq(field, "SocketProtocol")) + return bus_append_parse_ip_protocol(m, field, eq); +@@ -2725,7 +2728,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const + "RequiresMountsFor", + "WantsMountsFor", + "Markers")) +- return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE); ++ return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE); + + t = condition_type_from_string(field); + if (t >= 0) +diff --git a/test/units/TEST-87-AUX-UTILS-VM.mount.sh b/test/units/TEST-87-AUX-UTILS-VM.mount.sh +index 3075d0fe2e..c1bfbdc1a2 100755 +--- a/test/units/TEST-87-AUX-UTILS-VM.mount.sh ++++ b/test/units/TEST-87-AUX-UTILS-VM.mount.sh +@@ -180,3 +180,10 @@ systemctl status "$WORK_DIR/mnt" + touch "$WORK_DIR/mnt/hello" + [[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]] + systemd-umount LABEL=owner-vfat ++ ++# Mkae sure that graceful mount options work ++GRACEFULTEST="/tmp/graceful/$RANDOM" ++systemd-mount --tmpfs -p GracefulOptions=idefinitelydontexist,nr_inodes=4711,idonexisteither "$GRACEFULTEST" ++findmnt -n -o options "$GRACEFULTEST" ++findmnt -n -o options "$GRACEFULTEST" | grep -q nr_inodes=4711 ++umount "$GRACEFULTEST" diff --git a/0635-pid1-enable-usrquota-support-on-dev-shm.patch b/0635-pid1-enable-usrquota-support-on-dev-shm.patch new file mode 100644 index 0000000..660a40b --- /dev/null +++ b/0635-pid1-enable-usrquota-support-on-dev-shm.patch @@ -0,0 +1,75 @@ +From 75f712d4fb8d5b05f28eda98e9ae44512ba6d7f8 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 14 Jan 2025 16:51:49 +0100 +Subject: [PATCH] pid1: enable usrquota support on /dev/shm + +(cherry picked from commit 8f5131fb9e7979022521d685e69b6419f0884677) + +Related: RHEL-143028 +--- + src/shared/mount-setup.c | 33 +++++++++++++++++++++++++-------- + 1 file changed, 25 insertions(+), 8 deletions(-) + +diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c +index 73be3b5dce..db8c2b61d2 100644 +--- a/src/shared/mount-setup.c ++++ b/src/shared/mount-setup.c +@@ -34,11 +34,12 @@ + #include "virt.h" + + typedef enum MountMode { +- MNT_NONE = 0, +- MNT_FATAL = 1 << 0, +- MNT_IN_CONTAINER = 1 << 1, +- MNT_CHECK_WRITABLE = 1 << 2, +- MNT_FOLLOW_SYMLINK = 1 << 3, ++ MNT_NONE = 0, ++ MNT_FATAL = 1 << 0, ++ MNT_IN_CONTAINER = 1 << 1, ++ MNT_CHECK_WRITABLE = 1 << 2, ++ MNT_FOLLOW_SYMLINK = 1 << 3, ++ MNT_USRQUOTA_GRACEFUL = 1 << 4, + } MountMode; + + typedef struct MountPoint { +@@ -92,7 +93,7 @@ static const MountPoint mount_table[] = { + mac_smack_use, MNT_FATAL }, + #endif + { "tmpfs", "/dev/shm", "tmpfs", "mode=01777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, +- NULL, MNT_FATAL|MNT_IN_CONTAINER }, ++ NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_USRQUOTA_GRACEFUL }, + { "devpts", "/dev/pts", "devpts", "mode=" STRINGIFY(TTY_MODE) ",gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, + NULL, MNT_IN_CONTAINER }, + #if ENABLE_SMACK +@@ -189,13 +190,29 @@ static int mount_one(const MountPoint *p, bool relabel) { + else + (void) mkdir_p(p->where, 0755); + ++ _cleanup_free_ char *extend_options = NULL; ++ const char *o = p->options; ++ if (FLAGS_SET(p->mode, MNT_USRQUOTA_GRACEFUL)) { ++ r = mount_option_supported(p->type, "usrquota", /* value= */ NULL); ++ if (r < 0) ++ log_warning_errno(r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type); ++ else if (r == 0) ++ log_info("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where); ++ else { ++ if (!strextend_with_separator(&extend_options, ",", p->options ?: POINTER_MAX, "usrquota")) ++ return log_oom(); ++ ++ o = extend_options; ++ } ++ } ++ + log_debug("Mounting %s to %s of type %s with options %s.", + p->what, + p->where, + p->type, +- strna(p->options)); ++ strna(o)); + +- r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); ++ r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, o, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); + if (r < 0) + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; + diff --git a/0636-units-enable-usrquota-support-on-tmp.patch b/0636-units-enable-usrquota-support-on-tmp.patch new file mode 100644 index 0000000..44c90e8 --- /dev/null +++ b/0636-units-enable-usrquota-support-on-tmp.patch @@ -0,0 +1,24 @@ +From bc192261e4801ad27a8610fea4e10010d705bfc0 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 14 Jan 2025 16:52:04 +0100 +Subject: [PATCH] units: enable usrquota support on /tmp/ + +(cherry picked from commit c9f805674ace3e2d611596b871c2d12190457d87) + +Related: RHEL-143028 +--- + units/tmp.mount | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/units/tmp.mount b/units/tmp.mount +index d7beaa8d14..d347af56d3 100644 +--- a/units/tmp.mount ++++ b/units/tmp.mount +@@ -23,6 +23,7 @@ What=tmpfs + Where=/tmp + Type=tmpfs + Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m ++GracefulOptions=usrquota + + # Make 'systemctl enable tmp.mount' work: + [Install] diff --git a/0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch b/0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch new file mode 100644 index 0000000..c41ff3d --- /dev/null +++ b/0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch @@ -0,0 +1,71 @@ +From b9cfb8c02ec36304e0a3ba730363a6dd747dd26a Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 14 Jan 2025 16:51:27 +0100 +Subject: [PATCH] nspawn: enable usrquota support on /tmp/ and /dev/shm/ + +(cherry picked from commit 611ae598889471830b2f1d7251c271b79884b1c4) + +Related: RHEL-143028 +--- + src/nspawn/nspawn-mount.c | 21 +++++++++++++++++++-- + src/nspawn/nspawn-mount.h | 1 + + 2 files changed, 20 insertions(+), 2 deletions(-) + +diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c +index c233cdf600..6bd506f960 100644 +--- a/src/nspawn/nspawn-mount.c ++++ b/src/nspawn/nspawn-mount.c +@@ -592,7 +592,7 @@ int mount_all(const char *dest, + + /* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing when we are privileged) */ + { "tmpfs", "/tmp", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, +- MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR }, ++ MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL }, + { "tmpfs", "/sys", "tmpfs", "mode=0555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV, + MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_PRIVILEGED }, + { "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS, +@@ -602,7 +602,7 @@ int mount_all(const char *dest, + { "tmpfs", "/dev", "tmpfs", "mode=0755" TMPFS_LIMITS_PRIVATE_DEV, MS_NOSUID|MS_STRICTATIME, + MOUNT_FATAL|MOUNT_MKDIR }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, +- MOUNT_FATAL|MOUNT_MKDIR }, ++ MOUNT_FATAL|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL }, + { "tmpfs", "/run", "tmpfs", "mode=0755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME, + MOUNT_FATAL|MOUNT_MKDIR }, + { "/run/host", "/run/host", NULL, NULL, MS_BIND, +@@ -710,6 +710,23 @@ int mount_all(const char *dest, + o = options; + } + ++ if (FLAGS_SET(m->mount_settings, MOUNT_USRQUOTA_GRACEFUL)) { ++ r = mount_option_supported(m->type, /* key= */ "usrquota", /* value= */ NULL); ++ if (r < 0) ++ log_warning_errno(r, "Failed to determine if '%s' supports 'usrquota', assuming it doesn't: %m", m->type); ++ else if (r == 0) ++ log_info("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where); ++ else { ++ _cleanup_free_ char *joined = NULL; ++ ++ if (!strextend_with_separator(&joined, ",", o ?: POINTER_MAX, "usrquota")) ++ return log_oom(); ++ ++ free_and_replace(options, joined); ++ o = options; ++ } ++ } ++ + if (FLAGS_SET(m->mount_settings, MOUNT_PREFIX_ROOT)) { + /* Optionally prefix the mount source with the root dir. This is useful in bind + * mounts to be created within the container image before we transition into it. Note +diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h +index 5f66bc7328..529fa16658 100644 +--- a/src/nspawn/nspawn-mount.h ++++ b/src/nspawn/nspawn-mount.h +@@ -21,6 +21,7 @@ typedef enum MountSettingsMask { + MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */ + MOUNT_FOLLOW_SYMLINKS = 1 << 11,/* if set, we'll follow symlinks for the mount target */ + MOUNT_PRIVILEGED = 1 << 12,/* if set, we'll only mount this in the outer child if we are running in privileged mode */ ++ MOUNT_USRQUOTA_GRACEFUL = 1 << 13,/* if set, append "usrquota" to mount options if kernel tmpfs supports that */ + } MountSettingsMask; + + typedef enum CustomMountType { diff --git a/0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch b/0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch new file mode 100644 index 0000000..8088364 --- /dev/null +++ b/0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch @@ -0,0 +1,31 @@ +From 4a4f0b5dcb42533971116fd8f467f9866f50d58c Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Mon, 20 Jan 2025 19:05:11 +0000 +Subject: [PATCH] nspawn: downgrade log message about usrquota to debug + +This is shown every time nspawn is started, which is annoying +and there's nothing a user can do about it, since it depends on +an extremely new kernel. Downgrade to debug. + +Follow-up for 611ae598889471830b2f1d7251c271b79884b1c4 + +(cherry picked from commit 8fa7863a93047d310ec6600f939590f07854b497) + +Related: RHEL-143028 +--- + src/nspawn/nspawn-mount.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c +index 6bd506f960..cd5a634ec0 100644 +--- a/src/nspawn/nspawn-mount.c ++++ b/src/nspawn/nspawn-mount.c +@@ -715,7 +715,7 @@ int mount_all(const char *dest, + if (r < 0) + log_warning_errno(r, "Failed to determine if '%s' supports 'usrquota', assuming it doesn't: %m", m->type); + else if (r == 0) +- log_info("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where); ++ log_debug("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where); + else { + _cleanup_free_ char *joined = NULL; + diff --git a/0639-mount-setup-drop-outdated-comment.patch b/0639-mount-setup-drop-outdated-comment.patch new file mode 100644 index 0000000..463e23d --- /dev/null +++ b/0639-mount-setup-drop-outdated-comment.patch @@ -0,0 +1,24 @@ +From 2cc61dd003b3798a2beb19160501961056f8ee96 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Sat, 8 Mar 2025 19:13:44 +0100 +Subject: [PATCH] mount-setup: drop outdated comment + +(cherry picked from commit 80b7f60d2e7883cec3185172a85901d72fd5cdb3) + +Related: RHEL-143028 +--- + src/shared/mount-setup.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c +index db8c2b61d2..93e646d045 100644 +--- a/src/shared/mount-setup.c ++++ b/src/shared/mount-setup.c +@@ -179,7 +179,6 @@ static int mount_one(const MountPoint *p, bool relabel) { + if (r > 0) + return 0; + +- /* Skip securityfs in a container */ + if (!FLAGS_SET(p->mode, MNT_IN_CONTAINER) && detect_container() > 0) + return 0; + diff --git a/0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch b/0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch new file mode 100644 index 0000000..c94e24d --- /dev/null +++ b/0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch @@ -0,0 +1,40 @@ +From d5642d888c6bc1b8014b727b6b1b4851a0829239 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Thu, 6 Mar 2025 13:14:13 +0100 +Subject: [PATCH] mount-setup: tune down log level if usrquota is not + supported, apply usrquota when smack is in use too + +Follow-up for 8f5131fb9e7979022521d685e69b6419f0884677 + +(cherry picked from commit b05c495eedaa73d4e911f9617a9afba04c95395b) + +Related: RHEL-143028 +--- + src/shared/mount-setup.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c +index 93e646d045..4c5151c7db 100644 +--- a/src/shared/mount-setup.c ++++ b/src/shared/mount-setup.c +@@ -90,7 +90,7 @@ static const MountPoint mount_table[] = { + { "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV, + mac_smack_use, MNT_FATAL }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=01777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, +- mac_smack_use, MNT_FATAL }, ++ mac_smack_use, MNT_FATAL|MNT_USRQUOTA_GRACEFUL }, + #endif + { "tmpfs", "/dev/shm", "tmpfs", "mode=01777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_USRQUOTA_GRACEFUL }, +@@ -194,9 +194,9 @@ static int mount_one(const MountPoint *p, bool relabel) { + if (FLAGS_SET(p->mode, MNT_USRQUOTA_GRACEFUL)) { + r = mount_option_supported(p->type, "usrquota", /* value= */ NULL); + if (r < 0) +- log_warning_errno(r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type); ++ log_full_errno(priority, r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type); + else if (r == 0) +- log_info("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where); ++ log_debug("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where); + else { + if (!strextend_with_separator(&extend_options, ",", p->options ?: POINTER_MAX, "usrquota")) + return log_oom(); diff --git a/0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch b/0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch new file mode 100644 index 0000000..3aa7b3c --- /dev/null +++ b/0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch @@ -0,0 +1,89 @@ +From 72d7385b4390e3c4def7e488aef31b84e1bb5f5d Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 23 Jan 2025 11:40:36 +0100 +Subject: [PATCH] devnum-util: add macros to safely convert dev_t to pointers + and back + +Sometimes it's nice being able to store dev_t as pointer values in +hashmaps/tables, instead of having to allocate memory for them and using +devt_hash_ops. After all dev_t is weird on Linux/glibc: glibc defines it +as 64bit entity (which hence appears as something we cannot encode in a +pointer value for compat with 32bit archs) but it actually is 32bit in +the kernel apis. Hence we can safely cut off the upper 32bit, and still +retain compat with all archs. + +But let's hide this in new macros, and validate this is all correct via +a test. + +(cherry picked from commit d15811d7e5bb3fc3afc1c14e4d5bb3b18fddedc1) + +Related: RHEL-143028 +--- + src/basic/devnum-util.h | 10 ++++++++-- + src/test/test-devnum-util.c | 17 +++++++++++++++++ + 2 files changed, 25 insertions(+), 2 deletions(-) + +diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h +index e109de9913..0efca56780 100644 +--- a/src/basic/devnum-util.h ++++ b/src/basic/devnum-util.h +@@ -9,6 +9,9 @@ + + int parse_devnum(const char *s, dev_t *ret); + ++#define DEVNUM_MAJOR_MAX ((UINT32_C(1) << 12) - 1U) ++#define DEVNUM_MINOR_MAX ((UINT32_C(1) << 20) - 1U) ++ + /* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the + * specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of + * major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of +@@ -18,14 +21,14 @@ int parse_devnum(const char *s, dev_t *ret); + #define DEVICE_MAJOR_VALID(x) \ + ({ \ + typeof(x) _x = (x), _y = 0; \ +- _x >= _y && _x < (UINT32_C(1) << 12); \ ++ _x >= _y && _x <= DEVNUM_MAJOR_MAX; \ + \ + }) + + #define DEVICE_MINOR_VALID(x) \ + ({ \ + typeof(x) _x = (x), _y = 0; \ +- _x >= _y && _x < (UINT32_C(1) << 20); \ ++ _x >= _y && _x <= DEVNUM_MINOR_MAX; \ + }) + + int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret); +@@ -54,3 +57,6 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) { + static inline bool devnum_is_zero(dev_t d) { + return major(d) == 0 && minor(d) == 0; + } ++ ++#define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u)) ++#define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p))) +diff --git a/src/test/test-devnum-util.c b/src/test/test-devnum-util.c +index ebef794001..782f15d86f 100644 +--- a/src/test/test-devnum-util.c ++++ b/src/test/test-devnum-util.c +@@ -121,4 +121,21 @@ TEST(devnum_format_str) { + test_devnum_format_str_one(makedev(4095, 1048575), "4095:1048575"); + } + ++TEST(devnum_to_ptr) { ++ dev_t m = makedev(0, 0); ++ ASSERT_EQ(major(m), 0U); ++ ASSERT_EQ(minor(m), 0U); ++ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); ++ ++ m = makedev(DEVNUM_MAJOR_MAX, DEVNUM_MINOR_MAX); ++ ASSERT_EQ(major(m), DEVNUM_MAJOR_MAX); ++ ASSERT_EQ(minor(m), DEVNUM_MINOR_MAX); ++ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); ++ ++ m = makedev(5, 8); ++ ASSERT_EQ(major(m), 5U); ++ ASSERT_EQ(minor(m), 8U); ++ ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); ++} ++ + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch b/0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch new file mode 100644 index 0000000..ba38454 --- /dev/null +++ b/0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch @@ -0,0 +1,284 @@ +From 34ee2cbe4a0b8bdd1d48c31d6735f9d9b551e524 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 10 Jan 2025 11:33:59 +0100 +Subject: [PATCH] user-record: add fields for setting limits on /tmp/ and + /dev/shm/ + +(cherry picked from commit 72b932aac0fbd818208d78ec01256bb946401881) + +Related: RHEL-143028 +--- + docs/USER_RECORD.md | 35 ++++++++++++----- + src/shared/user-record-show.c | 24 ++++++++++++ + src/shared/user-record.c | 71 +++++++++++++++++++++++++++++++++++ + src/shared/user-record.h | 17 +++++++++ + 4 files changed, 138 insertions(+), 9 deletions(-) + +diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md +index 90675dd532..689dd4e599 100644 +--- a/docs/USER_RECORD.md ++++ b/docs/USER_RECORD.md +@@ -626,6 +626,19 @@ is allowed to edit. + `selfModifiablePrivileged` → Similar to `selfModifiableFields`, but it lists fields in + the `privileged` section that the user is allowed to edit. + ++`tmpLimit` → A numeric value encoding a disk quota limit in bytes enforced on ++`/tmp/` on login, in case it is backed by volatile file system (such as ++`tmpfs`). ++ ++`tmpLimitScale` → Similar, but encodes a relative value, normalized to ++`UINT32_MAX` as 100%. This value is applied relative to the file system ++size. If both `tmpLimit` and `tmpLimitScale` are set, the lower of the two ++should be enforced. If neither field is set the implementation might apply a ++default limit. ++ ++`devShmLimit`, `devShmLimitScale` → Similar to the previous two, but apply to ++`/dev/shm/` rather than `/tmp/`. ++ + `privileged` → An object, which contains the fields of the `privileged` section + of the user record, see below. + +@@ -777,22 +790,26 @@ These four are the only fields specific to this section. All other fields that + may be used in this section are identical to the equally named ones in the + `regular` section (i.e. at the top-level object). Specifically, these are: + +-`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`, +-`preferredLanguage`, `additionalLanguages`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`, +-`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`, +-`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`, ++`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`, ++`environment`, `timeZone`, `preferredLanguage`, `additionalLanguages`, ++`niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`, `notAfterUSec`, ++`storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`, `accessMode`, ++`tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`, + `mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`, + `cifsUserName`, `cifsService`, `cifsExtraMountOptions`, `imagePath`, `uid`, + `gid`, `memberOf`, `fileSystemType`, `partitionUuid`, `luksUuid`, + `fileSystemUuid`, `luksDiscard`, `luksOfflineDiscard`, `luksCipher`, + `luksCipherMode`, `luksVolumeKeySize`, `luksPbkdfHashAlgorithm`, +-`luksPbkdfType`, `luksPbkdfForceIterations`, `luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`, +-`luksPbkdfParallelThreads`, `luksSectorSize`, `autoResizeMode`, `rebalanceWeight`, +-`rateLimitIntervalUSec`, `rateLimitBurst`, `enforcePasswordPolicy`, +-`autoLogin`, `preferredSessionType`, `preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`, ++`luksPbkdfType`, `luksPbkdfForceIterations`, `luksPbkdfTimeCostUSec`, ++`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `luksSectorSize`, ++`autoResizeMode`, `rebalanceWeight`, `rateLimitIntervalUSec`, `rateLimitBurst`, ++`enforcePasswordPolicy`, `autoLogin`, `preferredSessionType`, ++`preferredSessionLauncher`, `stopDelayUSec`, `killProcesses`, + `passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`, + `passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`, +-`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, `selfModifiablePrivileged`. ++`fido2HmacCredential`, `selfModifiableFields`, `selfModifiableBlobs`, ++`selfModifiablePrivileged`, `tmpLimit`, `tmpLimitScale`, `devShmLimit`, ++`devShmLimitScale`. + + ## Fields in the `binding` section + +diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c +index dab75d1f79..a6173aabb8 100644 +--- a/src/shared/user-record-show.c ++++ b/src/shared/user-record-show.c +@@ -7,6 +7,7 @@ + #include "hashmap.h" + #include "hexdecoct.h" + #include "path-util.h" ++#include "percent-util.h" + #include "pretty-print.h" + #include "process-util.h" + #include "rlimit-util.h" +@@ -54,6 +55,26 @@ static void dump_self_modifiable( + printf("%13s %s\n", i == value ? heading : "", *i); + } + ++static void show_tmpfs_limit(const char *tmpfs, const TmpfsLimit *limit, uint32_t scale) { ++ assert(tmpfs); ++ assert(limit); ++ ++ if (!limit->is_set) ++ return; ++ ++ printf(" %s Limit:", tmpfs); ++ ++ if (limit->limit != UINT64_MAX) ++ printf(" %s", FORMAT_BYTES(limit->limit)); ++ if (limit->limit == UINT64_MAX || limit->limit_scale != UINT32_MAX) { ++ if (limit->limit != UINT64_MAX) ++ printf(" or"); ++ ++ printf(" %i%%", UINT32_SCALE_TO_PERCENT(scale)); ++ } ++ printf("\n"); ++} ++ + void user_record_show(UserRecord *hr, bool show_full_group_info) { + _cleanup_strv_free_ char **langs = NULL; + const char *hd, *ip, *shell; +@@ -371,6 +392,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { + if (hr->io_weight != UINT64_MAX) + printf(" IO Weight: %" PRIu64 "\n", hr->io_weight); + ++ show_tmpfs_limit("TMP", &hr->tmp_limit, user_record_tmp_limit_scale(hr)); ++ show_tmpfs_limit("SHM", &hr->dev_shm_limit, user_record_dev_shm_limit_scale(hr)); ++ + if (hr->access_mode != MODE_INVALID) + printf(" Access Mode: 0%03o\n", user_record_access_mode(hr)); + +diff --git a/src/shared/user-record.c b/src/shared/user-record.c +index bfea52427a..6a1813c402 100644 +--- a/src/shared/user-record.c ++++ b/src/shared/user-record.c +@@ -15,6 +15,7 @@ + #include "locale-util.h" + #include "memory-util.h" + #include "path-util.h" ++#include "percent-util.h" + #include "pkcs11-util.h" + #include "rlimit-util.h" + #include "sha256.h" +@@ -95,6 +96,8 @@ UserRecord* user_record_new(void) { + .drop_caches = -1, + .auto_resize_mode = _AUTO_RESIZE_MODE_INVALID, + .rebalance_weight = REBALANCE_WEIGHT_UNSET, ++ .tmp_limit = TMPFS_LIMIT_NULL, ++ .dev_shm_limit = TMPFS_LIMIT_NULL, + }; + + return h; +@@ -982,6 +985,40 @@ static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant, + return 0; + } + ++static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { ++ TmpfsLimit *limit = ASSERT_PTR(userdata); ++ int r; ++ ++ if (sd_json_variant_is_null(variant)) { ++ *limit = TMPFS_LIMIT_NULL; ++ return 0; ++ } ++ ++ r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit); ++ if (r < 0) ++ return r; ++ ++ limit->is_set = true; ++ return 0; ++} ++ ++static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { ++ TmpfsLimit *limit = ASSERT_PTR(userdata); ++ int r; ++ ++ if (sd_json_variant_is_null(variant)) { ++ *limit = TMPFS_LIMIT_NULL; ++ return 0; ++ } ++ ++ r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale); ++ if (r < 0) ++ return r; ++ ++ limit->is_set = true; ++ return 0; ++} ++ + static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + + static const sd_json_dispatch_field privileged_dispatch_table[] = { +@@ -1299,6 +1336,10 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j + { "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT }, + { "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT }, + { "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT }, ++ { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, }, ++ { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, }, ++ { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, }, ++ { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, }, + {}, + }; + +@@ -1650,6 +1691,10 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load + { "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT }, + { "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT }, + { "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT }, ++ { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, }, ++ { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, }, ++ { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, }, ++ { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, }, + + { "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 }, + { "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 }, +@@ -2163,6 +2208,32 @@ int user_record_languages(UserRecord *h, char ***ret) { + return 0; + } + ++uint32_t user_record_tmp_limit_scale(UserRecord *h) { ++ assert(h); ++ ++ if (h->tmp_limit.is_set) ++ return h->tmp_limit.limit_scale; ++ ++ /* By default grant regular users only 80% quota */ ++ if (user_record_disposition(h) == USER_REGULAR) ++ return UINT32_SCALE_FROM_PERCENT(80); ++ ++ return UINT32_MAX; ++} ++ ++uint32_t user_record_dev_shm_limit_scale(UserRecord *h) { ++ assert(h); ++ ++ if (h->dev_shm_limit.is_set) ++ return h->dev_shm_limit.limit_scale; ++ ++ /* By default grant regular users only 80% quota */ ++ if (user_record_disposition(h) == USER_REGULAR) ++ return UINT32_SCALE_FROM_PERCENT(80); ++ ++ return UINT32_MAX; ++} ++ + const char** user_record_self_modifiable_fields(UserRecord *h) { + /* As a rule of thumb: a setting is safe if it cannot be used by a + * user to give themselves some unfair advantage over other users on +diff --git a/src/shared/user-record.h b/src/shared/user-record.h +index a3d8f4eb07..8c709750ae 100644 +--- a/src/shared/user-record.h ++++ b/src/shared/user-record.h +@@ -232,6 +232,19 @@ typedef enum AutoResizeMode { + #define REBALANCE_WEIGHT_MAX UINT64_C(10000) + #define REBALANCE_WEIGHT_UNSET UINT64_MAX + ++typedef struct TmpfsLimit { ++ /* Absolute and relative tmpfs limits */ ++ uint64_t limit; ++ uint32_t limit_scale; ++ bool is_set; ++} TmpfsLimit; ++ ++#define TMPFS_LIMIT_NULL \ ++ (TmpfsLimit) { \ ++ .limit = UINT64_MAX, \ ++ .limit_scale = UINT32_MAX, \ ++ } \ ++ + typedef struct UserRecord { + /* The following three fields are not part of the JSON record */ + unsigned n_ref; +@@ -392,6 +405,8 @@ typedef struct UserRecord { + char **self_modifiable_blobs; + char **self_modifiable_privileged; + ++ TmpfsLimit tmp_limit, dev_shm_limit; ++ + sd_json_variant *json; + } UserRecord; + +@@ -439,6 +454,8 @@ uint64_t user_record_rebalance_weight(UserRecord *h); + uint64_t user_record_capability_bounding_set(UserRecord *h); + uint64_t user_record_capability_ambient_set(UserRecord *h); + int user_record_languages(UserRecord *h, char ***ret); ++uint32_t user_record_tmp_limit_scale(UserRecord *h); ++uint32_t user_record_dev_shm_limit_scale(UserRecord *h); + + const char **user_record_self_modifiable_fields(UserRecord *h); + const char **user_record_self_modifiable_blobs(UserRecord *h); diff --git a/0643-user-runtime-dir-some-smaller-modernizations-refacto.patch b/0643-user-runtime-dir-some-smaller-modernizations-refacto.patch new file mode 100644 index 0000000..7b0786c --- /dev/null +++ b/0643-user-runtime-dir-some-smaller-modernizations-refacto.patch @@ -0,0 +1,108 @@ +From 801e4945fff10fd4250202fed1101f7e236ddd41 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 23 Jan 2025 22:30:41 +0100 +Subject: [PATCH] user-runtime-dir: some smaller modernizations/refactorings + +(cherry picked from commit 9ef12bc1d73aa1c2d65ff006550180624e688a5c) + +Related: RHEL-143028 +--- + src/login/user-runtime-dir.c | 44 +++++++++++++++++++----------------- + 1 file changed, 23 insertions(+), 21 deletions(-) + +diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c +index b242f83429..f39c1ad225 100644 +--- a/src/login/user-runtime-dir.c ++++ b/src/login/user-runtime-dir.c +@@ -92,39 +92,38 @@ static int user_mkdir_runtime_path( + uid, gid, runtime_dir_size, runtime_dir_inodes, + mac_smack_use() ? ",smackfsroot=*" : ""); + ++ _cleanup_free_ char *d = strdup(runtime_path); ++ if (!d) ++ return log_oom(); ++ + r = mkdir_label(runtime_path, 0700); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to create %s: %m", runtime_path); + ++ _cleanup_(rmdir_and_freep) char *destroy = TAKE_PTR(d); /* auto-destroy */ ++ + r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options); + if (r < 0) { +- if (!ERRNO_IS_PRIVILEGE(r)) { +- log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path); +- goto fail; +- } ++ if (!ERRNO_IS_PRIVILEGE(r)) ++ return log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path); + + log_debug_errno(r, + "Failed to mount per-user tmpfs directory %s.\n" + "Assuming containerized execution, ignoring: %m", runtime_path); + + r = chmod_and_chown(runtime_path, 0700, uid, gid); +- if (r < 0) { +- log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path); +- goto fail; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path); + } + ++ destroy = mfree(destroy); /* deactivate auto-destroy */ ++ + r = label_fix(runtime_path, 0); + if (r < 0) + log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path); + } + + return 0; +- +-fail: +- /* Try to clean up, but ignore errors */ +- (void) rmdir(runtime_path); +- return r; + } + + static int user_remove_runtime_path(const char *runtime_path) { +@@ -139,9 +138,9 @@ static int user_remove_runtime_path(const char *runtime_path) { + + /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to + * mount something */ +- r = umount2(runtime_path, MNT_DETACH); +- if (r < 0 && !IN_SET(errno, EINVAL, ENOENT)) +- log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path); ++ r = RET_NERRNO(umount2(runtime_path, MNT_DETACH)); ++ if (r < 0 && !IN_SET(r, -EINVAL, -ENOENT)) ++ log_debug_errno(r, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path); + + r = rm_rf(runtime_path, REMOVE_ROOT); + if (r < 0 && r != -ENOENT) +@@ -206,7 +205,10 @@ static int run(int argc, char *argv[]) { + if (argc != 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes two arguments."); +- if (!STR_IN_SET(argv[1], "start", "stop")) ++ ++ const char *verb = argv[1], *user = argv[2]; ++ ++ if (!STR_IN_SET(verb, "start", "stop")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "First argument must be either \"start\" or \"stop\"."); + +@@ -216,10 +218,10 @@ static int run(int argc, char *argv[]) { + if (r < 0) + return r; + +- if (streq(argv[1], "start")) +- return do_mount(argv[2]); +- if (streq(argv[1], "stop")) +- return do_umount(argv[2]); ++ if (streq(verb, "start")) ++ return do_mount(user); ++ if (streq(verb, "stop")) ++ return do_umount(user); + assert_not_reached(); + } + diff --git a/0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch b/0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch new file mode 100644 index 0000000..59bd678 --- /dev/null +++ b/0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch @@ -0,0 +1,304 @@ +From 4aad3334b4618edc47d4813bdbed9b3ca6b86aec Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 10 Jan 2025 11:34:18 +0100 +Subject: [PATCH] user-runtime-dir: enforce /tmp/ and /dev/shm/ quota + +Enforce the quota on these two tmpfs at the same place where we mount +the per-user $XDG_RUNTIME_DIR. Conceptually these are very similar +concepts, and it makes sure to enforce the limits at the same place with +the same lifecycle. + +(cherry picked from commit b1c95fb2e9d11fc190017dec3d64f468f9d378bc) + +Resolves: RHEL-143028 +--- + README | 2 + + man/user@.service.xml | 13 +-- + src/login/user-runtime-dir.c | 191 ++++++++++++++++++++++++++++++----- + 3 files changed, 173 insertions(+), 33 deletions(-) + +diff --git a/README b/README +index b9a58389ad..7d38f17b5a 100644 +--- a/README ++++ b/README +@@ -41,6 +41,8 @@ REQUIREMENTS: + ≥ 5.3 for bounded loops in BPF program + ≥ 5.4 for pidfd and signed Verity images + ≥ 5.7 for CLONE_INTO_CGROUP, BPF links and the BPF LSM hook ++ ≥ 5.14 for quotactl_fd() ++ ≥ 6.6 for quota support on tmpfs + + ⛔ Kernel versions below 3.15 ("minimum baseline") are not supported at + all, and are missing required functionality (e.g. CLOCK_BOOTTIME +diff --git a/man/user@.service.xml b/man/user@.service.xml +index e9cbda4833..f2e83c1b16 100644 +--- a/man/user@.service.xml ++++ b/man/user@.service.xml +@@ -42,12 +42,13 @@ + systemd.special7 for a + list of units that form the basis of the unit hierarchies of system and user units. + +- user@UID.service is accompanied by the +- system unit user-runtime-dir@UID.service, which +- creates the user's runtime directory +- /run/user/UID, and then removes it when this +- unit is stopped. user-runtime-dir@UID.service +- executes the systemd-user-runtime-dir binary to do the actual work. ++ user@UID.service is accompanied by the system unit ++ user-runtime-dir@UID.service, which creates the user's ++ runtime directory /run/user/UID when started, and removes ++ it when it is stopped. It also might apply runtime quota settings on /tmp/ and/or ++ /dev/shm/ for the ++ user. user-runtime-dir@UID.service executes the ++ systemd-user-runtime-dir binary to do the actual work. + + User processes may be started by the user@.service instance, in which + case they will be part of that unit in the system hierarchy. They may also be started elsewhere, +diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c +index f39c1ad225..94117c95db 100644 +--- a/src/login/user-runtime-dir.c ++++ b/src/login/user-runtime-dir.c +@@ -8,15 +8,20 @@ + #include "bus-error.h" + #include "bus-locator.h" + #include "dev-setup.h" ++#include "devnum-util.h" ++#include "fd-util.h" + #include "format-util.h" + #include "fs-util.h" + #include "label-util.h" + #include "limits-util.h" + #include "main-func.h" ++#include "missing_magic.h" ++#include "missing_syscall.h" + #include "mkdir-label.h" + #include "mount-util.h" + #include "mountpoint-util.h" + #include "path-util.h" ++#include "quota-util.h" + #include "rm-rf.h" + #include "selinux-util.h" + #include "smack-util.h" +@@ -24,6 +29,7 @@ + #include "string-util.h" + #include "strv.h" + #include "user-util.h" ++#include "userdb.h" + + static int acquire_runtime_dir_properties(uint64_t *ret_size, uint64_t *ret_inodes) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +@@ -126,6 +132,26 @@ static int user_mkdir_runtime_path( + return 0; + } + ++static int do_mount(UserRecord *ur) { ++ int r; ++ ++ assert(ur); ++ ++ if (!uid_is_valid(ur->uid) || !gid_is_valid(ur->gid)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID or GID, refusing.", ur->user_name); ++ ++ uint64_t runtime_dir_size, runtime_dir_inodes; ++ r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes); ++ if (r < 0) ++ return r; ++ ++ char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; ++ xsprintf(runtime_path, "/run/user/" UID_FMT, ur->uid); ++ ++ log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, ur->uid, ur->gid); ++ return user_mkdir_runtime_path(runtime_path, ur->uid, ur->gid, runtime_dir_size, runtime_dir_inodes); ++} ++ + static int user_remove_runtime_path(const char *runtime_path) { + int r; + +@@ -149,31 +175,6 @@ static int user_remove_runtime_path(const char *runtime_path) { + return 0; + } + +-static int do_mount(const char *user) { +- char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; +- uint64_t runtime_dir_size, runtime_dir_inodes; +- uid_t uid; +- gid_t gid; +- int r; +- +- r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); +- if (r < 0) +- return log_error_errno(r, +- r == -ESRCH ? "No such user \"%s\"" : +- r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group" +- : "Failed to look up user \"%s\": %m", +- user); +- +- r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes); +- if (r < 0) +- return r; +- +- xsprintf(runtime_path, "/run/user/" UID_FMT, uid); +- +- log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, uid, gid); +- return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size, runtime_dir_inodes); +-} +- + static int do_umount(const char *user) { + char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; + uid_t uid; +@@ -197,6 +198,126 @@ static int do_umount(const char *user) { + return user_remove_runtime_path(runtime_path); + } + ++static int apply_tmpfs_quota( ++ char **paths, ++ uid_t uid, ++ uint64_t limit, ++ uint32_t scale) { ++ ++ _cleanup_set_free_ Set *processed = NULL; ++ int r; ++ ++ assert(uid_is_valid(uid)); ++ ++ STRV_FOREACH(p, paths) { ++ _cleanup_close_ int fd = open(*p, O_DIRECTORY|O_CLOEXEC); ++ if (fd < 0) { ++ log_warning_errno(errno, "Failed to open '%s' in order to set quota, ignoring: %m", *p); ++ continue; ++ } ++ ++ struct stat st; ++ if (fstat(fd, &st) < 0) { ++ log_warning_errno(errno, "Failed to stat '%s' in order to set quota, ignoring: %m", *p); ++ continue; ++ } ++ ++ /* Cover for bind mounted or symlinked /var/tmp/ + /tmp/ */ ++ if (set_contains(processed, DEVNUM_TO_PTR(st.st_dev))) { ++ log_debug("Not setting quota on '%s', since already processed.", *p); ++ continue; ++ } ++ ++ /* Remember we already dealt with this fs, even if the subsequent operation fails, since ++ * there's no point in appyling quota twice, regardless if it succeeds or not. */ ++ if (set_ensure_put(&processed, /* hash_ops= */ NULL, DEVNUM_TO_PTR(st.st_dev)) < 0) ++ return log_oom(); ++ ++ struct statfs sfs; ++ if (fstatfs(fd, &sfs) < 0) { ++ log_warning_errno(errno, "Failed to statfs '%s' in order to set quota, ignoring: %m", *p); ++ continue; ++ } ++ ++ if (!is_fs_type(&sfs, TMPFS_MAGIC)) { ++ log_debug("Not setting quota on '%s', since not tmpfs.", *p); ++ continue; ++ } ++ ++ struct dqblk req; ++ r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req)); ++ if (r == -ESRCH) ++ zero(req); ++ else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { ++ log_debug_errno(r, "No UID quota support on %s, not setting quota: %m", *p); ++ continue; ++ } else if (ERRNO_IS_NEG_PRIVILEGE(r)) { ++ log_debug_errno(r, "Lacking privileges to query UID quota on %s, not setting quota: %m", *p); ++ continue; ++ } else if (r < 0) { ++ log_warning_errno(r, "Failed to query disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid); ++ continue; ++ } ++ ++ uint64_t v = ++ (scale == 0) ? 0 : ++ (scale == UINT32_MAX) ? UINT64_MAX : ++ (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) / scale * UINT32_MAX); ++ ++ v = MIN(v, limit); ++ v /= QIF_DQBLKSIZE; ++ ++ if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && v == req.dqb_bhardlimit) { ++ /* Shortcut things if everything is set up properly already */ ++ log_debug("Configured quota on '%s' already matches the intended setting, not updating quota.", *p); ++ continue; ++ } ++ ++ req.dqb_valid = QIF_BLIMITS; ++ req.dqb_bsoftlimit = req.dqb_bhardlimit = v; ++ ++ r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), uid, &req)); ++ if (r == -ESRCH) { ++ log_debug_errno(r, "Not setting UID quota on %s since UID quota is not supported: %m", *p); ++ continue; ++ } else if (ERRNO_IS_NEG_PRIVILEGE(r)) { ++ log_debug_errno(r, "Lacking privileges to set UID quota on %s, skipping: %m", *p); ++ continue; ++ } else if (r < 0) { ++ log_warning_errno(r, "Failed to set disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid); ++ continue; ++ } ++ ++ log_info("Successfully configured disk quota for UID " UID_FMT " on %s to %s", uid, *p, FORMAT_BYTES(v * QIF_DQBLKSIZE)); ++ } ++ ++ return 0; ++} ++ ++static int do_tmpfs_quota(UserRecord *ur) { ++ int r; ++ ++ assert(ur); ++ ++ if (user_record_is_root(ur)) { ++ log_debug("Not applying tmpfs quota to root user."); ++ return 0; ++ } ++ ++ if (!uid_is_valid(ur->uid)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID, refusing.", ur->user_name); ++ ++ r = apply_tmpfs_quota(STRV_MAKE("/tmp", "/var/tmp"), ur->uid, ur->tmp_limit.limit, user_record_tmp_limit_scale(ur)); ++ if (r < 0) ++ return r; ++ ++ r = apply_tmpfs_quota(STRV_MAKE("/dev/shm"), ur->uid, ur->dev_shm_limit.limit, user_record_dev_shm_limit_scale(ur)); ++ if (r < 0) ++ return r; ++ ++ return 0; ++} ++ + static int run(int argc, char *argv[]) { + int r; + +@@ -218,10 +339,26 @@ static int run(int argc, char *argv[]) { + if (r < 0) + return r; + +- if (streq(verb, "start")) +- return do_mount(user); ++ if (streq(verb, "start")) { ++ _cleanup_(user_record_unrefp) UserRecord *ur = NULL; ++ r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur); ++ if (r == -ESRCH) ++ return log_error_errno(r, "User '%s' does not exist: %m", user); ++ if (r < 0) ++ return log_error_errno(r, "Failed to resolve user '%s': %m", user); ++ ++ /* We do two things here: mount the per-user XDG_RUNTIME_DIR, and set up tmpfs quota on /tmp/ ++ * and /dev/shm/. */ ++ ++ r = 0; ++ RET_GATHER(r, do_mount(ur)); ++ RET_GATHER(r, do_tmpfs_quota(ur)); ++ return r; ++ } ++ + if (streq(verb, "stop")) + return do_umount(user); ++ + assert_not_reached(); + } + diff --git a/0645-homectl-add-support-for-configuring-tmpfs-limits.patch b/0645-homectl-add-support-for-configuring-tmpfs-limits.patch new file mode 100644 index 0000000..2de5ffe --- /dev/null +++ b/0645-homectl-add-support-for-configuring-tmpfs-limits.patch @@ -0,0 +1,129 @@ +From aa2f389a0844384dc0ecfda291ddb5e4f2afbc40 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 10 Jan 2025 15:31:44 +0100 +Subject: [PATCH] homectl: add support for configuring tmpfs limits + +(cherry picked from commit 2b2aebf4dd695d52cf8b6cbd38ba57affdc5a6bb) + +Resolves: RHEL-143028 +--- + man/homectl.xml | 16 +++++++++++++ + src/home/homectl.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 73 insertions(+) + +diff --git a/man/homectl.xml b/man/homectl.xml +index d08972c421..adad89c54a 100644 +--- a/man/homectl.xml ++++ b/man/homectl.xml +@@ -792,6 +792,22 @@ + + + ++ ++ ++ ++ ++ ++ ++ Controls the per-user quota on /tmp/ and ++ /dev/shm/ that is applied when the user logs in. Takes either an absolute value ++ in bytes (with the usual K, M, G, T suffixes to the base of 1024), or a percentage. In the latter ++ case the limit is applied relative to the size of the respective file system. This limit is only ++ applied if the relevant file system is tmpfs and has no effect otherwise. Note ++ that if these options are not used, a default quota might still be enforced (typically 80%.) ++ ++ ++ ++ + + + +diff --git a/src/home/homectl.c b/src/home/homectl.c +index 6d5aec1602..c99663ffea 100644 +--- a/src/home/homectl.c ++++ b/src/home/homectl.c +@@ -2854,6 +2854,9 @@ static int help(int argc, char *argv[], void *userdata) { + " --memory-max=BYTES Set maximum memory limit\n" + " --cpu-weight=WEIGHT Set CPU weight\n" + " --io-weight=WEIGHT Set IO weight\n" ++ " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" ++ " --dev-shm-limit=BYTES|PERCENT\n" ++ " Set limit on /dev/shm/\n" + "\n%4$sStorage User Record Properties:%5$s\n" + " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" + " subvolume, cifs)\n" +@@ -3002,6 +3005,8 @@ static int parse_argv(int argc, char *argv[]) { + ARG_PROMPT_NEW_USER, + ARG_AVATAR, + ARG_LOGIN_BACKGROUND, ++ ARG_TMP_LIMIT, ++ ARG_DEV_SHM_LIMIT, + ARG_MATCH, + }; + +@@ -3103,6 +3108,8 @@ static int parse_argv(int argc, char *argv[]) { + { "blob", required_argument, NULL, 'b' }, + { "avatar", required_argument, NULL, ARG_AVATAR }, + { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, ++ { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, ++ { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, + { "match", required_argument, NULL, ARG_MATCH }, + {} + }; +@@ -4541,6 +4548,56 @@ static int parse_argv(int argc, char *argv[]) { + break; + } + ++ case ARG_TMP_LIMIT: ++ case ARG_DEV_SHM_LIMIT: { ++ const char *field = ++ c == ARG_TMP_LIMIT ? "tmpLimit" : ++ c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : NULL; ++ const char *field_scale = ++ c == ARG_TMP_LIMIT ? "tmpLimitScale" : ++ c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : NULL; ++ ++ assert(field); ++ assert(field_scale); ++ ++ if (isempty(optarg)) { ++ r = drop_from_identity(field); ++ if (r < 0) ++ return r; ++ r = drop_from_identity(field_scale); ++ if (r < 0) ++ return r; ++ break; ++ } ++ ++ r = parse_permyriad(optarg); ++ if (r < 0) { ++ uint64_t u; ++ ++ r = parse_size(optarg, 1024, &u); ++ if (r < 0) ++ return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, optarg); ++ ++ r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field, u); ++ if (r < 0) ++ return log_error_errno(r, "Failed to set %s field: %m", field); ++ ++ r = drop_from_identity(field_scale); ++ if (r < 0) ++ return r; ++ } else { ++ r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); ++ if (r < 0) ++ return log_error_errno(r, "Failed to set %s field: %m", field_scale); ++ ++ r = drop_from_identity(field); ++ if (r < 0) ++ return r; ++ } ++ ++ break; ++ } ++ + case ARG_MATCH: + if (streq(optarg, "any")) + match_identity = &arg_identity_extra; diff --git a/0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch b/0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch new file mode 100644 index 0000000..6367e55 --- /dev/null +++ b/0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch @@ -0,0 +1,40 @@ +From 2425eb1611a0176aa096296d28e612be41c917ee Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 15 Jan 2025 00:25:22 +0100 +Subject: [PATCH] test: add test case for tmpfs quota logic + PAMName= + ask-password logic + +(cherry picked from commit d58d449fc6b828429f2e4d06779a45318eb17f27) + +Related: RHEL-143028 +--- + test/units/TEST-46-HOMED.sh | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh +index 8de170a1c9..3663e53908 100755 +--- a/test/units/TEST-46-HOMED.sh ++++ b/test/units/TEST-46-HOMED.sh +@@ -652,6 +652,22 @@ getent passwd aliastest@myrealm + getent passwd aliastest2@myrealm + getent passwd aliastest3@myrealm + ++if findmnt -n -o options /tmp | grep -q usrquota ; then ++ ++ NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K -P ++ ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 ++ (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile2 bs=1024 count=30) ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 /dev/shm/quotatestfile2 ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 ++ ++ systemctl stop user@"$(id -u tmpfsquota)".service ++ ++ wait_for_state tmpfsquota inactive ++ homectl remove tmpfsquota ++fi ++ + systemd-analyze log-level info + + touch /testok diff --git a/0647-update-TODO.patch b/0647-update-TODO.patch new file mode 100644 index 0000000..c4f304e --- /dev/null +++ b/0647-update-TODO.patch @@ -0,0 +1,52 @@ +From aedee1c91632bc2f50e23733a94f3ca106960f05 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 15 Jan 2025 09:44:52 +0100 +Subject: [PATCH] update TODO + +(cherry picked from commit 0054b7dce986b73e6cb10bf4ae51a1dd5ef57191) + +Related: RHEL-143028 +--- + TODO | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/TODO b/TODO +index d6c2cef2f1..4ed9f5a834 100644 +--- a/TODO ++++ b/TODO +@@ -257,10 +257,6 @@ Features: + + * pcrlock: add support for multi-profile UKIs + +-* logind: when logging in use new tmpfs quota support to configure quota on +- /tmp/ + /dev/shm/. But do so only in case of tmpfs, because otherwise quota +- is persistent and any persistent settings mean we don#t have to reapply them. +- + * initrd: when transitioning from initrd to host, validate that + /lib/modules/`uname -r` exists, refuse otherwise + +@@ -1447,8 +1443,6 @@ Features: + + * rework recursive read-only remount to use new mount API + +-* PAM: pick up authentication token from credentials +- + * when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release + data in the image, make sure the image filename actually matches this, so + that images cannot be misused. +@@ -1515,7 +1509,6 @@ Features: + - pass creds via keyring? + - pass creds via memfd? + - acquire + decrypt creds from pkcs11? +- - make PAMName= acquire pw via creds logic + - make macsec code in networkd read key via creds logic (copy logic from + wireguard) + - make gatewayd/remote read key via creds logic +@@ -2378,7 +2371,6 @@ Features: + - maybe make automatic, read-only, time-based reflink-copies of LUKS disk + images (and btrfs snapshots of subvolumes) (think: time machine) + - distinguish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory) +- - in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work + - fingerprint authentication, pattern authentication, … + - make sure "classic" user records can also be managed by homed + - make size of $XDG_RUNTIME_DIR configurable in user record diff --git a/0648-core-dbus-service-fix-alignment.patch b/0648-core-dbus-service-fix-alignment.patch new file mode 100644 index 0000000..fab96be --- /dev/null +++ b/0648-core-dbus-service-fix-alignment.patch @@ -0,0 +1,31 @@ +From b1731e6bef1276d5081284fe7d0aa46a62e6749c Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Sun, 9 Feb 2025 19:59:13 +0100 +Subject: [PATCH] core/dbus-service: fix alignment + +(cherry picked from commit 5fe4c30ca78679551edd7152b70d66b481453b66) + +Related: RHEL-143028 +--- + src/core/dbus-service.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c +index 85eb99f218..a1c452e29f 100644 +--- a/src/core/dbus-service.c ++++ b/src/core/dbus-service.c +@@ -393,10 +393,10 @@ const sd_bus_vtable bus_service_vtable[] = { + SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_METHOD_WITH_ARGS("MountImage", +- SD_BUS_ARGS("s", source, "s", destination, "b", read_only, "b", mkdir, "a(ss)", options), +- SD_BUS_NO_RESULT, +- bus_service_method_mount_image, +- SD_BUS_VTABLE_UNPRIVILEGED), ++ SD_BUS_ARGS("s", source, "s", destination, "b", read_only, "b", mkdir, "a(ss)", options), ++ SD_BUS_NO_RESULT, ++ bus_service_method_mount_image, ++ SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_METHOD_WITH_ARGS("DumpFileDescriptorStore", + SD_BUS_NO_ARGS, diff --git a/0649-core-mount-filter-out-fail-option-as-well.patch b/0649-core-mount-filter-out-fail-option-as-well.patch new file mode 100644 index 0000000..c811dd4 --- /dev/null +++ b/0649-core-mount-filter-out-fail-option-as-well.patch @@ -0,0 +1,25 @@ +From 31d4a35652e852905137c532f2f8bd1fa2391dc0 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Sun, 9 Feb 2025 20:25:21 +0100 +Subject: [PATCH] core/mount: filter out "fail" option as well + +(cherry picked from commit 7e9a78d6bea96bf8e9de615f6013c72f912f3e2d) + +Related: RHEL-143028 +--- + src/core/mount.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/core/mount.c b/src/core/mount.c +index 136c3f1453..786c3ae5f5 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -1131,7 +1131,7 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete + } + + _cleanup_free_ char *opts = NULL; +- r = fstab_filter_options(p->options, "nofail\0" "noauto\0" "auto\0", NULL, NULL, NULL, &opts); ++ r = fstab_filter_options(p->options, "nofail\0" "fail\0" "noauto\0" "auto\0", NULL, NULL, NULL, &opts); + if (r < 0) + return r; + diff --git a/0650-core-mount-check-parameters_fragment-first-in-mount_.patch b/0650-core-mount-check-parameters_fragment-first-in-mount_.patch new file mode 100644 index 0000000..59de61d --- /dev/null +++ b/0650-core-mount-check-parameters_fragment-first-in-mount_.patch @@ -0,0 +1,155 @@ +From db653b7fc6bc1fb503152c34f0d3e374d115a236 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Sun, 9 Feb 2025 20:41:20 +0100 +Subject: [PATCH] core/mount: check parameters_fragment first in + mount_enter_(re)mounting() + +I.e. don't perform any action if we can't spawn mount task anyway. +Later the same check would be added to mount_can_start/reload(), +so this makes things more coherent too. + +(cherry picked from commit 65bc0c03b97065847b187af2092458977173def4) + +Related: RHEL-143028 +--- + src/core/mount.c | 85 +++++++++++++++++++++++------------------------- + 1 file changed, 41 insertions(+), 44 deletions(-) + +diff --git a/src/core/mount.c b/src/core/mount.c +index 786c3ae5f5..b317af03a7 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -1160,7 +1160,12 @@ static void mount_enter_mounting(Mount *m) { + goto fail; + + p = get_mount_parameters_fragment(m); +- if (p && mount_is_bind(p)) { ++ if (!p) { ++ r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on."); ++ goto fail; ++ } ++ ++ if (mount_is_bind(p)) { + r = is_dir(p->what, /* follow = */ true); + if (r < 0 && r != -ENOENT) + log_unit_info_errno(UNIT(m), r, "Failed to determine type of bind mount source '%s', ignoring: %m", p->what); +@@ -1176,7 +1181,7 @@ static void mount_enter_mounting(Mount *m) { + log_unit_warning_errno(UNIT(m), r, "Failed to create mount point '%s', ignoring: %m", m->where); + + /* If we are asked to create an OverlayFS, create the upper/work directories if they are missing */ +- if (p && streq_ptr(p->fstype, "overlay")) { ++ if (streq_ptr(p->fstype, "overlay")) { + _cleanup_strv_free_ char **dirs = NULL; + + r = fstab_filter_options( +@@ -1205,13 +1210,9 @@ static void mount_enter_mounting(Mount *m) { + + if (source_is_dir) + unit_warn_if_dir_nonempty(UNIT(m), m->where); +- unit_warn_leftover_processes(UNIT(m), /* start = */ true); +- +- m->control_command_id = MOUNT_EXEC_MOUNT; +- m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; + + /* Create the source directory for bind-mounts if needed */ +- if (p && mount_is_bind(p)) { ++ if (mount_is_bind(p)) { + r = mkdir_p_label(p->what, m->directory_mode); + /* mkdir_p_label() can return -EEXIST if the target path exists and is not a directory - which is + * totally OK, in case the user wants us to overmount a non-directory inode. Also -EROFS can be +@@ -1223,19 +1224,18 @@ static void mount_enter_mounting(Mount *m) { + r, "Failed to make bind mount source '%s', ignoring: %m", p->what); + } + +- if (p) { +- r = mount_set_mount_command(m, m->control_command, p); +- if (r < 0) { +- log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m"); +- goto fail; +- } +- } else { +- r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on."); ++ mount_unwatch_control_pid(m); ++ unit_warn_leftover_processes(UNIT(m), /* start = */ true); ++ ++ m->control_command_id = MOUNT_EXEC_MOUNT; ++ m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; ++ ++ r = mount_set_mount_command(m, m->control_command, p); ++ if (r < 0) { ++ log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m"); + goto fail; + } + +- mount_unwatch_control_pid(m); +- + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'mount' task: %m"); +@@ -1265,42 +1265,39 @@ static void mount_enter_remounting(Mount *m) { + + assert(m); + +- /* Reset reload result when we are about to start a new remount operation */ ++ p = get_mount_parameters_fragment(m); ++ if (!p) { ++ r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on."); ++ goto fail; ++ } ++ ++ mount_unwatch_control_pid(m); + m->reload_result = MOUNT_SUCCESS; + + m->control_command_id = MOUNT_EXEC_REMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; + +- p = get_mount_parameters_fragment(m); +- if (p) { +- const char *o; +- +- if (p->options) +- o = strjoina("remount,", p->options); +- else +- o = "remount"; +- +- r = exec_command_set(m->control_command, MOUNT_PATH, +- p->what, m->where, +- "-o", o, NULL); +- if (r >= 0 && m->sloppy_options) +- r = exec_command_append(m->control_command, "-s", NULL); +- if (r >= 0 && m->read_write_only) +- r = exec_command_append(m->control_command, "-w", NULL); +- if (r >= 0 && p->fstype) +- r = exec_command_append(m->control_command, "-t", p->fstype, NULL); +- if (r < 0) { +- log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m"); +- goto fail; +- } ++ const char *o; + +- } else { +- r = log_unit_warning_errno(UNIT(m), SYNTHETIC_ERRNO(ENOENT), "No mount parameters to operate on."); ++ if (p->options) ++ o = strjoina("remount,", p->options); ++ else ++ o = "remount"; ++ ++ r = exec_command_set(m->control_command, MOUNT_PATH, ++ p->what, m->where, ++ "-o", o, NULL); ++ if (r >= 0 && m->sloppy_options) ++ r = exec_command_append(m->control_command, "-s", NULL); ++ if (r >= 0 && m->read_write_only) ++ r = exec_command_append(m->control_command, "-w", NULL); ++ if (r >= 0 && p->fstype) ++ r = exec_command_append(m->control_command, "-t", p->fstype, NULL); ++ if (r < 0) { ++ log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m"); + goto fail; + } + +- mount_unwatch_control_pid(m); +- + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'remount' task: %m"); diff --git a/0651-core-mount-report-accurate-can_start-and-can_reload.patch b/0651-core-mount-report-accurate-can_start-and-can_reload.patch new file mode 100644 index 0000000..8ae9ede --- /dev/null +++ b/0651-core-mount-report-accurate-can_start-and-can_reload.patch @@ -0,0 +1,49 @@ +From 341da00c9aea8c3987cbf7042c5392bd7b8f2181 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Mon, 10 Feb 2025 20:22:09 +0100 +Subject: [PATCH] core/mount: report accurate can_start and can_reload + +(cherry picked from commit 74c0d9726cfbd4ecdf1807adf5433fcf4b1d86b4) + +Related: RHEL-143028 +--- + src/core/mount.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/core/mount.c b/src/core/mount.c +index b317af03a7..6b38ee9431 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -1393,6 +1393,12 @@ static int mount_reload(Unit *u) { + return 1; + } + ++static bool mount_can_reload(Unit *u) { ++ Mount *m = ASSERT_PTR(MOUNT(u)); ++ ++ return get_mount_parameters_fragment(m); ++} ++ + static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { + Mount *m = ASSERT_PTR(MOUNT(u)); + +@@ -2334,6 +2340,9 @@ static int mount_test_startable(Unit *u) { + return r; + } + ++ if (!get_mount_parameters_fragment(m)) ++ return -ENOENT; ++ + return true; + } + +@@ -2448,7 +2457,9 @@ const UnitVTable mount_vtable = { + + .start = mount_start, + .stop = mount_stop, ++ + .reload = mount_reload, ++ .can_reload = mount_can_reload, + + .clean = mount_clean, + .can_clean = mount_can_clean, diff --git a/0652-core-mount-trivial-coding-style-cleanups.patch b/0652-core-mount-trivial-coding-style-cleanups.patch new file mode 100644 index 0000000..ff63aea --- /dev/null +++ b/0652-core-mount-trivial-coding-style-cleanups.patch @@ -0,0 +1,67 @@ +From 3aa54774b2d343c63feb3f7eafa5b489e526a8dc Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Wed, 12 Feb 2025 14:56:34 +0100 +Subject: [PATCH] core/mount: trivial coding style cleanups + +(cherry picked from commit c7c6cf2031c1c0eb1839d2ea83b5a8de4af6da00) + +Related: RHEL-143028 +--- + src/core/mount.c | 7 +++---- + src/core/mount.h | 2 +- + 2 files changed, 4 insertions(+), 5 deletions(-) + +diff --git a/src/core/mount.c b/src/core/mount.c +index 6b38ee9431..8ec18d8952 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -45,9 +45,9 @@ static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVATING, + [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_RELOADING, +- [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, + [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, ++ [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_FAILED] = UNIT_FAILED, +@@ -1038,6 +1038,8 @@ static void mount_enter_unmounting(Mount *m) { + MOUNT_UNMOUNTING_SIGKILL)) + m->n_retry_umount = 0; + ++ mount_unwatch_control_pid(m); ++ + m->control_command_id = MOUNT_EXEC_UNMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; + +@@ -1047,8 +1049,6 @@ static void mount_enter_unmounting(Mount *m) { + goto fail; + } + +- mount_unwatch_control_pid(m); +- + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(m), r, "Failed to spawn 'umount' task: %m"); +@@ -1056,7 +1056,6 @@ static void mount_enter_unmounting(Mount *m) { + } + + mount_set_state(m, MOUNT_UNMOUNTING); +- + return; + + fail: +diff --git a/src/core/mount.h b/src/core/mount.h +index 28cc7785d8..a8ae25e638 100644 +--- a/src/core/mount.h ++++ b/src/core/mount.h +@@ -81,7 +81,7 @@ struct Mount { + + MountState state, deserialized_state; + +- ExecCommand* control_command; ++ ExecCommand *control_command; + MountExecCommand control_command_id; + PidRef control_pid; + diff --git a/0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch b/0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch new file mode 100644 index 0000000..7d3f43e --- /dev/null +++ b/0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch @@ -0,0 +1,73 @@ +From f9204b8dccc68a3256f1e03ac9aaa1db1721f9df Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Mon, 10 Feb 2025 20:24:22 +0100 +Subject: [PATCH] core/dbus-mount: add missing ReloadResult and CleanResult + properties + +(cherry picked from commit 0fa062f9836ea5ed09bc42026d2d576dfb52c409) + +Related: RHEL-143028 +--- + man/org.freedesktop.systemd1.xml | 14 +++++++++++++- + src/core/dbus-mount.c | 2 ++ + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml +index 71d2a11d29..4e9d72be25 100644 +--- a/man/org.freedesktop.systemd1.xml ++++ b/man/org.freedesktop.systemd1.xml +@@ -7002,6 +7002,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly b ReadWriteOnly = ...; + readonly s Result = '...'; ++ readonly s ReloadResult = '...'; ++ readonly s CleanResult = '...'; + readonly u UID = ...; + readonly u GID = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") +@@ -7602,6 +7604,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + ++ ++ ++ ++ + + + +@@ -8150,6 +8156,10 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + ++ ++ ++ ++ + + + +@@ -12386,7 +12396,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ + ManagedOOMMemoryPressureDurationUSec, + ProtectControlGroupsEx, and + PrivatePIDs were added in version 257. +- GracefulOptions were added in version 258. ++ GracefulOptions, ++ ReloadResult, and ++ CleanResult were added in version 258. + + + Swap Unit Objects +diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c +index 855300d025..72bd4c697a 100644 +--- a/src/core/dbus-mount.c ++++ b/src/core/dbus-mount.c +@@ -73,6 +73,8 @@ const sd_bus_vtable bus_mount_vtable[] = { + SD_BUS_PROPERTY("ForceUnmount", "b", bus_property_get_bool, offsetof(Mount, force_unmount), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReadWriteOnly", "b", bus_property_get_bool, offsetof(Mount, read_write_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), ++ SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Mount, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), ++ SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Mount, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/0654-bus-unit-util-add-missing-assertions.patch b/0654-bus-unit-util-add-missing-assertions.patch new file mode 100644 index 0000000..7c87fe7 --- /dev/null +++ b/0654-bus-unit-util-add-missing-assertions.patch @@ -0,0 +1,49 @@ +From 3f7834fccb42fa5ffffc17c93e43a1bbced00964 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Tue, 11 Feb 2025 18:13:01 +0100 +Subject: [PATCH] bus-unit-util: add missing assertions + +(cherry picked from commit 85f759baeeab59f25080bd19b2f338ae2d05ad8b) + +Related: RHEL-143028 +--- + src/shared/bus-unit-util.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 8667d10077..9128c06555 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -148,9 +148,11 @@ static int bus_append_string(sd_bus_message *m, const char *field, const char *e + } + + static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, const char *separator, ExtractFlags flags) { +- const char *p; + int r; + ++ assert(m); ++ assert(field); ++ + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); +@@ -167,16 +169,16 @@ static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, + if (r < 0) + return bus_log_create_error(r); + +- for (p = eq;;) { ++ for (const char *p = eq;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, separator, flags); +- if (r == 0) +- break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Invalid syntax: %s", eq); ++ if (r == 0) ++ break; + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) diff --git a/0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch b/0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch new file mode 100644 index 0000000..2136702 --- /dev/null +++ b/0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch @@ -0,0 +1,439 @@ +From 4c474d4d10e0c52e108bbb75ad3aad0ca2dca469 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Tue, 11 Feb 2025 18:43:25 +0100 +Subject: [PATCH] core/mount: rework GracefulOptions= to be just + x-systemd.graceful-option= + +09fbff57fcde47782a73f23b3d5cfdcd0e8f699b introduced new knob +for such functionality. However, that seems unnecessary. + +The mount option string is ubiquitous in that all of fstab, +kernel cmdline, credentials, systemd-mount, ... speak it. +And we already have x-systemd.device-bound= that's parsed +by pid1 instead of fstab-generator. It feels hence more natural +for graceful options to be an extension of that, rather than +its own property. + +There's also one nice side effect that the setting itself +is now more graceful for systemd versions not supporting +such feature. + +(cherry picked from commit 0d76f1c4237a3e4adda828c694721fd709374b36) + +Related: RHEL-143028 +--- + man/org.freedesktop.systemd1.xml | 9 +--- + man/systemd.mount.xml | 29 +++++------ + src/core/dbus-mount.c | 26 +--------- + src/core/load-fragment-gperf.gperf.in | 1 - + src/core/load-fragment.c | 45 ----------------- + src/core/load-fragment.h | 1 - + src/core/mount.c | 62 +++++++++++------------- + src/core/mount.h | 2 - + src/shared/bus-unit-util.c | 3 -- + test/units/TEST-87-AUX-UTILS-VM.mount.sh | 4 +- + units/tmp.mount | 3 +- + 11 files changed, 47 insertions(+), 138 deletions(-) + +diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml +index 4e9d72be25..0b6be4c755 100644 +--- a/man/org.freedesktop.systemd1.xml ++++ b/man/org.freedesktop.systemd1.xml +@@ -7006,8 +7006,6 @@ node /org/freedesktop/systemd1/unit/home_2emount { + readonly s CleanResult = '...'; + readonly u UID = ...; + readonly u GID = ...; +- @org.freedesktop.DBus.Property.EmitsChangedSignal("const") +- readonly as GracefulOptions = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates") + readonly a(sasbttttuii) ExecMount = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates") +@@ -7612,8 +7610,6 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + +- +- + + + +@@ -8164,8 +8160,6 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + +- +- + + + +@@ -12396,8 +12390,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ + ManagedOOMMemoryPressureDurationUSec, + ProtectControlGroupsEx, and + PrivatePIDs were added in version 257. +- GracefulOptions, +- ReloadResult, and ++ ReloadResult, and + CleanResult were added in version 258. + + +diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml +index 9b394f6079..e84b6a9d48 100644 +--- a/man/systemd.mount.xml ++++ b/man/systemd.mount.xml +@@ -284,6 +284,19 @@ + + + ++ ++ ++ ++ Additional mount option that shall be appended if supported by the kernel. ++ This may be used to configure mount options that are optional and only enabled on kernels that ++ support them. Note that this is supported only for native kernel mount options (i.e. explicitly not ++ for mount options implemented in userspace, such as those processed by ++ /usr/bin/mount itself, by FUSE or by mount helpers such as ++ mount.nfs). This option may be specified more than once. ++ ++ ++ ++ + + + +@@ -650,22 +663,6 @@ + systemd-system.conf5. + + +- +- +- GracefulOptions= +- +- Additional mount options that shall be appended to Options= if +- supported by the kernel. This may be used to configure mount options that are optional and only +- enabled on kernels that support them. Note that this is supported only for native kernel mount +- options (i.e. explicitly not for mount options implemented in userspace, such as those processed by +- /usr/bin/mount itself, by FUSE or by mount helpers such as +- mount.nfs). +- +- May be specified multiple times. If specified multiple times, all listed, supported mount +- options are combined. If an empty string is assigned, the list is reset. +- +- +- + + + +diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c +index 72bd4c697a..6b30b90f7f 100644 +--- a/src/core/dbus-mount.c ++++ b/src/core/dbus-mount.c +@@ -77,7 +77,7 @@ const sd_bus_vtable bus_mount_vtable[] = { + SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Mount, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), +- SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST), ++ + BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), +@@ -153,30 +153,6 @@ static int bus_mount_set_transient_property( + if (streq(name, "ReadWriteOnly")) + return bus_set_transient_bool(u, name, &m->read_write_only, message, flags, error); + +- if (streq(name, "GracefulOptions")) { +- _cleanup_strv_free_ char **add = NULL; +- r = sd_bus_message_read_strv(message, &add); +- if (r < 0) +- return r; +- +- if (!UNIT_WRITE_FLAGS_NOOP(flags)) { +- +- if (strv_isempty(add)) { +- m->graceful_options = strv_free(m->graceful_options); +- unit_write_settingf(u, flags, name, "GracefulOptions="); +- } else { +- r = strv_extend_strv(&m->graceful_options, add, /* filter_duplicates= */ false); +- if (r < 0) +- return r; +- +- STRV_FOREACH(a, add) +- unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "GracefulOptions=%s", *a); +- } +- } +- +- return 1; +- } +- + return 0; + } + +diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in +index 5b67d7b554..d7564b3767 100644 +--- a/src/core/load-fragment-gperf.gperf.in ++++ b/src/core/load-fragment-gperf.gperf.in +@@ -546,7 +546,6 @@ Mount.SloppyOptions, config_parse_bool, + Mount.LazyUnmount, config_parse_bool, 0, offsetof(Mount, lazy_unmount) + Mount.ForceUnmount, config_parse_bool, 0, offsetof(Mount, force_unmount) + Mount.ReadWriteOnly, config_parse_bool, 0, offsetof(Mount, read_write_only) +-Mount.GracefulOptions, config_parse_mount_graceful_options, 0, offsetof(Mount, graceful_options) + {{ EXEC_CONTEXT_CONFIG_ITEMS('Mount') }} + {{ CGROUP_CONTEXT_CONFIG_ITEMS('Mount') }} + {{ KILL_CONTEXT_CONFIG_ITEMS('Mount') }} +diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c +index 9007846e64..90f054ec9e 100644 +--- a/src/core/load-fragment.c ++++ b/src/core/load-fragment.c +@@ -6121,51 +6121,6 @@ int config_parse_mount_node( + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata); + } + +-int config_parse_mount_graceful_options( +- const char *unit, +- const char *filename, +- unsigned line, +- const char *section, +- unsigned section_line, +- const char *lvalue, +- int ltype, +- const char *rvalue, +- void *data, +- void *userdata) { +- +- const Unit *u = ASSERT_PTR(userdata); +- char ***sv = ASSERT_PTR(data); +- int r; +- +- assert(filename); +- assert(lvalue); +- assert(rvalue); +- +- if (isempty(rvalue)) { +- *sv = strv_free(*sv); +- return 1; +- } +- +- _cleanup_free_ char *resolved = NULL; +- r = unit_full_printf(u, rvalue, &resolved); +- if (r < 0) { +- log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue); +- return 0; +- } +- +- _cleanup_strv_free_ char **strv = NULL; +- +- r = strv_split_full(&strv, resolved, ",", EXTRACT_RETAIN_ESCAPE|EXTRACT_UNESCAPE_SEPARATORS); +- if (r < 0) +- return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); +- +- r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates = */ false); +- if (r < 0) +- return log_oom(); +- +- return 1; +-} +- + static int merge_by_names(Unit *u, Set *names, const char *id) { + char *k; + int r; +diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h +index 673088a42d..8ac962a94b 100644 +--- a/src/core/load-fragment.h ++++ b/src/core/load-fragment.h +@@ -164,7 +164,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_open_file); + CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch); + CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); + CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); +-CONFIG_PARSER_PROTOTYPE(config_parse_mount_graceful_options); + + /* gperf prototypes */ + const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); +diff --git a/src/core/mount.c b/src/core/mount.c +index 8ec18d8952..037902c91c 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -231,8 +231,6 @@ static void mount_done(Unit *u) { + mount_unwatch_control_pid(m); + + m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source); +- +- m->graceful_options = strv_free(m->graceful_options); + } + + static int update_parameters_proc_self_mountinfo( +@@ -1062,22 +1060,25 @@ fail: + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false); + } + +-static int mount_append_graceful_options(Mount *m, const MountParameters *p, char **opts) { ++static int mount_apply_graceful_options(Mount *m, const MountParameters *p, char **opts) { ++ _cleanup_strv_free_ char **graceful = NULL; ++ _cleanup_free_ char *filtered = NULL; + int r; + + assert(m); + assert(p); + assert(opts); + +- if (strv_isempty(m->graceful_options)) +- return 0; ++ r = fstab_filter_options(*opts, "x-systemd.graceful-option\0", NULL, NULL, &graceful, &filtered); ++ if (r <= 0) ++ return r; + + if (!p->fstype) { +- log_unit_warning(UNIT(m), "GracefulOptions= used but file system type not known, suppressing all graceful options."); ++ log_unit_warning(UNIT(m), "x-systemd.graceful-option= used but file system type not known, suppressing all graceful options."); + return 0; + } + +- STRV_FOREACH(o, m->graceful_options) { ++ STRV_FOREACH(o, graceful) { + _cleanup_free_ char *k = NULL, *v = NULL; + + r = split_pair(*o, "=", &k, &v); +@@ -1086,21 +1087,22 @@ static int mount_append_graceful_options(Mount *m, const MountParameters *p, cha + + r = mount_option_supported(p->fstype, k ?: *o, v); + if (r < 0) +- log_unit_warning_errno(UNIT(m), r, "GracefulOptions=%s specified, but cannot determine availability, suppressing.", *o); ++ log_unit_warning_errno(UNIT(m), r, ++ "x-systemd.graceful-option=%s specified, but cannot determine availability, suppressing: %m", *o); + else if (r == 0) +- log_unit_info(UNIT(m), "GracefulOptions=%s specified, but option is not available, suppressing.", *o); ++ log_unit_info(UNIT(m), "x-systemd.graceful-option=%s specified, but option is not available, suppressing.", *o); + else { +- log_unit_debug(UNIT(m), "GracefulOptions=%s specified and supported, appending to mount option string.", *o); ++ log_unit_debug(UNIT(m), "x-systemd.graceful-option=%s specified and supported, appending to mount option string.", *o); + +- if (!strextend_with_separator(opts, ",", *o)) ++ if (!strextend_with_separator(&filtered, ",", *o)) + return -ENOMEM; + } + } + +- return 0; ++ return free_and_replace(*opts, filtered); + } + +-static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) { ++static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p, bool remount) { + int r; + + assert(m); +@@ -1134,10 +1136,19 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete + if (r < 0) + return r; + +- r = mount_append_graceful_options(m, p, &opts); ++ r = mount_apply_graceful_options(m, p, &opts); + if (r < 0) + return r; + ++ if (remount) { ++ if (isempty(opts)) { ++ opts = strdup("remount"); ++ if (!opts) ++ return -ENOMEM; ++ } else if (!strprepend(&opts, "remount,")) ++ return -ENOMEM; ++ } ++ + if (!isempty(opts)) { + r = exec_command_append(c, "-o", opts, NULL); + if (r < 0) +@@ -1229,9 +1240,9 @@ static void mount_enter_mounting(Mount *m) { + m->control_command_id = MOUNT_EXEC_MOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; + +- r = mount_set_mount_command(m, m->control_command, p); ++ r = mount_set_mount_command(m, m->control_command, p, /* remount = */ false); + if (r < 0) { +- log_unit_warning_errno(UNIT(m), r, "Failed to prepare mount command line: %m"); ++ log_unit_error_errno(UNIT(m), r, "Failed to prepare mount command line: %m"); + goto fail; + } + +@@ -1276,24 +1287,9 @@ static void mount_enter_remounting(Mount *m) { + m->control_command_id = MOUNT_EXEC_REMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; + +- const char *o; +- +- if (p->options) +- o = strjoina("remount,", p->options); +- else +- o = "remount"; +- +- r = exec_command_set(m->control_command, MOUNT_PATH, +- p->what, m->where, +- "-o", o, NULL); +- if (r >= 0 && m->sloppy_options) +- r = exec_command_append(m->control_command, "-s", NULL); +- if (r >= 0 && m->read_write_only) +- r = exec_command_append(m->control_command, "-w", NULL); +- if (r >= 0 && p->fstype) +- r = exec_command_append(m->control_command, "-t", p->fstype, NULL); ++ r = mount_set_mount_command(m, m->control_command, p, /* remount = */ true); + if (r < 0) { +- log_unit_warning_errno(UNIT(m), r, "Failed to prepare remount command line: %m"); ++ log_unit_error_errno(UNIT(m), r, "Failed to prepare remount command line: %m"); + goto fail; + } + +diff --git a/src/core/mount.h b/src/core/mount.h +index a8ae25e638..7fd643f6fa 100644 +--- a/src/core/mount.h ++++ b/src/core/mount.h +@@ -88,8 +88,6 @@ struct Mount { + sd_event_source *timer_event_source; + + unsigned n_retry_umount; +- +- char **graceful_options; + }; + + extern const UnitVTable mount_vtable; +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 9128c06555..bc58d43140 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -2311,9 +2311,6 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const + "ReadwriteOnly")) + return bus_append_parse_boolean(m, field, eq); + +- if (streq(field, "GracefulOptions")) +- return bus_append_strv(m, field, eq, /* separator= */ ",", 0); +- + return 0; + } + +diff --git a/test/units/TEST-87-AUX-UTILS-VM.mount.sh b/test/units/TEST-87-AUX-UTILS-VM.mount.sh +index c1bfbdc1a2..8ad7ae6fc5 100755 +--- a/test/units/TEST-87-AUX-UTILS-VM.mount.sh ++++ b/test/units/TEST-87-AUX-UTILS-VM.mount.sh +@@ -181,9 +181,9 @@ touch "$WORK_DIR/mnt/hello" + [[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]] + systemd-umount LABEL=owner-vfat + +-# Mkae sure that graceful mount options work ++# Make sure that graceful mount options work + GRACEFULTEST="/tmp/graceful/$RANDOM" +-systemd-mount --tmpfs -p GracefulOptions=idefinitelydontexist,nr_inodes=4711,idonexisteither "$GRACEFULTEST" ++systemd-mount --tmpfs --options="x-systemd.graceful-option=idefinitelydontexist,x-systemd.graceful-option=nr_inodes=4711,x-systemd.graceful-option=idonexisteither" "$GRACEFULTEST" + findmnt -n -o options "$GRACEFULTEST" + findmnt -n -o options "$GRACEFULTEST" | grep -q nr_inodes=4711 + umount "$GRACEFULTEST" +diff --git a/units/tmp.mount b/units/tmp.mount +index d347af56d3..373b131211 100644 +--- a/units/tmp.mount ++++ b/units/tmp.mount +@@ -22,8 +22,7 @@ After=swap.target + What=tmpfs + Where=/tmp + Type=tmpfs +-Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m +-GracefulOptions=usrquota ++Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m,x-systemd.graceful-option=usrquota + + # Make 'systemctl enable tmp.mount' work: + [Install] diff --git a/0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch b/0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch new file mode 100644 index 0000000..3c63ff3 --- /dev/null +++ b/0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch @@ -0,0 +1,48 @@ +From 61d7212e99466780278b2f75be35c5d19cc93087 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Wed, 12 Feb 2025 15:44:13 +0100 +Subject: [PATCH] mountpoint-util: assume fsopen() works in + mount_option_supported() + +Our baseline includes it now. + +(cherry picked from commit c2198d0c3fdbebd30b0653f2e3d148372c5fff31) + +Related: RHEL-143028 +--- + src/basic/mountpoint-util.c | 12 ++++-------- + 1 file changed, 4 insertions(+), 8 deletions(-) + +diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c +index e8471d5974..76f1a6d6b4 100644 +--- a/src/basic/mountpoint-util.c ++++ b/src/basic/mountpoint-util.c +@@ -803,14 +803,10 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu + assert(key); + + fd = fsopen(fstype, FSOPEN_CLOEXEC); +- if (fd < 0) { +- if (ERRNO_IS_NOT_SUPPORTED(errno)) +- return -EAGAIN; /* new mount API not available → don't know */ +- ++ if (fd < 0) + return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype); +- } + +- /* Various file systems have not been converted to the new mount API yet. For such file systems ++ /* Various file systems support fs context only in recent kernels (e.g. btrfs). For older kernels + * fsconfig() with FSCONFIG_SET_STRING/FSCONFIG_SET_FLAG never fail. Which sucks, because we want to + * use it for testing support, after all. Let's hence do a check if the file system got converted yet + * first. */ +@@ -819,9 +815,9 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu + * the new mount API yet. If it returns EINVAL the mount option doesn't exist, but the fstype + * is converted. */ + if (errno == EOPNOTSUPP) +- return -EAGAIN; /* FSCONFIG_SET_FD not supported on the fs, hence not converted to new mount API → don't know */ ++ return -EAGAIN; /* fs not converted to new mount API → don't know */ + if (errno != EINVAL) +- return log_debug_errno(errno, "Failed to check if file system has been converted to new mount API: %m"); ++ return log_debug_errno(errno, "Failed to check if file system '%s' has been converted to new mount API: %m", fstype); + + /* So FSCONFIG_SET_FD worked, but the option didn't exist (we got EINVAL), this means the fs + * is converted. Let's now ask the actual question we wonder about. */ diff --git a/0657-core-mount-log-only-once-about-fs-not-supporting-new.patch b/0657-core-mount-log-only-once-about-fs-not-supporting-new.patch new file mode 100644 index 0000000..e7f0e22 --- /dev/null +++ b/0657-core-mount-log-only-once-about-fs-not-supporting-new.patch @@ -0,0 +1,30 @@ +From 84f15472bcaa058a19bd63434cfb74eeb91d9cc6 Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Tue, 11 Feb 2025 19:44:59 +0100 +Subject: [PATCH] core/mount: log only once about fs not supporting new mount + API + +(cherry picked from commit f565e5a94a778979172705e8c3a8581ec9f15865) + +Related: RHEL-143028 +--- + src/core/mount.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/core/mount.c b/src/core/mount.c +index 037902c91c..530d567a96 100644 +--- a/src/core/mount.c ++++ b/src/core/mount.c +@@ -1086,6 +1086,12 @@ static int mount_apply_graceful_options(Mount *m, const MountParameters *p, char + return r; + + r = mount_option_supported(p->fstype, k ?: *o, v); ++ if (r == -EAGAIN) { ++ log_unit_warning_errno(UNIT(m), r, ++ "x-systemd.graceful-option= used but not supported by file system %s, suppressing all.", ++ p->fstype); ++ break; ++ } + if (r < 0) + log_unit_warning_errno(UNIT(m), r, + "x-systemd.graceful-option=%s specified, but cannot determine availability, suppressing: %m", *o); diff --git a/0658-user-runtime-dir-correct-quota-size-calculation.patch b/0658-user-runtime-dir-correct-quota-size-calculation.patch new file mode 100644 index 0000000..02fe4af --- /dev/null +++ b/0658-user-runtime-dir-correct-quota-size-calculation.patch @@ -0,0 +1,29 @@ +From 94ce567ebb8175392a2dd7416b15e6561d100cbc Mon Sep 17 00:00:00 2001 +From: Mike Yuan +Date: Mon, 3 Feb 2025 16:18:14 +0100 +Subject: [PATCH] user-runtime-dir: correct quota size calculation + +Follow-up for b1c95fb2e9d11fc190017dec3d64f468f9d378bc + +Fixes #36245 + +(cherry picked from commit 6790db81d6bf59c34ca89f901b34e9f81cbde1a5) + +Related: RHEL-143028 +--- + src/login/user-runtime-dir.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c +index 94117c95db..590b445d41 100644 +--- a/src/login/user-runtime-dir.c ++++ b/src/login/user-runtime-dir.c +@@ -262,7 +262,7 @@ static int apply_tmpfs_quota( + uint64_t v = + (scale == 0) ? 0 : + (scale == UINT32_MAX) ? UINT64_MAX : +- (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) / scale * UINT32_MAX); ++ (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) * scale / UINT32_MAX); + + v = MIN(v, limit); + v /= QIF_DQBLKSIZE; diff --git a/0659-test-display-quota-add-a-little-helper-binary-to-sho.patch b/0659-test-display-quota-add-a-little-helper-binary-to-sho.patch new file mode 100644 index 0000000..1a6ce59 --- /dev/null +++ b/0659-test-display-quota-add-a-little-helper-binary-to-sho.patch @@ -0,0 +1,135 @@ +From 73f66207da99d5f577a4be1cbcf34030e5b4a9b4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Fri, 28 Mar 2025 18:45:23 +0100 +Subject: [PATCH] test-display-quota: add a little helper binary to show quota + on tmpfs + +quota from quota project fails: +$ quota +quota: Cannot stat() mounted device tmpfs: No such file or directory +quota: Cannot stat() mounted device tmpfs: No such file or directory + +Having this helper helped me understand what is going on with the quotas when +the tests failed. I think it'd be useful to keep it around for now, even though +it is not actually connected in the tests. + +(cherry picked from commit cb6b1611622ee717d4d6e9251fa41652b8ba8e2f) + +Related: RHEL-143028 +--- + src/test/meson.build | 4 ++ + src/test/test-display-quota.c | 90 +++++++++++++++++++++++++++++++++++ + 2 files changed, 94 insertions(+) + create mode 100644 src/test/test-display-quota.c + +diff --git a/src/test/meson.build b/src/test/meson.build +index 5dd9e3b8bd..0f5de6a5d0 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -277,6 +277,10 @@ executables += [ + 'dependencies' : lib_openssl_or_gcrypt, + 'conditions' : ['HAVE_OPENSSL_OR_GCRYPT'], + }, ++ test_template + { ++ 'sources' : files('test-display-quota.c'), ++ 'type' : 'manual', ++ }, + test_template + { + 'sources' : files('test-dlopen-so.c'), + 'dependencies' : [ +diff --git a/src/test/test-display-quota.c b/src/test/test-display-quota.c +new file mode 100644 +index 0000000000..57e8352826 +--- /dev/null ++++ b/src/test/test-display-quota.c +@@ -0,0 +1,90 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include "bitfield.h" ++#include "fd-util.h" ++#include "log.h" ++#include "main-func.h" ++#include "missing_syscall.h" ++#include "quota-util.h" ++#include "strv.h" ++#include "userdb.h" ++#include "user-util.h" ++ ++static int show_quota(uid_t uid, const char *path) { ++ int r; ++ ++ _cleanup_close_ int fd = open(path, O_DIRECTORY|O_CLOEXEC); ++ if (fd < 0) ++ return log_error_errno(errno, "Failed to open '%s': %m", path); ++ ++ struct dqblk req; ++ r = quotactl_fd_with_fallback(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req); ++ if (r == -ESRCH) { ++ log_info_errno(r, "No quota set on %s for UID "UID_FMT": %m", path, uid); ++ return 0; ++ } ++ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) ++ return log_warning_errno(r, "No UID quota support on %s: %m", path); ++ if (ERRNO_IS_NEG_PRIVILEGE(r)) ++ return log_error_errno(r, "Lacking privileges to query UID quota on %s: %m", path); ++ if (r < 0) ++ return log_error_errno(r, "Failed to query disk quota on %s for UID "UID_FMT": %m", path, uid); ++ ++ printf("** Quota on %s for UID "UID_FMT" **\n" ++ "block hardlimit: %"PRIu64"\n" ++ "block softlimit: %"PRIu64"\n" ++ "blocks current: %"PRIu64"\n" ++ "inodes hardlimit: %"PRIu64"\n" ++ "inodes softlimit: %"PRIu64"\n" ++ "inodes current: %"PRIu64"\n" ++ "excess block time: %"PRIu64"\n" ++ "excess inode time: %"PRIu64"\n" ++ "validity mask: 0x%"PRIx32, ++ path, uid, ++ req.dqb_bhardlimit, ++ req.dqb_bsoftlimit, ++ req.dqb_curspace, ++ req.dqb_ihardlimit, ++ req.dqb_isoftlimit, ++ req.dqb_curinodes, ++ req.dqb_btime, ++ req.dqb_itime, ++ req.dqb_valid); ++ ++ const char* fields[] = {"BLIMITS", "SPACE", "INODES", "BTIME", "ITIME"}; ++ bool first = true; ++ for (size_t i = 0; i < ELEMENTSOF(fields); i++) ++ if (BIT_SET(req.dqb_valid, i)) { ++ printf("%c%s", first ? ' ' : '|', fields[i]); ++ first = false; ++ } ++ printf("%s\n", first ? "(none)" : ""); ++ ++ return 0; ++} ++ ++static int run(int argc, char **argv) { ++ int r; ++ ++ if (argc < 2) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "This program requires at least one argument\n" ++ "syntax: test-display-quota USER PATH…"); ++ ++ const char *user = argv[1]; ++ _cleanup_(user_record_unrefp) UserRecord *ur = NULL; ++ r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur); ++ if (r < 0) ++ return log_error_errno(r, "Failed to resolve user '%s': %m", user); ++ ++ if (!uid_is_valid(ur->uid)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID.", ur->user_name); ++ ++ r = 0; ++ STRV_FOREACH(path, strv_skip(argv, 2)) ++ RET_GATHER(r, show_quota(ur->uid, *path)); ++ ++ return r; ++} ++ ++DEFINE_MAIN_FUNCTION(run); diff --git a/0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch b/0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch new file mode 100644 index 0000000..ff12019 --- /dev/null +++ b/0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch @@ -0,0 +1,61 @@ +From e94a7c655a3493ddcd7bb68ac1b9112f8976e57c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Sat, 29 Mar 2025 09:24:34 +0100 +Subject: [PATCH] TEST-46-HOMED: check for support on /dev/shm and /tmp + separately + +The test fails in CI. My guess was this is because the enablement of quota on +/tmp and /dev/shm is independent. The former fs is mounted by systemd in the +host, while the latter is mounted in the initrd, so we can end up with quota +support on one but not the other, which is the situation I had on my laptop. +This wasn't actually the source of the problems in CI, but it's a reasonable +change to make anyway. + +While at it, test both mountpoints separately. + +(cherry picked from commit cb86668f2cf7bc94ed28147fab7293b4795e6ab4) + +Related: RHEL-143028 +--- + test/units/TEST-46-HOMED.sh | 27 +++++++++++++-------------- + 1 file changed, 13 insertions(+), 14 deletions(-) + +diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh +index 3663e53908..4af79e971c 100755 +--- a/test/units/TEST-46-HOMED.sh ++++ b/test/units/TEST-46-HOMED.sh +@@ -652,21 +652,20 @@ getent passwd aliastest@myrealm + getent passwd aliastest2@myrealm + getent passwd aliastest3@myrealm + +-if findmnt -n -o options /tmp | grep -q usrquota ; then +- +- NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K -P +- +- run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 +- (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile2 bs=1024 count=30) +- run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 /dev/shm/quotatestfile2 +- run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/urandom of=/dev/shm/quotatestfile1 bs=1024 count=30 +- run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm /dev/shm/quotatestfile1 +- +- systemctl stop user@"$(id -u tmpfsquota)".service ++NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K --tmp-limit=50K -P ++for p in /dev/shm /tmp; do ++ if findmnt -n -o options "$p" | grep -q usrquota; then ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 ++ (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile2" bs=1024 count=30) ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" "$p/quotatestfile2" ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 ++ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" ++ fi ++done + +- wait_for_state tmpfsquota inactive +- homectl remove tmpfsquota +-fi ++systemctl stop user@"$(id -u tmpfsquota)".service ++wait_for_state tmpfsquota inactive ++homectl remove tmpfsquota + + systemd-analyze log-level info + diff --git a/0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch b/0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch new file mode 100644 index 0000000..4f047f0 --- /dev/null +++ b/0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch @@ -0,0 +1,46 @@ +From 05fcb907f1a80d1d1b6a2899a1499faf47aed5d3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 31 Mar 2025 22:50:38 +0200 +Subject: [PATCH] TEST-46-HOMED: conditionally skip usrquota tests + +The tests were failing, because the quota was not enforced. +It seems that we simply don't have privileges to set or display the quota. +The test is running priviled, so this is probably some SELinux: +TEST-46-HOMED.sh[117]: + /usr/lib/systemd/tests/unit-tests/manual/test-display-quota tmpfsquota /dev/shm /tmp +TEST-46-HOMED.sh[1103]: Lacking privileges to query UID quota on /dev/shm: Operation not permitted +TEST-46-HOMED.sh[1103]: Lacking privileges to query UID quota on /tmp: Operation not permitted + +If we cannot display the quota, ignore the test results. +In a local run under mkosi, quota is shown and the tests pass. So this is something +about how the testing-farm:fedora-rawhide-x86_64 is configured. + +(cherry picked from commit f77a8edfefd47e481fab78e7dd75d5d088d4e5e3) + +Related: RHEL-143028 +--- + test/units/TEST-46-HOMED.sh | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh +index 4af79e971c..998a52c76a 100755 +--- a/test/units/TEST-46-HOMED.sh ++++ b/test/units/TEST-46-HOMED.sh +@@ -655,11 +655,18 @@ getent passwd aliastest3@myrealm + NEWPASSWORD=quux homectl create tmpfsquota --storage=subvolume --dev-shm-limit=50K --tmp-limit=50K -P + for p in /dev/shm /tmp; do + if findmnt -n -o options "$p" | grep -q usrquota; then ++ # Check if we can display the quotas. If we cannot, than it's likely ++ # that PID1 was also not able to set the limits and we should not fail ++ # in the tests below. ++ /usr/lib/systemd/tests/unit-tests/manual/test-display-quota tmpfsquota "$p" || set +e ++ + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 + (! run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile2" bs=1024 count=30) + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" "$p/quotatestfile2" + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota dd if=/dev/zero of="$p/quotatestfile1" bs=1024 count=30 + run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u tmpfsquota rm "$p/quotatestfile1" ++ ++ set -e + fi + done + diff --git a/systemd.spec b/systemd.spec index eeafe96..ad0e811 100644 --- a/systemd.spec +++ b/systemd.spec @@ -48,7 +48,7 @@ Url: https://systemd.io # Allow users to specify the version and release when building the rpm by # setting the %%version_override and %%release_override macros. Version: %{?version_override}%{!?version_override:257} -Release: 23%{?dist} +Release: 24%{?dist} %global stable %(c="%version"; [ "$c" = "${c#*.*}" ]; echo $?) @@ -719,6 +719,58 @@ Patch0606: 0606-TEST-35-LOGIN-test-coldplug-without-fdstore-on-kerne.patch Patch0607: 0607-logind-fix-potential-fd-leak-in-deliver_session_lead.patch Patch0608: 0608-Revert-coredump-lock-down-EnterNamespace-mount-even-.patch Patch0609: 0609-coredump-add-compat-support-for-SYSTEMD_COREDUMP_ALL.patch +Patch0610: 0610-ci-re-enable-bpf-framework-option-for-build-and-unit.patch +Patch0611: 0611-ci-add-bpftool-workaround-to-codeql-job-too.patch +Patch0612: 0612-ci-fix-workaround-about-bpftool-for-codeql.patch +Patch0613: 0613-ci-add-bpftool-workaround-to-coverity-too.patch +Patch0614: 0614-Revert-man-mention-RHEL-documentation-in-systemctl-s.patch +Patch0615: 0615-path-util-add-flavour-of-path_startswith-that-leaves.patch +Patch0616: 0616-cgroup-port-some-code-over-to-path_startswith_full.patch +Patch0617: 0617-path-util-invert-PATH_STARTSWITH_ACCEPT_DOT_DOT-flag.patch +Patch0618: 0618-sd-json-fix-off-by-one-issue-when-updating-parent-fo.patch +Patch0619: 0619-core-cgroup-avoid-one-unnecessary-strjoina.patch +Patch0620: 0620-core-validate-input-cgroup-path-more-prudently.patch +Patch0621: 0621-nspawn-apply-BindUser-Ephemeral-from-settings-file-o.patch +Patch0622: 0622-nspawn-normalize-pivot_root-paths.patch +Patch0623: 0623-udev-check-for-invalid-chars-in-various-fields-recei.patch +Patch0624: 0624-udev-ensure-there-is-space-for-trailing-NUL-before-c.patch +Patch0625: 0625-udev-ensure-tag-parsing-stays-within-bounds.patch +Patch0626: 0626-udev-fix-review-mixup.patch +Patch0627: 0627-udev-scsi-id-check-for-invalid-chars-in-various-fiel.patch +Patch0628: 0628-udev-builtin-net-id-print-cescaped-bad-attributes.patch +Patch0629: 0629-core-only-activate-transaction-that-contain-useful-j.patch +Patch0630: 0630-journald-extend-STDOUT_STREAMS_MAX-to-64k.patch +Patch0631: 0631-string-util-introduce-strprepend-helper.patch +Patch0632: 0632-missing-add-quotactl_fd-wrapper.patch +Patch0633: 0633-homed-always-use-quotactl_fd-if-its-available.patch +Patch0634: 0634-pid1-add-GracefulOptions-setting-to-.mount-units.patch +Patch0635: 0635-pid1-enable-usrquota-support-on-dev-shm.patch +Patch0636: 0636-units-enable-usrquota-support-on-tmp.patch +Patch0637: 0637-nspawn-enable-usrquota-support-on-tmp-and-dev-shm.patch +Patch0638: 0638-nspawn-downgrade-log-message-about-usrquota-to-debug.patch +Patch0639: 0639-mount-setup-drop-outdated-comment.patch +Patch0640: 0640-mount-setup-tune-down-log-level-if-usrquota-is-not-s.patch +Patch0641: 0641-devnum-util-add-macros-to-safely-convert-dev_t-to-po.patch +Patch0642: 0642-user-record-add-fields-for-setting-limits-on-tmp-and.patch +Patch0643: 0643-user-runtime-dir-some-smaller-modernizations-refacto.patch +Patch0644: 0644-user-runtime-dir-enforce-tmp-and-dev-shm-quota.patch +Patch0645: 0645-homectl-add-support-for-configuring-tmpfs-limits.patch +Patch0646: 0646-test-add-test-case-for-tmpfs-quota-logic-PAMName-ask.patch +Patch0647: 0647-update-TODO.patch +Patch0648: 0648-core-dbus-service-fix-alignment.patch +Patch0649: 0649-core-mount-filter-out-fail-option-as-well.patch +Patch0650: 0650-core-mount-check-parameters_fragment-first-in-mount_.patch +Patch0651: 0651-core-mount-report-accurate-can_start-and-can_reload.patch +Patch0652: 0652-core-mount-trivial-coding-style-cleanups.patch +Patch0653: 0653-core-dbus-mount-add-missing-ReloadResult-and-CleanRe.patch +Patch0654: 0654-bus-unit-util-add-missing-assertions.patch +Patch0655: 0655-core-mount-rework-GracefulOptions-to-be-just-x-syste.patch +Patch0656: 0656-mountpoint-util-assume-fsopen-works-in-mount_option_.patch +Patch0657: 0657-core-mount-log-only-once-about-fs-not-supporting-new.patch +Patch0658: 0658-user-runtime-dir-correct-quota-size-calculation.patch +Patch0659: 0659-test-display-quota-add-a-little-helper-binary-to-sho.patch +Patch0660: 0660-TEST-46-HOMED-check-for-support-on-dev-shm-and-tmp-s.patch +Patch0661: 0661-TEST-46-HOMED-conditionally-skip-usrquota-tests.patch # Downstream-only patches (9000–9999) %endif @@ -1670,6 +1722,60 @@ rm -f .file-list-* rm -f %{name}.lang %changelog +* Thu Apr 16 2026 systemd maintenance team - 257-24 +- ci: re-enable bpf-framework option for build and unit test jobs (RHEL-155454) +- ci: add bpftool workaround to codeql job too (RHEL-155454) +- ci: fix workaround about bpftool for codeql (RHEL-155454) +- ci: add bpftool workaround to coverity too (RHEL-155454) +- Revert "man: mention RHEL documentation in systemctl's man page" (RHEL-155805) +- path-util: add flavour of path_startswith() that leaves a leading slash in place (RHEL-155396) +- cgroup: port some code over to path_startswith_full() (RHEL-155396) +- path-util: invert PATH_STARTSWITH_ACCEPT_DOT_DOT flag (RHEL-155396) +- sd-json: fix off-by-one issue when updating parent for array elements (RHEL-155396) +- core/cgroup: avoid one unnecessary strjoina() (RHEL-155396) +- core: validate input cgroup path more prudently (RHEL-155396) +- nspawn: apply BindUser/Ephemeral from settings file only if trusted (RHEL-158303) +- nspawn: normalize pivot_root paths (RHEL-158303) +- udev: check for invalid chars in various fields received from the kernel (RHEL-158354) +- udev: ensure there is space for trailing NUL before calling sprintf (RHEL-158354) +- udev: ensure tag parsing stays within bounds (RHEL-158354) +- udev: fix review mixup (RHEL-158354) +- udev/scsi-id: check for invalid chars in various fields received from the kernel (RHEL-158354) +- udev-builtin-net-id: print cescaped bad attributes (RHEL-158354) +- core: only activate transaction that contain useful jobs (RHEL-143728) +- journald: extend STDOUT_STREAMS_MAX to 64k (RHEL-168098) +- string-util: introduce strprepend() helper (RHEL-143028) +- missing: add quotactl_fd() wrapper (RHEL-143028) +- homed: always use quotactl_fd() if its available (RHEL-143028) +- pid1: add GracefulOptions= setting to .mount units (RHEL-143028) +- pid1: enable usrquota support on /dev/shm (RHEL-143028) +- units: enable usrquota support on /tmp/ (RHEL-143028) +- nspawn: enable usrquota support on /tmp/ and /dev/shm/ (RHEL-143028) +- nspawn: downgrade log message about usrquota to debug (RHEL-143028) +- mount-setup: drop outdated comment (RHEL-143028) +- mount-setup: tune down log level if usrquota is not supported, apply usrquota when smack is in use too (RHEL-143028) +- devnum-util: add macros to safely convert dev_t to pointers and back (RHEL-143028) +- user-record: add fields for setting limits on /tmp/ and /dev/shm/ (RHEL-143028) +- user-runtime-dir: some smaller modernizations/refactorings (RHEL-143028) +- user-runtime-dir: enforce /tmp/ and /dev/shm/ quota (RHEL-143028) +- homectl: add support for configuring tmpfs limits (RHEL-143028) +- test: add test case for tmpfs quota logic + PAMName= ask-password logic (RHEL-143028) +- update TODO (RHEL-143028) +- core/dbus-service: fix alignment (RHEL-143028) +- core/mount: filter out "fail" option as well (RHEL-143028) +- core/mount: check parameters_fragment first in mount_enter_(re)mounting() (RHEL-143028) +- core/mount: report accurate can_start and can_reload (RHEL-143028) +- core/mount: trivial coding style cleanups (RHEL-143028) +- core/dbus-mount: add missing ReloadResult and CleanResult properties (RHEL-143028) +- bus-unit-util: add missing assertions (RHEL-143028) +- core/mount: rework GracefulOptions= to be just x-systemd.graceful-option= (RHEL-143028) +- mountpoint-util: assume fsopen() works in mount_option_supported() (RHEL-143028) +- core/mount: log only once about fs not supporting new mount API (RHEL-143028) +- user-runtime-dir: correct quota size calculation (RHEL-143028) +- test-display-quota: add a little helper binary to show quota on tmpfs (RHEL-143028) +- TEST-46-HOMED: check for support on /dev/shm and /tmp separately (RHEL-143028) +- TEST-46-HOMED: conditionally skip usrquota tests (RHEL-143028) + * Tue Feb 17 2026 systemd maintenance team - 257-23 - test-journal-dump: dump the headers of journal files (RHEL-106795) - journal: store counts, not byte sizes, in table size constants (RHEL-106795)