diff mbox series

[v3] xfs_db: release ip resource before returning from get_next_unlinked()

Message ID 20240807193801.248101-3-bodonnel@redhat.com (mailing list archive)
State Superseded, archived
Headers show
Series [v3] xfs_db: release ip resource before returning from get_next_unlinked() | expand

Commit Message

Bill O'Donnell Aug. 7, 2024, 7:38 p.m. UTC
Fix potential memory leak in function get_next_unlinked(). Call
libxfs_irele(ip) before exiting.

Details:
Error: RESOURCE_LEAK (CWE-772):
xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
#   74|   	libxfs_buf_relse(ino_bp);
#   75|
#   76|-> 	return ret;
#   77|   bad:
#   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));

Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
---
v2: cover error case.
v3: fix coverage to not release unitialized variable.
---
 db/iunlink.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

Comments

Christoph Hellwig Aug. 8, 2024, 2:14 p.m. UTC | #1
Looks good:

Reviewed-by: Christoph Hellwig <hch@lst.de>
Darrick J. Wong Aug. 8, 2024, 6:28 p.m. UTC | #2
On Wed, Aug 07, 2024 at 02:38:03PM -0500, Bill O'Donnell wrote:
> Fix potential memory leak in function get_next_unlinked(). Call
> libxfs_irele(ip) before exiting.
> 
> Details:
> Error: RESOURCE_LEAK (CWE-772):
> xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
> xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
> xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
> #   74|   	libxfs_buf_relse(ino_bp);
> #   75|
> #   76|-> 	return ret;
> #   77|   bad:
> #   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
> 
> Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
> ---
> v2: cover error case.
> v3: fix coverage to not release unitialized variable.
> ---
>  db/iunlink.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/db/iunlink.c b/db/iunlink.c
> index d87562e3..57e51140 100644
> --- a/db/iunlink.c
> +++ b/db/iunlink.c
> @@ -66,15 +66,18 @@ get_next_unlinked(
>  	}
>  
>  	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
> -	if (error)
> +	if (error) {
> +		libxfs_buf_relse(ino_bp);

Sorry, I think I've led you astray -- it's not necessary to
libxfs_buf_relse in any of the bailouts.

--D

>  		goto bad;
> -
> +	}
>  	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
>  	ret = be32_to_cpu(dip->di_next_unlinked);
>  	libxfs_buf_relse(ino_bp);
> +	libxfs_irele(ip);
>  
>  	return ret;
>  bad:
> +	libxfs_irele(ip);
>  	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
>  	return NULLAGINO;
>  }
> -- 
> 2.45.2
> 
>
Eric Sandeen Aug. 8, 2024, 7 p.m. UTC | #3
On 8/8/24 1:28 PM, Darrick J. Wong wrote:
> On Wed, Aug 07, 2024 at 02:38:03PM -0500, Bill O'Donnell wrote:
>> Fix potential memory leak in function get_next_unlinked(). Call
>> libxfs_irele(ip) before exiting.
>>
>> Details:
>> Error: RESOURCE_LEAK (CWE-772):
>> xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
>> xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
>> xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
>> #   74|   	libxfs_buf_relse(ino_bp);
>> #   75|
>> #   76|-> 	return ret;
>> #   77|   bad:
>> #   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
>>
>> Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
>> ---
>> v2: cover error case.
>> v3: fix coverage to not release unitialized variable.
>> ---
>>  db/iunlink.c | 7 +++++--
>>  1 file changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/db/iunlink.c b/db/iunlink.c
>> index d87562e3..57e51140 100644
>> --- a/db/iunlink.c
>> +++ b/db/iunlink.c
>> @@ -66,15 +66,18 @@ get_next_unlinked(
>>  	}
>>  
>>  	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
>> -	if (error)
>> +	if (error) {
>> +		libxfs_buf_relse(ino_bp);
> 
> Sorry, I think I've led you astray -- it's not necessary to
> libxfs_buf_relse in any of the bailouts.
> 
> --D
> 
>>  		goto bad;
>> -
>> +	}
>>  	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
>>  	ret = be32_to_cpu(dip->di_next_unlinked);
>>  	libxfs_buf_relse(ino_bp);
>> +	libxfs_irele(ip);
>>  
>>  	return ret;
>>  bad:
>> +	libxfs_irele(ip);

