Drivers: hv: vmbus: fix the race when querying & updating the percpu list
Dexuan Cui
maintainerPATCH v2 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.
---
v2: added tasklet_schedule() after tasklet_enable(). Thanks, Vitaly!
drivers/hv/channel.c | 5 +++++ drivers/hv/channel_mgmt.c | 24 +++++++++++++----------- include/linux/hyperv.h | 3 +++ 3 files changed, 21 insertions(+), 11 deletions(-)
drivers/hv/channel.c +5 -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 (); }
@@ -592,6 +595,8 @@ static int vmbus_close_internal(struct vmbus_channel *channel) out:tasklet_enable (tasklet); +/* for possible pending event */ +tasklet_schedule (tasklet);return ret; }
drivers/hv/channel_mgmt.c +13 -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,9 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)percpu_channel_enq (newchannel);put_cpu (); } +tasklet_enable (tasklet); +/* for possible pending event */ +tasklet_schedule (tasklet);/* * This state is used to indicate a successful open
@@ -469,6 +467,7 @@ err_deq_chan: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 +476,9 @@ err_deq_chan:percpu_channel_deq (newchannel);put_cpu (); } +tasklet_enable (tasklet); +/* for possible pending event */ +tasklet_schedule (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
2.7.4
Dexuan Cui
maintainerQuoted 25 lines
From: devel [mailto:[email protected]] On Behalf Of Dexuan Cui
Sent: Wednesday, May 18, 2016 11:44
To: [email protected]; [email protected]; driverdev- [email protected]; [email protected]; [email protected]; [email protected]; KY Srinivasan <[email protected]>; [email protected]
Cc: Haiyang Zhang <[email protected]>
Subject: [PATCH v2] Drivers: hv: vmbus: fix the race when querying & updating the percpu listThere 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().
Quoted 8 lines
We can resolve the issue by disabling the tasklet when updating the list.
Reported-by Rolf Neugebauer <[email protected]>Cc Vitaly Kuznetsov <[email protected]>Signed-off-by Dexuan Cui <[email protected]>---
v2: added
tasklet_schedule()aftertasklet_enable(). Thanks, Vitaly!
Please ignore the patch for now.
I found an issue with the patch: after I moved percpu_channel_deq() from hv_process_channel_removal() to vmbus_close_internal(), the channel couldn't be removed from the per-cpu list, if the channel state was not CHANNEL_OPENED_STATE.
I'll have to think about this and fix the issue in the next version.
Thanks,
-- Dexuan