diff mbox

[for-next,V5,1/5] IB/uverbs: Fix reference counting usage of event files

Message ID 1434984438-21733-2-git-send-email-yishaih@mellanox.com (mailing list archive)
State Superseded
Headers show

Commit Message

Yishai Hadas June 22, 2015, 2:47 p.m. UTC
Fix the reference counting usage to be handled in the event file
creation/destruction function, instead of being done by the caller.
This is done for both async/non-async event files.

Based on Jason Gunthorpe report at https://www.mail-archive.com/
linux-rdma@vger.kernel.org/msg24680.html:
"The existing code for this is broken, in ib_uverbs_get_context all
the error paths between ib_uverbs_alloc_event_file and the
kref_get(file->ref) are wrong - this will result in fput() which will
call ib_uverbs_event_close, which will try to do kref_put and
ib_unregister_event_handler - which are no longer paired."

Signed-off-by: Yishai Hadas <yishaih@mellanox.com>
Signed-off-by: Shachar Raindel <raindel@mellanox.com>
---
 drivers/infiniband/core/uverbs.h      |    1 +
 drivers/infiniband/core/uverbs_cmd.c  |   11 +--------
 drivers/infiniband/core/uverbs_main.c |   41 ++++++++++++++++++++++++++++-----
 3 files changed, 37 insertions(+), 16 deletions(-)

Comments

Jason Gunthorpe June 24, 2015, 5:57 p.m. UTC | #1
On Mon, Jun 22, 2015 at 05:47:14PM +0300, Yishai Hadas wrote:
>  	fd_install(resp.async_fd, filp);
> @@ -386,6 +376,7 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
>  	return in_len;
>  
>  err_file:
> +	ib_uverbs_free_async_event_file(file);
>  	fput(filp);

This looks really weird. 

> +void ib_uverbs_free_async_event_file(struct ib_uverbs_file *file)
> +{
> +	kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
> +	file->async_file = NULL;
> +}

So that put is supposed to pair with this get?

> +		uverbs_file->async_file = ev_file;
> +		kref_get(&uverbs_file->async_file->ref);

[..]

> +	fput(filp);
> +	uverbs_file->async_file = NULL;

But isn't it null?

Again again, WTF? async_file is a kref'd thing, if you copy or assign
to it you need to manipulate the kref, so the null assign should be
dropping the ref.

Whis looks good enough to remove ib_uverbs_free_async_event_file, if
the flip is created OK then the uverbs_file->async file can remain set
until the uverbs file is closed.

Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Yishai Hadas June 25, 2015, 11:46 a.m. UTC | #2
On 6/24/2015 8:57 PM, Jason Gunthorpe wrote:
> On Mon, Jun 22, 2015 at 05:47:14PM +0300, Yishai Hadas wrote:
>>   	fd_install(resp.async_fd, filp);
>> @@ -386,6 +376,7 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
>>   	return in_len;
>>
>>   err_file:
>> +	ib_uverbs_free_async_event_file(file);
>>   	fput(filp);
>
> This looks really weird.

We need to cleanup all by that step otherwise we might leak that async 
file, see my last comment below which clarifies that point.
As ib_uverbs_release_event_file is static function in uverbs_main it 
makes sense to expose this cleanup function similar to 
ib_uverbs_alloc_event_file which is exposed from uverbs_main and make 
the allocation.

>> +void ib_uverbs_free_async_event_file(struct ib_uverbs_file *file)
>> +{
>> +	kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
>> +	file->async_file = NULL;
>> +}
>
> So that put is supposed to pair with this get?
Yes

>
>> +		uverbs_file->async_file = ev_file;
>> +		kref_get(&uverbs_file->async_file->ref);
>
> [..]
>
>> +	fput(filp);
>> +	uverbs_file->async_file = NULL;
>
> But isn't it null?
>

No, this NULL is set when ib_uverbs_alloc_event_file failed internally, 
the above flow occurs when file was created OK but later it fails via 
copy_to_user.

