https://github.com/theihor created 
https://github.com/llvm/llvm-project/pull/234280

__builtin_preserve_field_info on a field of a preserve_static_offset record 
crashes clang in BPFAbstractMemberAccess, for example 
bpf_core_field_exists(skb->mark) with a marked struct __sk_buff.

Every release since 18 is affected.

BPFAbstractMemberAccess computes the field relocation from the 
preserve.*.access.index calls that CodeGen emits for the builtin's argument. 
CodeGen also wraps each access to a marked record in 
llvm.preserve.static.offset, and BPFPreserveStaticOffset replaces the 
access-index calls under such markers with plain GEPs, so that loads and stores 
keep static offsets. The field-info call is left without type metadata, and 
BPFAbstractMemberAccess dereferences a nullptr.

Field info never dereferences the field, so the markers serve no purpose there. 
Do not emit them while emitting the builtin's argument.

bpftool's generated vmlinux.h is about to mark the BPF context types with 
preserve_static_offset [1], which makes the crash reachable through plain 
libbpf CO-RE macros.

[1] 
https://lore.kernel.org/bpf/[email protected]/

Assisted-by: Claude Code

>From 4388d3dcbf91cf58526bdf69c2895b38cd5c5f82 Mon Sep 17 00:00:00 2001
From: Ihor Solodrai <[email protected]>
Date: Tue, 20 Oct 2026 16:29:47 -0700
Subject: [PATCH] [clang][BPF] Do not emit preserve_static_offset markers for
 field info

__builtin_preserve_field_info on a field of a preserve_static_offset
record crashes clang in BPFAbstractMemberAccess, for example
bpf_core_field_exists(skb->mark) with a marked struct __sk_buff.

Every release since 18 is affected.

BPFAbstractMemberAccess computes the field relocation from the
preserve.*.access.index calls that CodeGen emits for the builtin's
argument. CodeGen also wraps each access to a marked record in
llvm.preserve.static.offset, and BPFPreserveStaticOffset replaces the
access-index calls under such markers with plain GEPs, so that loads
and stores keep static offsets. The field-info call is left without
type metadata, and BPFAbstractMemberAccess dereferences a nullptr.

Field info never dereferences the field, so the markers serve no purpose
there. Do not emit them while emitting the builtin's argument.

bpftool's generated vmlinux.h is about to mark the BPF context types
with preserve_static_offset [1], which makes the crash reachable
through plain libbpf CO-RE macros.

[1] 
https://lore.kernel.org/bpf/[email protected]/

