ftrace: Fix modification of direct_function hash while in use

Masami Hiramatsu reported a memory leak in register_ftrace_direct() where
if the number of new entries are added is large enough to cause two
allocations in the loop:

for (i = 0; i < size; i++) {
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
if (!new)
goto out_remove;
entry->direct = addr;
}
}

Where ftrace_add_rec_direct() has:

if (ftrace_hash_empty(direct_functions) ||
direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
struct ftrace_hash *new_hash;
int size = ftrace_hash_empty(direct_functions) ? 0 :
direct_functions->count + 1;

if (size < 32)
size = 32;

new_hash = dup_hash(direct_functions, size);
if (!new_hash)
return NULL;

*free_hash = direct_functions;
direct_functions = new_hash;
}

The "*free_hash = direct_functions;" can happen twice, losing the previous
allocation of direct_functions.

But this also exposed a more serious bug.

The modification of direct_functions above is not safe. As
direct_functions can be referenced at any time to find what direct caller
it should call, the time between:

new_hash = dup_hash(direct_functions, size);
and
direct_functions = new_hash;

can have a race with another CPU (or even this one if it gets interrupted),
and the entries being moved to the new hash are not referenced.

That's because the "dup_hash()" is really misnamed and is really a
"move_hash()". It moves the entries from the old hash to the new one.

Now even if that was changed, this code is not proper as direct_functions
should not be updated until the end. That is the best way to handle
function reference changes, and is the way other parts of ftrace handles
this.

The following is done:

1. Change add_hash_entry() to return the entry it created and inserted
into the hash, and not just return success or not.

2. Replace ftrace_add_rec_direct() with add_hash_entry(), and remove
the former.

3. Allocate a "new_hash" at the start that is made for holding both the
new hash entries as well as the existing entries in direct_functions.

4. Copy (not move) the direct_function entries over to the new_hash.

5. Copy the entries of the added hash to the new_hash.

6. If everything succeeds, then use rcu_pointer_assign() to update the
direct_functions with the new_hash.

This simplifies the code and fixes both the memory leak as well as the
race condition mentioned above.

Link: https://lore.kernel.org/all/170368070504.42064.8960569647118388081.stgit@devnote2/
Link: https://lore.kernel.org/linux-trace-kernel/20231229115134.08dd5174@gandalf.local.home

Cc: stable@vger.kernel.org
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Fixes: 763e34e74bb7d ("ftrace: Add register_ftrace_direct()")
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>