> Again again, WTF? async_file is a kref'd thing, if you copy or assign
> to it you need to manipulate the kref, so the null assign should be
> dropping the ref.
>

The logic in ib_uverbs_alloc_event_file if kref oriented, the original 
code which uses free didn't follow this convention, currently in case 
ib_uverbs_alloc_event_file failed internally it uses 
kref_put(&ev_file->ref, ib_uverbs_release_event_file) which internally 
frees the memory, in case we had an error after that the file was 
created (e.g ib_register_event_handler) need to use fput(filp) to make 
an extra cleanup which again uses ref count handling.
The uverbs_file->async_file = NULL command just cleans the previous 
setting of uverbs_file->async_file = ev_file that was done before to 
prevent it be cleanup when the uverbs_file itself is closed.

> Whis looks good enough to remove ib_uverbs_free_async_event_file, if
> the flip is created OK then the uverbs_file->async file can remain set
> until the uverbs file is closed.

This is wrong, as file->ucontext is NULL because of the failure in 
copy_to_user, application might recall to ib_uverbs_get_context in that 
case we leak that previous async_file, that's why the code must cleanup 
and put NULL by that step. To your request put 
WARN_ON(uverbs_file->async_file) as part of ib_uverbs_alloc_event_file 
to make sure that we don't hit that case.

This patch fixes a bug that currently exists upstream that you pointed 
on that may cause an oops, hope that above clarifies the issues and you 
can ACK on.



--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jason Gunthorpe June 25, 2015, 5:52 p.m. UTC | #3
On Thu, Jun 25, 2015 at 02:46:02PM +0300, Yishai Hadas wrote:
> On 6/24/2015 8:57 PM, Jason Gunthorpe wrote:
> >On Mon, Jun 22, 2015 at 05:47:14PM +0300, Yishai Hadas wrote:
> >>  	fd_install(resp.async_fd, filp);
> >>@@ -386,6 +376,7 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
> >>  	return in_len;
> >>
> >>  err_file:
> >>+	ib_uverbs_free_async_event_file(file);
> >>  	fput(filp);
> >
> >This looks really weird.
> 
> We need to cleanup all by that step otherwise we might leak that async file,
> see my last comment below which clarifies that point.
> As ib_uverbs_release_event_file is static function in uverbs_main it makes
> sense to expose this cleanup function similar to ib_uverbs_alloc_event_file
> which is exposed from uverbs_main and make the allocation.

Okay, sure, that makes sense.

> >Again again, WTF? async_file is a kref'd thing, if you copy or assign
> >to it you need to manipulate the kref, so the null assign should be
> >dropping the ref.
>
> The logic in ib_uverbs_alloc_event_file if kref oriented, the original code
> which uses free didn't follow this convention, currently in case

Yes, and you fixed it, this is good:

@@ -557,15 +563,38 @@  struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
	ev_file = kmalloc(sizeof *ev_file, GFP_KERNEL);
	kref_init(&ev_file->ref);
        if (IS_ERR(filp))
+		goto err;
+err:
+	kref_put(&ev_file->ref, ib_uverbs_release_event_file);

The kref_init and the kref_put are a matching pair, great!

> had an error after that the file was created (e.g ib_register_event_handler)
> need to use fput(filp) to make an extra cleanup which again uses ref count
> handling.

Yep, that is a good idea too. But understand what it means,

When this happens:
 	filp = anon_inode_getfile("[infinibandevent]", &uverbs_event_fops,
 				  ev_file, O_RDONLY);
The unref pairs for these gets:
	kref_init(&ev_file->ref);
+	kref_get(&uverbs_file->ref);

Are logically moved into flip, and now fput(flip) will call
ib_uverbs_event_close() and both those krefs will be released.

For this reason, for clarity, I would also move the
'kref_get(&uverbs_file->ref);' to be before anon_inode_getfile()

