Drivers: hv: vmbus: fix the race when querying & updating the percpu list
Dexuan Cui
maintainerPATCH 1/1 Drivers: hv: vmbus: fix the race when querying & updating the percpu list
There is a rare race when we remove an entry from the global list hv_context.percpu_list[cpu] in hv_process_channel_removal() -> percpu_channel_deq() -> list_del(): at this time, if vmbus_on_event() -> process_chn_event() -> pcpu_relid2channel() is trying to query the list, we can get the general protection fault:
general protection fault: 0000 [#1] SMP ... RIP: 0010:[<ffffffff81461b6b>] [<ffffffff81461b6b>] vmbus_on_event+0xc4/0x149
Similarly, we also have the issue in the code path: vmbus_process_offer() -> percpu_channel_enq().
We can resolve the issue by disabling the tasklet when updating the list.
--- drivers/hv/channel.c | 3 +++ drivers/hv/channel_mgmt.c | 20 +++++++++----------- include/linux/hyperv.h | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-)
drivers/hv/channel.c +3 -0
@@ -546,8 +546,11 @@ static int vmbus_close_internal(struct vmbus_channel *channel)put_cpu ();smp_call_function_single (channel->target_cpu, reset_channel_cb, channel, true); +smp_call_function_single (channel->target_cpu, + percpu_channel_deq, channel, true); }else {reset_channel_cb (channel); +percpu_channel_deq (channel);put_cpu (); }
drivers/hv/channel_mgmt.c +9 -11
@@ -21,6 +21,7 @@#define pr_fmt (fmt) KBUILD_MODNAME ": " fmt#include <linux/kernel.h> +#include <linux/interrupt.h> #include <linux/sched.h> #include <linux/wait.h> #include <linux/mm.h>
@@ -277,7 +278,7 @@ static void free_channel(struct vmbus_channel *channel)kfree (channel); } -static void percpu_channel_enq (void *arg) +void percpu_channel_enq (void *arg) {struct vmbus_channel *channel = arg;int cpu =smp_processor_id ();
@@ -285,7 +286,7 @@ static void percpu_channel_enq(void *arg)list_add_tail (&channel->percpu_list, &hv_context.percpu_list[cpu]); } -static void percpu_channel_deq (void *arg) +void percpu_channel_deq (void *arg) {struct vmbus_channel *channel = arg;
@@ -313,15 +314,6 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)BUG_ON (!channel->rescind);BUG_ON (!mutex_is_locked (&vmbus_connection.channel_mutex)); -if (channel->target_cpu !=get_cpu ()) { -put_cpu (); -smp_call_function_single (channel->target_cpu, - percpu_channel_deq, channel, true); - }else { -percpu_channel_deq (channel); -put_cpu (); - } -if (channel->primary_channel ==NULL ) {list_del (&channel->listentry);
@@ -363,6 +355,7 @@ void vmbus_free_channels(void)*/ static void vmbus_process_offer (struct vmbus_channel *newchannel) { +struct tasklet_struct *tasklet;struct vmbus_channel *channel;bool fnew = true;unsigned long flags;
@@ -409,6 +402,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)init_vp_index (newchannel, dev_type); + tasklet = hv_context.event_dpc[newchannel->target_cpu]; +tasklet_disable (tasklet);if (newchannel->target_cpu !=get_cpu ()) {put_cpu (); smp_call_function_single(newchannel->target_cpu,
@@ -418,6 +413,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)percpu_channel_enq (newchannel);put_cpu (); } +tasklet_enable (tasklet);/* * This state is used to indicate a successful open
@@ -469,6 +465,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)list_del (&newchannel->listentry);mutex_unlock (&vmbus_connection.channel_mutex); +tasklet_disable (tasklet);if (newchannel->target_cpu !=get_cpu ()) {put_cpu (); smp_call_function_single(newchannel->target_cpu,
@@ -477,6 +474,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)percpu_channel_deq (newchannel);put_cpu (); } +tasklet_enable (tasklet); err_free_chan:free_channel (newchannel);
include/linux/hyperv.h +3 -0
@@ -1328,6 +1328,9 @@ extern bool vmbus_prep_negotiate_resp(struct icmsg_hdr *, struct icmsg_negotiate *, u8 *, int, int); +void percpu_channel_enq (void *arg); +void percpu_channel_deq (void *arg); +void hv_process_channel_removal (struct vmbus_channel *channel,u32 relid);/*
Quoted 103 lines · Dexuan Cui
There is a rare race when we remove an entry from the global list hv_context.percpu_list[cpu] in
hv_process_channel_removal()->percpu_channel_deq()->list_del(): at this time, ifvmbus_on_event()->process_chn_event()->pcpu_relid2channel()is trying to query the list, we can get the general protection fault:general protection fault: 0000 [#1] SMP ... RIP: 0010:[<ffffffff81461b6b>] [<ffffffff81461b6b>] vmbus_on_event+0xc4/0x149Similarly, we also have the issue in the code path:
vmbus_process_offer()->percpu_channel_enq().We can resolve the issue by disabling the tasklet when updating the list.
Reported-by Rolf Neugebauer <[email protected]>Signed-off-by Dexuan Cui <[email protected]>--- drivers/hv/channel.c | 3 +++ drivers/hv/channel_mgmt.c | 20 +++++++++----------- include/linux/hyperv.h | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-)drivers/hv/channel.c +3
-0@@ -546,8 +546,11 @@ static int vmbus_close_internal(struct vmbus_channel *channel)put_cpu ();smp_call_function_single (channel->target_cpu, reset_channel_cb, channel, true); +smp_call_function_single (channel->target_cpu, + percpu_channel_deq, channel, true); }else {reset_channel_cb (channel); +percpu_channel_deq (channel);put_cpu (); }drivers/hv/channel_mgmt.c +6
-11@@ -21,6 +21,7 @@#define pr_fmt (fmt) KBUILD_MODNAME ": " fmt#include <linux/kernel.h> +#include <linux/interrupt.h> #include <linux/sched.h> #include <linux/wait.h> #include <linux/mm.h> @@ -277,7 +278,7 @@ static void free_channel(struct vmbus_channel *channel)kfree (channel); } -static void percpu_channel_enq (void *arg) +void percpu_channel_enq (void *arg) {struct vmbus_channel *channel = arg;int cpu =smp_processor_id ();@@ -285,7 +286,7 @@ static void percpu_channel_enq(void *arg)list_add_tail (&channel->percpu_list, &hv_context.percpu_list[cpu]); } -static void percpu_channel_deq (void *arg) +void percpu_channel_deq (void *arg) {struct vmbus_channel *channel = arg;@@ -313,15 +314,6 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)BUG_ON (!channel->rescind);BUG_ON (!mutex_is_locked (&vmbus_connection.channel_mutex)); -if (channel->target_cpu !=get_cpu ()) { -put_cpu (); -smp_call_function_single (channel->target_cpu, - percpu_channel_deq, channel, true); - }else { -percpu_channel_deq (channel); -put_cpu (); - } -if (channel->primary_channel ==NULL ) {list_del (&channel->listentry);@@ -363,6 +355,7 @@ void vmbus_free_channels(void)*/ static void vmbus_process_offer (struct vmbus_channel *newchannel) { +struct tasklet_struct *tasklet;struct vmbus_channel *channel;bool fnew = true;unsigned long flags;@@ -409,6 +402,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)init_vp_index (newchannel, dev_type); + tasklet = hv_context.event_dpc[newchannel->target_cpu]; +tasklet_disable (tasklet);if (newchannel->target_cpu !=get_cpu ()) {put_cpu (); smp_call_function_single(newchannel->target_cpu,
drivers/hv/channel_mgmt.c static void vmbus_process_offer(struct vmbus_channel *newchannel) · line 416
Quoted 4 lines
drivers/hv/channel_mgmt.c +1
-0@@ -418,6 +413,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)percpu_channel_enq (newchannel);put_cpu (); } +tasklet_enable (tasklet);
Do we need to do tasklet_schedule() to make sure there are no events pending? This is probably not a big issue as some other event will trigger scheduling but in some corner cases it may bite. Same question applies to the code below and to vmbus_close_internal().
Quoted 33 lines
drivers/hv/channel_mgmt.c +2
-0/* * This state is used to indicate a successful open @@ -469,6 +465,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)list_del (&newchannel->listentry);mutex_unlock (&vmbus_connection.channel_mutex); +tasklet_disable (tasklet);if (newchannel->target_cpu !=get_cpu ()) {put_cpu (); smp_call_function_single(newchannel->target_cpu,@@ -477,6 +474,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)percpu_channel_deq (newchannel);put_cpu (); } +tasklet_enable (tasklet); err_free_chan:free_channel (newchannel);include/linux/hyperv.h +3
-0@@ -1328,6 +1328,9 @@ extern bool vmbus_prep_negotiate_resp(struct icmsg_hdr *, struct icmsg_negotiate *, u8 *, int, int); +void percpu_channel_enq (void *arg); +void percpu_channel_deq (void *arg); +void hv_process_channel_removal (struct vmbus_channel *channel,u32 relid);/*
Signature · 1 line
Vitaly
Dexuan Cui
maintainerQuoted 23 lines
From: Vitaly Kuznetsov [mailto:[email protected]]
Sent: Tuesday, May 17, 2016 16:15
To: Dexuan Cui <[email protected]>
Cc: [email protected]; [email protected]; driverdev- [email protected]; [email protected]; [email protected]; [email protected]; KY Srinivasan <[email protected]>; Haiyang Zhang <[email protected]>
Subject: Re: [PATCH] Drivers: hv: vmbus: fix the race when querying & updating the percpu listReview static void vmbus_process_offer(struct
Quoted 10 lines · Dexuan Cui
There is a rare race when we remove an entry from the global list hv_context.percpu_list[cpu] in
hv_process_channel_removal()->percpu_channel_deq()->list_del(): at this time, ifvmbus_on_event()->process_chn_event()->pcpu_relid2channel()is trying to query the list, we can get the general protection fault:
...We can resolve the issue by disabling the tasklet when updating the list.
(quoted diff) +0
-0@@ -418,6 +413,7 @@ static void vmbus_process_offer(struct
vmbus_channel*newchannel)
Quoted 9 lines
drivers/hv/channel_mgmt.c static void vmbus_process_offer(struct vmbus_channel *newchannel) · line 416
Quoted 4 lines
drivers/hv/channel_mgmt.c +1
-0percpu_channel_enq (newchannel);put_cpu (); } +tasklet_enable (tasklet);Do we need to do
tasklet_schedule()to make sure there are no events pending? This is probably not a big issue as some other event will trigger scheduling but in some corner cases it may bite. Same question applies to the code below and tovmbus_close_internal().
Hi Vitaly,
Thanks for spotting this!
I think you're correct.
I'll add tasklet_schedul() before the tasklet_enable() in the 2 places.
Thanks,
-- Dexuan
Dexuan Cui
maintainerQuoted 8 lines
From: devel [mailto:[email protected]] On Behalf Of Dexuan Cui
Quoted 5 lines
Do we need to do
tasklet_schedule()to make sure there are no events pending? This is probably not a big issue as some other event will trigger scheduling but in some corner cases it may bite. Same question applies to the code below and tovmbus_close_internal().
Quoted 4 lines
Hi Vitaly,
Thanks for spotting this!
I think you're correct.
I'll addtasklet_schedul()before thetasklet_enable()in the 2 places.
Sorry for the typo -- I meant "after" rather than "before".
Thanks
-- Dexuan