[PATCH v1 bpf-next 10/18] bpf: Allow writes to private stack objects in bpf_scan_ir
From: Anton Protopopov
Date: Fri Nov 06 2026 - 15:57:18 EST
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@xxxxxxxxx>
---
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