When truncating files the vfs will verify that the caller is privileged
over the inode. Since the do_truncate() helper is only used in a few places
in the vfs code extend it to handle idmapped mounts instead of adding a new
helper.  If the inode is accessed through an idmapped mount it is mapped
according to the mount's user namespace. Afterwards the permissions checks
are identical to non-idmapped mounts. If the initial user namespace is
passed all mapping operations are a nop so non-idmapped mounts will not see
a change in behavior and will also not see any performance impact.

Cc: Christoph Hellwig <h...@lst.de>
Cc: David Howells <dhowe...@redhat.com>
Cc: Al Viro <v...@zeniv.linux.org.uk>
Cc: linux-fsde...@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brau...@ubuntu.com>
---
/* v2 */
unchanged
---
 fs/coredump.c      | 12 +++++++++---
 fs/inode.c         | 13 +++++++++----
 fs/namei.c         |  6 +++---
 fs/open.c          | 21 +++++++++++++--------
 include/linux/fs.h |  4 ++--
 5 files changed, 36 insertions(+), 20 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index 0cd9056d79cc..25beac7230ff 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -703,6 +703,7 @@ void do_coredump(const kernel_siginfo_t *siginfo)
                        goto close_fail;
                }
        } else {
+               struct user_namespace *user_ns;
                struct inode *inode;
                int open_flags = O_CREAT | O_RDWR | O_NOFOLLOW |
                                 O_LARGEFILE | O_EXCL;
@@ -786,7 +787,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
                        goto close_fail;
                if (!(cprm.file->f_mode & FMODE_CAN_WRITE))
                        goto close_fail;
-               if (do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file))
+               user_ns = mnt_user_ns(cprm.file->f_path.mnt);
+               if (do_truncate(user_ns, cprm.file->f_path.dentry, 0, 0, 
cprm.file))
                        goto close_fail;
        }
 
@@ -931,8 +933,12 @@ void dump_truncate(struct coredump_params *cprm)
 
        if (file->f_op->llseek && file->f_op->llseek != no_llseek) {
                offset = file->f_op->llseek(file, 0, SEEK_CUR);
-               if (i_size_read(file->f_mapping->host) < offset)
-                       do_truncate(file->f_path.dentry, offset, 0, file);
+               if (i_size_read(file->f_mapping->host) < offset) {
+                       struct user_namespace *user_ns;
+
+                       user_ns = mnt_user_ns(file->f_path.mnt);
+                       do_truncate(user_ns, file->f_path.dentry, offset, 0, 
file);
+               }
        }
 }
 EXPORT_SYMBOL(dump_truncate);
diff --git a/fs/inode.c b/fs/inode.c
index 75c64f003c45..0ccdd673636d 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1904,7 +1904,8 @@ int dentry_needs_remove_privs(struct dentry *dentry)
        return mask;
 }
 
