diff mbox

KVM/Eventfd: Avoid crash when assign and deassign same eventfd in parallel.

Message ID 1513554007-12302-1-git-send-email-tianyu.lan@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

lan,Tianyu Dec. 17, 2017, 11:40 p.m. UTC
Syzroot reports crash in kvm_irqfd_assign() is caused by use-after-free.
Because kvm_irqfd_assign() and kvm_irqfd_deassign() can't run in parallel
for same eventfd. When assign path hasn't been finished after irqfd
has been added to kvm->irqfds.items list, another thead may deassign the
eventfd and free struct kvm_kernel_irqfd(). This causes assign path still
uses struct kvm_kernel_irqfd freed by deassign path. To avoid such issue,
add "initialized" flag in the struct kvm_kernel_irqfd and check the flag before
deactivating irqfd. If irqfd is still in initialization, deassign path
return fault.

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Radim Krčmář <rkrcmar@redhat.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Wanpeng Li <kernellwp@gmail.com>
Signed-off-by: Tianyu Lan <tianyu.lan@intel.com>
---
 include/linux/kvm_irqfd.h |  1 +
 virt/kvm/eventfd.c        | 11 +++++++++--
 2 files changed, 10 insertions(+), 2 deletions(-)

Comments

David Hildenbrand Dec. 18, 2017, 8:30 a.m. UTC | #1
On 18.12.2017 00:40, Lan Tianyu wrote:
> Syzroot reports crash in kvm_irqfd_assign() is caused by use-after-free.
> Because kvm_irqfd_assign() and kvm_irqfd_deassign() can't run in parallel
> for same eventfd. When assign path hasn't been finished after irqfd
> has been added to kvm->irqfds.items list, another thead may deassign the
> eventfd and free struct kvm_kernel_irqfd(). This causes assign path still
> uses struct kvm_kernel_irqfd freed by deassign path. To avoid such issue,
> add "initialized" flag in the struct kvm_kernel_irqfd and check the flag before
> deactivating irqfd. If irqfd is still in initialization, deassign path
> return fault.>
> Reported-by: Dmitry Vyukov <dvyukov@google.com>
> Cc: Paolo Bonzini <pbonzini@redhat.com>
> Cc: Radim Krčmář <rkrcmar@redhat.com>
> Cc: Dmitry Vyukov <dvyukov@google.com>
> Cc: Wanpeng Li <kernellwp@gmail.com>
> Signed-off-by: Tianyu Lan <tianyu.lan@intel.com>
> ---
>  include/linux/kvm_irqfd.h |  1 +
>  virt/kvm/eventfd.c        | 11 +++++++++--
>  2 files changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/include/linux/kvm_irqfd.h b/include/linux/kvm_irqfd.h
> index 76c2fbc..be6b254 100644
> --- a/include/linux/kvm_irqfd.h
> +++ b/include/linux/kvm_irqfd.h
> @@ -66,6 +66,7 @@ struct kvm_kernel_irqfd {
>  	struct work_struct shutdown;
>  	struct irq_bypass_consumer consumer;
>  	struct irq_bypass_producer *producer;
> +	u8 initialized:1;
>  };
>  
>  #endif /* __LINUX_KVM_IRQFD_H */
> diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c
> index a334399..80f06e6 100644
> --- a/virt/kvm/eventfd.c
> +++ b/virt/kvm/eventfd.c
> @@ -421,6 +421,7 @@ int  __attribute__((weak)) kvm_arch_update_irqfd_routing(
>  	}
>  #endif
>  
> +	irqfd->initialized = 1;

The ugly thing in kvm_irqfd_assign() is that we access irqfd without
holding a lock. I think that should rather be fixed than working around
that issue. (e.g. lock() -> lookup again -> verify still in list ->
unlock())

>  	return 0;
>  
>  fail:
> @@ -525,6 +526,7 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>  {

Which tool are you using to generate diffs? git format-patch?

Mentioning, because the indicated function here .... (kvm_irqfd_deassign)

>  	struct kvm_kernel_irqfd *irqfd, *tmp;
>  	struct eventfd_ctx *eventfd;
> +	int ret = 0;
>  
>  	eventfd = eventfd_ctx_fdget(args->fd);
>  	if (IS_ERR(eventfd))
> @@ -543,7 +545,12 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>  			write_seqcount_begin(&irqfd->irq_entry_sc);
>  			irqfd->irq_entry.type = 0;
>  			write_seqcount_end(&irqfd->irq_entry_sc);
> -			irqfd_deactivate(irqfd);
> +
> +			if (irqfd->initialized)
> +				irqfd_deactivate(irqfd);
> +			else
> +				ret = -EFAULT;
> +
>  		}
>  	}
>  
> @@ -557,7 +564,7 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>  	 */

and here are wrong and misleading (kvm_irqfd_deassig). (and just noticed
also in the second hunk)

>  	flush_workqueue(irqfd_cleanup_wq);
>  
> -	return 0;
> +	return ret;
>  }
>  
>  int
>
lan,Tianyu Dec. 19, 2017, 6:21 a.m. UTC | #2
Hi David:
	Thanks for your review.