Which means this:

@@ -557,15 +563,38 @@  struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
+	kref_get(&uverbs_file->ref);
+	if (is_async) {
+		if (ret)
+			goto put_file;
+put_file:
+	fput(filp);
+err:
+	kref_put(&ev_file->ref, ib_uverbs_release_event_file);

Is a double put on ev_file, since fput -> ib_uverbs_event_close does one, and
then err does another. (remember, the ref was moved into the flip)

Next:

+	if (is_async) {
+		uverbs_file->async_file = ev_file;
+		kref_get(&uverbs_file->async_file->ref);
+		if (ret)
+			goto put_file;
+put_file:
+	uverbs_file->async_file = NULL;

Where is the paired kref_put? There are two I can see:

+void ib_uverbs_free_async_event_file(struct ib_uverbs_file *file)
+{
+	kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
-----
static int ib_uverbs_close(struct inode *inode, struct file *filp)
		kref_put(&file->async_file->ref, ib_uverbs_release_event_file);

As far as I can tell, neither of these are called, so we have an
unbalanced put.

Which brings me back to my statement:

> >Again again, WTF? async_file is a kref'd thing, if you copy or assign
> >to it you need to manipulate the kref, so the null assign should be
> >dropping the ref.

So, the two possible fixes are
1) Make the call to ib_uverbs_free_async_event_file() happen even if
   ib_uverbs_alloc_event_file() fails
2) Do in ib_uverbs_alloc_event_file
+	kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_event_file);
+	uverbs_file->async_file = NULL;
   Which may as well just be a call to ib_uverbs_free_async_event_file()

In either situation the naked async_file = NULL is fixed to have a
kref beside it.

This is basic invariant of how kref works. Read the 'rules' in
Documentation/kref.txt

So, as I reviewer, I see a kref'd variable (async_file) being
manipulated without corresponding kref code. I *KNOW* something is
wrong. You answer didn't address this, you needed to ID where the
pair'd kref_put was and justify having the '= NULL' so far away from
it.

> >Whis looks good enough to remove ib_uverbs_free_async_event_file, if
> >the flip is created OK then the uverbs_file->async file can remain set
> >until the uverbs file is closed.
> 
> This is wrong, as file->ucontext is NULL because of the failure in
> copy_to_user, application might recall to ib_uverbs_get_context in that case
> we leak that previous async_file, that's why the code must cleanup and put
> NULL by that step. To your request put WARN_ON(uverbs_file->async_file) as
> part of ib_uverbs_alloc_event_file to make sure that we don't hit that case.

