Re: [PATCH 1/4] Revert "ceph: fix dentry leak in splice_dentry()"

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




> On Oct 15, 2018, at 19:29, Jeff Layton <jlayton@xxxxxxxxxx> wrote:
> 
> On Mon, 2018-10-15 at 09:58 +0800, Yan, Zheng wrote:
>>> On Oct 12, 2018, at 18:56, Jeff Layton <jlayton@xxxxxxxxxx> wrote:
>>> 
>>> On Fri, 2018-10-12 at 10:39 +0800, Yan, Zheng wrote:
>>>>> On Oct 11, 2018, at 22:49, Jeff Layton <jlayton@xxxxxxxxxx> wrote:
>>>>> 
>>>>> On Fri, 2018-09-28 at 17:43 +0800, Yan, Zheng wrote:
>>>>>> This reverts commit 8b8f53af1ed9df88a4c0fbfdf3db58f62060edf3.
>>>>>> 
>>>>>> splice_dentry() is used by three places. For two places, req->r_dentry
>>>>>> is passed to splice_dentry(). In the case of error, req->r_dentry does
>>>>>> not get updated. So splice_dentry() should not drop reference.
>>>>>> 
>>>>>> Cc: stable@xxxxxxxxxxxxxxx #4.18
>>>>>> Signed-off-by: "Yan, Zheng" <zyan@xxxxxxxxxx>
>>>>>> ---
>>>>>> fs/ceph/inode.c | 1 -
>>>>>> 1 file changed, 1 deletion(-)
>>>>>> 
>>>>>> diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
>>>>>> index c6bbb7aa99e4..375924b2bc86 100644
>>>>>> --- a/fs/ceph/inode.c
>>>>>> +++ b/fs/ceph/inode.c
>>>>>> @@ -1140,7 +1140,6 @@ static struct dentry *splice_dentry(struct dentry *dn, struct inode *in)
>>>>>> 	if (IS_ERR(realdn)) {
>>>>>> 		pr_err("splice_dentry error %ld %p inode %p ino %llx.%llx\n",
>>>>>> 		       PTR_ERR(realdn), dn, in, ceph_vinop(in));
>>>>>> -		dput(dn);
>>>>>> 		dn = realdn; /* note realdn contains the error */
>>>>>> 		goto out;
>>>>>> 	} else if (realdn) {
>>>>> 
>>>>> This might be ok, buI have some real concerns about splice_dentry and
>>>>> its callers -- particularly ceph_fill_trace:
>>>>> 
>>>>> We hold a reference to dn on entry to splice_dentry. We then call
>>>>> d_splice_alias and get back an error, and now we don't put the old
>>>>> dentry.
>>>>> 
>>>>> Fine -- we have to then expect the caller to do it. Unfortunately, the
>>>>> callers in ceph_fill_trace do this:
>>>>> 
>>>>>                      dn = splice_dentry(dn, in);                             
>>>>>                      if (IS_ERR(dn)) {                                       
>>>>>                              err = PTR_ERR(dn);                              
>>>>>                              goto done;                                      
>>>>>                      }                                                       
>>>>>                      req->r_dentry = dn;  /* may have spliced */
>>>>> 
>>>>> The old value of dn gets clobbered once that comes back with an ERR_PTR.
>>>>> I guess we could claim that r_dentry will still be set to the old value
>>>>> at that point and that it would get cleaned up when it gets cleaned up. 
>>>>> 
>>>>> But...I see this higher up in ceph_fill_trace at the end of the
>>>>> CEPH_MFS_OP_RENAME condition block:
>>>>> 
>>>>>      dn = req->r_old_dentry;  /* use old_dentry */
>>>>> 
>>>>> So now I'm worried about the case where the splice succeeds. ISTM that
>>>>> "dn" can represent either r_dentry or r_old_dentry at the point where
>>>>> splice_dentry gets called, but we only ever reset the value of r_dentry
>>>>> there.
>>>>> 
>>>>> If dn == r_old_dentry at the time that splice_dentry is called, and then
>>>>> that succeeds, we'll end up leaking the reference to r_dentry and then
>>>>> doing an overput on r_old_dentry.
>>>>> 
>>>> 
>>>> Good catch
>>>> 
>>>>> I think it might help to establish clear "ownership" of the dentry
>>>>> references throughout that function. Consider zeroing out r_dentry and
>>>>> r_old_dentry at the time that you set the local variables? That might
>>>>> make this whole thing less fragile.
>>>> 
>>>> Before the function return, we need to set req->r_dentry again. This will introduce duplicated code because there are several ‘goto’ in the function. How about following change.
>>>> 
>>>> 
>>>> -                       dn = req->r_old_dentry;  /* use old_dentry */
>>>> +                       /* swap r_dentry and r_old_dentry */
>>>> +                       req->r_dentry = req->r_old_dentry;
>>>> +                       req->r_old_dentry = dn;
>>>> +                       dn = req->r_dentry;
>>>> 
>>> 
>>> Why do we need to reset r_dentry? I don't see where it gets used after
>>> ceph_fill_trace returns, so I gather we're just resetting it to clean up
>>> the references? Your proposed fix looks like it would fix that bug. It
>>> will mean that the values in the req will change but maybe that's ok.
>>> 
>> 
>> It’s used by ceph_finish_lookup
>> 
> 
> In that case, it might be bad idea to overwrite the value of r_dentry
> with the r_old_dentry pointer? 

ceph_finish_lookup() is not used in the rename case ;)

Rgeards
Yan, Zheng

> 
>> 
>>> Another idea might be to just keep an extra reference to "dn", and not
>>> reset r_dentry and r_old_dentry? It's a bit more code, but it might be a
>>> cleaner fix, long-term.
>> 
>> I will check, Thanks
>> 
>> Yan, Zheng
>> 
>>> -- 
>>> Jeff Layton <jlayton@xxxxxxxxxx>
>> 
>> 
> 
> -- 
> Jeff Layton <jlayton@xxxxxxxxxx>
> 





[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux