[CFGuard] Consider function aliases as indirect call targets (#188223)

With vector deleting destructors, it's common to include function
aliases in vftables.

After #185653 it's become more likely that the alias gets overridden in
a different TU. It's therefore important that it's the alias itself that
goes in the control-flow guard table.
This commit is contained in:
Hans Wennborg 2026-03-24 18:55:46 +01:00 committed by GitHub
parent efaf001944
commit 8228749607
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 3 deletions

View File

@ -42,14 +42,14 @@ void WinCFGuard::endFunction(const MachineFunction *MF) {
/// it an indirect call target. Function::hasAddressTaken gives different
/// results when a function is called directly with a function prototype
/// mismatch, which requires a cast.
static bool isPossibleIndirectCallTarget(const Function *F) {
SmallVector<const Value *, 4> Users{F};
static bool isPossibleIndirectCallTarget(const GlobalValue *GV) {
SmallVector<const Value *, 4> Users{GV};
while (!Users.empty()) {
const Value *FnOrCast = Users.pop_back_val();
for (const Use &U : FnOrCast->uses()) {
const User *FnUser = U.getUser();
if (const auto *Call = dyn_cast<CallBase>(FnUser)) {
if ((!Call->isCallee(&U) || U.get() != F) &&
if ((!Call->isCallee(&U) || U.get() != GV) &&
!Call->getFunction()->getName().ends_with("$exit_thunk")) {
// Passing a function pointer to a call may lead to an indirect
// call. As an exception, ignore ARM64EC exit thunks.
@ -60,6 +60,10 @@ static bool isPossibleIndirectCallTarget(const Function *F) {
// consequences like no-op intrinsics being an escape or a store *to* a
// function address being an escape.
return true;
} else if (isa<GlobalAlias>(FnUser)) {
// If the function is used via the alias, it's really the alias that's
// a possible call target. See "Consider aliases" in endModule().
continue;
} else if (const auto *G = dyn_cast<GlobalValue>(FnUser)) {
// Ignore llvm.arm64ec.symbolmap; it doesn't lower to an actual address.
if (G->getName() == "llvm.arm64ec.symbolmap")
@ -104,6 +108,13 @@ void WinCFGuard::endModule() {
}
}
for (const GlobalAlias &GA : M->aliases()) {
// Consider aliases to functions as possible call targets.
const GlobalObject *Aliasee = GA.getAliaseeObject();
if (Aliasee && isa<Function>(Aliasee) && isPossibleIndirectCallTarget(&GA))
GFIDsEntries.push_back(Asm->getSymbol(&GA));
}
if (GFIDsEntries.empty() && GIATsEntries.empty() && LongjmpTargets.empty())
return;

View File

@ -0,0 +1,28 @@
; RUN: llc < %s -mtriple=x86_64-pc-windows-msvc | FileCheck %s
; CHECK: .section .gfids$y
; CHECK: .symidx alias
; CHECK-NOT: .symidx calledalias
; CHECK-NOT: .symidx func
define void @func() {
ret void
}
@alias = alias ptr, ptr @func
; This makes @alias a potential indirect call target.
; The aliasee (@func) is not considered as such.
@ptrs = global [1 x ptr] [ptr @alias]
@calledalias = alias ptr, ptr @func
define void @caller() {
; A direct call does not make the alias an indirect call target.
call void @calledalias()
ret void
}
!llvm.module.flags = !{!0}
!0 = !{i32 2, !"cfguard", i32 2}