Yes, that makes sense.

Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Yishai Hadas June 28, 2015, 2:33 p.m. UTC | #4
On 6/25/2015 8:52 PM, Jason Gunthorpe wrote:
> On Thu, Jun 25, 2015 at 02:46:02PM +0300, Yishai Hadas wrote:
>> On 6/24/2015 8:57 PM, Jason Gunthorpe wrote:
>>> On Mon, Jun 22, 2015 at 05:47:14PM +0300, Yishai Hadas wrote:
>>>>   	fd_install(resp.async_fd, filp);
>>>> @@ -386,6 +376,7 @@ ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
>>>>   	return in_len;
>>>>
>>>>   err_file:
>>>> +	ib_uverbs_free_async_event_file(file);
>>>>   	fput(filp);
>>>
>>> This looks really weird.
>>
>> We need to cleanup all by that step otherwise we might leak that async file,
>> see my last comment below which clarifies that point.
>> As ib_uverbs_release_event_file is static function in uverbs_main it makes
>> sense to expose this cleanup function similar to ib_uverbs_alloc_event_file
>> which is exposed from uverbs_main and make the allocation.
>
> Okay, sure, that makes sense.
>
>>> Again again, WTF? async_file is a kref'd thing, if you copy or assign
>>> to it you need to manipulate the kref, so the null assign should be
>>> dropping the ref.
>>
>> The logic in ib_uverbs_alloc_event_file if kref oriented, the original code
>> which uses free didn't follow this convention, currently in case
>
> Yes, and you fixed it, this is good:
>
> @@ -557,15 +563,38 @@  struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
> 	ev_file = kmalloc(sizeof *ev_file, GFP_KERNEL);
> 	kref_init(&ev_file->ref);
>          if (IS_ERR(filp))
> +		goto err;
> +err:
> +	kref_put(&ev_file->ref, ib_uverbs_release_event_file);
>
> The kref_init and the kref_put are a matching pair, great!
>
>> had an error after that the file was created (e.g ib_register_event_handler)
>> need to use fput(filp) to make an extra cleanup which again uses ref count
>> handling.
>
> Yep, that is a good idea too. But understand what it means,
>
> When this happens:
>   	filp = anon_inode_getfile("[infinibandevent]", &uverbs_event_fops,
>   				  ev_file, O_RDONLY);
> The unref pairs for these gets:
> 	kref_init(&ev_file->ref);
> +	kref_get(&uverbs_file->ref);
>
> Are logically moved into flip, and now fput(flip) will call
> ib_uverbs_event_close() and both those krefs will be released.
>
> For this reason, for clarity, I would also move the
> 'kref_get(&uverbs_file->ref);' to be before anon_inode_getfile()

Taking kref_get(&uverbs_file->ref) before anon_inode_getfile has 
succeeded is not correct, only when anon_inode_getfile succeeds we can 
call fput(filp) which internally makes the kref_get on the uverbs_file 
as part of ib_uverbs_event_close. In current code we take ref count on 
the ev_file as part kref_init(&ev_file->ref) which is put as part of the 
"err" label
err:
	kref_put(&ev_file->ref, ib_uverbs_release_event_file);
In case anon_inode_getfile has succeeded we take also the ref_count on 
the uverbs_file which will be put back upon closing the file and the 
kref put/get are fully symmetric. For the async case we also fully 
symmetric here, see next comment below.


> Which means this:
>
> @@ -557,15 +563,38 @@  struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
> +	kref_get(&uverbs_file->ref);
> +	if (is_async) {
> +		if (ret)
> +			goto put_file;
> +put_file:
> +	fput(filp);
> +err:
> +	kref_put(&ev_file->ref, ib_uverbs_release_event_file);
>
> Is a double put on ev_file, since fput -> ib_uverbs_event_close does one, and
> then err does another. (remember, the ref was moved into the flip)
>
> Next:
>
> +	if (is_async) {
> +		uverbs_file->async_file = ev_file;
> +		kref_get(&uverbs_file->async_file->ref);
> +		if (ret)
> +			goto put_file;
> +put_file:
> +	uverbs_file->async_file = NULL;
>
> Where is the paired kref_put? There are two I can see:
>
> +void ib_uverbs_free_async_event_file(struct ib_uverbs_file *file)
> +{
> +	kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
> -----
> static int ib_uverbs_close(struct inode *inode, struct file *filp)
> 		kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
>
> As far as I can tell, neither of these are called, so we have an
> unbalanced put.

You are wrong here, we have here balanced put, the first is done as part 
of fput(filp) -> ib_uverbs_event_close_file -> kref_put(&file->ref, 
ib_uverbs_release_event_file) and the second at the end of this function 
as part of the err: label kref_put(&ev_file->ref, 
ib_uverbs_release_event_file);




> So, as I reviewer, I see a kref'd variable (async_file) being
> manipulated without corresponding kref code. I *KNOW* something is
> wrong. You answer didn't address this, you needed to ID where the
> pair'd kref_put was and justify having the '= NULL' so far away from
> it.

See my comment above, we have here pair kref_put, the 
uverbs_file->async_file = NULL comes to prevent a third in-correct 
kref_put as part of ib_uverbs_close.

Please review and let me know whether you can ACK on this patch.

--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jason Gunthorpe June 29, 2015, 5:40 p.m. UTC | #5
On Sun, Jun 28, 2015 at 05:33:04PM +0300, Yishai Hadas wrote:

> You are wrong here, we have here balanced put, the first is done as
> part of fput(filp) -> ib_uverbs_event_close_file ->
> kref_put(&file->ref, ib_uverbs_release_event_file) and the second at
> the end of this function as part of the err: label
> kref_put(&ev_file->ref, ib_uverbs_release_event_file);

Ugh, that is so gross, seriously? Sure, the counts work out, but that
is *totally unreadable*.

Sigh, just use this:

 struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
                                        int is_async)
 {
        struct ib_uverbs_event_file *ev_file;
        struct file *filp;
+       int ret;
 
-       ev_file = kmalloc(sizeof *ev_file, GFP_KERNEL);
+       ev_file = kzalloc(sizeof(*ev_file), GFP_KERNEL);
        if (!ev_file)
                return ERR_PTR(-ENOMEM);
 
@@ -555,15 +561,41 @@ struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
        INIT_LIST_HEAD(&ev_file->event_list);
        init_waitqueue_head(&ev_file->poll_wait);
        ev_file->uverbs_file = uverbs_file;
+       kref_get(&ev_file->uverbs_file->ref);
        ev_file->async_queue = NULL;
-       ev_file->is_async    = is_async;
        ev_file->is_closed   = 0;
 
        filp = anon_inode_getfile("[infinibandevent]", &uverbs_event_fops,
                                  ev_file, O_RDONLY);
        if (IS_ERR(filp))
-               kfree(ev_file);
+               goto err_put_refs;
+
+       if (is_async) {
+               WARN_ON(uverbs_file->async_file);
+               uverbs_file->async_file = ev_file;
+               kref_get(&uverbs_file->async_file->ref);
+               INIT_IB_EVENT_HANDLER(&uverbs_file->event_handler,
+                                     uverbs_file->device->ib_dev,
+                                     ib_uverbs_event_handler);
+               ret = ib_register_event_handler(&uverbs_file->event_handler);
+               if (ret)
+                       goto err_put_file;
+
+               /* At that point async file stuff was fully set */
+               ev_file->is_async = 1;
+       }
+
+       return filp;
+
+err_put_file:
+       fput(filp);
+       kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_file);
+       uverbs_file->async_file = NULL;
+       return ERR_PTR(ret);
 
+err_put_refs:
+       kref_put(&ev_file->uverbs_file->ref, ib_uverbs_release_file);
+       kref_put(&ev_file->ref, ib_uverbs_release_event_file);
        return filp;
 }

Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Yishai Hadas June 29, 2015, 9:22 p.m. UTC | #6
On 6/29/2015 8:40 PM, Jason Gunthorpe wrote:
> On Sun, Jun 28, 2015 at 05:33:04PM +0300, Yishai Hadas wrote:
>
>> You are wrong here, we have here balanced put, the first is done as
>> part of fput(filp) -> ib_uverbs_event_close_file ->
>> kref_put(&file->ref, ib_uverbs_release_event_file) and the second at
>> the end of this function as part of the err: label
>> kref_put(&ev_file->ref, ib_uverbs_release_event_file);
>
> Ugh, that is so gross, seriously? Sure, the counts work out, but that
> is *totally unreadable*.
>
> Sigh, just use this:
>
>   struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
>                                          int is_async)
>   {
>          struct ib_uverbs_event_file *ev_file;
>          struct file *filp;
> +       int ret;
>
> -       ev_file = kmalloc(sizeof *ev_file, GFP_KERNEL);
> +       ev_file = kzalloc(sizeof(*ev_file), GFP_KERNEL);
>          if (!ev_file)
>                  return ERR_PTR(-ENOMEM);
>
> @@ -555,15 +561,41 @@ struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
>          INIT_LIST_HEAD(&ev_file->event_list);
>          init_waitqueue_head(&ev_file->poll_wait);
>          ev_file->uverbs_file = uverbs_file;
> +       kref_get(&ev_file->uverbs_file->ref);
>          ev_file->async_queue = NULL;
> -       ev_file->is_async    = is_async;
>          ev_file->is_closed   = 0;
>
>          filp = anon_inode_getfile("[infinibandevent]", &uverbs_event_fops,
>                                    ev_file, O_RDONLY);
>          if (IS_ERR(filp))
> -               kfree(ev_file);
> +               goto err_put_refs;
> +
> +       if (is_async) {
> +               WARN_ON(uverbs_file->async_file);
> +               uverbs_file->async_file = ev_file;
> +               kref_get(&uverbs_file->async_file->ref);
> +               INIT_IB_EVENT_HANDLER(&uverbs_file->event_handler,
> +                                     uverbs_file->device->ib_dev,
> +                                     ib_uverbs_event_handler);
> +               ret = ib_register_event_handler(&uverbs_file->event_handler);
> +               if (ret)
> +                       goto err_put_file;
> +
> +               /* At that point async file stuff was fully set */
> +               ev_file->is_async = 1;
> +       }
> +
> +       return filp;
> +
> +err_put_file:
> +       fput(filp);
> +       kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_file);