-static int __remove_privs(struct dentry *dentry, int kill)
+static int __remove_privs(struct user_namespace *user_ns, struct dentry 
*dentry,
+                         int kill)
 {
        struct iattr newattrs;
 
@@ -1913,7 +1914,7 @@ static int __remove_privs(struct dentry *dentry, int kill)
         * Note we call this on write, so notify_change will not
         * encounter any conflicting delegations:
         */
-       return notify_change(&init_user_ns, dentry, &newattrs, NULL);
+       return notify_change(user_ns, dentry, &newattrs, NULL);
 }
 
 /*
@@ -1939,8 +1940,12 @@ int file_remove_privs(struct file *file)
        kill = dentry_needs_remove_privs(dentry);
        if (kill < 0)
                return kill;
-       if (kill)
-               error = __remove_privs(dentry, kill);
+       if (kill) {
+               struct user_namespace *user_ns;
+
+               user_ns = mnt_user_ns(file->f_path.mnt);
+               error = __remove_privs(user_ns, dentry, kill);
+       }
        if (!error)
                inode_has_no_xattr(inode);
 
diff --git a/fs/namei.c b/fs/namei.c
index b91bf923d22c..5601b6680d4c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2940,9 +2940,9 @@ static int handle_truncate(struct file *filp)
        if (!error)
                error = security_path_truncate(path);
        if (!error) {
-               error = do_truncate(path->dentry, 0,
-                                   ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
-                                   filp);
+               error = do_truncate(mnt_user_ns(filp->f_path.mnt),
+                                   path->dentry, 0,
+                                   ATTR_MTIME | ATTR_CTIME | ATTR_OPEN, filp);
        }
        put_write_access(inode);
        return error;
diff --git a/fs/open.c b/fs/open.c
index 2dc94689a7dc..137dcc52d2f8 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -35,8 +35,8 @@
 
 #include "internal.h"
 
-int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
-       struct file *filp)
+int do_truncate(struct user_namespace *user_ns, struct dentry *dentry,
+               loff_t length, unsigned int time_attrs, struct file *filp)
 {
        int ret;
        struct iattr newattrs;
@@ -61,13 +61,14 @@ int do_truncate(struct dentry *dentry, loff_t length, 
unsigned int time_attrs,
 
        inode_lock(dentry->d_inode);
        /* Note any delegations or leases have already been broken: */
-       ret = notify_change(&init_user_ns, dentry, &newattrs, NULL);
+       ret = notify_change(user_ns, dentry, &newattrs, NULL);
        inode_unlock(dentry->d_inode);
        return ret;
 }
 
 long vfs_truncate(const struct path *path, loff_t length)
 {
+       struct user_namespace *user_ns;
        struct inode *inode;
        long error;
 
@@ -83,7 +84,8 @@ long vfs_truncate(const struct path *path, loff_t length)
        if (error)
                goto out;
 
-       error = inode_permission(&init_user_ns, inode, MAY_WRITE);
+       user_ns = mnt_user_ns(path->mnt);
+       error = inode_permission(user_ns, inode, MAY_WRITE);
        if (error)
                goto mnt_drop_write_and_out;
 
@@ -107,7 +109,7 @@ long vfs_truncate(const struct path *path, loff_t length)
        if (!error)
                error = security_path_truncate(path);
        if (!error)
-               error = do_truncate(path->dentry, length, 0, NULL);
+               error = do_truncate(user_ns, path->dentry, length, 0, NULL);
 
 put_write_and_out:
        put_write_access(inode);
@@ -186,13 +188,16 @@ long do_sys_ftruncate(unsigned int fd, loff_t length, int 
small)
        /* Check IS_APPEND on real upper inode */
        if (IS_APPEND(file_inode(f.file)))
                goto out_putf;
-
        sb_start_write(inode->i_sb);
        error = locks_verify_truncate(inode, f.file, length);
        if (!error)
                error = security_path_truncate(&f.file->f_path);
-       if (!error)
-               error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, 
f.file);
+       if (!error) {
+               struct user_namespace *user_ns;
+
+               user_ns = mnt_user_ns(f.file->f_path.mnt);
+               error = do_truncate(user_ns, dentry, length, ATTR_MTIME | 
ATTR_CTIME, f.file);
+       }
        sb_end_write(inode->i_sb);
 out_putf:
        fdput(f);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f29909139838..1f2ec4c3c70b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2565,8 +2565,8 @@ struct filename {
 static_assert(offsetof(struct filename, iname) % sizeof(long) == 0);
 
 extern long vfs_truncate(const struct path *, loff_t);
-extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs,
-                      struct file *filp);
+extern int do_truncate(struct user_namespace *, struct dentry *, loff_t start,
+                      unsigned int time_attrs, struct file *filp);
 extern int vfs_fallocate(struct file *file, int mode, loff_t offset,
                        loff_t len);
 extern long do_sys_open(int dfd, const char __user *filename, int flags,
-- 
2.29.2

--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

Reply via email to