Skip to content

Commit d05cb47

Browse files
committed
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: 763e34e ("ftrace: Add register_ftrace_direct()") Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
1 parent 39a7dc2 commit d05cb47

File tree

1 file changed

+47
-53
lines changed

1 file changed

+47
-53
lines changed

kernel/trace/ftrace.c

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,18 +1183,19 @@ static void __add_hash_entry(struct ftrace_hash *hash,
11831183
hash->count++;
11841184
}
11851185

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)
11871188
{
11881189
struct ftrace_func_entry *entry;
11891190

11901191
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
11911192
if (!entry)
1192-
return -ENOMEM;
1193+
return NULL;
11931194

11941195
entry->ip = ip;
11951196
__add_hash_entry(hash, entry);
11961197

1197-
return 0;
1198+
return entry;
11981199
}
11991200

12001201
static void
@@ -1349,7 +1350,6 @@ alloc_and_copy_ftrace_hash(int size_bits, struct ftrace_hash *hash)
13491350
struct ftrace_func_entry *entry;
13501351
struct ftrace_hash *new_hash;
13511352
int size;
1352-
int ret;
13531353
int i;
13541354

13551355
new_hash = alloc_ftrace_hash(size_bits);
@@ -1366,8 +1366,7 @@ alloc_and_copy_ftrace_hash(int size_bits, struct ftrace_hash *hash)
13661366
size = 1 << hash->size_bits;
13671367
for (i = 0; i < size; i++) {
13681368
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)
13711370
goto free_hash;
13721371
}
13731372
}
@@ -2536,7 +2535,7 @@ ftrace_find_unique_ops(struct dyn_ftrace *rec)
25362535

25372536
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
25382537
/* Protected by rcu_tasks for reading, and direct_mutex for writing */
2539-
static struct ftrace_hash *direct_functions = EMPTY_HASH;
2538+
static struct ftrace_hash __rcu *direct_functions = EMPTY_HASH;
25402539
static DEFINE_MUTEX(direct_mutex);
25412540
int ftrace_direct_func_count;
25422541

@@ -2555,39 +2554,6 @@ unsigned long ftrace_find_rec_direct(unsigned long ip)
25552554
return entry->direct;
25562555
}
25572556

2558-
static struct ftrace_func_entry*
2559-
ftrace_add_rec_direct(unsigned long ip, unsigned long addr,
2560-
struct ftrace_hash **free_hash)
2561-
{
2562-
struct ftrace_func_entry *entry;
2563-
2564-
if (ftrace_hash_empty(direct_functions) ||
2565-
direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
2566-
struct ftrace_hash *new_hash;
2567-
int size = ftrace_hash_empty(direct_functions) ? 0 :
2568-
direct_functions->count + 1;
2569-
2570-
if (size < 32)
2571-
size = 32;
2572-
2573-
new_hash = dup_hash(direct_functions, size);
2574-
if (!new_hash)
2575-
return NULL;
2576-
2577-
*free_hash = direct_functions;
2578-
direct_functions = new_hash;
2579-
}
2580-
2581-
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
2582-
if (!entry)
2583-
return NULL;
2584-
2585-
entry->ip = ip;
2586-
entry->direct = addr;
2587-
__add_hash_entry(direct_functions, entry);
2588-
return entry;
2589-
}
2590-
25912557
static void call_direct_funcs(unsigned long ip, unsigned long pip,
25922558
struct ftrace_ops *ops, struct ftrace_regs *fregs)
25932559
{
@@ -4223,8 +4189,8 @@ enter_record(struct ftrace_hash *hash, struct dyn_ftrace *rec, int clear_filter)
42234189
/* Do nothing if it exists */
42244190
if (entry)
42254191
return 0;
4226-
4227-
ret = add_hash_entry(hash, rec->ip);
4192+
if (add_hash_entry(hash, rec->ip) == NULL)
4193+
ret = -ENOMEM;
42284194
}
42294195
return ret;
42304196
}
@@ -5266,7 +5232,8 @@ __ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
52665232
return 0;
52675233
}
52685234

5269-
return add_hash_entry(hash, ip);
5235+
entry = add_hash_entry(hash, ip);
5236+
return entry ? 0 : -ENOMEM;
52705237
}
52715238

52725239
static int
@@ -5410,7 +5377,7 @@ static void remove_direct_functions_hash(struct ftrace_hash *hash, unsigned long
54105377
*/
54115378
int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
54125379
{
5413-
struct ftrace_hash *hash, *free_hash = NULL;
5380+
struct ftrace_hash *hash, *new_hash = NULL, *free_hash = NULL;
54145381
struct ftrace_func_entry *entry, *new;
54155382
int err = -EBUSY, size, i;
54165383

@@ -5436,35 +5403,62 @@ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
54365403
}
54375404
}
54385405

5439-
/* ... and insert them to direct_functions hash. */
54405406
err = -ENOMEM;
5407+
5408+
/* Make a copy hash to place the new and the old entries in */
5409+
size = hash->count + direct_functions->count;
5410+
if (size > 32)
5411+
size = 32;
5412+
new_hash = alloc_ftrace_hash(fls(size));
5413+
if (!new_hash)
5414+
goto out_unlock;
5415+
5416+
/* Now copy over the existing direct entries */
5417+
size = 1 << direct_functions->size_bits;
5418+
for (i = 0; i < size; i++) {
5419+
hlist_for_each_entry(entry, &direct_functions->buckets[i], hlist) {
5420+
new = add_hash_entry(new_hash, entry->ip);
5421+
if (!new)
5422+
goto out_unlock;
5423+
new->direct = entry->direct;
5424+
}
5425+
}
5426+
5427+
/* ... and add the new entries */
5428+
size = 1 << hash->size_bits;
54415429
for (i = 0; i < size; i++) {
54425430
hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
5443-
new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
5431+
new = add_hash_entry(new_hash, entry->ip);
54445432
if (!new)
5445-
goto out_remove;
5433+
goto out_unlock;
5434+
/* Update both the copy and the hash entry */
5435+
new->direct = addr;
54465436
entry->direct = addr;
54475437
}
54485438
}
54495439

5440+
free_hash = direct_functions;
5441+
rcu_assign_pointer(direct_functions, new_hash);
5442+
new_hash = NULL;
5443+
54505444
ops->func = call_direct_funcs;
54515445
ops->flags = MULTI_FLAGS;
54525446
ops->trampoline = FTRACE_REGS_ADDR;
54535447
ops->direct_call = addr;
54545448

54555449
err = register_ftrace_function_nolock(ops);
54565450

5457-
out_remove:
5458-
if (err)
5459-
remove_direct_functions_hash(hash, addr);
5460-
54615451
out_unlock:
54625452
mutex_unlock(&direct_mutex);
54635453

5464-
if (free_hash) {
5454+
if (free_hash && free_hash != EMPTY_HASH) {
54655455
synchronize_rcu_tasks();
54665456
free_ftrace_hash(free_hash);
54675457
}
5458+
5459+
if (new_hash)
5460+
free_ftrace_hash(new_hash);
5461+
54685462
return err;
54695463
}
54705464
EXPORT_SYMBOL_GPL(register_ftrace_direct);
@@ -6309,7 +6303,7 @@ ftrace_graph_set_hash(struct ftrace_hash *hash, char *buffer)
63096303

63106304
if (entry)
63116305
continue;
6312-
if (add_hash_entry(hash, rec->ip) < 0)
6306+
if (add_hash_entry(hash, rec->ip) == NULL)
63136307
goto out;
63146308
} else {
63156309
if (entry) {

0 commit comments

Comments
 (0)