And this addition results in a libxfs_irele of an ip() which failed iget()
via the first goto bad, so you're releasing a thing which was never obtained,
which doesn't make sense.


There are 2 relevant actions here. The libxfs_iget, and the libxfs_imap_to_bp.
Only after libxfs_iget(ip) /succeeds/ does it need a libxfs_irele(ip), on either
error paths or normal exit. The fact that it does neither leads to the two leaks
noted in CID 1554242.

libxfs_imap_to_bp needs a corresponding libxfs_buf_relse() (thanks for clarifying
djwong) but that libxfs_buf_relse() is already present if libxfs_imap_to_bp
succeeds. It's not needed if it fails, because there's nothing to release.

When Darrick said

> I think this needs to cover the error return for libxfs_imap_to_bp too,
> doesn't it?

I think he meant that in the error case where libxfs_imap_to_bp fails, libxfs_irele
is also needed. (In addition to being needed on a normal return.)

-Eric
Bill O'Donnell Aug. 8, 2024, 7:41 p.m. UTC | #4
On Thu, Aug 08, 2024 at 02:00:22PM -0500, Eric Sandeen wrote:
> On 8/8/24 1:28 PM, Darrick J. Wong wrote:
> > On Wed, Aug 07, 2024 at 02:38:03PM -0500, Bill O'Donnell wrote:
> >> Fix potential memory leak in function get_next_unlinked(). Call
> >> libxfs_irele(ip) before exiting.
> >>
> >> Details:
> >> Error: RESOURCE_LEAK (CWE-772):
> >> xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
> >> xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
> >> xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
> >> #   74|   	libxfs_buf_relse(ino_bp);
> >> #   75|
> >> #   76|-> 	return ret;
> >> #   77|   bad:
> >> #   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
> >>
> >> Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
> >> ---
> >> v2: cover error case.
> >> v3: fix coverage to not release unitialized variable.
> >> ---
> >>  db/iunlink.c | 7 +++++--
> >>  1 file changed, 5 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/db/iunlink.c b/db/iunlink.c
> >> index d87562e3..57e51140 100644
> >> --- a/db/iunlink.c
> >> +++ b/db/iunlink.c
> >> @@ -66,15 +66,18 @@ get_next_unlinked(
> >>  	}
> >>  
> >>  	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
> >> -	if (error)
> >> +	if (error) {
> >> +		libxfs_buf_relse(ino_bp);
> > 
> > Sorry, I think I've led you astray -- it's not necessary to
> > libxfs_buf_relse in any of the bailouts.
> > 
> > --D
> > 
> >>  		goto bad;
> >> -
> >> +	}
> >>  	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
> >>  	ret = be32_to_cpu(dip->di_next_unlinked);
> >>  	libxfs_buf_relse(ino_bp);
> >> +	libxfs_irele(ip);
> >>  
> >>  	return ret;
> >>  bad:
> >> +	libxfs_irele(ip);
> 
> And this addition results in a libxfs_irele of an ip() which failed iget()
> via the first goto bad, so you're releasing a thing which was never obtained,
> which doesn't make sense.
> 
> 
> There are 2 relevant actions here. The libxfs_iget, and the libxfs_imap_to_bp.
> Only after libxfs_iget(ip) /succeeds/ does it need a libxfs_irele(ip), on either
> error paths or normal exit. The fact that it does neither leads to the two leaks
> noted in CID 1554242.

In libxfs_iget, -ENOMEM is returned when kmem_cache_zalloc() fails. For all other
error cases in that function, kmem_cache_free() releases the memory that was presumably
successfully allocated. I had wondered if we need to use libxfs_irele() at all in
get_next_unlinked() (except for the success case)?


