From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Alan Maguire <almagui@kernel.org>,
John Fastabend <john.fastabend@gmail.com>,
Quentin Monnet <qmo@kernel.org>,
Christian Brauner <brauner@kernel.org>,
KP Singh <kpsingh@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
Sasha Levin <sashal@kernel.org>,
bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Anton Protopopov <a.s.protopopov@gmail.com>
Subject: [PATCH v1 bpf-next 10/18] bpf: Allow writes to private stack objects in bpf_scan_ir
Date: Fri, 6 Nov 2026 21:02:59 +0000 [thread overview]
Message-ID: <20261106210314.1767267-10-a.s.protopopov@gmail.com> (raw)
In-Reply-To: 20261106205523.1765622-1-a.s.protopopov@gmail.com
A write to a stack object of the function is not observable outside of
it as long as the object has not escaped. Let such writes and memsets
stay on a clean path. A write after the object may have been captured,
a volatile or atomic one and a write to other memory still make the
path dirty.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
scripts/llvm-plugins/bpf_scan_ir.cpp | 85 +++++++++++++++++++++-------
1 file changed, 64 insertions(+), 21 deletions(-)
diff --git a/scripts/llvm-plugins/bpf_scan_ir.cpp b/scripts/llvm-plugins/bpf_scan_ir.cpp
index 6828fa73717d..0fc60b2130b1 100644
--- a/scripts/llvm-plugins/bpf_scan_ir.cpp
+++ b/scripts/llvm-plugins/bpf_scan_ir.cpp
@@ -4,13 +4,17 @@
#include "../bpf/fmodret_metadata.h"
#include <algorithm>
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/CaptureTracking.h"
+#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/Analysis.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Constants.h"
+#include "llvm/IR/Dominators.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
@@ -45,41 +49,76 @@ static bool errnoConstant(const Value *V, int64_t &N)
return N >= -MaxErrno && N < 0;
}
-static bool cleanInstruction(const Instruction &I)
-{
- if (auto *Intrinsic = dyn_cast<IntrinsicInst>(&I)) {
- switch (Intrinsic->getIntrinsicID()) {
- case Intrinsic::assume:
- case Intrinsic::lifetime_start:
- case Intrinsic::lifetime_end:
- case Intrinsic::invariant_start:
- case Intrinsic::invariant_end:
- return true;
- default:
- break;
+class CleanInstructions {
+ DominatorTree &DT;
+ DenseMap<const Instruction *, bool> PrivateWrites;
+
+ bool privateDestination(const Value *Pointer, const Instruction &I)
+ {
+ auto It = PrivateWrites.find(&I);
+ if (It != PrivateWrites.end())
+ return It->second;
+ auto *Allocation = dyn_cast<AllocaInst>(getUnderlyingObject(Pointer));
+ bool Private = false;
+
+ if (Allocation && Allocation->getFunction() == I.getFunction()) {
+ // A later call may receive the initialized object. Only captures
+ // that can precede this write make the write externally visible.
+#if LLVM_VERSION_MAJOR >= 21
+ Private = !PointerMayBeCapturedBefore(Allocation, true, &I, &DT, true, 256);
+#else
+ Private = !PointerMayBeCapturedBefore(Allocation, true, true, &I, &DT, true, 256);
+#endif
}
+ PrivateWrites[&I] = Private;
+ return Private;
}
- return !I.mayHaveSideEffects();
-}
-static bool cleanBB(BasicBlock *BB)
+public:
+ explicit CleanInstructions(DominatorTree &DT) : DT(DT) {}
+
+ bool isClean(const Instruction &I)
+ {
+ if (auto *Store = dyn_cast<StoreInst>(&I))
+ return !Store->isVolatile() && !Store->isAtomic() &&
+ privateDestination(Store->getPointerOperand(), I);
+ if (auto *Memory = dyn_cast<MemIntrinsic>(&I))
+ return !Memory->isVolatile() &&
+ privateDestination(Memory->getRawDest(), I);
+ if (auto *Intrinsic = dyn_cast<IntrinsicInst>(&I)) {
+ switch (Intrinsic->getIntrinsicID()) {
+ case Intrinsic::assume:
+ case Intrinsic::lifetime_start:
+ case Intrinsic::lifetime_end:
+ case Intrinsic::invariant_start:
+ case Intrinsic::invariant_end:
+ return true;
+ default:
+ break;
+ }
+ }
+ return !I.mayHaveSideEffects();
+ }
+};
+
+static bool cleanBB(BasicBlock *BB, CleanInstructions &Clean)
{
for (Instruction &I : *BB) {
- if (!cleanInstruction(I))
+ if (!Clean.isClean(I))
return false;
}
return true;
}
/* Add the errors which clean paths of F are known to return to VALUES */
-static void cleanReturnValues(Function &F, ReturnValues &Values)
+static void cleanReturnValues(Function &F, CleanInstructions &Clean, ReturnValues &Values)
{
SmallPtrSet<BasicBlock *, 32> CleanBBs;
SmallVector<BasicBlock *, 32> Worklist;
bool Changed;
/* The clean blocks which the entry reaches through clean blocks */
- if (cleanBB(&F.getEntryBlock())) {
+ if (cleanBB(&F.getEntryBlock(), Clean)) {
CleanBBs.insert(&F.getEntryBlock());
Worklist.push_back(&F.getEntryBlock());
}
@@ -87,7 +126,7 @@ static void cleanReturnValues(Function &F, ReturnValues &Values)
BasicBlock *BB = Worklist.pop_back_val();
for (BasicBlock *Successor : successors(BB)) {
- if (!CleanBBs.contains(Successor) && cleanBB(Successor)) {
+ if (!CleanBBs.contains(Successor) && cleanBB(Successor, Clean)) {
CleanBBs.insert(Successor);
Worklist.push_back(Successor);
}
@@ -123,7 +162,7 @@ static void cleanReturnValues(Function &F, ReturnValues &Values)
bool Entered = CleanBBs.contains(&BB);
int64_t N;
- if (!Return || !Return->getReturnValue() || !cleanBB(&BB))
+ if (!Return || !Return->getReturnValue() || !cleanBB(&BB, Clean))
continue;
for (BasicBlock *Pred : predecessors(&BB))
Entered |= CleanBBs.contains(Pred);
@@ -183,6 +222,7 @@ public:
return PreservedAnalyses::all();
SmallVector<FunctionDescription, 32> Descriptions;
+ auto &FAM = MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
for (Function &F : M) {
if (F.isIntrinsic() || F.isDeclaration() || F.hasAvailableExternallyLinkage())
@@ -197,7 +237,10 @@ public:
Flags |= FMODRET_FN_WEAK;
if (Ret->isIntegerTy(64))
Flags |= FMODRET_FN_RET64;
- cleanReturnValues(F, Values);
+ auto &DT = FAM.getResult<DominatorTreeAnalysis>(F);
+ CleanInstructions Clean(DT);
+
+ cleanReturnValues(F, Clean, Values);
/* A weak definition always has a record, see fmodret_metadata.h */
if (!Values.empty() || (Flags & FMODRET_FN_WEAK))
Descriptions.push_back({&F, Flags, std::move(Values)});
--
2.43.0
next prev parent reply other threads:[~2026-11-06 20:52 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 21:02 [PATCH v1 bpf-next 01/18] bpf: Add BTF ID sets for fmod_ret targets Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 02/18] bpf: Add a btf_id_set8_entry helper Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 03/18] bpf: Build fmod_ret target sets from BTF decl tags Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 04/18] selftests/bpf: Declare bpf_fentry_test1 in tracing tests Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 05/18] bpf: Declare fmod_ret targets with BTF_FMODRET sets Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 06/18] selftests/bpf: Test fmod_ret BTF decl tags and attach permission Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 07/18] scripts: Add LLVM plugin infrastructure Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 08/18] bpf: Add the bpf_scan_ir LLVM plugin Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 09/18] bpf: Find fmod_ret candidates in bpf_scan_ir Anton Protopopov
2026-11-06 21:02 ` Anton Protopopov [this message]
2026-11-06 21:03 ` [PATCH v1 bpf-next 11/18] bpf: Follow PHIs " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 12/18] bpf: Follow integer casts " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 13/18] bpf: Check the return values of fmod_ret programs on found targets Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 14/18] bpf: Require the JIT for fmod_ret programs on found 64-bit targets Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 15/18] resolve_btfids: Tag fmod_ret candidates as targets Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 16/18] selftests/bpf: Test fmod_ret candidates found by bpf_scan_ir Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 17/18] selftests/bpf: Enable bpf_scan_ir in the config Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 18/18] bpftool: List and show fmod_ret targets Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106210314.1767267-10-a.s.protopopov@gmail.com \
--to=a.s.protopopov@gmail.com \
--cc=almagui@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=qmo@kernel.org \
--cc=sashal@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®