• Steven Rostedt (Google)'s avatar
    ftrace: Fix modification of direct_function hash while in use · d05cb470
    Steven Rostedt (Google) authored
    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: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
    Fixes: 763e34e7 ("ftrace: Add register_ftrace_direct()")
    Signed-off-by: default avatarSteven Rostedt (Google) <rostedt@goodmis.org>
    d05cb470
ftrace.c 196 KB