From cd11346c60451032d97062e25ed025bf692dff91 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Sat, 21 May 2016 16:55:50 +0800 Subject: [PATCH 41/42] 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:[] [] 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. Reported-by: Rolf Neugebauer Signed-off-by: Dexuan Cui Origin: https://github.com/dcui/linux/commit/fbcca73228b9b90911ab30fdf75f532b2b7c07e5 --- drivers/hv/channel.c | 1 + drivers/hv/channel_mgmt.c | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 57a1b65..da76a2e 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -592,6 +592,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel) out: tasklet_enable(tasklet); + tasklet_schedule(tasklet); return ret; } diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index c892db5..0a54317 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -21,6 +21,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -307,12 +308,13 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid) { unsigned long flags; struct vmbus_channel *primary_channel; - - vmbus_release_relid(relid); + struct tasklet_struct *tasklet; BUG_ON(!channel->rescind); BUG_ON(!mutex_is_locked(&vmbus_connection.channel_mutex)); + tasklet = hv_context.event_dpc[channel->target_cpu]; + tasklet_disable(tasklet); if (channel->target_cpu != get_cpu()) { put_cpu(); smp_call_function_single(channel->target_cpu, @@ -321,6 +323,8 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid) percpu_channel_deq(channel); put_cpu(); } + tasklet_enable(tasklet); + tasklet_schedule(tasklet); if (channel->primary_channel == NULL) { list_del(&channel->listentry); @@ -342,6 +346,8 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid) &primary_channel->alloced_cpus_in_node); free_channel(channel); + + vmbus_release_relid(relid); } void vmbus_free_channels(void) @@ -363,6 +369,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 +416,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 +427,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) percpu_channel_enq(newchannel); put_cpu(); } + tasklet_enable(tasklet); + tasklet_schedule(tasklet); /* * This state is used to indicate a successful open @@ -469,6 +480,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 +489,8 @@ err_deq_chan: percpu_channel_deq(newchannel); put_cpu(); } + tasklet_enable(tasklet); + tasklet_schedule(tasklet); err_free_chan: free_channel(newchannel); -- 2.10.0