diff mbox series

fuse: Avoid fuse_file_args null pointer dereference

Message ID b923f900-3e09-4c6e-a199-05053376d7c2@fastmail.fm (mailing list archive)
State New, archived
Headers show
Series fuse: Avoid fuse_file_args null pointer dereference | expand

Commit Message

Bernd Schubert April 22, 2024, 5:40 p.m. UTC
The test for NULL was done for the member of union fuse_file_args,
but not for fuse_file_args itself.

Fixes: e26ee4efbc796 ("fuse: allocate ff->release_args only if release is needed")
Signed-off-by: Bernd Schubert <bschubert@ddn.com>

---
I'm currently going through all the recent patches again and noticed
in code review. I guess this falls through testing, because we don't
run xfstests that have !fc->no_opendir || !fc->no_open.

Note: Untested except that it compiles.

Note2: Our IT just broke sendmail, I'm quickly sending through thunderbird,
I hope doesn't change the patch format.

  fs/fuse/file.c |    7 ++++---
  1 file changed, 4 insertions(+), 3 deletions(-)

Comments

Miklos Szeredi April 23, 2024, 10:46 a.m. UTC | #1
On Mon, 22 Apr 2024 at 19:40, Bernd Schubert <bernd.schubert@fastmail.fm> wrote:
>
> The test for NULL was done for the member of union fuse_file_args,
> but not for fuse_file_args itself.
>
> Fixes: e26ee4efbc796 ("fuse: allocate ff->release_args only if release is needed")
> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
>
> ---
> I'm currently going through all the recent patches again and noticed
> in code review. I guess this falls through testing, because we don't
> run xfstests that have !fc->no_opendir || !fc->no_open.
>
> Note: Untested except that it compiles.
>
> Note2: Our IT just broke sendmail, I'm quickly sending through thunderbird,
> I hope doesn't change the patch format.
>
>   fs/fuse/file.c |    7 ++++---
>   1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index b57ce4157640..0ff865457ea6 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -102,7 +102,8 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
>   static void fuse_file_put(struct fuse_file *ff, bool sync)
>   {
>         if (refcount_dec_and_test(&ff->count)) {
> -               struct fuse_release_args *ra = &ff->args->release_args;
> +               struct fuse_release_args *ra =
> +                       ff->args ? &ff->args->release_args : NULL;

While this looks like a NULL pointer dereference, it isn't, because
&foo->bar is just pointer arithmetic, and in this case the pointers
will be identical.  So it will work, but the whole ff->args thing is a
bit confusing.   Not sure how to properly clean this up, your patch
seems to be just adding more obfuscation.

Thanks,
Miklos
Bernd Schubert April 23, 2024, 12:23 p.m. UTC | #2
On 4/23/24 12:46, Miklos Szeredi wrote:
> On Mon, 22 Apr 2024 at 19:40, Bernd Schubert <bernd.schubert@fastmail.fm> wrote:
>>
>> The test for NULL was done for the member of union fuse_file_args,
>> but not for fuse_file_args itself.
>>
>> Fixes: e26ee4efbc796 ("fuse: allocate ff->release_args only if release is needed")
>> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
>>
>> ---
>> I'm currently going through all the recent patches again and noticed
>> in code review. I guess this falls through testing, because we don't
>> run xfstests that have !fc->no_opendir || !fc->no_open.
>>
>> Note: Untested except that it compiles.
>>
>> Note2: Our IT just broke sendmail, I'm quickly sending through thunderbird,
>> I hope doesn't change the patch format.
>>
>>   fs/fuse/file.c |    7 ++++---
>>   1 file changed, 4 insertions(+), 3 deletions(-)
>>
>> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
>> index b57ce4157640..0ff865457ea6 100644
>> --- a/fs/fuse/file.c
>> +++ b/fs/fuse/file.c
>> @@ -102,7 +102,8 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
>>   static void fuse_file_put(struct fuse_file *ff, bool sync)
>>   {
>>         if (refcount_dec_and_test(&ff->count)) {
>> -               struct fuse_release_args *ra = &ff->args->release_args;
>> +               struct fuse_release_args *ra =
>> +                       ff->args ? &ff->args->release_args : NULL;
> 
> While this looks like a NULL pointer dereference, it isn't, because
> &foo->bar is just pointer arithmetic, and in this case the pointers
> will be identical.  So it will work, but the whole ff->args thing is a
> bit confusing.   Not sure how to properly clean this up, your patch
> seems to be just adding more obfuscation.

Hmm, right, I had actually thought about that and written a small test,
before creating the patch. But then had it slightly different - caused
the null-ptr deref.
Updated code works, but UBSAN still complains.


bschubert2@imesrv6 test>./test-union
test-union.c:23:10: runtime error: member access within null pointer of
type 'union bar'
No ptr


cat test-union.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

union bar
{
    int foo;
    int foo2;
};

struct test
{
    union bar *bar;
};

int main(void)
{
    struct test *test = calloc(1, sizeof(test));
    assert(test);

    int *foo_ptr = &test->bar->foo;

    if (foo_ptr)
        printf("Have ptr\n");
    else
        printf("No ptr\n");

    free(test);

    return 0;
}



Thanks,
Bernd
Bernd Schubert April 23, 2024, 2:56 p.m. UTC | #3
On 4/23/24 14:23, Bernd Schubert wrote:
> 
> 
> On 4/23/24 12:46, Miklos Szeredi wrote:
>> On Mon, 22 Apr 2024 at 19:40, Bernd Schubert <bernd.schubert@fastmail.fm> wrote:
>>>
>>> The test for NULL was done for the member of union fuse_file_args,
>>> but not for fuse_file_args itself.
>>>
>>> Fixes: e26ee4efbc796 ("fuse: allocate ff->release_args only if release is needed")
>>> Signed-off-by: Bernd Schubert <bschubert@ddn.com>
>>>
>>> ---
>>> I'm currently going through all the recent patches again and noticed
>>> in code review. I guess this falls through testing, because we don't
>>> run xfstests that have !fc->no_opendir || !fc->no_open.
>>>
>>> Note: Untested except that it compiles.
>>>
>>> Note2: Our IT just broke sendmail, I'm quickly sending through thunderbird,
>>> I hope doesn't change the patch format.
>>>
>>>   fs/fuse/file.c |    7 ++++---
>>>   1 file changed, 4 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
>>> index b57ce4157640..0ff865457ea6 100644
>>> --- a/fs/fuse/file.c
>>> +++ b/fs/fuse/file.c
>>> @@ -102,7 +102,8 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
>>>   static void fuse_file_put(struct fuse_file *ff, bool sync)
>>>   {
>>>         if (refcount_dec_and_test(&ff->count)) {
>>> -               struct fuse_release_args *ra = &ff->args->release_args;
>>> +               struct fuse_release_args *ra =
>>> +                       ff->args ? &ff->args->release_args : NULL;
>>
>> While this looks like a NULL pointer dereference, it isn't, because
>> &foo->bar is just pointer arithmetic, and in this case the pointers
>> will be identical.  So it will work, but the whole ff->args thing is a
>> bit confusing.   Not sure how to properly clean this up, your patch
>> seems to be just adding more obfuscation.
> 
> Hmm, right, I had actually thought about that and written a small test,
> before creating the patch. But then had it slightly different - caused
> the null-ptr deref.
> Updated code works, but UBSAN still complains.
> 
> 
> bschubert2@imesrv6 test>./test-union
> test-union.c:23:10: runtime error: member access within null pointer of
> type 'union bar'
> No ptr
> 
> 
> cat test-union.c
> 
> #include <stdio.h>
> #include <string.h>
> #include <stdlib.h>
> #include <assert.h>
> 
> union bar
> {
>     int foo;
>     int foo2;
> };
> 
> struct test
> {
>     union bar *bar;
> };
> 
> int main(void)
> {
>     struct test *test = calloc(1, sizeof(test));
>     assert(test);
> 
>     int *foo_ptr = &test->bar->foo;
> 
>     if (foo_ptr)
>         printf("Have ptr\n");
>     else
>         printf("No ptr\n");
> 
>     free(test);
> 
>     return 0;
> }

Tested with an UBSAN enabled kernel and libfuse-hacked-in-no-opendir. No
UBSAN warning - so sorry for the noise!
At least I learned that libfuse by default doesn't deactivate
open/opendir, I had never looked into that before.


Thanks,
Bernd
diff mbox series

Patch

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index b57ce4157640..0ff865457ea6 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -102,7 +102,8 @@  static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
  static void fuse_file_put(struct fuse_file *ff, bool sync)
  {
  	if (refcount_dec_and_test(&ff->count)) {
-		struct fuse_release_args *ra = &ff->args->release_args;
+		struct fuse_release_args *ra =
+			ff->args ? &ff->args->release_args : NULL;
  		struct fuse_args *args = (ra ? &ra->args : NULL);
  
  		if (ra && ra->inode)
@@ -292,7 +293,7 @@  static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
  				 unsigned int flags, int opcode, bool sync)
  {
  	struct fuse_conn *fc = ff->fm->fc;
-	struct fuse_release_args *ra = &ff->args->release_args;
+	struct fuse_release_args *ra = ff->args ? &ff->args->release_args : NULL;
  
  	if (fuse_file_passthrough(ff))
  		fuse_passthrough_release(ff, fuse_inode_backing(fi));
@@ -337,7 +338,7 @@  void fuse_file_release(struct inode *inode, struct fuse_file *ff,
  		       unsigned int open_flags, fl_owner_t id, bool isdir)
  {
  	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct fuse_release_args *ra = &ff->args->release_args;
+	struct fuse_release_args *ra = ff->args ? &ff->args->release_args : NULL;
  	int opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
  
  	fuse_prepare_release(fi, ff, open_flags, opcode, false);