diff mbox series

fscrypt: don't set policy for a dead directory

Message ID 1557204108-29048-1-git-send-email-hongjiefang@asrmicro.com (mailing list archive)
State Superseded
Headers show
Series fscrypt: don't set policy for a dead directory | expand

Commit Message

Hongjie Fang May 7, 2019, 4:41 a.m. UTC
if the directory had been removed, should not set policy for it.

Signed-off-by: hongjiefang <hongjiefang@asrmicro.com>
---
 fs/crypto/policy.c | 7 +++++++
 1 file changed, 7 insertions(+)

Comments

Eric Biggers May 7, 2019, 3:55 p.m. UTC | #1
Hi,

On Tue, May 07, 2019 at 12:41:48PM +0800, hongjiefang wrote:
> if the directory had been removed, should not set policy for it.
> 
> Signed-off-by: hongjiefang <hongjiefang@asrmicro.com>

Can you explain the motivation for this change?  It makes some sense, but I
don't see why it's really needed.  If you look at all the other IS_DEADDIR()
checks in the kernel, they're not for operations on the directory inode itself,
but rather for creating/finding/listing entries in the directory.  I think
FS_IOC_SET_ENCRYPTION_POLICY is more like the former (though it does have to
check whether the directory is empty).

> ---
>  fs/crypto/policy.c | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> index bd7eaf9..82900a4 100644
> --- a/fs/crypto/policy.c
> +++ b/fs/crypto/policy.c
> @@ -77,6 +77,12 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
>  
>  	inode_lock(inode);
>  
> +	/* don't set policy for a dead directory */
> +	if (IS_DEADDIR(inode)) {
> +		ret = -ENOENT;
> +		goto deaddir_out;
> +	}
> +
>  	ret = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
>  	if (ret == -ENODATA) {
>  		if (!S_ISDIR(inode->i_mode))
> @@ -96,6 +102,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
>  		ret = -EEXIST;
>  	}
>  
> +deaddir_out:
>  	inode_unlock(inode);

Call this label 'out_unlock' instead?

>  
>  	mnt_drop_write_file(filp);
> -- 
> 1.9.1
> 

Thanks,

- Eric
Hongjie Fang May 8, 2019, 2:11 a.m. UTC | #2
> -----Original Message-----
> From: Eric Biggers [mailto:ebiggers@kernel.org]
> Sent: Tuesday, May 07, 2019 11:56 PM
> To: Fang Hongjie(方洪杰)
> Cc: tytso@mit.edu; jaegeuk@kernel.org; linux-fscrypt@vger.kernel.org
> Subject: Re: [PATCH] fscrypt: don't set policy for a dead directory
> 
> Hi,
> 
> On Tue, May 07, 2019 at 12:41:48PM +0800, hongjiefang wrote:
> > if the directory had been removed, should not set policy for it.
> >
> > Signed-off-by: hongjiefang <hongjiefang@asrmicro.com>
> 
> Can you explain the motivation for this change?  It makes some sense, but I
> don't see why it's really needed.  If you look at all the other IS_DEADDIR()
> checks in the kernel, they're not for operations on the directory inode itself,
> but rather for creating/finding/listing entries in the directory.  I think
> FS_IOC_SET_ENCRYPTION_POLICY is more like the former (though it does have to
> check whether the directory is empty).

I met a panic issue when run the syzkaller on kernel 4.14.81(EXT4 FBE enabled).
the flow of case as follow:
r0 = openat$dir(0xffffffffffffff9c, &(0x7f0000000000)='.\x00', 0x0, 0x0)
mkdirat(r0, &(0x7f0000000040)='./file0\x00', 0x0)
r1 = openat$dir(0xffffffffffffff9c, &(0x7f0000000140)='./file0\x00', 0x0, 0x0)
unlinkat(r0, &(0x7f0000000240)='./file0\x00', 0x200)
ioctl$FS_IOC_SET_ENCRYPTION_POLICY(r1, 0x800c6613, &(0x7f00000000c0)
={0x0, @aes128, 0x0, "8acc73da97d6accc"})

