From 1b55621dabf741dd963f59ac706ea62cd6e3e95c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Jan 2021 16:47:07 +0100 Subject: [PATCH] rm-rf: fstatat() might fail if containing dir has limited access mode, patch that too --- src/basic/rm-rf.c | 84 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index 4c39ce8290..2f2ebc39e9 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -23,23 +23,14 @@ static bool is_physical_fs(const struct statfs *sfs) { return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs); } -static int unlinkat_harder( +static int patch_dirfd_mode( int dfd, - const char *filename, - int unlink_flags, - RemoveFlags remove_flags) { + mode_t *ret_old_mode) { struct stat st; - int r; - /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the - * directory. This is useful if we run unprivileged and have some files where the w bit is - * missing. */ - - if (unlinkat(dfd, filename, unlink_flags) >= 0) - return 0; - if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) - return -errno; + assert(dfd >= 0); + assert(ret_old_mode); if (fstat(dfd, &st) < 0) return -errno; @@ -53,10 +44,68 @@ static int unlinkat_harder( if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0) return -errno; + *ret_old_mode = st.st_mode; + return 0; +} + +static int unlinkat_harder( + int dfd, + const char *filename, + int unlink_flags, + RemoveFlags remove_flags) { + + mode_t old_mode; + int r; + + /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the + * directory. This is useful if we run unprivileged and have some files where the w bit is + * missing. */ + + if (unlinkat(dfd, filename, unlink_flags) >= 0) + return 0; + if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) + return -errno; + + r = patch_dirfd_mode(dfd, &old_mode); + if (r < 0) + return r; + if (unlinkat(dfd, filename, unlink_flags) < 0) { r = -errno; /* Try to restore the original access mode if this didn't work */ - (void) fchmod(dfd, st.st_mode & 07777); + (void) fchmod(dfd, old_mode); + return r; + } + + /* If this worked, we won't reset the old mode, since we'll need it for other entries too, and we + * should destroy the whole thing */ + return 0; +} + +static int fstatat_harder( + int dfd, + const char *filename, + struct stat *ret, + int fstatat_flags, + RemoveFlags remove_flags) { + + mode_t old_mode; + int r; + + /* Like unlink_harder() but does the same for fstatat() */ + + if (fstatat(dfd, filename, ret, fstatat_flags) >= 0) + return 0; + if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) + return -errno; + + r = patch_dirfd_mode(dfd, &old_mode); + if (r < 0) + return r; + + if (fstatat(dfd, filename, ret, fstatat_flags) < 0) { + r = -errno; + (void) fchmod(dfd, old_mode); return r; } @@ -112,9 +161,10 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { if (de->d_type == DT_UNKNOWN || (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; + r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags); + if (r < 0) { + if (ret == 0 && r != -ENOENT) + ret = r; continue; }