Changed files
+47 -53
kernel
trace
+47 -53
kernel/trace/ftrace.c
··· 1183 1183 hash->count++; 1184 1184 } 1185 1185 1186 - static int add_hash_entry(struct ftrace_hash *hash, unsigned long ip) 1186 + static struct ftrace_func_entry * 1187 + add_hash_entry(struct ftrace_hash *hash, unsigned long ip) 1187 1188 { 1188 1189 struct ftrace_func_entry *entry; 1189 1190 1190 1191 entry = kmalloc(sizeof(*entry), GFP_KERNEL); 1191 1192 if (!entry) 1192 - return -ENOMEM; 1193 + return NULL; 1193 1194 1194 1195 entry->ip = ip; 1195 1196 __add_hash_entry(hash, entry); 1196 1197 1197 - return 0; 1198 + return entry; 1198 1199 } 1199 1200 1200 1201 static void ··· 1350 1349 struct ftrace_func_entry *entry; 1351 1350 struct ftrace_hash *new_hash; 1352 1351 int size; 1353 - int ret; 1354 1352 int i; 1355 1353 1356 1354 new_hash = alloc_ftrace_hash(size_bits); ··· 1366 1366 size = 1 << hash->size_bits; 1367 1367 for (i = 0; i < size; i++) { 1368 1368 hlist_for_each_entry(entry, &hash->buckets[i], hlist) { 1369 - ret = add_hash_entry(new_hash, entry->ip); 1370 - if (ret < 0) 1369 + if (add_hash_entry(new_hash, entry->ip) == NULL) 1371 1370 goto free_hash; 1372 1371 } 1373 1372 } ··· 2535 2536 2536 2537 #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS 2537 2538 /* Protected by rcu_tasks for reading, and direct_mutex for writing */ 2538 - static struct ftrace_hash *direct_functions = EMPTY_HASH; 2539 + static struct ftrace_hash __rcu *direct_functions = EMPTY_HASH; 2539 2540 static DEFINE_MUTEX(direct_mutex); 2540 2541 int ftrace_direct_func_count; 2541 2542 ··· 2552 2553 return 0; 2553 2554 2554 2555 return entry->direct; 2555 - } 2556 - 2557 - static struct ftrace_func_entry* 2558 - ftrace_add_rec_direct(unsigned long ip, unsigned long addr, 2559 - struct ftrace_hash **free_hash) 2560 - { 2561 - struct ftrace_func_entry *entry; 2562 - 2563 - if (ftrace_hash_empty(direct_functions) || 2564 - direct_functions->count > 2 * (1 << direct_functions->size_bits)) { 2565 - struct ftrace_hash *new_hash; 2566 - int size = ftrace_hash_empty(direct_functions) ? 0 : 2567 - direct_functions->count + 1; 2568 - 2569 - if (size < 32) 2570 - size = 32; 2571 - 2572 - new_hash = dup_hash(direct_functions, size); 2573 - if (!new_hash) 2574 - return NULL; 2575 - 2576 - *free_hash = direct_functions; 2577 - direct_functions = new_hash; 2578 - } 2579 - 2580 - entry = kmalloc(sizeof(*entry), GFP_KERNEL); 2581 - if (!entry) 2582 - return NULL; 2583 - 2584 - entry->ip = ip; 2585 - entry->direct = addr; 2586 - __add_hash_entry(direct_functions, entry); 2587 - return entry; 2588 2556 } 2589 2557 2590 2558 static void call_direct_funcs(unsigned long ip, unsigned long pip, ··· 4189 4223 /* Do nothing if it exists */ 4190 4224 if (entry) 4191 4225 return 0; 4192 - 4193 - ret = add_hash_entry(hash, rec->ip); 4226 + if (add_hash_entry(hash, rec->ip) == NULL) 4227 + ret = -ENOMEM; 4194 4228 } 4195 4229 return ret; 4196 4230 } ··· 5232 5266 return 0; 5233 5267 } 5234 5268 5235 - return add_hash_entry(hash, ip); 5269 + entry = add_hash_entry(hash, ip); 5270 + return entry ? 0 : -ENOMEM; 5236 5271 } 5237 5272 5238 5273 static int ··· 5377 5410 */ 5378 5411 int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr) 5379 5412 { 5380 - struct ftrace_hash *hash, *free_hash = NULL; 5413 + struct ftrace_hash *hash, *new_hash = NULL, *free_hash = NULL; 5381 5414 struct ftrace_func_entry *entry, *new; 5382 5415 int err = -EBUSY, size, i; 5383 5416 ··· 5403 5436 } 5404 5437 } 5405 5438 5406 - /* ... and insert them to direct_functions hash. */ 5407 5439 err = -ENOMEM; 5440 + 5441 + /* Make a copy hash to place the new and the old entries in */ 5442 + size = hash->count + direct_functions->count; 5443 + if (size > 32) 5444 + size = 32; 5445 + new_hash = alloc_ftrace_hash(fls(size)); 5446 + if (!new_hash) 5447 + goto out_unlock; 5448 + 5449 + /* Now copy over the existing direct entries */ 5450 + size = 1 << direct_functions->size_bits; 5451 + for (i = 0; i < size; i++) { 5452 + hlist_for_each_entry(entry, &direct_functions->buckets[i], hlist) { 5453 + new = add_hash_entry(new_hash, entry->ip); 5454 + if (!new) 5455 + goto out_unlock; 5456 + new->direct = entry->direct; 5457 + } 5458 + } 5459 + 5460 + /* ... and add the new entries */ 5461 + size = 1 << hash->size_bits; 5408 5462 for (i = 0; i < size; i++) { 5409 5463 hlist_for_each_entry(entry, &hash->buckets[i], hlist) { 5410 - new = ftrace_add_rec_direct(entry->ip, addr, &free_hash); 5464 + new = add_hash_entry(new_hash, entry->ip); 5411 5465 if (!new) 5412 - goto out_remove; 5466 + goto out_unlock; 5467 + /* Update both the copy and the hash entry */ 5468 + new->direct = addr; 5413 5469 entry->direct = addr; 5414 5470 } 5415 5471 } 5472 + 5473 + free_hash = direct_functions; 5474 + rcu_assign_pointer(direct_functions, new_hash); 5475 + new_hash = NULL; 5416 5476 5417 5477 ops->func = call_direct_funcs; 5418 5478 ops->flags = MULTI_FLAGS; ··· 5448 5454 5449 5455 err = register_ftrace_function_nolock(ops); 5450 5456 5451 - out_remove: 5452 - if (err) 5453 - remove_direct_functions_hash(hash, addr); 5454 - 5455 5457 out_unlock: 5456 5458 mutex_unlock(&direct_mutex); 5457 5459 5458 - if (free_hash) { 5460 + if (free_hash && free_hash != EMPTY_HASH) { 5459 5461 synchronize_rcu_tasks(); 5460 5462 free_ftrace_hash(free_hash); 5461 5463 } 5464 + 5465 + if (new_hash) 5466 + free_ftrace_hash(new_hash); 5467 + 5462 5468 return err; 5463 5469 } 5464 5470 EXPORT_SYMBOL_GPL(register_ftrace_direct); ··· 6303 6309 6304 6310 if (entry) 6305 6311 continue; 6306 - if (add_hash_entry(hash, rec->ip) < 0) 6312 + if (add_hash_entry(hash, rec->ip) == NULL) 6307 6313 goto out; 6308 6314 } else { 6309 6315 if (entry) {