From nobody Mon May 25 06:41:09 2026 Received: from mail-ej1-f47.google.com (mail-ej1-f47.google.com [209.85.218.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 488C6155C87 for ; Sun, 17 May 2026 11:02:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779015723; cv=none; b=S5m9Z7XW/PF3eTA4tlfrM85u9vbhoQibuB7opXpOZSi0PiWpBj870VLfITJbJMGmMowN+YOTmIyIZq/KpLgdTBsgQBjgiEdeili+8H0x7qDvQf19N5RNb/kbrfKulZkZ2/tSvob3n+fjhDOUyh/qsn6MKVU1u+wiwcpSjW/i9AU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779015723; c=relaxed/simple; bh=eTdb565plt91PEiNuS67rwBcXAWzhTaoy3yOCPulguo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=G8jCVbSI/amrT24rtGntceQuk0aL/+IMg32lJ7lX7XlbayogJmu9wsZ7zGJaNE3ULq/bFNNpaXZlFjGJF1LIf+houqGlt/yImx+YiPBi0mCduqDxfrw2XLfwxayCNU3/qL19LKdVTB+Sc4rCplJLySQ4h4aXDjYH+poKXfsZpIY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=G/F2fvRh; arc=none smtp.client-ip=209.85.218.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="G/F2fvRh" Received: by mail-ej1-f47.google.com with SMTP id a640c23a62f3a-bd2e8931915so342497566b.1 for ; Sun, 17 May 2026 04:02:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779015721; x=1779620521; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=LAI1tCItT+prsS3BUZu8zZivM7G/QxPMste08XbQ9aE=; b=G/F2fvRhrSzMnWwhJUpXDJwhkAhmqWPZQwgUjbd1hJ0qnjwPKJwLhbAI+WNp29VNp8 DSi9TVBGl1ukiMGaHpgMtu3U5rj+Plb1k4WVa03R1ii1lAf+NBCLOrtQNbzljnbMGqdY ZU1AnXKInTX0W75szopAv9RVyLek00Duc2q2PxfQ5yVcA9++6RdFCLxhkHbhfIHSoRp6 +SSNAeo5cqEENnaARJEz3tgqJwx/rCj3Mtep37/K/ZUwnpOZT6NrYoo6P766r9wa2knr inWxqHWqoIacwne2ZswlIYiKe2BZAhHjF9MKESeji7AmcfdLYYd/vBk82r6kEcVjporL qPEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779015721; x=1779620521; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=LAI1tCItT+prsS3BUZu8zZivM7G/QxPMste08XbQ9aE=; b=QHzj5YY9sfvNae3o+6CgLDFEMdiKmOGiXKlN9uZZQrcOQ2zzaCoFzV8MPOr4SLo4Bt iaqaWKu7g5fjMUnq5bLqTfAu/GXjsoC9x+DZv0wxeiQcfn1NrZ/j1IiXM0uhArSDV5nI V6frwdbu92P0IIGGRpCYak0WY/kC6gNZYU7RCW/Qd58qsvl3w7Ni5ZMe38gFf5LyX1L8 Q2N8/0CUm5jTaP52F+0vXhWgOxTOSrq0LrIIs5QCBJtYlb0SaCCzkI+hi0XBOvplnkCi wBCYdvmC4+lHQABbGjZeXyNQGmDbmIhzg8eWcgYngvYOQJjoaK725fzYYAWeXganQsl6 41Bw== X-Forwarded-Encrypted: i=1; AFNElJ95ydJP6tK+IyjM90yoTH+9g52i5xnxnV1ciAXzsbmgPQZIjw+0Ci5xgegs6mZcoro3+q1qgGlxAoKuK28=@vger.kernel.org X-Gm-Message-State: AOJu0YyaVPCo/lnRJ6TnNvvYqpff/38wPRQBgYOwuVvXOdimXSACZXJF rYVC2/3qg5zFY1EfFxnRv0ArQW8vhcgv4+c8OnCpeKum4JOcL9A1eH10cDFr0vqJ X-Gm-Gg: Acq92OHqXNYOO6LFcMm6qKjP0ToZvZYbY7LdkZrE5/Iwg9OkRo2+M9RdEIv3wGolcdO FH9AHlukalwrqJgVZDUI18rb4dsR94Zt+bH3ApHxtf7l2sRtYN0bMQxcW/K9vtEM2QmVikgXaWg mfnESsJXOMxptwceivgZPeAkBS/Cr8+c1R4Z+4bkPAtOJF0z17wyCH9fDn58xZdU0A8LQ1PuqU0 ahrO85hhzPHop+iUDBmamPxhbCu776qIFR4NA2sbzNUNDSUvqRFUIj0/Va/+sQo9L1lcVXyT4pT DYlUDv7d7H1c6H19VIs/Us2jfue+Fnrl1hGgfwtQRTJb9oyh2PSgncGxJD4dmcTE5ObBLO1uJ4U /ay9SE8+/tnobBb+N2Oj3Auo2UrNTNET6ofKj+KABkVTv05GwW36iZkVg41io8esOC/YwgAWgQU 0FhyEdPesVy+Uu1EKJ5YiZi84+wJInCA== X-Received: by 2002:a17:907:e144:10b0:bac:6585:b02b with SMTP id a640c23a62f3a-bd51538cabfmr387224366b.9.1779015720519; Sun, 17 May 2026 04:02:00 -0700 (PDT) Received: from nixbug.lan ([146.120.47.171]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-bd4f4c2a68dsm449437866b.18.2026.05.17.04.01.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 04:02:00 -0700 (PDT) From: Andrii Kuchmenko To: linux-trace-kernel@vger.kernel.org Cc: rostedt@goodmis.org, mhiramat@kernel.org, linux-kernel@vger.kernel.org, Andrii Kuchmenko , stable@vger.kernel.org Subject: [PATCH] ftrace: fix race in __modify_ftrace_direct() between tmp_ops registration and direct_functions update Date: Sun, 17 May 2026 14:01:53 +0300 Message-ID: <20260517110155.21706-1-capyenglishlite@gmail.com> X-Mailer: git-send-email 2.51.2 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable In __modify_ftrace_direct(), register_ftrace_function_nolock() makes tmp_ops visible in ftrace_ops_list before entry->direct is updated under ftrace_lock. During this window any CPU entering the traced function calls call_direct_funcs(), reads the old address from direct_functions via RCU, and jumps to it via arch_ftrace_set_direct_caller(). If the caller freed or invalidated the old trampoline before calling modify_ftrace_direct(), this is a use-after-free in executable code context. The race window: CPU 0 (__modify_ftrace_direct) CPU 1 (executing traced func) =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80 =E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2= =94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94= =80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80=E2=94=80= =E2=94=80=E2=94=80=E2=94=80 register_ftrace_function_nolock() -> tmp_ops visible in ops_list call_direct_funcs() ftrace_find_rec_direct() -> old_a= ddr arch_ftrace_set_direct_caller(old= _addr) jump to old_addr <- UAF if freed mutex_lock(&ftrace_lock) entry->direct =3D addr <- too late mutex_unlock(&ftrace_lock) Fix: update entry->direct under ftrace_lock BEFORE registering tmp_ops. Any CPU that observes tmp_ops in ftrace_ops_list after this point will already see the new address when it calls ftrace_find_rec_direct(). Add smp_wmb() between the store and the registration to ensure the write is visible on weakly-ordered architectures before tmp_ops becomes observable via ftrace_ops_list. On error from register_ftrace_function_nolock(), restore entry->direct to old_addr since tmp_ops never became visible to other CPUs. This affects all callers of __modify_ftrace_direct(), including: - modify_ftrace_direct() used by kernel modules and live patching - modify_ftrace_direct_nolock() used by BPF trampolines (kernel/bpf/trampoline.c) reachable with CAP_BPF + CAP_PERFMON Fixes: 0567d6809440 ("ftrace: Add modify_ftrace_direct()") Cc: Steven Rostedt Cc: Masami Hiramatsu Cc: stable@vger.kernel.org Signed-off-by: Andrii Kuchmenko --- kernel/trace/ftrace.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index a1b2c3d4e5f6..b7c8d9e0f1a2 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -5950,6 +5950,7 @@ static int __modify_ftrace_direct(struct ftrace_ops *= ops, unsigned long addr) struct ftrace_func_entry *entry; struct ftrace_ops tmp_ops; + unsigned long old_addr; int err; =20 lockdep_assert_held(&direct_mutex); @@ -5960,22 +5961,36 @@ static int __modify_ftrace_direct(struct ftrace_ops= *ops, unsigned long addr) if (!entry) return -ENODEV; =20 - /* - * tmp_ops is registered into ftrace_ops_list here, making it - * visible to all CPUs executing the traced function. However, - * entry->direct is not updated until after this call returns, - * leaving a window where CPUs read the stale (possibly freed) - * direct call address via ftrace_find_rec_direct(). - */ - err =3D register_ftrace_function_nolock(&tmp_ops); - if (err) - return err; - + /* Save old address in case we need to roll back on error. */ + old_addr =3D entry->direct; + + /* + * Update entry->direct BEFORE registering tmp_ops into + * ftrace_ops_list. This closes the race window where a CPU + * executing the traced function could read the old (potentially + * freed) direct call address between tmp_ops becoming visible + * and entry->direct being updated. + * + * Any CPU that observes tmp_ops in ftrace_ops_list after the + * smp_wmb() below is guaranteed to see the new address when + * it calls ftrace_find_rec_direct(). + */ mutex_lock(&ftrace_lock); entry->direct =3D addr; mutex_unlock(&ftrace_lock); =20 + /* + * Ensure entry->direct store is ordered before tmp_ops + * becomes visible via ftrace_ops_list on weakly-ordered archs. + */ + smp_wmb(); + + err =3D register_ftrace_function_nolock(&tmp_ops); + if (err) { + /* tmp_ops never became visible; safe to restore old_addr. */ + mutex_lock(&ftrace_lock); + entry->direct =3D old_addr; + mutex_unlock(&ftrace_lock); + return err; + } + /* * Now that tmp_ops is registered and entry->direct is updated, * unregister the original ops and clean up. --=20 2.39.0