> libxfs_imap_to_bp needs a corresponding libxfs_buf_relse() (thanks for clarifying
> djwong) but that libxfs_buf_relse() is already present if libxfs_imap_to_bp
> succeeds. It's not needed if it fails, because there's nothing to release.
> 
> When Darrick said
> 
> > I think this needs to cover the error return for libxfs_imap_to_bp too,
> > doesn't it?
> 
> I think he meant that in the error case where libxfs_imap_to_bp fails, libxfs_irele
> is also needed. (In addition to being needed on a normal return.)
> 
> -Eric
>
Bill O'Donnell Aug. 8, 2024, 9:13 p.m. UTC | #5
On Thu, Aug 08, 2024 at 02:41:39PM -0500, Bill O'Donnell wrote:
> On Thu, Aug 08, 2024 at 02:00:22PM -0500, Eric Sandeen wrote:
> > On 8/8/24 1:28 PM, Darrick J. Wong wrote:
> > > On Wed, Aug 07, 2024 at 02:38:03PM -0500, Bill O'Donnell wrote:
> > >> Fix potential memory leak in function get_next_unlinked(). Call
> > >> libxfs_irele(ip) before exiting.
> > >>
> > >> Details:
> > >> Error: RESOURCE_LEAK (CWE-772):
> > >> xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
> > >> xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
> > >> xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
> > >> #   74|   	libxfs_buf_relse(ino_bp);
> > >> #   75|
> > >> #   76|-> 	return ret;
> > >> #   77|   bad:
> > >> #   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
> > >>
> > >> Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
> > >> ---
> > >> v2: cover error case.
> > >> v3: fix coverage to not release unitialized variable.
> > >> ---
> > >>  db/iunlink.c | 7 +++++--
> > >>  1 file changed, 5 insertions(+), 2 deletions(-)
> > >>
> > >> diff --git a/db/iunlink.c b/db/iunlink.c
> > >> index d87562e3..57e51140 100644
> > >> --- a/db/iunlink.c
> > >> +++ b/db/iunlink.c
> > >> @@ -66,15 +66,18 @@ get_next_unlinked(
> > >>  	}
> > >>  
> > >>  	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
> > >> -	if (error)
> > >> +	if (error) {
> > >> +		libxfs_buf_relse(ino_bp);
> > > 
> > > Sorry, I think I've led you astray -- it's not necessary to
> > > libxfs_buf_relse in any of the bailouts.
> > > 
> > > --D
> > > 
> > >>  		goto bad;
> > >> -
> > >> +	}
> > >>  	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
> > >>  	ret = be32_to_cpu(dip->di_next_unlinked);
> > >>  	libxfs_buf_relse(ino_bp);
> > >> +	libxfs_irele(ip);
> > >>  
> > >>  	return ret;
> > >>  bad:
> > >> +	libxfs_irele(ip);
> > 
> > And this addition results in a libxfs_irele of an ip() which failed iget()
> > via the first goto bad, so you're releasing a thing which was never obtained,
> > which doesn't make sense.
> > 
> > 
> > There are 2 relevant actions here. The libxfs_iget, and the libxfs_imap_to_bp.
> > Only after libxfs_iget(ip) /succeeds/ does it need a libxfs_irele(ip), on either
> > error paths or normal exit. The fact that it does neither leads to the two leaks
> > noted in CID 1554242.
> 
> In libxfs_iget, -ENOMEM is returned when kmem_cache_zalloc() fails. For all other
> error cases in that function, kmem_cache_free() releases the memory that was presumably
> successfully allocated. I had wondered if we need to use libxfs_irele() at all in
> get_next_unlinked() (except for the success case)?

So, if that's the case, I'm back to v1 of this patch.
-Bill

