Re: d_path() results in presence of detached mounts

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Apr 07, 2025 at 06:00:07PM +0200, Jan Kara wrote:
> Hello!
> 
> Recently I've got a question from a user about the following:
> 
> # unshare --mount swapon /dev/sda3
> # cat /proc/swaps
> Filename                                Type            Size            Used            Priority
> /sda3                                   partition       2098152         0               -2
> 
> Now everything works as expected here AFAICS. When namespace gets created
> /dev mount is cloned into it. When swapon exits, the namespace is
> destroyed and /dev mount clone is detached (no parent, namespace NULL).
> Hence when d_path() crawls the path it stops at the mountpoint root and
> exits. There's not much we can do about this but when discussing the
> situation internally, Michal proposed that d_path() could append something
> like "(detached)" to the path string - similarly to "(deleted)". That could
> somewhat reduce the confusion about such paths? What do people think about
> this?

You can get into this situation in plenty of other ways. For example by
using detached mount via open_tree(OPEN_TREE_CLONE) as layers in
overlayfs. Or simply:

        int fd;
        char dpath[PATH_MAX];

        fd = open_tree(-EBADF, "/var/lib/fwupd", OPEN_TREE_CLONE);
        dup2(fd, 500);
        close(fd);
        readlink("/proc/self/fd/500", dpath, sizeof(dpath));
        printf("dpath = %s\n", dpath);

Showing "(detached)" will be ambiguous just like "(deleted)" is. If that
doesn't matter and it's clearly documented then it's probably fine. But
note that this will also affect /proc/<pid>/fd/ as can be seen from the
above example.

int main(int argc, char *argv[])
{
        int fd;
        char dpath[PATH_MAX];
        char *dirs[] = { "/ONE", "TWO", "THREE", "FOUR", NULL };

        for (char **dir = dirs; *dir; dir++) {
                mkdir(*dir, 0777);
                chdir(*dir);
        }

        chdir("/");
        fd = open_tree(-EBADF, "/ONE/TWO/THREE/FOUR", OPEN_TREE_CLONE);
        if (fd < 0) {
                perror("open_tree");
                _exit(1);
        }

        rmdir("/ONE/TWO/THREE/FOUR");

        if (dup2(fd, 500) < 0) {
                perror("dup2");
                _exit(1);
        }
        close(fd);

        readlink("/proc/self/fd/500", dpath, sizeof(dpath));
        if (strcmp("/ (detached) (deleted)", dpath) != 0) {
                printf("wrong dpath = %s\n", dpath);
                _exit(1);
        }
        printf("dpath = %s\n", dpath);
        _exit(0);
}

user1@localhost:~/data/scripts$ sudo ./open_tree_detached
dpath = / (detached) (deleted)

Seems good to me.

Should be as simple as:

diff --git a/fs/d_path.c b/fs/d_path.c
index 5f4da5c8d5db..58874a107634 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -246,6 +246,12 @@ static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
        } while (read_seqcount_retry(&fs->seq, seq));
 }

+static inline bool is_detached(struct mount *mnt)
+{
+       struct mnt_namespace *mnt_ns = READ_ONCE(mnt->mnt_ns);
+       return unlikely(!mnt_ns || is_anon_ns(mnt_ns));
+}
+
 /**
  * d_path - return the path of a dentry
  * @path: path to report
@@ -284,7 +290,11 @@ char *d_path(const struct path *path, char *buf, int buflen)

        rcu_read_lock();
        get_fs_root_rcu(current->fs, &root);
-       if (unlikely(d_unlinked(path->dentry)))
+       if (unlikely(is_detached(real_mount(path->mnt))) && d_unlinked(path->dentry))
+               prepend(&b, " (detached) (deleted)", 22);
+       else if (unlikely(is_detached(real_mount(path->mnt))))
+               prepend(&b, " (detached)", 11);
+       else if (unlikely(d_unlinked(path->dentry)))
                prepend(&b, " (deleted)", 11);
        else
                prepend_char(&b, 0);

This is racy. Iow, after the first check it's still possible that it's
both detached and deleted. But I don't think that matters. (deleted)
must stay at the end because there's userspace out there that expects
(deleted) to be at the end.





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux