]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
rm-rf: fail closed when the root check fails
authorLuca Boccassi <luca.boccassi@gmail.com>
Fri, 10 Jul 2026 17:22:34 +0000 (18:22 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 15 Jul 2026 20:13:24 +0000 (21:13 +0100)
path_is_root_at() returns a negative errno when it cannot determine whether
a target is the root file system. rm_rf_at() treats those errors as a
negative answer and continued with destructive operations.

Propagate root-check errors before modifying the target, while preserving
REMOVE_MISSING_OK for an absent path.

Follow-up for c0228b4fa3e4e633afa5eb8417e6cd3e311cd250

src/shared/rm-rf.c
src/test/test-rm-rf.c

index 573d6aeb045574d120d55ac5b791b3813822b498..56a56ed9ab5ec4a4c545e5743429cc51a38cf273 100644 (file)
@@ -443,7 +443,20 @@ int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags) {
 
         /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
          * really seriously broken system. */
-        if (path_is_root_at(dir_fd, path) > 0)
+        r = path_is_root_at(dir_fd, path);
+        if (r == -ENOENT) {
+                struct stat st;
+
+                if (fstatat(dir_fd, path, &st, AT_SYMLINK_NOFOLLOW) < 0)
+                        r = -errno;
+                else
+                        r = 0; /* The entry exists, but is most likely a dangling symlink. */
+        }
+        if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine whether '%s' is the root file system: %m", path);
+        if (r > 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EPERM),
                                        "Attempted to remove entire root file system, and we can't allow that.");
 
index d029f608e2acfacd2962e3f9cf6fa38123d45e93..3a59be7d97bf5974c7d74f8764755ae23c093a0e 100644 (file)
@@ -119,4 +119,20 @@ TEST(rm_rf_chmod) {
         test_rm_rf_chmod_inner();
 }
 
+TEST(rm_rf_dangling_symlink) {
+        _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+        const char *link;
+
+        ASSERT_OK(mkdtemp_malloc("/tmp/test-rm-rf-dangling.XXXXXXX", &d));
+        link = strjoina(d, "/link");
+
+        ASSERT_OK_ERRNO(symlink("missing", link));
+        ASSERT_OK(rm_rf(link, REMOVE_ROOT|REMOVE_PHYSICAL));
+        ASSERT_ERROR_ERRNO(lstat(link, &(struct stat) {}), ENOENT);
+
+        ASSERT_OK_ERRNO(symlink("missing", link));
+        ASSERT_OK(rm_rf(link, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK));
+        ASSERT_ERROR_ERRNO(lstat(link, &(struct stat) {}), ENOENT);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);