diff mbox series

ovl: fix mmap denywrite

Message ID YNHXzBgzRrZu1MrD@miu.piliscsaba.redhat.com (mailing list archive)
State New
Headers show
Series ovl: fix mmap denywrite | expand

Commit Message

Miklos Szeredi June 22, 2021, 12:30 p.m. UTC
Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
mappings.  Similarly negative i_mmap_writable counts were ignored for
VM_SHARED mappings.

Fix by making vma_set_file() switch the temporary counts obtained and
released by mmap_region().

Reported-by: Chengguang Xu <cgxu519@mykernel.net>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/overlayfs/file.c |    4 +++-
 include/linux/mm.h  |    1 +
 mm/mmap.c           |    2 +-
 mm/util.c           |   38 +++++++++++++++++++++++++++++++++++++-
 4 files changed, 42 insertions(+), 3 deletions(-)

Comments

Christian König June 22, 2021, 12:43 p.m. UTC | #1
Am 22.06.21 um 14:30 schrieb Miklos Szeredi:
> Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
> mappings.  Similarly negative i_mmap_writable counts were ignored for
> VM_SHARED mappings.
>
> Fix by making vma_set_file() switch the temporary counts obtained and
> released by mmap_region().

Mhm, I don't fully understand the background but that looks like 
something specific to overlayfs to me.

So why are you changing the common helper?

Thanks,
Christian.