It should be:
kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_event_file)
instead of:
kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_file);

Please note that in that approach we duplicate above line as it appears 
here and in below err_put_refs label as uverbs_file->async_file is 
really ev_file.

In my patch we used one line instead of those duplicated lines, however 
as you think that it clarifies things will go with your suggestion after 
fixing above note, thanks.


> +       uverbs_file->async_file = NULL;
> +       return ERR_PTR(ret);
>
> +err_put_refs:
> +       kref_put(&ev_file->uverbs_file->ref, ib_uverbs_release_file);
> +       kref_put(&ev_file->ref, ib_uverbs_release_event_file);
>          return filp;
>   }
>
> Jason
>

--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jason Gunthorpe June 29, 2015, 9:48 p.m. UTC | #7
On Tue, Jun 30, 2015 at 12:22:02AM +0300, Yishai Hadas wrote:
> It should be:
> kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_event_file)
> instead of:
> kref_put(&uverbs_file->async_file->ref, ib_uverbs_release_file);

Right

> Please note that in that approach we duplicate above line as it appears here
> and in below err_put_refs label as uverbs_file->async_file is really
> ev_file.

Well, sort of. Yes, it is the same value, but no, it isn't the same
kref.

Just like locking always works on data, not code, krefs should always
be applied to the pointer not to the pointed data. The kref
manipulation should always be near the pointer value change it is
working for, and have a clear relation to the pointer it is krefing.

> In my patch we used one line instead of those duplicated lines, however as
> you think that it clarifies things will go with your suggestion after fixing
> above note, thanks.

You shouldn't think of them as duplicates, they are for different
things, even if the actual functionality is the same. It is
*documentation*

Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/infiniband/core/uverbs.h b/drivers/infiniband/core/uverbs.h
index ba365b6..60e6e3d 100644
--- a/drivers/infiniband/core/uverbs.h
+++ b/drivers/infiniband/core/uverbs.h
@@ -178,6 +178,7 @@  void idr_remove_uobj(struct idr *idp, struct ib_uobject *uobj);
 
 struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
 					int is_async);
+void ib_uverbs_free_async_event_file(struct ib_uverbs_file *uverbs_file);
 struct ib_uverbs_event_file *ib_uverbs_lookup_comp_file(int fd);
 
 void ib_uverbs_release_ucq(struct ib_uverbs_file *file,
diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c
index bbb02ff..5720a92 100644
--- a/drivers/infiniband/core/uverbs_cmd.c
+++ b/drivers/infiniband/core/uverbs_cmd.c
@@ -367,16 +367,6 @@  ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
 		goto err_file;
 	}
 
