Skip to content

[clang] Implement function pointer signing and authenticated function calls #93906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/CodeGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef LLVM_CLANG_BASIC_CODEGENOPTIONS_H
#define LLVM_CLANG_BASIC_CODEGENOPTIONS_H

#include "clang/Basic/PointerAuthOptions.h"
#include "clang/Basic/Sanitizers.h"
#include "clang/Basic/XRayInstr.h"
#include "llvm/ADT/FloatingPointMode.h"
Expand Down Expand Up @@ -391,6 +392,9 @@ class CodeGenOptions : public CodeGenOptionsBase {

std::vector<std::string> Reciprocals;

/// Configuration for pointer-signing.
PointerAuthOptions PointerAuth;

/// The preferred width for auto-vectorization transforms. This is intended to
/// override default transforms based on the width of the architected vector
/// registers.
Expand Down
134 changes: 134 additions & 0 deletions clang/include/clang/Basic/PointerAuthOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,144 @@
#ifndef LLVM_CLANG_BASIC_POINTERAUTHOPTIONS_H
#define LLVM_CLANG_BASIC_POINTERAUTHOPTIONS_H

#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Target/TargetOptions.h"
#include <optional>

namespace clang {

constexpr unsigned PointerAuthKeyNone = -1;

class PointerAuthSchema {
public:
enum class Kind : unsigned {
None,
ARM8_3,
};

/// Hardware pointer-signing keys in ARM8.3.
///
/// These values are the same used in ptrauth.h.
enum class ARM8_3Key : unsigned {
ASIA = 0,
ASIB = 1,
ASDA = 2,
ASDB = 3
};

/// Forms of extra discrimination.
enum class Discrimination : unsigned {
/// No additional discrimination.
None,

/// Discriminate using a constant value.
Constant,
};

private:
Kind TheKind : 2;
unsigned IsAddressDiscriminated : 1;
unsigned IsIsaPointer : 1;
unsigned AuthenticatesNullValues : 1;
PointerAuthenticationMode SelectedAuthenticationMode : 2;
Discrimination DiscriminationKind : 2;
unsigned Key : 2;
unsigned ConstantDiscriminator : 16;

public:
PointerAuthSchema() : TheKind(Kind::None) {}

PointerAuthSchema(
ARM8_3Key Key, bool IsAddressDiscriminated,
PointerAuthenticationMode AuthenticationMode,
Discrimination OtherDiscrimination,
std::optional<uint16_t> ConstantDiscriminatorOrNone = std::nullopt,
bool IsIsaPointer = false, bool AuthenticatesNullValues = false)
: TheKind(Kind::ARM8_3), IsAddressDiscriminated(IsAddressDiscriminated),
IsIsaPointer(IsIsaPointer),
AuthenticatesNullValues(AuthenticatesNullValues),
SelectedAuthenticationMode(AuthenticationMode),
DiscriminationKind(OtherDiscrimination), Key(llvm::to_underlying(Key)) {
assert((getOtherDiscrimination() != Discrimination::Constant ||
ConstantDiscriminatorOrNone) &&
"constant discrimination requires a constant!");
if (ConstantDiscriminatorOrNone)
ConstantDiscriminator = *ConstantDiscriminatorOrNone;
Comment on lines +81 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ahmedbougacha @ahatanak The static verifier is reporting that 'ConstantDiscriminator' is not initialized when taking the false branch of that if. Is that possible?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikerice1969 The logic is a bit convoluted here. But essentially this field is only initialized when getOtherDiscrimination() == Discrimination::Constant. And there is assert above for that and below in getConstantDiscrimination(). But probably it would be better to initialize it regardless.

}

PointerAuthSchema(
ARM8_3Key Key, bool IsAddressDiscriminated,
Discrimination OtherDiscrimination,
std::optional<uint16_t> ConstantDiscriminatorOrNone = std::nullopt,
bool IsIsaPointer = false, bool AuthenticatesNullValues = false)
: PointerAuthSchema(Key, IsAddressDiscriminated,
PointerAuthenticationMode::SignAndAuth,
OtherDiscrimination, ConstantDiscriminatorOrNone,
IsIsaPointer, AuthenticatesNullValues) {}

Kind getKind() const { return TheKind; }

explicit operator bool() const { return isEnabled(); }

bool isEnabled() const { return getKind() != Kind::None; }

bool isAddressDiscriminated() const {
assert(getKind() != Kind::None);
return IsAddressDiscriminated;
}

bool isIsaPointer() const {
assert(getKind() != Kind::None);
return IsIsaPointer;
}

bool authenticatesNullValues() const {
assert(getKind() != Kind::None);
return AuthenticatesNullValues;
}

bool hasOtherDiscrimination() const {
return getOtherDiscrimination() != Discrimination::None;
}

Discrimination getOtherDiscrimination() const {
assert(getKind() != Kind::None);
return DiscriminationKind;
}

uint16_t getConstantDiscrimination() const {
assert(getOtherDiscrimination() == Discrimination::Constant);
return ConstantDiscriminator;
}

unsigned getKey() const {
switch (getKind()) {
case Kind::None:
llvm_unreachable("calling getKey() on disabled schema");
case Kind::ARM8_3:
return llvm::to_underlying(getARM8_3Key());
}
llvm_unreachable("bad key kind");
}

PointerAuthenticationMode getAuthenticationMode() const {
return SelectedAuthenticationMode;
}

ARM8_3Key getARM8_3Key() const {
assert(getKind() == Kind::ARM8_3);
return ARM8_3Key(Key);
}
};

struct PointerAuthOptions {
/// The ABI for C function pointers.
PointerAuthSchema FunctionPointers;
};

} // end namespace clang

#endif
9 changes: 9 additions & 0 deletions clang/include/clang/Frontend/CompilerInvocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,15 @@ class CompilerInvocation : public CompilerInvocationBase {
/// executable), for finding the builtin compiler path.
static std::string GetResourcesPath(const char *Argv0, void *MainAddr);

/// Populate \p Opts with the default set of pointer authentication-related
/// options given \p LangOpts and \p Triple.
///
/// Note: This is intended to be used by tools which must be aware of
/// pointer authentication-related code generation, e.g. lldb.
static void setDefaultPointerAuthOptions(PointerAuthOptions &Opts,
const LangOptions &LangOpts,
const llvm::Triple &Triple);

/// Retrieve a module hash string that is suitable for uniquely
/// identifying the conditions under which the module was built.
std::string getModuleHash() const;
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6015,8 +6015,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
// If this is a predefined lib function (e.g. malloc), emit the call
// using exactly the normal call path.
if (getContext().BuiltinInfo.isPredefinedLibFunction(BuiltinID))
return emitLibraryCall(
*this, FD, E, cast<llvm::Constant>(EmitScalarExpr(E->getCallee())));
return emitLibraryCall(*this, FD, E, CGM.getRawFunctionPointer(FD));

// Check that a call to a target specific builtin has the correct target
// features.
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5678,6 +5678,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
!isa_and_nonnull<FunctionDecl>(TargetDecl))
EmitKCFIOperandBundle(ConcreteCallee, BundleList);

// Add the pointer-authentication bundle.
EmitPointerAuthOperandBundle(ConcreteCallee.getPointerAuthInfo(), BundleList);

if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CurFuncDecl))
if (FD->hasAttr<StrictFPAttr>())
// All calls within a strictfp function are marked strictfp
Expand Down
24 changes: 20 additions & 4 deletions clang/lib/CodeGen/CGCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_LIB_CODEGEN_CGCALL_H
#define LLVM_CLANG_LIB_CODEGEN_CGCALL_H

#include "CGPointerAuthInfo.h"
#include "CGValue.h"
#include "EHScopeStack.h"
#include "clang/AST/ASTFwd.h"
Expand Down Expand Up @@ -69,6 +70,10 @@ class CGCallee {
Last = Virtual
};

struct OrdinaryInfoStorage {
CGCalleeInfo AbstractInfo;
CGPointerAuthInfo PointerAuthInfo;
};
struct BuiltinInfoStorage {
const FunctionDecl *Decl;
unsigned ID;
Expand All @@ -85,7 +90,7 @@ class CGCallee {

SpecialKind KindOrFunctionPointer;
union {
CGCalleeInfo AbstractInfo;
OrdinaryInfoStorage OrdinaryInfo;
BuiltinInfoStorage BuiltinInfo;
PseudoDestructorInfoStorage PseudoDestructorInfo;
VirtualInfoStorage VirtualInfo;
Expand All @@ -104,10 +109,13 @@ class CGCallee {

/// Construct a callee. Call this constructor directly when this
/// isn't a direct call.
CGCallee(const CGCalleeInfo &abstractInfo, llvm::Value *functionPtr)
CGCallee(const CGCalleeInfo &abstractInfo, llvm::Value *functionPtr,
/* FIXME: make parameter pointerAuthInfo mandatory */
const CGPointerAuthInfo &pointerAuthInfo = CGPointerAuthInfo())
: KindOrFunctionPointer(
SpecialKind(reinterpret_cast<uintptr_t>(functionPtr))) {
AbstractInfo = abstractInfo;
OrdinaryInfo.AbstractInfo = abstractInfo;
OrdinaryInfo.PointerAuthInfo = pointerAuthInfo;
assert(functionPtr && "configuring callee without function pointer");
assert(functionPtr->getType()->isPointerTy());
}
Expand Down Expand Up @@ -173,7 +181,11 @@ class CGCallee {
if (isVirtual())
return VirtualInfo.MD;
assert(isOrdinary());
return AbstractInfo;
return OrdinaryInfo.AbstractInfo;
}
const CGPointerAuthInfo &getPointerAuthInfo() const {
assert(isOrdinary());
return OrdinaryInfo.PointerAuthInfo;
}
llvm::Value *getFunctionPointer() const {
assert(isOrdinary());
Expand All @@ -184,6 +196,10 @@ class CGCallee {
KindOrFunctionPointer =
SpecialKind(reinterpret_cast<uintptr_t>(functionPtr));
}
void setPointerAuthInfo(CGPointerAuthInfo PointerAuth) {
assert(isOrdinary());
OrdinaryInfo.PointerAuthInfo = PointerAuth;
}

bool isVirtual() const {
return KindOrFunctionPointer == SpecialKind::Virtual;
Expand Down
17 changes: 9 additions & 8 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2856,22 +2856,22 @@ static LValue EmitGlobalVarDeclLValue(CodeGenFunction &CGF,
return LV;
}

static llvm::Constant *EmitFunctionDeclPointer(CodeGenModule &CGM,
GlobalDecl GD) {
llvm::Constant *CodeGenModule::getRawFunctionPointer(GlobalDecl GD,
llvm::Type *Ty) {
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
if (FD->hasAttr<WeakRefAttr>()) {
ConstantAddress aliasee = CGM.GetWeakRefReference(FD);
ConstantAddress aliasee = GetWeakRefReference(FD);
return aliasee.getPointer();
}

llvm::Constant *V = CGM.GetAddrOfFunction(GD);
llvm::Constant *V = GetAddrOfFunction(GD, Ty);
return V;
}

static LValue EmitFunctionDeclLValue(CodeGenFunction &CGF, const Expr *E,
GlobalDecl GD) {
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
llvm::Value *V = EmitFunctionDeclPointer(CGF.CGM, GD);
llvm::Constant *V = CGF.CGM.getFunctionPointer(GD);
CharUnits Alignment = CGF.getContext().getDeclAlign(FD);
return CGF.MakeAddrLValue(V, E->getType(), Alignment,
AlignmentSource::Decl);
Expand Down Expand Up @@ -5506,7 +5506,7 @@ static CGCallee EmitDirectCallee(CodeGenFunction &CGF, GlobalDecl GD) {
// name to make it clear it's not the actual builtin.
if (CGF.CurFn->getName() != FDInlineName &&
OnlyHasInlineBuiltinDeclaration(FD)) {
llvm::Constant *CalleePtr = EmitFunctionDeclPointer(CGF.CGM, GD);
llvm::Constant *CalleePtr = CGF.CGM.getRawFunctionPointer(GD);
llvm::Function *Fn = llvm::cast<llvm::Function>(CalleePtr);
llvm::Module *M = Fn->getParent();
llvm::Function *Clone = M->getFunction(FDInlineName);
Expand All @@ -5529,7 +5529,7 @@ static CGCallee EmitDirectCallee(CodeGenFunction &CGF, GlobalDecl GD) {
return CGCallee::forBuiltin(builtinID, FD);
}

llvm::Constant *CalleePtr = EmitFunctionDeclPointer(CGF.CGM, GD);
llvm::Constant *CalleePtr = CGF.CGM.getRawFunctionPointer(GD);
if (CGF.CGM.getLangOpts().CUDA && !CGF.CGM.getLangOpts().CUDAIsDevice &&
FD->hasAttr<CUDAGlobalAttr>())
CalleePtr = CGF.CGM.getCUDARuntime().getKernelStub(
Expand Down Expand Up @@ -5586,7 +5586,8 @@ CGCallee CodeGenFunction::EmitCallee(const Expr *E) {
GD = GlobalDecl(VD);

CGCalleeInfo calleeInfo(functionType->getAs<FunctionProtoType>(), GD);
CGCallee callee(calleeInfo, calleePtr);
CGPointerAuthInfo pointerAuth = CGM.getFunctionPointerAuthInfo(functionType);
CGCallee callee(calleeInfo, calleePtr, pointerAuth);
return callee;
}

Expand Down
19 changes: 18 additions & 1 deletion clang/lib/CodeGen/CGExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2024,8 +2024,25 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
if (D->hasAttr<WeakRefAttr>())
return CGM.GetWeakRefReference(D).getPointer();

auto PtrAuthSign = [&](llvm::Constant *C) {
CGPointerAuthInfo AuthInfo = CGM.getFunctionPointerAuthInfo(DestType);

if (AuthInfo) {
if (hasNonZeroOffset())
return ConstantLValue(nullptr);

C = applyOffset(C);
C = CGM.getConstantSignedPointer(
C, AuthInfo.getKey(), nullptr,
cast_or_null<llvm::ConstantInt>(AuthInfo.getDiscriminator()));
return ConstantLValue(C, /*applied offset*/ true);
}

return ConstantLValue(C);
};

if (auto FD = dyn_cast<FunctionDecl>(D))
return CGM.GetAddrOfFunction(FD);
return PtrAuthSign(CGM.getRawFunctionPointer(FD));

if (auto VD = dyn_cast<VarDecl>(D)) {
// We can never refer to a variable with local storage.
Expand Down
39 changes: 39 additions & 0 deletions clang/lib/CodeGen/CGPointerAuth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@
using namespace clang;
using namespace CodeGen;

/// Return the abstract pointer authentication schema for a pointer to the given
/// function type.
CGPointerAuthInfo CodeGenModule::getFunctionPointerAuthInfo(QualType T) {
const auto &Schema = getCodeGenOpts().PointerAuth.FunctionPointers;
if (!Schema)
return CGPointerAuthInfo();

assert(!Schema.isAddressDiscriminated() &&
"function pointers cannot use address-specific discrimination");

assert(!Schema.hasOtherDiscrimination() &&
"function pointers don't support any discrimination yet");

return CGPointerAuthInfo(Schema.getKey(), Schema.getAuthenticationMode(),
/*IsaPointer=*/false, /*AuthenticatesNull=*/false,
/*Discriminator=*/nullptr);
}

llvm::Constant *
CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
llvm::Constant *StorageAddress,
Expand All @@ -41,3 +59,24 @@ CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
llvm::ConstantInt::get(Int32Ty, Key),
IntegerDiscriminator, AddressDiscriminator);
}

llvm::Constant *CodeGenModule::getFunctionPointer(llvm::Constant *Pointer,
QualType FunctionType) {
assert(FunctionType->isFunctionType() ||
FunctionType->isFunctionReferenceType() ||
FunctionType->isFunctionPointerType());

if (auto PointerAuth = getFunctionPointerAuthInfo(FunctionType))
return getConstantSignedPointer(
Pointer, PointerAuth.getKey(), /*StorageAddress=*/nullptr,
cast_or_null<llvm::ConstantInt>(PointerAuth.getDiscriminator()));

return Pointer;
}

llvm::Constant *CodeGenModule::getFunctionPointer(GlobalDecl GD,
llvm::Type *Ty) {
const auto *FD = cast<FunctionDecl>(GD.getDecl());
QualType FuncType = FD->getType();
return getFunctionPointer(getRawFunctionPointer(GD, Ty), FuncType);
}
Loading
Loading