Assisted-by: Claude Code
---
 clang/docs/ReleaseNotes.md                    |  2 +
 clang/lib/CodeGen/CGExpr.cpp                  |  2 +-
 clang/lib/CodeGen/CodeGenFunction.h           |  4 ++
 clang/lib/CodeGen/TargetBuiltins/ARM.cpp      |  7 ++-
 .../bpf-preserve-static-offset-field-info.c   | 55 +++++++++++++++++++
 5 files changed, 68 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGen/bpf-preserve-static-offset-field-info.c

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 830109b8c4a399..7d37ffa352bde5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -793,6 +793,8 @@ features cannot lower the translation-unit ABI level;
 - Fixed an assertion failure when constant-evaluating `__builtin_object_size`
   or `__builtin_dynamic_object_size` with a type argument that is not an
   integer constant expression. (#GH231108)
+- Fixed a crash on BPF targets when `__builtin_preserve_field_info` is
+  applied to a field of a record marked `preserve_static_offset`.
 
 - `__has_unique_object_representations` now returns `true` for `_Atomic` types
   whose object representation is identical to that of their value type, such
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index beebd67116e5ff..eef6c551b186b4 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -4879,7 +4879,7 @@ static bool hasBPFPreserveStaticOffset(const Expr *E) {
 // Wraps Addr with a call to llvm.preserve.static.offset intrinsic.
 static Address wrapWithBPFPreserveStaticOffset(CodeGenFunction &CGF,
                                                Address &Addr) {
-  if (!CGF.getTarget().getTriple().isBPF())
+  if (!CGF.getTarget().getTriple().isBPF() || CGF.IsInPreserveFieldInfo)
     return Addr;
 
   llvm::Function *Fn =
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 733d9ab310affb..06611f08d86a15 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -627,6 +627,10 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// region.
   bool IsInPreservedAIRegion = false;
 
+  /// True if CodeGen currently emits the argument of
+  /// __builtin_preserve_field_info.
+  bool IsInPreserveFieldInfo = false;
+
   /// True if the current statement has nomerge attribute.
   bool InNoMergeAttributedStmt = false;
 
diff --git a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp 
b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
index 4a7bdba6d917ee..5d4b87b75abe7b 100644
--- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp
@@ -7314,12 +7314,17 @@ Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned 
BuiltinID,
                         : EmitLValue(Arg).emitRawPointer(*this);
     }
 
-    // Enable underlying preserve_*_access_index() generation.
+    // Enable underlying preserve_*_access_index() generation. Field info
+    // never dereferences the field, so skip preserve_static_offset markers:
+    // the backend would replace the access indices under them with plain GEPs.
     bool OldIsInPreservedAIRegion = IsInPreservedAIRegion;
+    bool OldIsInPreserveFieldInfo = IsInPreserveFieldInfo;
     IsInPreservedAIRegion = true;
+    IsInPreserveFieldInfo = true;
     Value *FieldAddr = IsBitField ? 
EmitLValue(Arg).getRawBitFieldPointer(*this)
                                   : EmitLValue(Arg).emitRawPointer(*this);
     IsInPreservedAIRegion = OldIsInPreservedAIRegion;
+    IsInPreserveFieldInfo = OldIsInPreserveFieldInfo;
 
     ConstantInt *C = cast<ConstantInt>(EmitScalarExpr(E->getArg(1)));
     Value *InfoKind = ConstantInt::get(Int64Ty, C->getSExtValue());
diff --git a/clang/test/CodeGen/bpf-preserve-static-offset-field-info.c 
b/clang/test/CodeGen/bpf-preserve-static-offset-field-info.c
new file mode 100644
index 00000000000000..b5a524c4558761
--- /dev/null
+++ b/clang/test/CodeGen/bpf-preserve-static-offset-field-info.c
@@ -0,0 +1,55 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 2
+// REQUIRES: bpf-registered-target
+// RUN: %clang_cc1 -triple bpf -disable-llvm-passes -emit-llvm 
-debug-info-kind=limited -o - %s | FileCheck %s
+
+// Verify that the argument of __builtin_preserve_field_info is not wrapped
+// in llvm.preserve.static.offset calls, while ordinary accesses still are.
+
+#define __ctx __attribute__((preserve_static_offset))
+
+struct foo {
+  int a;
+} __ctx;
+
+struct bar {
+  int b;
+  struct foo c[2];
+} __ctx;
+
+// CHECK-LABEL: define dso_local i32 @buz
+// CHECK-SAME: (ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] !dbg 
[[DBG13:![0-9]+]] {
+// CHECK-NEXT:  entry:
+// CHECK-NEXT:    [[RETVAL:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    store ptr [[P]], ptr [[P_ADDR]], align 8
+// CHECK-NEXT:      #dbg_declare(ptr [[P_ADDR]], [[META22:![0-9]+]], 
!DIExpression(), [[META23:![0-9]+]])
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8, !dbg 
[[DBG24:![0-9]+]]
+// CHECK-NEXT:    [[TMP1:%.*]] = call ptr 
@llvm.preserve.struct.access.index.p0.p0(ptr elementtype([[STRUCT_BAR:%.*]]) 
[[TMP0]], i32 1, i32 1), !dbg [[DBG26:![0-9]+]], !llvm.preserve.access.index 
[[META17:![0-9]+]]
+// CHECK-NEXT:    [[TMP2:%.*]] = call ptr 
@llvm.preserve.array.access.index.p0.p0(ptr elementtype([2 x 
[[STRUCT_FOO:%.*]]]) [[TMP1]], i32 1, i32 1), !dbg [[DBG24]], 
!llvm.preserve.access.index [[META3:![0-9]+]]
+// CHECK-NEXT:    [[TMP3:%.*]] = call ptr 
@llvm.preserve.struct.access.index.p0.p0(ptr elementtype([[STRUCT_FOO]]) 
[[TMP2]], i32 0, i32 0), !dbg [[DBG27:![0-9]+]], !llvm.preserve.access.index 
[[META4:![0-9]+]]
+// CHECK-NEXT:    [[TMP4:%.*]] = call i32 @llvm.bpf.preserve.field.info.p0(ptr 
[[TMP3]], i64 2), !dbg [[DBG28:![0-9]+]]
+// CHECK-NEXT:    [[TOBOOL:%.*]] = icmp ne i32 [[TMP4]], 0, !dbg [[DBG28]]
+// CHECK-NEXT:    br i1 [[TOBOOL]], label [[IF_THEN:%.*]], label 
[[IF_END:%.*]], !dbg [[DBG28]]
+// CHECK:       if.then:
+// CHECK-NEXT:    [[TMP5:%.*]] = load ptr, ptr [[P_ADDR]], align 8, !dbg 
[[DBG29:![0-9]+]]
+// CHECK-NEXT:    [[TMP6:%.*]] = call ptr @llvm.preserve.static.offset(ptr 
[[TMP5]]), !dbg [[DBG30:![0-9]+]]
+// CHECK-NEXT:    [[C:%.*]] = getelementptr inbounds nuw [[STRUCT_BAR]], ptr 
[[TMP6]], i32 0, i32 1, !dbg [[DBG30]]
+// CHECK-NEXT:    [[TMP7:%.*]] = call ptr @llvm.preserve.static.offset(ptr 
[[C]]), !dbg [[DBG29]]
+// CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds [2 x 
[[STRUCT_FOO]]], ptr [[TMP7]], i64 0, i64 1, !dbg [[DBG29]]
+// CHECK-NEXT:    [[TMP8:%.*]] = call ptr @llvm.preserve.static.offset(ptr 
[[ARRAYIDX]]), !dbg [[DBG31:![0-9]+]]
+// CHECK-NEXT:    [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_FOO]], ptr 
[[TMP8]], i32 0, i32 0, !dbg [[DBG31]]
+// CHECK-NEXT:    [[TMP9:%.*]] = load i32, ptr [[A]], align 4, !dbg [[DBG31]]
+// CHECK-NEXT:    store i32 [[TMP9]], ptr [[RETVAL]], align 4, !dbg 
[[DBG32:![0-9]+]]
+// CHECK-NEXT:    br label [[RETURN:%.*]], !dbg [[DBG32]]
+// CHECK:       if.end:
+// CHECK-NEXT:    store i32 0, ptr [[RETVAL]], align 4, !dbg [[DBG33:![0-9]+]]
+// CHECK-NEXT:    br label [[RETURN]], !dbg [[DBG33]]
+// CHECK:       return:
+// CHECK-NEXT:    [[TMP10:%.*]] = load i32, ptr [[RETVAL]], align 4, !dbg 
[[DBG34:![0-9]+]]
+// CHECK-NEXT:    ret i32 [[TMP10]], !dbg [[DBG34]]
+//
+int buz(struct bar *p) {
+  if (__builtin_preserve_field_info(p->c[1].a, 2))
+    return p->c[1].a;
+  return 0;
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to