-	file->async_file = filp->private_data;
-
-	INIT_IB_EVENT_HANDLER(&file->event_handler, file->device->ib_dev,
-			      ib_uverbs_event_handler);
-	ret = ib_register_event_handler(&file->event_handler);
-	if (ret)
-		goto err_file;
-
-	kref_get(&file->async_file->ref);
-	kref_get(&file->ref);
 	file->ucontext = ucontext;
 
 	fd_install(resp.async_fd, filp);
@@ -386,6 +376,7 @@  ssize_t ib_uverbs_get_context(struct ib_uverbs_file *file,
 	return in_len;
 
 err_file:
+	ib_uverbs_free_async_event_file(file);
 	fput(filp);
 
 err_fd:
diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c
index f6eef2d..ed54e4e 100644
--- a/drivers/infiniband/core/uverbs_main.c
+++ b/drivers/infiniband/core/uverbs_main.c
@@ -406,10 +406,9 @@  static int ib_uverbs_event_close(struct inode *inode, struct file *filp)
 	}
 	spin_unlock_irq(&file->lock);
 
-	if (file->is_async) {
+	if (file->is_async)
 		ib_unregister_event_handler(&file->uverbs_file->event_handler);
-		kref_put(&file->uverbs_file->ref, ib_uverbs_release_file);
-	}
+	kref_put(&file->uverbs_file->ref, ib_uverbs_release_file);
 	kref_put(&file->ref, ib_uverbs_release_event_file);
 
 	return 0;
@@ -541,13 +540,20 @@  void ib_uverbs_event_handler(struct ib_event_handler *handler,
 				NULL, NULL);
 }
 
+void ib_uverbs_free_async_event_file(struct ib_uverbs_file *file)
+{
+	kref_put(&file->async_file->ref, ib_uverbs_release_event_file);
+	file->async_file = NULL;
+}
+
 struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
 					int is_async)
 {
 	struct ib_uverbs_event_file *ev_file;
 	struct file *filp;
+	int ret;
 
-	ev_file = kmalloc(sizeof *ev_file, GFP_KERNEL);
+	ev_file = kzalloc(sizeof(*ev_file), GFP_KERNEL);
 	if (!ev_file)
 		return ERR_PTR(-ENOMEM);
 
@@ -557,15 +563,38 @@  struct file *ib_uverbs_alloc_event_file(struct ib_uverbs_file *uverbs_file,
 	init_waitqueue_head(&ev_file->poll_wait);
 	ev_file->uverbs_file = uverbs_file;
 	ev_file->async_queue = NULL;
-	ev_file->is_async    = is_async;
 	ev_file->is_closed   = 0;
 
 	filp = anon_inode_getfile("[infinibandevent]", &uverbs_event_fops,
 				  ev_file, O_RDONLY);
 	if (IS_ERR(filp))
-		kfree(ev_file);
+		goto err;
+
+	kref_get(&uverbs_file->ref);
+	if (is_async) {
+		WARN_ON(uverbs_file->async_file);
+		uverbs_file->async_file = ev_file;
+		kref_get(&uverbs_file->async_file->ref);
+		INIT_IB_EVENT_HANDLER(&uverbs_file->event_handler,
+				      uverbs_file->device->ib_dev,
+				      ib_uverbs_event_handler);
+		ret = ib_register_event_handler(&uverbs_file->event_handler);
+		if (ret)
+			goto put_file;
+
+		/* At that point async file stuff was fully set */
+		ev_file->is_async = 1;
+	}
 
 	return filp;
+
+put_file:
+	fput(filp);
+	uverbs_file->async_file = NULL;
+	filp = ERR_PTR(ret);
+err:
+	kref_put(&ev_file->ref, ib_uverbs_release_event_file);
+	return filp;
 }
 
 /*