On 2017年12月18日 16:30, David Hildenbrand wrote:
> On 18.12.2017 00:40, Lan Tianyu wrote:
>> Syzroot reports crash in kvm_irqfd_assign() is caused by use-after-free.
>> Because kvm_irqfd_assign() and kvm_irqfd_deassign() can't run in parallel
>> for same eventfd. When assign path hasn't been finished after irqfd
>> has been added to kvm->irqfds.items list, another thead may deassign the
>> eventfd and free struct kvm_kernel_irqfd(). This causes assign path still
>> uses struct kvm_kernel_irqfd freed by deassign path. To avoid such issue,
>> add "initialized" flag in the struct kvm_kernel_irqfd and check the flag before
>> deactivating irqfd. If irqfd is still in initialization, deassign path
>> return fault.>
>> Reported-by: Dmitry Vyukov <dvyukov@google.com>
>> Cc: Paolo Bonzini <pbonzini@redhat.com>
>> Cc: Radim Krčmář <rkrcmar@redhat.com>
>> Cc: Dmitry Vyukov <dvyukov@google.com>
>> Cc: Wanpeng Li <kernellwp@gmail.com>
>> Signed-off-by: Tianyu Lan <tianyu.lan@intel.com>
>> ---
>>  include/linux/kvm_irqfd.h |  1 +
>>  virt/kvm/eventfd.c        | 11 +++++++++--
>>  2 files changed, 10 insertions(+), 2 deletions(-)
>>
>> diff --git a/include/linux/kvm_irqfd.h b/include/linux/kvm_irqfd.h
>> index 76c2fbc..be6b254 100644
>> --- a/include/linux/kvm_irqfd.h
>> +++ b/include/linux/kvm_irqfd.h
>> @@ -66,6 +66,7 @@ struct kvm_kernel_irqfd {
>>  	struct work_struct shutdown;
>>  	struct irq_bypass_consumer consumer;
>>  	struct irq_bypass_producer *producer;
>> +	u8 initialized:1;
>>  };
>>  
>>  #endif /* __LINUX_KVM_IRQFD_H */
>> diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c
>> index a334399..80f06e6 100644
>> --- a/virt/kvm/eventfd.c
>> +++ b/virt/kvm/eventfd.c
>> @@ -421,6 +421,7 @@ int  __attribute__((weak)) kvm_arch_update_irqfd_routing(
>>  	}
>>  #endif
>>  
>> +	irqfd->initialized = 1;
> 
> The ugly thing in kvm_irqfd_assign() is that we access irqfd without
> holding a lock. I think that should rather be fixed than working around
> that issue. (e.g. lock() -> lookup again -> verify still in list ->
> unlock())

The new lock should be always held in assign path otherwise we need to
lookup irqfds list frequently, right?

At first, I tried to use a mutex lock between assign and deassign path
but assign path already involves some locks and add new lock maybe
introduce dead lock. So I used flag check to replace with new lock.

> 
>>  	return 0;
>>  
>>  fail:
>> @@ -525,6 +526,7 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>>  {
> 
> Which tool are you using to generate diffs? git format-patch?

Yes, I used git version 1.8.3.1 :)
This also confused me. I will try newer version.

> 
> Mentioning, because the indicated function here .... (kvm_irqfd_deassign)
> 
>>  	struct kvm_kernel_irqfd *irqfd, *tmp;
>>  	struct eventfd_ctx *eventfd;
>> +	int ret = 0;
>>  
>>  	eventfd = eventfd_ctx_fdget(args->fd);
>>  	if (IS_ERR(eventfd))
>> @@ -543,7 +545,12 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>>  			write_seqcount_begin(&irqfd->irq_entry_sc);
>>  			irqfd->irq_entry.type = 0;
>>  			write_seqcount_end(&irqfd->irq_entry_sc);
>> -			irqfd_deactivate(irqfd);
>> +
>> +			if (irqfd->initialized)
>> +				irqfd_deactivate(irqfd);
>> +			else
>> +				ret = -EFAULT;
>> +
>>  		}
>>  	}
>>  
>> @@ -557,7 +564,7 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
>>  	 */
> 
> and here are wrong and misleading (kvm_irqfd_deassig). (and just noticed
> also in the second hunk)
> 
>>  	flush_workqueue(irqfd_cleanup_wq);
>>  
>> -	return 0;
>> +	return ret;
>>  }
>>  
>>  int
>>
> 
>
diff mbox

Patch

diff --git a/include/linux/kvm_irqfd.h b/include/linux/kvm_irqfd.h
index 76c2fbc..be6b254 100644
--- a/include/linux/kvm_irqfd.h
+++ b/include/linux/kvm_irqfd.h
@@ -66,6 +66,7 @@  struct kvm_kernel_irqfd {
 	struct work_struct shutdown;
 	struct irq_bypass_consumer consumer;
 	struct irq_bypass_producer *producer;
+	u8 initialized:1;
 };
 
 #endif /* __LINUX_KVM_IRQFD_H */
diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c
index a334399..80f06e6 100644
--- a/virt/kvm/eventfd.c
+++ b/virt/kvm/eventfd.c
@@ -421,6 +421,7 @@  int  __attribute__((weak)) kvm_arch_update_irqfd_routing(
 	}
 #endif
 
+	irqfd->initialized = 1;
 	return 0;
 
 fail:
@@ -525,6 +526,7 @@  void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
 {
 	struct kvm_kernel_irqfd *irqfd, *tmp;
 	struct eventfd_ctx *eventfd;
+	int ret = 0;
 
 	eventfd = eventfd_ctx_fdget(args->fd);
 	if (IS_ERR(eventfd))
@@ -543,7 +545,12 @@  void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
 			write_seqcount_begin(&irqfd->irq_entry_sc);
 			irqfd->irq_entry.type = 0;
 			write_seqcount_end(&irqfd->irq_entry_sc);
-			irqfd_deactivate(irqfd);
+
+			if (irqfd->initialized)
+				irqfd_deactivate(irqfd);
+			else
+				ret = -EFAULT;
+
 		}
 	}
 
@@ -557,7 +564,7 @@  void kvm_unregister_irq_ack_notifier(struct kvm *kvm,
 	 */
 	flush_workqueue(irqfd_cleanup_wq);
 
-	return 0;
+	return ret;
 }
 
 int