Skip to content

[PAC][IR][AArch64] Add "ptrauth(...)" Constant to represent signed pointers. #85738

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 1 commit into from
May 28, 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
34 changes: 34 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4754,6 +4754,40 @@ reference to the CFI jump table in the ``LowerTypeTests`` pass. These constants
may be useful in low-level programs, such as operating system kernels, which
need to refer to the actual function body.

.. _ptrauth_constant:

Pointer Authentication Constants
--------------------------------

``ptrauth (ptr CST, i32 KEY[, i64 DISC[, ptr ADDRDISC]?]?)``

A '``ptrauth``' constant represents a pointer with a cryptographic
authentication signature embedded into some bits, as described in the
`Pointer Authentication <PointerAuth.html>`__ document.

A '``ptrauth``' constant is simply a constant equivalent to the
``llvm.ptrauth.sign`` intrinsic, potentially fed by a discriminator
``llvm.ptrauth.blend`` if needed.

Its type is the same as the first argument. An integer constant discriminator
and an address discriminator may be optionally specified. Otherwise, they have
values ``i64 0`` and ``ptr null``.

If the address discriminator is ``null`` then the expression is equivalent to

.. code-block:: llvm

%tmp = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr CST to i64), i32 KEY, i64 DISC)
%val = inttoptr i64 %tmp to ptr

Otherwise, the expression is equivalent to:

.. code-block:: llvm

%tmp1 = call i64 @llvm.ptrauth.blend(i64 ptrtoint (ptr ADDRDISC to i64), i64 DISC)
%tmp2 = call i64 @llvm.ptrauth.sign(i64 ptrtoint (ptr CST to i64), i32 KEY, i64 %tmp1)
%val = inttoptr i64 %tmp2 to ptr

.. _constantexprs:

Constant Expressions
Expand Down
22 changes: 22 additions & 0 deletions llvm/docs/PointerAuth.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For more details, see the clang documentation page for
At the IR level, it is represented using:

