x86/idle: Fix get_cpu_idle_time()'s interaction with offline pcpus
authorAndrew Cooper <andrew.cooper3@citrix.com>
Fri, 4 Oct 2013 10:23:23 +0000 (12:23 +0200)
committerJan Beulich <jbeulich@suse.com>
Fri, 4 Oct 2013 10:23:23 +0000 (12:23 +0200)
Checking for "idle_vcpu[cpu] != NULL" is insufficient protection against
offline pcpus.  From a hypercall, vcpu_runstate_get() will determine "v !=
current", and try to take the vcpu_schedule_lock().  This will try to look up
per_cpu(schedule_data, v->processor) and promptly suffer a NULL structure
deference as v->processors' __per_cpu_offset is INVALID_PERCPU_AREA.

One example might look like this:

...
Xen call trace:
   [<ffff82c4c0126ddb>] vcpu_runstate_get+0x50/0x113
   [<ffff82c4c0126ec6>] get_cpu_idle_time+0x28/0x2e
   [<ffff82c4c012b5cb>] do_sysctl+0x3db/0xeb8
   [<ffff82c4c023280d>] compat_hypercall+0xbd/0x116

Pagetable walk from 0000000000000040:
 L4[0x000] = 0000000186df8027 0000000000028207
 L3[0x000] = 0000000188e36027 00000000000261c9
 L2[0x000] = 0000000000000000 ffffffffffffffff

****************************************
Panic on CPU 11:
...

get_cpu_idle_time() has been updated to correctly deal with offline pcpus
itself by returning 0, in the same way as it would if it was missing the
idle_vcpu[] pointer.

In doing so, XENPF_getidletime needed updating to correctly retain its
described behaviour of clearing bits in the cpumap for offline pcpus.

As this crash can only be triggered with toolstack hypercalls, it is not a
security issue and just a simple bug.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Acked-by: Keir Fraser <keir@xen.org>
xen/arch/x86/platform_hypercall.c
xen/common/schedule.c

index 7175a826587d5f043c1e472650b6b53fd0fbeb26..21628113006ae31183fdeb41f5a1a186a49bda0e 100644 (file)
@@ -355,10 +355,14 @@ ret_t do_platform_op(XEN_GUEST_HANDLE_PARAM(xen_platform_op_t) u_xenpf_op)
 
         for_each_cpu ( cpu, cpumap )
         {
-            if ( idle_vcpu[cpu] == NULL )
-                cpumask_clear_cpu(cpu, cpumap);
             idletime = get_cpu_idle_time(cpu);
 
+            if ( !idletime )
+            {
+                cpumask_clear_cpu(cpu, cpumap);
+                continue;
+            }
+
             if ( copy_to_guest_offset(idletimes, cpu, &idletime, 1) )
             {
                 ret = -EFAULT;
index a8398bd9ed4827564bed4346e1fdfbb98ec5907e..1ddfb22df68a40470c0581e329de87389ea32f6b 100644 (file)
@@ -176,13 +176,12 @@ void vcpu_runstate_get(struct vcpu *v, struct vcpu_runstate_info *runstate)
 
 uint64_t get_cpu_idle_time(unsigned int cpu)
 {
-    struct vcpu_runstate_info state;
-    struct vcpu *v;
+    struct vcpu_runstate_info state = { 0 };
+    struct vcpu *v = idle_vcpu[cpu];
 
-    if ( (v = idle_vcpu[cpu]) == NULL )
-        return 0;
+    if ( cpu_online(cpu) && v )
+        vcpu_runstate_get(v, &state);
 
-    vcpu_runstate_get(v, &state);
     return state.time[RUNSTATE_running];
 }