>
> Reported-by: Chengguang Xu <cgxu519@mykernel.net>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
>   fs/overlayfs/file.c |    4 +++-
>   include/linux/mm.h  |    1 +
>   mm/mmap.c           |    2 +-
>   mm/util.c           |   38 +++++++++++++++++++++++++++++++++++++-
>   4 files changed, 42 insertions(+), 3 deletions(-)
>
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -430,7 +430,9 @@ static int ovl_mmap(struct file *file, s
>   	if (WARN_ON(file != vma->vm_file))
>   		return -EIO;
>   
> -	vma_set_file(vma, realfile);
> +	ret = vma_set_file_checkwrite(vma, realfile);
> +	if (ret)
> +		return ret;
>   
>   	old_cred = ovl_override_creds(file_inode(file)->i_sb);
>   	ret = call_mmap(vma->vm_file, vma);
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -2751,6 +2751,7 @@ static inline void vma_set_page_prot(str
>   #endif
>   
>   void vma_set_file(struct vm_area_struct *vma, struct file *file);
> +int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file);
>   
>   #ifdef CONFIG_NUMA_BALANCING
>   unsigned long change_prot_numa(struct vm_area_struct *vma,
> --- a/mm/mmap.c
> +++ b/mm/mmap.c
> @@ -1809,6 +1809,7 @@ unsigned long mmap_region(struct file *f
>   		 */
>   		vma->vm_file = get_file(file);
>   		error = call_mmap(file, vma);
> +		file = vma->vm_file;


>   		if (error)
>   			goto unmap_and_free_vma;
>   
> @@ -1870,7 +1871,6 @@ unsigned long mmap_region(struct file *f
>   		if (vm_flags & VM_DENYWRITE)
>   			allow_write_access(file);
>   	}
> -	file = vma->vm_file;
>   out:
>   	perf_event_mmap(vma);
>   
> --- a/mm/util.c
> +++ b/mm/util.c
> @@ -314,12 +314,48 @@ int vma_is_stack_for_current(struct vm_a
>   /*
>    * Change backing file, only valid to use during initial VMA setup.
>    */
> -void vma_set_file(struct vm_area_struct *vma, struct file *file)
> +int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file)
>   {
> +	vm_flags_t vm_flags = vma->vm_flags;
> +	int err = 0;
> +
>   	/* Changing an anonymous vma with this is illegal */
>   	get_file(file);
> +
> +	/* Get temporary denial counts on replacement */
> +	if (vm_flags & VM_DENYWRITE) {
> +		err = deny_write_access(file);
> +		if (err)
> +			goto out_put;
> +	}
> +	if (vm_flags & VM_SHARED) {
> +		err = mapping_map_writable(file->f_mapping);
> +		if (err)
> +			goto out_allow;
> +	}
> +
>   	swap(vma->vm_file, file);
> +
> +	/* Undo temporary denial counts on replaced */
> +	if (vm_flags & VM_SHARED)
> +		mapping_unmap_writable(file->f_mapping);
> +out_allow:
> +	if (vm_flags & VM_DENYWRITE)
> +		allow_write_access(file);
> +out_put:
>   	fput(file);
> +	return err;
> +}
> +EXPORT_SYMBOL(vma_set_file_checkwrite);
> +
> +/*
> + * Change backing file, only valid to use during initial VMA setup.
> + */
> +void vma_set_file(struct vm_area_struct *vma, struct file *file)
> +{
> +	int err = vma_set_file_checkwrite(vma, file);
> +
> +	WARN_ON_ONCE(err);
>   }
>   EXPORT_SYMBOL(vma_set_file);
>   
>
>
Miklos Szeredi June 22, 2021, 3:10 p.m. UTC | #2
On Tue, 22 Jun 2021 at 14:43, Christian König <christian.koenig@amd.com> wrote:
>
> Am 22.06.21 um 14:30 schrieb Miklos Szeredi:
> > Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
> > mappings.  Similarly negative i_mmap_writable counts were ignored for
> > VM_SHARED mappings.
> >
> > Fix by making vma_set_file() switch the temporary counts obtained and
> > released by mmap_region().
>
> Mhm, I don't fully understand the background but that looks like
> something specific to overlayfs to me.
>
> So why are you changing the common helper?

Need to hold the temporary counts until the final ones are obtained in
vma_link(), which is out of overlayfs' scope.

Thanks,
Miklos
Chengguang Xu June 23, 2021, 6:06 a.m. UTC | #3
---- 在 星期二, 2021-06-22 20:30:04 Miklos Szeredi <miklos@szeredi.hu> 撰写 ----
 > Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
 > mappings.  Similarly negative i_mmap_writable counts were ignored for
 > VM_SHARED mappings.
 > 
 > Fix by making vma_set_file() switch the temporary counts obtained and
 > released by mmap_region().
 > 
 > Reported-by: Chengguang Xu <cgxu519@mykernel.net>
 > Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
 > ---
 >  fs/overlayfs/file.c |    4 +++-
 >  include/linux/mm.h  |    1 +
 >  mm/mmap.c           |    2 +-
 >  mm/util.c           |   38 +++++++++++++++++++++++++++++++++++++-
 >  4 files changed, 42 insertions(+), 3 deletions(-)
 > 
 > --- a/fs/overlayfs/file.c
 > +++ b/fs/overlayfs/file.c
 > @@ -430,7 +430,9 @@ static int ovl_mmap(struct file *file, s
 >      if (WARN_ON(file != vma->vm_file))
 >          return -EIO;
 >  
 > -    vma_set_file(vma, realfile);
 > +    ret = vma_set_file_checkwrite(vma, realfile);
 > +    if (ret)
 > +        return ret;

I'm afraid that it may affect other overlayfs instances which share lower layers(no upper),
so could we just check those permissions for upper layer?



 >  
 >      old_cred = ovl_override_creds(file_inode(file)->i_sb);
 >      ret = call_mmap(vma->vm_file, vma);
 > --- a/include/linux/mm.h
 > +++ b/include/linux/mm.h
 > @@ -2751,6 +2751,7 @@ static inline void vma_set_page_prot(str
 >  #endif
 >  
 >  void vma_set_file(struct vm_area_struct *vma, struct file *file);
 > +int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file);
 >  
 >  #ifdef CONFIG_NUMA_BALANCING
 >  unsigned long change_prot_numa(struct vm_area_struct *vma,
 > --- a/mm/mmap.c
 > +++ b/mm/mmap.c
 > @@ -1809,6 +1809,7 @@ unsigned long mmap_region(struct file *f
 >           */
 >          vma->vm_file = get_file(file);
 >          error = call_mmap(file, vma);
 > +        file = vma->vm_file;


I'm not sure the behavior of changing vma_file is always safe for vma merging case.
In vma merging case, before go to tag 'unmap_writable' the reference of vma->vm_file will be released by fput().
For overlayfs, it probably safe because overlayfs file will get another reference for lower/upper file.



Thanks,
Chengguang Xu




 >          if (error)
 >              goto unmap_and_free_vma;
 >  
 > @@ -1870,7 +1871,6 @@ unsigned long mmap_region(struct file *f
 >          if (vm_flags & VM_DENYWRITE)
 >              allow_write_access(file);
 >      }
 > -    file = vma->vm_file;
 >  out:
 >      perf_event_mmap(vma);
 >  
 > --- a/mm/util.c
 > +++ b/mm/util.c
 > @@ -314,12 +314,48 @@ int vma_is_stack_for_current(struct vm_a
 >  /*
 >   * Change backing file, only valid to use during initial VMA setup.
 >   */
 > -void vma_set_file(struct vm_area_struct *vma, struct file *file)
 > +int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file)
 >  {
 > +    vm_flags_t vm_flags = vma->vm_flags;
 > +    int err = 0;
 > +
 >      /* Changing an anonymous vma with this is illegal */
 >      get_file(file);
 > +
 > +    /* Get temporary denial counts on replacement */
 > +    if (vm_flags & VM_DENYWRITE) {
 > +        err = deny_write_access(file);
 > +        if (err)
 > +            goto out_put;
 > +    }
 > +    if (vm_flags & VM_SHARED) {
 > +        err = mapping_map_writable(file->f_mapping);
 > +        if (err)
 > +            goto out_allow;
 > +    }
 > +
 >      swap(vma->vm_file, file);
 > +
 > +    /* Undo temporary denial counts on replaced */
 > +    if (vm_flags & VM_SHARED)
 > +        mapping_unmap_writable(file->f_mapping);
 > +out_allow:
 > +    if (vm_flags & VM_DENYWRITE)
 > +        allow_write_access(file);
 > +out_put:
 >      fput(file);
 > +    return err;
 > +}
 > +EXPORT_SYMBOL(vma_set_file_checkwrite);
 > +
 > +/*
 > + * Change backing file, only valid to use during initial VMA setup.
 > + */
 > +void vma_set_file(struct vm_area_struct *vma, struct file *file)
 > +{
 > +    int err = vma_set_file_checkwrite(vma, file);
 > +
 > +    WARN_ON_ONCE(err);
 >  }
 >  EXPORT_SYMBOL(vma_set_file);
 >  
 > 
 > 
 >
Christian König June 23, 2021, 11:41 a.m. UTC | #4
Am 22.06.21 um 17:10 schrieb Miklos Szeredi:
> On Tue, 22 Jun 2021 at 14:43, Christian König <christian.koenig@amd.com> wrote:
>> Am 22.06.21 um 14:30 schrieb Miklos Szeredi:
>>> Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
>>> mappings.  Similarly negative i_mmap_writable counts were ignored for
>>> VM_SHARED mappings.
>>>
>>> Fix by making vma_set_file() switch the temporary counts obtained and
>>> released by mmap_region().
>> Mhm, I don't fully understand the background but that looks like
>> something specific to overlayfs to me.
>>
>> So why are you changing the common helper?
> Need to hold the temporary counts until the final ones are obtained in
> vma_link(), which is out of overlayfs' scope.

Ah! So basically we need to move the denial counts which mmap_region() 
added to the original file to the new one as well. That's indeed a 
rather good point.

Can you rather change the vma_set_file() function to return the error 
and add a __must_check?

I can take care fixing the users in DMA-buf and DRM subsystem.

Thanks,
Christian.

>
> Thanks,
> Miklos
Miklos Szeredi July 9, 2021, 1:48 p.m. UTC | #5
On Wed, Jun 23, 2021 at 01:41:02PM +0200, Christian König wrote:
> 
> 
> Am 22.06.21 um 17:10 schrieb Miklos Szeredi:
> > On Tue, 22 Jun 2021 at 14:43, Christian König <christian.koenig@amd.com> wrote:
> > > Am 22.06.21 um 14:30 schrieb Miklos Szeredi:
> > > > Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
> > > > mappings.  Similarly negative i_mmap_writable counts were ignored for
> > > > VM_SHARED mappings.
> > > > 
> > > > Fix by making vma_set_file() switch the temporary counts obtained and
> > > > released by mmap_region().
> > > Mhm, I don't fully understand the background but that looks like
> > > something specific to overlayfs to me.
> > > 
> > > So why are you changing the common helper?
> > Need to hold the temporary counts until the final ones are obtained in
> > vma_link(), which is out of overlayfs' scope.
> 
> Ah! So basically we need to move the denial counts which mmap_region() added
> to the original file to the new one as well. That's indeed a rather good
> point.
> 
> Can you rather change the vma_set_file() function to return the error and
> add a __must_check?
> 
> I can take care fixing the users in DMA-buf and DRM subsystem.

Okay, but changing to __must_check has to be the last step to avoid compile
errors.  This v2 is with __must_check commented out.

Thanks,
Miklos
---

From: Miklos Szeredi <mszeredi@redhat.com>
Subject: [PATCH v2] ovl: fix mmap denywrite

Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
mappings.  Similarly negative i_mmap_writable counts were ignored for
VM_SHARED mappings.

Fix by making vma_set_file() switch the temporary counts obtained and
released by mmap_region().

Reported-by: Chengguang Xu <cgxu519@mykernel.net>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
 fs/overlayfs/file.c |    4 +++-
 include/linux/mm.h  |    2 +-
 mm/mmap.c           |    2 +-
 mm/util.c           |   27 ++++++++++++++++++++++++++-
 4 files changed, 31 insertions(+), 4 deletions(-)

--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -430,7 +430,9 @@ static int ovl_mmap(struct file *file, s
 	if (WARN_ON(file != vma->vm_file))
 		return -EIO;
 
-	vma_set_file(vma, realfile);
+	ret = vma_set_file(vma, realfile);
+	if (ret)
+		return ret;
 
 	old_cred = ovl_override_creds(file_inode(file)->i_sb);
 	ret = call_mmap(vma->vm_file, vma);
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2776,7 +2776,7 @@ static inline void vma_set_page_prot(str
 }
 #endif
 
-void vma_set_file(struct vm_area_struct *vma, struct file *file);
+int /* __must_check */ vma_set_file(struct vm_area_struct *vma, struct file *file);
 
 #ifdef CONFIG_NUMA_BALANCING
 unsigned long change_prot_numa(struct vm_area_struct *vma,
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1807,6 +1807,7 @@ unsigned long mmap_region(struct file *f
 		 */
 		vma->vm_file = get_file(file);
 		error = call_mmap(file, vma);
+		file = vma->vm_file;
 		if (error)
 			goto unmap_and_free_vma;
 
@@ -1868,7 +1869,6 @@ unsigned long mmap_region(struct file *f
 		if (vm_flags & VM_DENYWRITE)
 			allow_write_access(file);
 	}
-	file = vma->vm_file;
 out:
 	perf_event_mmap(vma);
 
--- a/mm/util.c
+++ b/mm/util.c
@@ -314,12 +314,37 @@ int vma_is_stack_for_current(struct vm_a
 /*
  * Change backing file, only valid to use during initial VMA setup.
  */
-void vma_set_file(struct vm_area_struct *vma, struct file *file)
+int vma_set_file(struct vm_area_struct *vma, struct file *file)
 {
+	vm_flags_t vm_flags = vma->vm_flags;
+	int err = 0;
+
 	/* Changing an anonymous vma with this is illegal */
 	get_file(file);
+
+	/* Get temporary denial counts on replacement */
+	if (vm_flags & VM_DENYWRITE) {
+		err = deny_write_access(file);
+		if (err)
+			goto out_put;
+	}
+	if (vm_flags & VM_SHARED) {
+		err = mapping_map_writable(file->f_mapping);
+		if (err)
+			goto out_allow;
+	}
+
 	swap(vma->vm_file, file);
+
+	/* Undo temporary denial counts on replaced */
+	if (vm_flags & VM_SHARED)
+		mapping_unmap_writable(file->f_mapping);
+out_allow:
+	if (vm_flags & VM_DENYWRITE)
+		allow_write_access(file);
+out_put:
 	fput(file);
+	return err;
 }
 EXPORT_SYMBOL(vma_set_file);
Christian König July 12, 2021, 11:15 a.m. UTC | #6
Am 09.07.21 um 15:48 schrieb Miklos Szeredi:
> On Wed, Jun 23, 2021 at 01:41:02PM +0200, Christian König wrote:
>>
>> Am 22.06.21 um 17:10 schrieb Miklos Szeredi:
>>> On Tue, 22 Jun 2021 at 14:43, Christian König <christian.koenig@amd.com> wrote:
>>>> Am 22.06.21 um 14:30 schrieb Miklos Szeredi:
>>>>> Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
>>>>> mappings.  Similarly negative i_mmap_writable counts were ignored for
>>>>> VM_SHARED mappings.
>>>>>
>>>>> Fix by making vma_set_file() switch the temporary counts obtained and
>>>>> released by mmap_region().
>>>> Mhm, I don't fully understand the background but that looks like
>>>> something specific to overlayfs to me.
>>>>
>>>> So why are you changing the common helper?
>>> Need to hold the temporary counts until the final ones are obtained in
>>> vma_link(), which is out of overlayfs' scope.
>> Ah! So basically we need to move the denial counts which mmap_region() added
>> to the original file to the new one as well. That's indeed a rather good
>> point.
>>
>> Can you rather change the vma_set_file() function to return the error and
>> add a __must_check?
>>
>> I can take care fixing the users in DMA-buf and DRM subsystem.
> Okay, but changing to __must_check has to be the last step to avoid compile
> errors.  This v2 is with __must_check commented out.

Good point. Please ping me whenever that is upstream and I will take 
care of fixing all the callers in the DRM subsystem and DMA-buf

>
> Thanks,
> Miklos
> ---
>
> From: Miklos Szeredi <mszeredi@redhat.com>
> Subject: [PATCH v2] ovl: fix mmap denywrite
>
> Overlayfs did not honor positive i_writecount on realfile for VM_DENYWRITE
> mappings.  Similarly negative i_mmap_writable counts were ignored for
> VM_SHARED mappings.
>
> Fix by making vma_set_file() switch the temporary counts obtained and
> released by mmap_region().
>
> Reported-by: Chengguang Xu <cgxu519@mykernel.net>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

Reviewed-by: Christian König <christian.koenig@amd.com>

Regards,
Christian.

> ---
>   fs/overlayfs/file.c |    4 +++-
>   include/linux/mm.h  |    2 +-
>   mm/mmap.c           |    2 +-
>   mm/util.c           |   27 ++++++++++++++++++++++++++-
>   4 files changed, 31 insertions(+), 4 deletions(-)
>
> --- a/fs/overlayfs/file.c
> +++ b/fs/overlayfs/file.c
> @@ -430,7 +430,9 @@ static int ovl_mmap(struct file *file, s
>   	if (WARN_ON(file != vma->vm_file))
>   		return -EIO;
>   
> -	vma_set_file(vma, realfile);
> +	ret = vma_set_file(vma, realfile);
> +	if (ret)
> +		return ret;
>   
>   	old_cred = ovl_override_creds(file_inode(file)->i_sb);
>   	ret = call_mmap(vma->vm_file, vma);
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -2776,7 +2776,7 @@ static inline void vma_set_page_prot(str
>   }
>   #endif
>   
> -void vma_set_file(struct vm_area_struct *vma, struct file *file);
> +int /* __must_check */ vma_set_file(struct vm_area_struct *vma, struct file *file);
>   
>   #ifdef CONFIG_NUMA_BALANCING
>   unsigned long change_prot_numa(struct vm_area_struct *vma,
> --- a/mm/mmap.c
> +++ b/mm/mmap.c
> @@ -1807,6 +1807,7 @@ unsigned long mmap_region(struct file *f
>   		 */
>   		vma->vm_file = get_file(file);
>   		error = call_mmap(file, vma);
> +		file = vma->vm_file;
>   		if (error)
>   			goto unmap_and_free_vma;
>   
> @@ -1868,7 +1869,6 @@ unsigned long mmap_region(struct file *f
>   		if (vm_flags & VM_DENYWRITE)
>   			allow_write_access(file);
>   	}
> -	file = vma->vm_file;
>   out:
>   	perf_event_mmap(vma);
>   
> --- a/mm/util.c
> +++ b/mm/util.c
> @@ -314,12 +314,37 @@ int vma_is_stack_for_current(struct vm_a
>   /*
>    * Change backing file, only valid to use during initial VMA setup.
>    */
> -void vma_set_file(struct vm_area_struct *vma, struct file *file)
> +int vma_set_file(struct vm_area_struct *vma, struct file *file)
>   {
> +	vm_flags_t vm_flags = vma->vm_flags;
> +	int err = 0;
> +
>   	/* Changing an anonymous vma with this is illegal */
>   	get_file(file);
> +
> +	/* Get temporary denial counts on replacement */
> +	if (vm_flags & VM_DENYWRITE) {
> +		err = deny_write_access(file);
> +		if (err)
> +			goto out_put;
> +	}
> +	if (vm_flags & VM_SHARED) {
> +		err = mapping_map_writable(file->f_mapping);
> +		if (err)
> +			goto out_allow;
> +	}
> +
>   	swap(vma->vm_file, file);
> +
> +	/* Undo temporary denial counts on replaced */
> +	if (vm_flags & VM_SHARED)
> +		mapping_unmap_writable(file->f_mapping);
> +out_allow:
> +	if (vm_flags & VM_DENYWRITE)
> +		allow_write_access(file);
> +out_put:
>   	fput(file);
> +	return err;
>   }
>   EXPORT_SYMBOL(vma_set_file);
>
diff mbox series

Patch

--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -430,7 +430,9 @@  static int ovl_mmap(struct file *file, s
 	if (WARN_ON(file != vma->vm_file))
 		return -EIO;
 
-	vma_set_file(vma, realfile);
+	ret = vma_set_file_checkwrite(vma, realfile);
+	if (ret)
+		return ret;
 
 	old_cred = ovl_override_creds(file_inode(file)->i_sb);
 	ret = call_mmap(vma->vm_file, vma);
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2751,6 +2751,7 @@  static inline void vma_set_page_prot(str
 #endif
 
 void vma_set_file(struct vm_area_struct *vma, struct file *file);
+int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file);
 
 #ifdef CONFIG_NUMA_BALANCING
 unsigned long change_prot_numa(struct vm_area_struct *vma,
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1809,6 +1809,7 @@  unsigned long mmap_region(struct file *f
 		 */
 		vma->vm_file = get_file(file);
 		error = call_mmap(file, vma);
+		file = vma->vm_file;
 		if (error)
 			goto unmap_and_free_vma;
 
@@ -1870,7 +1871,6 @@  unsigned long mmap_region(struct file *f
 		if (vm_flags & VM_DENYWRITE)
 			allow_write_access(file);
 	}
-	file = vma->vm_file;
 out:
 	perf_event_mmap(vma);
 
--- a/mm/util.c
+++ b/mm/util.c
@@ -314,12 +314,48 @@  int vma_is_stack_for_current(struct vm_a
 /*
  * Change backing file, only valid to use during initial VMA setup.
  */
-void vma_set_file(struct vm_area_struct *vma, struct file *file)
+int vma_set_file_checkwrite(struct vm_area_struct *vma, struct file *file)
 {
+	vm_flags_t vm_flags = vma->vm_flags;
+	int err = 0;
+
 	/* Changing an anonymous vma with this is illegal */
 	get_file(file);
+
+	/* Get temporary denial counts on replacement */
+	if (vm_flags & VM_DENYWRITE) {
+		err = deny_write_access(file);
+		if (err)
+			goto out_put;
+	}
+	if (vm_flags & VM_SHARED) {
+		err = mapping_map_writable(file->f_mapping);
+		if (err)
+			goto out_allow;
+	}
+
 	swap(vma->vm_file, file);
+
+	/* Undo temporary denial counts on replaced */
+	if (vm_flags & VM_SHARED)
+		mapping_unmap_writable(file->f_mapping);
+out_allow:
+	if (vm_flags & VM_DENYWRITE)
+		allow_write_access(file);
+out_put:
 	fput(file);
+	return err;
+}
+EXPORT_SYMBOL(vma_set_file_checkwrite);
+
+/*
+ * Change backing file, only valid to use during initial VMA setup.
+ */
+void vma_set_file(struct vm_area_struct *vma, struct file *file)
+{
+	int err = vma_set_file_checkwrite(vma, file);
+
+	WARN_ON_ONCE(err);
 }
 EXPORT_SYMBOL(vma_set_file);