* a [set of intrinsics](#intrinsics) (to sign/authenticate pointers)
* a [signed pointer constant](#constant) (to sign globals)
* a [call operand bundle](#operand-bundle) (to authenticate called pointers)

The current implementation leverages the
Expand Down Expand Up @@ -225,6 +226,27 @@ with a pointer address discriminator, in a way that is specified by the target
implementation.


### Constant

[Intrinsics](#intrinsics) can be used to produce signed pointers dynamically,
in code, but not for signed pointers referenced by constants, in, e.g., global
initializers.

The latter are represented using a
[``ptrauth`` constant](https://llvm.org/docs/LangRef.html#ptrauth-constant),
which describes an authenticated relocation producing a signed pointer.

```llvm
ptrauth (ptr CST, i32 KEY, i64 DISC, ptr ADDRDISC)
Copy link
Contributor

Choose a reason for hiding this comment

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

Any restrictions on ptr worth documenting? Or is it just the same restrictions as constant expressions in globals, ie, that they can eventually be lowered to Mach-o/ELF relocations?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. Eventually this will be lowered to ELF pauth relocs (see e.g. https://github.com/ARM-software/abi-aa/blob/main/pauthabielf64/pauthabielf64.rst)

```

is equivalent to:

```llvm
%disc = call i64 @llvm.ptrauth.blend(i64 ptrtoint(ptr ADDRDISC to i64), i64 DISC)
%signedval = call i64 @llvm.ptrauth.sign(ptr CST, i32 KEY, i64 %disc)
```

### Operand Bundle

Function pointers used as indirect call targets can be signed when materialized,
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/AsmParser/LLToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ enum Kind {
kw_blockaddress,
kw_dso_local_equivalent,
kw_no_cfi,
kw_ptrauth,

kw_freeze,

Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/Bitcode/LLVMBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ enum ConstantsCodes {
// asmstr,conststr]
CST_CODE_CE_GEP_WITH_INRANGE = 31, // [opty, flags, range, n x operands]
CST_CODE_CE_GEP = 32, // [opty, flags, n x operands]
CST_CODE_PTRAUTH = 33, // [ptr, key, disc, addrdisc]
};

/// CastOpcodes - These are values used in the bitcode files to encode which
Expand Down
66 changes: 66 additions & 0 deletions llvm/include/llvm/IR/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,72 @@ struct OperandTraits<NoCFIValue> : public FixedNumOperandTraits<NoCFIValue, 1> {

DEFINE_TRANSPARENT_OPERAND_ACCESSORS(NoCFIValue, Value)

/// A signed pointer, in the ptrauth sense.
class ConstantPtrAuth final : public Constant {
friend struct ConstantPtrAuthKeyType;
friend class Constant;

ConstantPtrAuth(Constant *Ptr, ConstantInt *Key, ConstantInt *Disc,
Constant *AddrDisc);

void *operator new(size_t s) { return User::operator new(s, 4); }

void destroyConstantImpl();
Value *handleOperandChangeImpl(Value *From, Value *To);

public:
/// Return a pointer signed with the specified parameters.
static ConstantPtrAuth *get(Constant *Ptr, ConstantInt *Key,
ConstantInt *Disc, Constant *AddrDisc);

/// Produce a new ptrauth expression signing the given value using
/// the same schema as is stored in one.
ConstantPtrAuth *getWithSameSchema(Constant *Pointer) const;

/// Transparently provide more efficient getOperand methods.
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Constant);

/// The pointer that is signed in this ptrauth signed pointer.
Constant *getPointer() const { return cast<Constant>(Op<0>().get()); }
Copy link
Contributor

Choose a reason for hiding this comment

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

@ahmedbougacha Is it a desired behavior that we return a non-const-qualified pointer to associated data when calling getters allowed for calls on const-qualified object? Shouldn't we have two getter overloads, one with const for implicit this parameter and one without that?

Copy link
Member Author

Choose a reason for hiding this comment

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

We have generally treated a lot of these sorts of types as value types, and there is little you can do with a mutable Constant that you can't with a const pointer. What little you can do is deeply suspicious and not common.
It's certainly reasonable to ask to add const variants here and elsewhere, but that comes at the cost of verbosity for arguable utility given the above – either way I'm very specifically trying not to litigate that here or in these PRs ;) This is just matching the rest of the file, see e.g. BlockAddress above


/// The Key ID, an i32 constant.
ConstantInt *getKey() const { return cast<ConstantInt>(Op<1>().get()); }

/// The integer discriminator, an i64 constant, or 0.
ConstantInt *getDiscriminator() const {
return cast<ConstantInt>(Op<2>().get());
}

/// The address discriminator if any, or the null constant.
/// If present, this must be a value equivalent to the storage location of
/// the only global-initializer user of the ptrauth signed pointer.
Constant *getAddrDiscriminator() const {
return cast<Constant>(Op<3>().get());
}

/// Whether there is any non-null address discriminator.
bool hasAddressDiscriminator() const {
return !getAddrDiscriminator()->isNullValue();
}

/// Check whether an authentication operation with key \p Key and (possibly
/// blended) discriminator \p Discriminator is known to be compatible with
/// this ptrauth signed pointer.
bool isKnownCompatibleWith(const Value *Key, const Value *Discriminator,
const DataLayout &DL) const;

/// Methods for support type inquiry through isa, cast, and dyn_cast:
static bool classof(const Value *V) {
return V->getValueID() == ConstantPtrAuthVal;
}
};

template <>
struct OperandTraits<ConstantPtrAuth>
: public FixedNumOperandTraits<ConstantPtrAuth, 4> {};

DEFINE_TRANSPARENT_OPERAND_ACCESSORS(ConstantPtrAuth, Constant)

//===----------------------------------------------------------------------===//
/// A constant value that is initialized with an expression using
/// other constant values.
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/Value.def
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ HANDLE_CONSTANT(BlockAddress)
HANDLE_CONSTANT(ConstantExpr)
HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(DSOLocalEquivalent)
HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(NoCFIValue)
HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(ConstantPtrAuth)

// ConstantAggregate.
HANDLE_CONSTANT(ConstantArray)
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Analysis/ValueTracking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3140,6 +3140,10 @@ bool isKnownNonZero(const Value *V, const APInt &DemandedElts,
return true;
}

// Constant ptrauth can be null, iff the base pointer can be.
if (auto *CPA = dyn_cast<ConstantPtrAuth>(V))
return isKnownNonZero(CPA->getPointer(), DemandedElts, Q, Depth);

// A global variable in address space 0 is non null unless extern weak
// or an absolute symbol reference. Other address spaces may have null as a
// valid address for a global, so we can't assume anything.
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/AsmParser/LLLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(blockaddress);
KEYWORD(dso_local_equivalent);
KEYWORD(no_cfi);
KEYWORD(ptrauth);

// Metadata types.
KEYWORD(distinct);
Expand Down
54 changes: 54 additions & 0 deletions llvm/lib/AsmParser/LLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,60 @@ bool LLParser::parseValID(ValID &ID, PerFunctionState *PFS, Type *ExpectedTy) {
ID.NoCFI = true;
return false;
}
case lltok::kw_ptrauth: {
// ValID ::= 'ptrauth' '(' ptr @foo ',' i32 <key>
// (',' i64 <disc> (',' ptr addrdisc)? )? ')'
Lex.Lex();

Constant *Ptr, *Key;
Constant *Disc = nullptr, *AddrDisc = nullptr;

if (parseToken(lltok::lparen,
"expected '(' in constant ptrauth expression") ||
parseGlobalTypeAndValue(Ptr) ||
parseToken(lltok::comma,
"expected comma in constant ptrauth expression") ||
parseGlobalTypeAndValue(Key))
return true;
// If present, parse the optional disc/addrdisc.
if (EatIfPresent(lltok::comma))
if (parseGlobalTypeAndValue(Disc) ||
(EatIfPresent(lltok::comma) && parseGlobalTypeAndValue(AddrDisc)))
return true;
if (parseToken(lltok::rparen,
"expected ')' in constant ptrauth expression"))
return true;

if (!Ptr->getType()->isPointerTy())
return error(ID.Loc, "constant ptrauth base pointer must be a pointer");

auto *KeyC = dyn_cast<ConstantInt>(Key);
if (!KeyC || KeyC->getBitWidth() != 32)
return error(ID.Loc, "constant ptrauth key must be i32 constant");

ConstantInt *DiscC = nullptr;
if (Disc) {
DiscC = dyn_cast<ConstantInt>(Disc);
if (!DiscC || DiscC->getBitWidth() != 64)
return error(
ID.Loc,
"constant ptrauth integer discriminator must be i64 constant");
} else {
DiscC = ConstantInt::get(Type::getInt64Ty(Context), 0);
}

if (AddrDisc) {
if (!AddrDisc->getType()->isPointerTy())
return error(
ID.Loc, "constant ptrauth address discriminator must be a pointer");
} else {
AddrDisc = ConstantPointerNull::get(PointerType::get(Context, 0));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can Ptr be in a non-default address space, and if so, does AddrDisc have to be in the same address space?

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I see a test below where this is the case. I guess the question then changes to: Should the default address space of AddrDisc be 0 or the address space of Ptr?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the base pointer and the addr-disc pointer aren't really related, other than "addr-disc points to memory that's initialized with the full ptrauth() signed pointer." So IMO it makes sense to leave the addr-disc as address space 0 by default, though the targets where we'd have real ptrauth implementations don't have very sophisticated usage of address-spaces, so it's probably not the end of the world to forbid them entirely for now.

}

ID.ConstantVal = ConstantPtrAuth::get(Ptr, KeyC, DiscC, AddrDisc);
ID.Kind = ValID::t_Constant;
return false;
}

case lltok::kw_trunc:
case lltok::kw_bitcast:
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ GetCodeName(unsigned CodeID, unsigned BlockID,
STRINGIFY_CODE(CST_CODE, CE_UNOP)
STRINGIFY_CODE(CST_CODE, DSO_LOCAL_EQUIVALENT)
STRINGIFY_CODE(CST_CODE, NO_CFI_VALUE)
STRINGIFY_CODE(CST_CODE, PTRAUTH)
case bitc::CST_CODE_BLOCKADDRESS:
return "CST_CODE_BLOCKADDRESS";
STRINGIFY_CODE(CST_CODE, DATA)
Expand Down
25 changes: 24 additions & 1 deletion llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,8 @@ class BitcodeConstant final : public Value,
static constexpr uint8_t NoCFIOpcode = 252;
static constexpr uint8_t DSOLocalEquivalentOpcode = 251;
static constexpr uint8_t BlockAddressOpcode = 250;
static constexpr uint8_t FirstSpecialOpcode = BlockAddressOpcode;
static constexpr uint8_t ConstantPtrAuthOpcode = 249;
static constexpr uint8_t FirstSpecialOpcode = ConstantPtrAuthOpcode;

// Separate struct to make passing different number of parameters to
// BitcodeConstant::create() more convenient.
Expand Down Expand Up @@ -1562,6 +1563,18 @@ Expected<Value *> BitcodeReader::materializeValue(unsigned StartValID,
C = ConstantExpr::get(BC->Opcode, ConstOps[0], ConstOps[1], BC->Flags);
} else {
switch (BC->Opcode) {
case BitcodeConstant::ConstantPtrAuthOpcode: {
auto *Key = dyn_cast<ConstantInt>(ConstOps[1]);
if (!Key)
return error("ptrauth key operand must be ConstantInt");

auto *Disc = dyn_cast<ConstantInt>(ConstOps[2]);
if (!Disc)
return error("ptrauth disc operand must be ConstantInt");

C = ConstantPtrAuth::get(ConstOps[0], Key, Disc, ConstOps[3]);
break;
}
case BitcodeConstant::NoCFIOpcode: {
auto *GV = dyn_cast<GlobalValue>(ConstOps[0]);
if (!GV)
Expand Down Expand Up @@ -3644,6 +3657,16 @@ Error BitcodeReader::parseConstants() {
Record[1]);
break;
}
case bitc::CST_CODE_PTRAUTH: {
if (Record.size() < 4)
return error("Invalid ptrauth record");
// Ptr, Key, Disc, AddrDisc
V = BitcodeConstant::create(Alloc, CurTy,
BitcodeConstant::ConstantPtrAuthOpcode,
{(unsigned)Record[0], (unsigned)Record[1],
(unsigned)Record[2], (unsigned)Record[3]});
break;
}
}

assert(V->getType() == getTypeByID(CurTyID) && "Incorrect result type ID");
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2848,6 +2848,12 @@ void ModuleBitcodeWriter::writeConstants(unsigned FirstVal, unsigned LastVal,
Code = bitc::CST_CODE_NO_CFI_VALUE;
Record.push_back(VE.getTypeID(NC->getGlobalValue()->getType()));
Record.push_back(VE.getValueID(NC->getGlobalValue()));
} else if (const auto *CPA = dyn_cast<ConstantPtrAuth>(C)) {
Code = bitc::CST_CODE_PTRAUTH;
Record.push_back(VE.getValueID(CPA->getPointer()));
Record.push_back(VE.getValueID(CPA->getKey()));
Record.push_back(VE.getValueID(CPA->getDiscriminator()));
Record.push_back(VE.getValueID(CPA->getAddrDiscriminator()));
} else {
#ifndef NDEBUG
C->dump();
Expand Down
21 changes: 21 additions & 0 deletions llvm/lib/IR/AsmWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,27 @@ static void WriteConstantInternal(raw_ostream &Out, const Constant *CV,
return;
}

if (const ConstantPtrAuth *CPA = dyn_cast<ConstantPtrAuth>(CV)) {
Out << "ptrauth (";

// ptrauth (ptr CST, i32 KEY[, i64 DISC[, ptr ADDRDISC]?]?)
unsigned NumOpsToWrite = 2;
if (!CPA->getOperand(2)->isNullValue())
NumOpsToWrite = 3;
if (!CPA->getOperand(3)->isNullValue())
NumOpsToWrite = 4;

ListSeparator LS;
for (unsigned i = 0, e = NumOpsToWrite; i != e; ++i) {
Out << LS;
WriterCtx.TypePrinter->print(CPA->getOperand(i)->getType(), Out);
Out << ' ';
WriteAsOperandInternal(Out, CPA->getOperand(i), WriterCtx);
}
Out << ')';
return;
}

if (const ConstantArray *CA = dyn_cast<ConstantArray>(CV)) {
Type *ETy = CA->getType()->getElementType();
Out << '[';
Expand Down
Loading
Loading