The file0 directory maybe removed before doing FS_IOC_SET_ENCRYPTION_POLICY.
In this case, fscrypt_ioctl_set_policy()-> ext4_empty_dir() will return the
" invalid size " and trigger a panic when check the i_size of inode.
the panic stack as follow:
PID: 2682   TASK: ffffffc087d18080  CPU: 3   COMMAND: "syz-executor"
 #0 [ffffffc087d26fc0] panic at ffffff90080dc04c
 #1 [ffffffc087d27260] ext4_handle_error at ffffff9008689b08
 #2 [ffffffc087d27290] __ext4_error_inode at ffffff9008689e90
 #3 [ffffffc087d273f0] ext4_empty_dir at ffffff900865b064
 #4 [ffffffc087d274d0] fscrypt_ioctl_set_policy at ffffff9008565d70
 #5 [ffffffc087d27630] ext4_ioctl at ffffff900863105c
 #6 [ffffffc087d27b00] do_vfs_ioctl at ffffff90084cc440
 #7 [ffffffc087d27e80] sys_ioctl at ffffff90084cdaf0
 #8 [ffffffc087d27ff0] el0_svc_naked at ffffff9008084ffc

So, it need to check the directory status in the fscrypt_ioctl_set_policy().


> 
> > ---
> >  fs/crypto/policy.c | 7 +++++++
> >  1 file changed, 7 insertions(+)
> >
> > diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> > index bd7eaf9..82900a4 100644
> > --- a/fs/crypto/policy.c
> > +++ b/fs/crypto/policy.c
> > @@ -77,6 +77,12 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user
> *arg)
> >
> >  	inode_lock(inode);
> >
> > +	/* don't set policy for a dead directory */
> > +	if (IS_DEADDIR(inode)) {
> > +		ret = -ENOENT;
> > +		goto deaddir_out;
> > +	}
> > +
> >  	ret = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
> >  	if (ret == -ENODATA) {
> >  		if (!S_ISDIR(inode->i_mode))
> > @@ -96,6 +102,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user
> *arg)
> >  		ret = -EEXIST;
> >  	}
> >
> > +deaddir_out:
> >  	inode_unlock(inode);
> 
> Call this label 'out_unlock' instead?
> 
> >
> >  	mnt_drop_write_file(filp);
> > --
> > 1.9.1
> >
> 
> Thanks,
> 
> - Eric

B&R
Hongjie
Eric Biggers May 8, 2019, 3:55 a.m. UTC | #3
[+Cc linux-ext4]

