Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

profiling: remove prof_cpu_mask

syzbot is reporting uninit-value at profile_hits(), for there is a race
window between

if (!alloc_cpumask_var(&prof_cpu_mask, GFP_KERNEL))
return -ENOMEM;
cpumask_copy(prof_cpu_mask, cpu_possible_mask);

in profile_init() and

cpumask_available(prof_cpu_mask) &&
cpumask_test_cpu(smp_processor_id(), prof_cpu_mask))

in profile_tick(); prof_cpu_mask remains uninitialzed until cpumask_copy()
completes while cpumask_available(prof_cpu_mask) returns true as soon as
alloc_cpumask_var(&prof_cpu_mask) completes.

We could replace alloc_cpumask_var() with zalloc_cpumask_var() and
call cpumask_copy() from create_proc_profile() on only UP kernels, for
profile_online_cpu() calls cpumask_set_cpu() as needed via
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN) on SMP kernels. But this patch
removes prof_cpu_mask because it seems unnecessary.

The cpumask_test_cpu(smp_processor_id(), prof_cpu_mask) test
in profile_tick() is likely always true due to

a CPU cannot call profile_tick() if that CPU is offline

and

cpumask_set_cpu(cpu, prof_cpu_mask) is called when that CPU becomes
online and cpumask_clear_cpu(cpu, prof_cpu_mask) is called when that
CPU becomes offline

. This test could be false during transition between online and offline.

But according to include/linux/cpuhotplug.h , CPUHP_PROFILE_PREPARE
belongs to PREPARE section, which means that the CPU subjected to
profile_dead_cpu() cannot be inside profile_tick() (i.e. no risk of
use-after-free bug) because interrupt for that CPU is disabled during
PREPARE section. Therefore, this test is guaranteed to be true, and
can be removed. (Since profile_hits() checks prof_buffer != NULL, we
don't need to check prof_buffer != NULL here unless get_irq_regs() or
user_mode() is such slow that we want to avoid when prof_buffer == NULL).

do_profile_hits() is called from profile_tick() from timer interrupt
only if cpumask_test_cpu(smp_processor_id(), prof_cpu_mask) is true and
prof_buffer is not NULL. But syzbot is also reporting that sometimes
do_profile_hits() is called while current thread is still doing vzalloc(),
where prof_buffer must be NULL at this moment. This indicates that multiple
threads concurrently tried to write to /sys/kernel/profiling interface,
which caused that somebody else try to re-allocate prof_buffer despite
somebody has already allocated prof_buffer. Fix this by using
serialization.

Reported-by: syzbot <syzbot+b1a83ab2a9eb9321fbdd@syzkaller.appspotmail.com>
Closes: https://syzkaller.appspot.com/bug?extid=b1a83ab2a9eb9321fbdd
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Tested-by: syzbot <syzbot+b1a83ab2a9eb9321fbdd@syzkaller.appspotmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Tetsuo Handa and committed by
Linus Torvalds
7c51f7bb 99d3bf5f

+13 -40
+7
kernel/ksysfs.c
··· 92 92 const char *buf, size_t count) 93 93 { 94 94 int ret; 95 + static DEFINE_MUTEX(lock); 95 96 97 + /* 98 + * We need serialization, for profile_setup() initializes prof_on 99 + * value and profile_init() must not reallocate prof_buffer after 100 + * once allocated. 101 + */ 102 + guard(mutex)(&lock); 96 103 if (prof_on) 97 104 return -EEXIST; 98 105 /*
+6 -40
kernel/profile.c
··· 47 47 int prof_on __read_mostly; 48 48 EXPORT_SYMBOL_GPL(prof_on); 49 49 50 - static cpumask_var_t prof_cpu_mask; 51 50 #if defined(CONFIG_SMP) && defined(CONFIG_PROC_FS) 52 51 static DEFINE_PER_CPU(struct profile_hit *[2], cpu_profile_hits); 53 52 static DEFINE_PER_CPU(int, cpu_profile_flip); ··· 113 114 114 115 buffer_bytes = prof_len*sizeof(atomic_t); 115 116 116 - if (!alloc_cpumask_var(&prof_cpu_mask, GFP_KERNEL)) 117 - return -ENOMEM; 118 - 119 - cpumask_copy(prof_cpu_mask, cpu_possible_mask); 120 - 121 117 prof_buffer = kzalloc(buffer_bytes, GFP_KERNEL|__GFP_NOWARN); 122 118 if (prof_buffer) 123 119 return 0; ··· 126 132 if (prof_buffer) 127 133 return 0; 128 134 129 - free_cpumask_var(prof_cpu_mask); 130 135 return -ENOMEM; 131 136 } 132 137 ··· 260 267 struct page *page; 261 268 int i; 262 269 263 - if (cpumask_available(prof_cpu_mask)) 264 - cpumask_clear_cpu(cpu, prof_cpu_mask); 265 - 266 270 for (i = 0; i < 2; i++) { 267 271 if (per_cpu(cpu_profile_hits, cpu)[i]) { 268 272 page = virt_to_page(per_cpu(cpu_profile_hits, cpu)[i]); ··· 292 302 return 0; 293 303 } 294 304 295 - static int profile_online_cpu(unsigned int cpu) 296 - { 297 - if (cpumask_available(prof_cpu_mask)) 298 - cpumask_set_cpu(cpu, prof_cpu_mask); 299 - 300 - return 0; 301 - } 302 - 303 305 #else /* !CONFIG_SMP */ 304 306 #define profile_flip_buffers() do { } while (0) 305 307 #define profile_discard_flip_buffers() do { } while (0) ··· 316 334 { 317 335 struct pt_regs *regs = get_irq_regs(); 318 336 319 - if (!user_mode(regs) && cpumask_available(prof_cpu_mask) && 320 - cpumask_test_cpu(smp_processor_id(), prof_cpu_mask)) 337 + /* This is the old kernel-only legacy profiling */ 338 + if (!user_mode(regs)) 321 339 profile_hit(type, (void *)profile_pc(regs)); 322 340 } 323 341 ··· 400 418 int __ref create_proc_profile(void) 401 419 { 402 420 struct proc_dir_entry *entry; 403 - #ifdef CONFIG_SMP 404 - enum cpuhp_state online_state; 405 - #endif 406 - 407 421 int err = 0; 408 422 409 423 if (!prof_on) ··· 409 431 profile_prepare_cpu, profile_dead_cpu); 410 432 if (err) 411 433 return err; 412 - 413 - err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "AP_PROFILE_ONLINE", 414 - profile_online_cpu, NULL); 415 - if (err < 0) 416 - goto err_state_prep; 417 - online_state = err; 418 - err = 0; 419 434 #endif 420 435 entry = proc_create("profile", S_IWUSR | S_IRUGO, 421 436 NULL, &profile_proc_ops); 422 - if (!entry) 423 - goto err_state_onl; 424 - proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t)); 425 - 426 - return err; 427 - err_state_onl: 437 + if (entry) 438 + proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t)); 428 439 #ifdef CONFIG_SMP 429 - cpuhp_remove_state(online_state); 430 - err_state_prep: 431 - cpuhp_remove_state(CPUHP_PROFILE_PREPARE); 440 + else 441 + cpuhp_remove_state(CPUHP_PROFILE_PREPARE); 432 442 #endif 433 443 return err; 434 444 }