proc_sys_make_inode() checks under sysctl_lock that @head isn't being
unregistered, lets go of the lock, and then reads the mode of @table
and calls ->set_ownership() with @head. That's fine as long as the
caller holds @head in use, which keeps unregistering from getting past
that check. The next patch has a caller that only pins the memory of
@head. For that one the table, and whatever the owner of the header
keeps behind it, is only there for as long as nobody unregisters the
header, which happens under sysctl_lock. So read the mode and set the
ownership before letting go of the lock. The ->set_ownership()
callbacks only look up ids in a user namespace.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 fs/proc/proc_sysctl.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 2150cf842919..a1242247f492 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -500,6 +500,7 @@ static struct inode *proc_sys_make_inode(struct super_block 
*sb,
        struct ctl_table_root *root = head->root;
        struct inode *inode;
        struct proc_inode *ei;
+       umode_t mode;
 
        inode = new_inode(sb);
        if (!inode)
@@ -509,12 +510,22 @@ static struct inode *proc_sys_make_inode(struct 
super_block *sb,
 
        ei = PROC_I(inode);
 
+       inode->i_uid = GLOBAL_ROOT_UID;
+       inode->i_gid = GLOBAL_ROOT_GID;
        spin_lock(&sysctl_lock);
        if (unlikely(head->unregistering)) {
                spin_unlock(&sysctl_lock);
                iput(inode);
                return ERR_PTR(-ENOENT);
        }
+       /*
+        * The table, and whatever the owner of the header keeps behind it,
+        * is only there for as long as nobody unregisters the header, which
+        * happens under sysctl_lock. So look at them before letting go of it.
+        */
+       mode = table->mode;
+       if (root->set_ownership)
+               root->set_ownership(head, &inode->i_uid, &inode->i_gid);
        ei->sysctl = head;
        ei->sysctl_entry = table;
        hlist_add_head_rcu(&ei->sibling_inodes, &head->inodes);
@@ -522,8 +533,8 @@ static struct inode *proc_sys_make_inode(struct super_block 
*sb,
        spin_unlock(&sysctl_lock);
 
        simple_inode_init_ts(inode);
-       inode->i_mode = table->mode;
-       if (!S_ISDIR(table->mode)) {
+       inode->i_mode = mode;
+       if (!S_ISDIR(mode)) {
                inode->i_mode |= S_IFREG;
                inode->i_op = &proc_sys_inode_operations;
                inode->i_fop = &proc_sys_file_operations;
@@ -535,11 +546,6 @@ static struct inode *proc_sys_make_inode(struct 
super_block *sb,
                        make_empty_dir_inode(inode);
        }
 
-       inode->i_uid = GLOBAL_ROOT_UID;
-       inode->i_gid = GLOBAL_ROOT_GID;
-       if (root->set_ownership)
-               root->set_ownership(head, &inode->i_uid, &inode->i_gid);
-
        return inode;
 }
 

-- 
2.53.0


Reply via email to