On Wed, May 08, 2019 at 02:11:10AM +0000, Fang Hongjie(方洪杰) wrote:
> 
> > -----Original Message-----
> > From: Eric Biggers [mailto:ebiggers@kernel.org]
> > Sent: Tuesday, May 07, 2019 11:56 PM
> > To: Fang Hongjie(方洪杰)
> > Cc: tytso@mit.edu; jaegeuk@kernel.org; linux-fscrypt@vger.kernel.org
> > Subject: Re: [PATCH] fscrypt: don't set policy for a dead directory
> > 
> > Hi,
> > 
> > On Tue, May 07, 2019 at 12:41:48PM +0800, hongjiefang wrote:
> > > if the directory had been removed, should not set policy for it.
> > >
> > > Signed-off-by: hongjiefang <hongjiefang@asrmicro.com>
> > 
> > Can you explain the motivation for this change?  It makes some sense, but I
> > don't see why it's really needed.  If you look at all the other IS_DEADDIR()
> > checks in the kernel, they're not for operations on the directory inode itself,
> > but rather for creating/finding/listing entries in the directory.  I think
> > FS_IOC_SET_ENCRYPTION_POLICY is more like the former (though it does have to
> > check whether the directory is empty).
> 
> I met a panic issue when run the syzkaller on kernel 4.14.81(EXT4 FBE enabled).
> the flow of case as follow:
> r0 = openat$dir(0xffffffffffffff9c, &(0x7f0000000000)='.\x00', 0x0, 0x0)
> mkdirat(r0, &(0x7f0000000040)='./file0\x00', 0x0)
> r1 = openat$dir(0xffffffffffffff9c, &(0x7f0000000140)='./file0\x00', 0x0, 0x0)
> unlinkat(r0, &(0x7f0000000240)='./file0\x00', 0x200)
> ioctl$FS_IOC_SET_ENCRYPTION_POLICY(r1, 0x800c6613, &(0x7f00000000c0)
> ={0x0, @aes128, 0x0, "8acc73da97d6accc"})
> 
> The file0 directory maybe removed before doing FS_IOC_SET_ENCRYPTION_POLICY.
> In this case, fscrypt_ioctl_set_policy()-> ext4_empty_dir() will return the
> " invalid size " and trigger a panic when check the i_size of inode.
> the panic stack as follow:
> PID: 2682   TASK: ffffffc087d18080  CPU: 3   COMMAND: "syz-executor"
>  #0 [ffffffc087d26fc0] panic at ffffff90080dc04c
>  #1 [ffffffc087d27260] ext4_handle_error at ffffff9008689b08
>  #2 [ffffffc087d27290] __ext4_error_inode at ffffff9008689e90
>  #3 [ffffffc087d273f0] ext4_empty_dir at ffffff900865b064
>  #4 [ffffffc087d274d0] fscrypt_ioctl_set_policy at ffffff9008565d70
>  #5 [ffffffc087d27630] ext4_ioctl at ffffff900863105c
>  #6 [ffffffc087d27b00] do_vfs_ioctl at ffffff90084cc440
>  #7 [ffffffc087d27e80] sys_ioctl at ffffff90084cdaf0
>  #8 [ffffffc087d27ff0] el0_svc_naked at ffffff9008084ffc
> 
> So, it need to check the directory status in the fscrypt_ioctl_set_policy().
> 

Okay, this is a real bug, thanks for reporting this!  So ext4_rmdir() sets
i_size = 0, then ext4_empty_dir() reports an error because 'inode->i_size <
EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2)'.  Note that it's actually an ext4
error, not necessarily a panic.  But the fs may be mounted with errors=panic.

This could also be fixed by updating ext4_empty_dir() to allow i_size == 0.  But
we might as well check IS_DEADDIR() in fscrypt_ioctl_set_policy() either way.

Can you please update the commit message to describe the problem, and add:

	Fixes: 9bd8212f981e ("ext4 crypto: add encryption policy and password salt support")
	Cc: <stable@vger.kernel.org> # v4.1+

(Another comment below)

> 
> > 
> > > ---
> > >  fs/crypto/policy.c | 7 +++++++
> > >  1 file changed, 7 insertions(+)
> > >
> > > diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> > > index bd7eaf9..82900a4 100644
> > > --- a/fs/crypto/policy.c
> > > +++ b/fs/crypto/policy.c
> > > @@ -77,6 +77,12 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user
> > *arg)
> > >
> > >  	inode_lock(inode);
> > >
> > > +	/* don't set policy for a dead directory */
> > > +	if (IS_DEADDIR(inode)) {
> > > +		ret = -ENOENT;
> > > +		goto deaddir_out;
> > > +	}
> > > +

This seems a bit misplaced given the actual purpose of the check, and the
comment doesn't help explain it.  How about moving this to just before the
->empty_dir() call, so it's only done when actually setting a new policy?
I think that would make it more obvious:

diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index d536889ac31bf..4941fe8471cef 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -81,6 +81,8 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
 	if (ret == -ENODATA) {
 		if (!S_ISDIR(inode->i_mode))
 			ret = -ENOTDIR;
+		else if (IS_DEADDIR(inode))
+			ret = -ENOENT;
 		else if (!inode->i_sb->s_cop->empty_dir(inode))
 			ret = -ENOTEMPTY;
 		else

(Then the label below wouldn't be needed, of course.)

> > >  	ret = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
> > >  	if (ret == -ENODATA) {
> > >  		if (!S_ISDIR(inode->i_mode))
> > > @@ -96,6 +102,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user
> > *arg)
> > >  		ret = -EEXIST;
> > >  	}
> > >
> > > +deaddir_out:
> > >  	inode_unlock(inode);
> > 
> > Call this label 'out_unlock' instead?
> > 
> > >
> > >  	mnt_drop_write_file(filp);
> > > --

Thanks,

- Eric
Hongjie Fang May 8, 2019, 4:55 a.m. UTC | #4
> From: Eric Biggers [mailto:ebiggers@kernel.org]
> Sent: Wednesday, May 08, 2019 11:55 AM
> To: Fang Hongjie(方洪杰)
> Cc: tytso@mit.edu; jaegeuk@kernel.org; linux-fscrypt@vger.kernel.org;
> linux-ext4@vger.kernel.org
> Subject: Re: [PATCH] fscrypt: don't set policy for a dead directory
> 
> [+Cc linux-ext4]
> 
> On Wed, May 08, 2019 at 02:11:10AM +0000, Fang Hongjie(方洪杰) wrote:
> >
> > > -----Original Message-----
> > > From: Eric Biggers [mailto:ebiggers@kernel.org]
> > > Sent: Tuesday, May 07, 2019 11:56 PM
> > > To: Fang Hongjie(方洪杰)
> > > Cc: tytso@mit.edu; jaegeuk@kernel.org; linux-fscrypt@vger.kernel.org
> > > Subject: Re: [PATCH] fscrypt: don't set policy for a dead directory
> > >
> > > Hi,
> > >
> > > On Tue, May 07, 2019 at 12:41:48PM +0800, hongjiefang wrote:
> > > > if the directory had been removed, should not set policy for it.
> > > >
> > > > Signed-off-by: hongjiefang <hongjiefang@asrmicro.com>
> > >
> > > Can you explain the motivation for this change?  It makes some sense, but I
> > > don't see why it's really needed.  If you look at all the other IS_DEADDIR()
> > > checks in the kernel, they're not for operations on the directory inode itself,
> > > but rather for creating/finding/listing entries in the directory.  I think
> > > FS_IOC_SET_ENCRYPTION_POLICY is more like the former (though it does have
> to
> > > check whether the directory is empty).
> >
> > I met a panic issue when run the syzkaller on kernel 4.14.81(EXT4 FBE enabled).
> > the flow of case as follow:
> > r0 = openat$dir(0xffffffffffffff9c, &(0x7f0000000000)='.\x00', 0x0, 0x0)
> > mkdirat(r0, &(0x7f0000000040)='./file0\x00', 0x0)
> > r1 = openat$dir(0xffffffffffffff9c, &(0x7f0000000140)='./file0\x00', 0x0, 0x0)
> > unlinkat(r0, &(0x7f0000000240)='./file0\x00', 0x200)
> > ioctl$FS_IOC_SET_ENCRYPTION_POLICY(r1, 0x800c6613, &(0x7f00000000c0)
> > ={0x0, @aes128, 0x0, "8acc73da97d6accc"})
> >
> > The file0 directory maybe removed before doing
> FS_IOC_SET_ENCRYPTION_POLICY.
> > In this case, fscrypt_ioctl_set_policy()-> ext4_empty_dir() will return the
> > " invalid size " and trigger a panic when check the i_size of inode.
> > the panic stack as follow:
> > PID: 2682   TASK: ffffffc087d18080  CPU: 3   COMMAND: "syz-executor"
> >  #0 [ffffffc087d26fc0] panic at ffffff90080dc04c
> >  #1 [ffffffc087d27260] ext4_handle_error at ffffff9008689b08
> >  #2 [ffffffc087d27290] __ext4_error_inode at ffffff9008689e90
> >  #3 [ffffffc087d273f0] ext4_empty_dir at ffffff900865b064
> >  #4 [ffffffc087d274d0] fscrypt_ioctl_set_policy at ffffff9008565d70
> >  #5 [ffffffc087d27630] ext4_ioctl at ffffff900863105c
> >  #6 [ffffffc087d27b00] do_vfs_ioctl at ffffff90084cc440
> >  #7 [ffffffc087d27e80] sys_ioctl at ffffff90084cdaf0
> >  #8 [ffffffc087d27ff0] el0_svc_naked at ffffff9008084ffc
> >
> > So, it need to check the directory status in the fscrypt_ioctl_set_policy().
> >
> 
> Okay, this is a real bug, thanks for reporting this!  So ext4_rmdir() sets
> i_size = 0, then ext4_empty_dir() reports an error because 'inode->i_size <
> EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2)'.  Note that it's actually an ext4
> error, not necessarily a panic.  But the fs may be mounted with errors=panic.
> 
> This could also be fixed by updating ext4_empty_dir() to allow i_size == 0.  But
> we might as well check IS_DEADDIR() in fscrypt_ioctl_set_policy() either way.
> 
> Can you please update the commit message to describe the problem, and add:
> 
> 	Fixes: 9bd8212f981e ("ext4 crypto: add encryption policy and password salt
> support")
> 	Cc: <stable@vger.kernel.org> # v4.1+
> 

Okay,I will update it.

> (Another comment below)
> 
> >
> > >
> > > > ---
> > > >  fs/crypto/policy.c | 7 +++++++
> > > >  1 file changed, 7 insertions(+)
> > > >
> > > > diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> > > > index bd7eaf9..82900a4 100644
> > > > --- a/fs/crypto/policy.c
> > > > +++ b/fs/crypto/policy.c
> > > > @@ -77,6 +77,12 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void
> __user
> > > *arg)
> > > >
> > > >  	inode_lock(inode);
> > > >
> > > > +	/* don't set policy for a dead directory */
> > > > +	if (IS_DEADDIR(inode)) {
> > > > +		ret = -ENOENT;
> > > > +		goto deaddir_out;
> > > > +	}
> > > > +
> 
> This seems a bit misplaced given the actual purpose of the check, and the
> comment doesn't help explain it.  How about moving this to just before the
> ->empty_dir() call, so it's only done when actually setting a new policy?
> I think that would make it more obvious:
> 
> diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> index d536889ac31bf..4941fe8471cef 100644
> --- a/fs/crypto/policy.c
> +++ b/fs/crypto/policy.c
> @@ -81,6 +81,8 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user
> *arg)
>  	if (ret == -ENODATA) {
>  		if (!S_ISDIR(inode->i_mode))
>  			ret = -ENOTDIR;
> +		else if (IS_DEADDIR(inode))
> +			ret = -ENOENT;
>  		else if (!inode->i_sb->s_cop->empty_dir(inode))
>  			ret = -ENOTEMPTY;
>  		else
> 
> (Then the label below wouldn't be needed, of course.)
> 

Yeah, this is a more appropriate way.
Thanks for the comment.

> > > >  	ret = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
> > > >  	if (ret == -ENODATA) {
> > > >  		if (!S_ISDIR(inode->i_mode))
> > > > @@ -96,6 +102,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void
> __user
> > > *arg)
> > > >  		ret = -EEXIST;
> > > >  	}
> > > >
> > > > +deaddir_out:
> > > >  	inode_unlock(inode);
> > >
> > > Call this label 'out_unlock' instead?
> > >
> > > >
> > > >  	mnt_drop_write_file(filp);
> > > > --
> 
> Thanks,
> 
> - Eric


B&R
Hongjie
diff mbox series

Patch

diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index bd7eaf9..82900a4 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -77,6 +77,12 @@  int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
 
 	inode_lock(inode);
 
+	/* don't set policy for a dead directory */
+	if (IS_DEADDIR(inode)) {
+		ret = -ENOENT;
+		goto deaddir_out;
+	}
+
 	ret = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
 	if (ret == -ENODATA) {
 		if (!S_ISDIR(inode->i_mode))
@@ -96,6 +102,7 @@  int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
 		ret = -EEXIST;
 	}
 
+deaddir_out:
 	inode_unlock(inode);
 
 	mnt_drop_write_file(filp);