Skip to content

Commit

Permalink
[IR][AArch64][PAC] Add "ptrauth(...)" Constant to represent signed po…
Browse files Browse the repository at this point in the history
…inters. (llvm#85738)

This defines a new kind of IR Constant that represents a ptrauth signed
pointer, as used in AArch64 PAuth.

It allows representing most kinds of signed pointer constants used thus
far in the llvm ptrauth implementations, notably those used in the
Darwin and ELF ABIs being implemented for c/c++.  These signed pointer
constants are then lowered to ELF/MachO relocations.

These can be simply thought of as a constant `llvm.ptrauth.sign`, with
the interesting addition of discriminator computation: the `ptrauth`
constant can also represent a combined blend, when both address and
integer discriminator operands are used.  Both operands are otherwise
optional, with default values 0/null.
  • Loading branch information
ahmedbougacha authored May 28, 2024
1 parent 60bce6e commit 0edc97f
Show file tree
Hide file tree
Showing 25 changed files with 488 additions and 1 deletion.
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)
```

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()); }

/// 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));
}

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

0 comments on commit 0edc97f

Please sign in to comment.