]> Git Repo - linux.git/commitdiff
fs: don't block write during exec on pre-content watched files
authorAmir Goldstein <[email protected]>
Thu, 28 Nov 2024 14:25:32 +0000 (15:25 +0100)
committerJan Kara <[email protected]>
Wed, 11 Dec 2024 16:45:18 +0000 (17:45 +0100)
Commit 2a010c412853 ("fs: don't block i_writecount during exec") removed
the legacy behavior of getting ETXTBSY on attempt to open and executable
file for write while it is being executed.

This commit was reverted because an application that depends on this
legacy behavior was broken by the change.

We need to allow HSM writing into executable files while executed to
fill their content on-the-fly.

To that end, disable the ETXTBSY legacy behavior for files that are
watched by pre-content events.

This change is not expected to cause regressions with existing systems
which do not have any pre-content event listeners.

Signed-off-by: Amir Goldstein <[email protected]>
Acked-by: Christian Brauner <[email protected]>
Signed-off-by: Jan Kara <[email protected]>
Link: https://patch.msgid.link/[email protected]
fs/binfmt_elf.c
fs/binfmt_elf_fdpic.c
fs/exec.c
include/linux/fs.h
kernel/fork.c

index 106f0e8af17799ffc54a673e77e60477a05c34dd..8054f44d39cf7c60f2526b95731486455ea83e7d 100644 (file)
@@ -1257,7 +1257,7 @@ out_free_interp:
                }
                reloc_func_desc = interp_load_addr;
 
-               allow_write_access(interpreter);
+               exe_file_allow_write_access(interpreter);
                fput(interpreter);
 
                kfree(interp_elf_ex);
@@ -1354,7 +1354,7 @@ out_free_dentry:
        kfree(interp_elf_ex);
        kfree(interp_elf_phdata);
 out_free_file:
-       allow_write_access(interpreter);
+       exe_file_allow_write_access(interpreter);
        if (interpreter)
                fput(interpreter);
 out_free_ph:
index f1a7c4875c4aa7401f4f02c01d7a1edce4e6ab50..c13ee8180b17599a2b655f8f51864e7885a5d516 100644 (file)
@@ -394,7 +394,7 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
                        goto error;
                }
 
-               allow_write_access(interpreter);
+               exe_file_allow_write_access(interpreter);
                fput(interpreter);
                interpreter = NULL;
        }
@@ -467,7 +467,7 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
 
 error:
        if (interpreter) {
-               allow_write_access(interpreter);
+               exe_file_allow_write_access(interpreter);
                fput(interpreter);
        }
        kfree(interpreter_name);
index 98cb7ba9983c7f55017e01a2d53185ef35d87c7c..c41cfd35c74c169239c9db7ed4c40df1beeae94e 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -912,7 +912,7 @@ static struct file *do_open_execat(int fd, struct filename *name, int flags)
            path_noexec(&file->f_path))
                return ERR_PTR(-EACCES);
 
-       err = deny_write_access(file);
+       err = exe_file_deny_write_access(file);
        if (err)
                return ERR_PTR(err);
 
@@ -927,7 +927,7 @@ static struct file *do_open_execat(int fd, struct filename *name, int flags)
  * Returns ERR_PTR on failure or allocated struct file on success.
  *
  * As this is a wrapper for the internal do_open_execat(), callers
- * must call allow_write_access() before fput() on release. Also see
+ * must call exe_file_allow_write_access() before fput() on release. Also see
  * do_close_execat().
  */
 struct file *open_exec(const char *name)
@@ -1471,7 +1471,7 @@ static void do_close_execat(struct file *file)
 {
        if (!file)
                return;
-       allow_write_access(file);
+       exe_file_allow_write_access(file);
        fput(file);
 }
 
@@ -1797,7 +1797,7 @@ static int exec_binprm(struct linux_binprm *bprm)
                bprm->file = bprm->interpreter;
                bprm->interpreter = NULL;
 
-               allow_write_access(exec);
+               exe_file_allow_write_access(exec);
                if (unlikely(bprm->have_execfd)) {
                        if (bprm->executable) {
                                fput(exec);
index 3f4d5946496595a452a40fdbfacdfb3642d9e7b9..a1230c40fef17ff90dff5ab273faa5f1c01b758f 100644 (file)
@@ -3095,6 +3095,28 @@ static inline void allow_write_access(struct file *file)
        if (file)
                atomic_inc(&file_inode(file)->i_writecount);
 }
+
+/*
+ * Do not prevent write to executable file when watched by pre-content events.
+ *
+ * Note that FMODE_FSNOTIFY_HSM mode is set depending on pre-content watches at
+ * the time of file open and remains constant for entire lifetime of the file,
+ * so if pre-content watches are added post execution or removed before the end
+ * of the execution, it will not cause i_writecount reference leak.
+ */
+static inline int exe_file_deny_write_access(struct file *exe_file)
+{
+       if (unlikely(FMODE_FSNOTIFY_HSM(exe_file->f_mode)))
+               return 0;
+       return deny_write_access(exe_file);
+}
+static inline void exe_file_allow_write_access(struct file *exe_file)
+{
+       if (unlikely(!exe_file || FMODE_FSNOTIFY_HSM(exe_file->f_mode)))
+               return;
+       allow_write_access(exe_file);
+}
+
 static inline bool inode_is_open_for_write(const struct inode *inode)
 {
        return atomic_read(&inode->i_writecount) > 0;
index 1450b461d196a1efee0e120780467a96f6c7d491..015c397f47cad73a6f381d86edeb26c2c88b9dae 100644 (file)
@@ -625,8 +625,8 @@ static void dup_mm_exe_file(struct mm_struct *mm, struct mm_struct *oldmm)
         * We depend on the oldmm having properly denied write access to the
         * exe_file already.
         */
-       if (exe_file && deny_write_access(exe_file))
-               pr_warn_once("deny_write_access() failed in %s\n", __func__);
+       if (exe_file && exe_file_deny_write_access(exe_file))
+               pr_warn_once("exe_file_deny_write_access() failed in %s\n", __func__);
 }
 
 #ifdef CONFIG_MMU
@@ -1424,13 +1424,13 @@ int set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file)
                 * We expect the caller (i.e., sys_execve) to already denied
                 * write access, so this is unlikely to fail.
                 */
-               if (unlikely(deny_write_access(new_exe_file)))
+               if (unlikely(exe_file_deny_write_access(new_exe_file)))
                        return -EACCES;
                get_file(new_exe_file);
        }
        rcu_assign_pointer(mm->exe_file, new_exe_file);
        if (old_exe_file) {
-               allow_write_access(old_exe_file);
+               exe_file_allow_write_access(old_exe_file);
                fput(old_exe_file);
        }
        return 0;
@@ -1471,7 +1471,7 @@ int replace_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file)
                        return ret;
        }
 
-       ret = deny_write_access(new_exe_file);
+       ret = exe_file_deny_write_access(new_exe_file);
        if (ret)
                return -EACCES;
        get_file(new_exe_file);
@@ -1483,7 +1483,7 @@ int replace_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file)
        mmap_write_unlock(mm);
 
        if (old_exe_file) {
-               allow_write_access(old_exe_file);
+               exe_file_allow_write_access(old_exe_file);
                fput(old_exe_file);
        }
        return 0;
This page took 0.083915 seconds and 4 git commands to generate.