> 
> 
> > libxfs_imap_to_bp needs a corresponding libxfs_buf_relse() (thanks for clarifying
> > djwong) but that libxfs_buf_relse() is already present if libxfs_imap_to_bp
> > succeeds. It's not needed if it fails, because there's nothing to release.
> > 
> > When Darrick said
> > 
> > > I think this needs to cover the error return for libxfs_imap_to_bp too,
> > > doesn't it?
> > 
> > I think he meant that in the error case where libxfs_imap_to_bp fails, libxfs_irele
> > is also needed. (In addition to being needed on a normal return.)
> > 
> > -Eric
> > 
> 
>
Eric Sandeen Aug. 9, 2024, 2:04 a.m. UTC | #6
On 8/8/24 4:13 PM, Bill O'Donnell wrote:
> On Thu, Aug 08, 2024 at 02:41:39PM -0500, Bill O'Donnell wrote:
>> On Thu, Aug 08, 2024 at 02:00:22PM -0500, Eric Sandeen wrote:
>>> On 8/8/24 1:28 PM, Darrick J. Wong wrote:
>>>> On Wed, Aug 07, 2024 at 02:38:03PM -0500, Bill O'Donnell wrote:
>>>>> Fix potential memory leak in function get_next_unlinked(). Call
>>>>> libxfs_irele(ip) before exiting.
>>>>>
>>>>> Details:
>>>>> Error: RESOURCE_LEAK (CWE-772):
>>>>> xfsprogs-6.5.0/db/iunlink.c:51:2: alloc_arg: "libxfs_iget" allocates memory that is stored into "ip".
>>>>> xfsprogs-6.5.0/db/iunlink.c:68:2: noescape: Resource "&ip->i_imap" is not freed or pointed-to in "libxfs_imap_to_bp".
>>>>> xfsprogs-6.5.0/db/iunlink.c:76:2: leaked_storage: Variable "ip" going out of scope leaks the storage it points to.
>>>>> #   74|   	libxfs_buf_relse(ino_bp);
>>>>> #   75|
>>>>> #   76|-> 	return ret;
>>>>> #   77|   bad:
>>>>> #   78|   	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
>>>>>
>>>>> Signed-off-by: Bill O'Donnell <bodonnel@redhat.com>
>>>>> ---
>>>>> v2: cover error case.
>>>>> v3: fix coverage to not release unitialized variable.
>>>>> ---
>>>>>  db/iunlink.c | 7 +++++--
>>>>>  1 file changed, 5 insertions(+), 2 deletions(-)
>>>>>
>>>>> diff --git a/db/iunlink.c b/db/iunlink.c
>>>>> index d87562e3..57e51140 100644
>>>>> --- a/db/iunlink.c
>>>>> +++ b/db/iunlink.c
>>>>> @@ -66,15 +66,18 @@ get_next_unlinked(
>>>>>  	}
>>>>>  
>>>>>  	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
>>>>> -	if (error)
>>>>> +	if (error) {
>>>>> +		libxfs_buf_relse(ino_bp);
>>>>
>>>> Sorry, I think I've led you astray -- it's not necessary to
>>>> libxfs_buf_relse in any of the bailouts.
>>>>
>>>> --D
>>>>
>>>>>  		goto bad;
>>>>> -
>>>>> +	}
>>>>>  	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
>>>>>  	ret = be32_to_cpu(dip->di_next_unlinked);
>>>>>  	libxfs_buf_relse(ino_bp);
>>>>> +	libxfs_irele(ip);
>>>>>  
>>>>>  	return ret;
>>>>>  bad:
>>>>> +	libxfs_irele(ip);
>>>
>>> And this addition results in a libxfs_irele of an ip() which failed iget()
>>> via the first goto bad, so you're releasing a thing which was never obtained,
>>> which doesn't make sense.
>>>
>>>
>>> There are 2 relevant actions here. The libxfs_iget, and the libxfs_imap_to_bp.
>>> Only after libxfs_iget(ip) /succeeds/ does it need a libxfs_irele(ip), on either
>>> error paths or normal exit. The fact that it does neither leads to the two leaks
>>> noted in CID 1554242.
>>
>> In libxfs_iget, -ENOMEM is returned when kmem_cache_zalloc() fails. For all other
>> error cases in that function, kmem_cache_free() releases the memory that was presumably
>> successfully allocated. I had wondered if we need to use libxfs_irele() at all in
>> get_next_unlinked() (except for the success case)?
> 
> So, if that's the case, I'm back to v1 of this patch.
> -Bill

when libxfs_iget succeeds, it has obtained an inode.

After that has happened, libxfs_irele needs to be called either in the normal
return path, or any subsequent error path, to free that inode in this function.

As Darrick pointed out in his first reply, V1 missed irele on the error path,
so it was not sufficient.

-Eric
diff mbox series

Patch

diff --git a/db/iunlink.c b/db/iunlink.c
index d87562e3..57e51140 100644
--- a/db/iunlink.c
+++ b/db/iunlink.c
@@ -66,15 +66,18 @@  get_next_unlinked(
 	}
 
 	error = -libxfs_imap_to_bp(mp, NULL, &ip->i_imap, &ino_bp);
-	if (error)
+	if (error) {
+		libxfs_buf_relse(ino_bp);
 		goto bad;
-
+	}
 	dip = xfs_buf_offset(ino_bp, ip->i_imap.im_boffset);
 	ret = be32_to_cpu(dip->di_next_unlinked);
 	libxfs_buf_relse(ino_bp);
+	libxfs_irele(ip);
 
 	return ret;
 bad:
+	libxfs_irele(ip);
 	dbprintf(_("AG %u agino %u: %s\n"), agno, agino, strerror(error));
 	return NULLAGINO;
 }