@@ -173,10 +173,24 @@ class TrackedRegisters {
173
173
// / X30 is safe-to-dereference - the state computed for sub- and
174
174
// / super-registers is not inspected.
175
175
struct SrcState {
176
- // / A BitVector containing the registers that are either safe at function
177
- // / entry and were not clobbered yet, or those not clobbered since being
178
- // / authenticated.
176
+ // / A BitVector containing the registers that are either authenticated
177
+ // / (assuming failed authentication is permitted to produce an invalid
178
+ // / address, provided it generates an error on memory access) or whose
179
+ // / value is known not to be attacker-controlled under Pointer Authentication
180
+ // / threat model. The registers in this set are either
181
+ // / * not clobbered since being authenticated, or
182
+ // / * trusted at function entry and were not clobbered yet, or
183
+ // / * contain a safely materialized address.
179
184
BitVector SafeToDerefRegs;
185
+ // / A BitVector containing the registers that are either authenticated
186
+ // / *successfully* or whose value is known not to be attacker-controlled
187
+ // / under Pointer Authentication threat model.
188
+ // / The registers in this set are either
189
+ // / * authenticated and then checked to be authenticated successfully
190
+ // / (and not clobbered since then), or
191
+ // / * trusted at function entry and were not clobbered yet, or
192
+ // / * contain a safely materialized address.
193
+ BitVector TrustedRegs;
180
194
// / A vector of sets, only used in the second data flow run.
181
195
// / Each element in the vector represents one of the registers for which we
182
196
// / track the set of last instructions that wrote to this register. For
@@ -189,7 +203,8 @@ struct SrcState {
189
203
SrcState () {}
190
204
191
205
SrcState (unsigned NumRegs, unsigned NumRegsToTrack)
192
- : SafeToDerefRegs(NumRegs), LastInstWritingReg(NumRegsToTrack) {}
206
+ : SafeToDerefRegs(NumRegs), TrustedRegs(NumRegs),
207
+ LastInstWritingReg (NumRegsToTrack) {}
193
208
194
209
SrcState &merge (const SrcState &StateIn) {
195
210
if (StateIn.empty ())
@@ -198,6 +213,7 @@ struct SrcState {
198
213
return (*this = StateIn);
199
214
200
215
SafeToDerefRegs &= StateIn.SafeToDerefRegs ;
216
+ TrustedRegs &= StateIn.TrustedRegs ;
201
217
for (unsigned I = 0 ; I < LastInstWritingReg.size (); ++I)
202
218
for (const MCInst *J : StateIn.LastInstWritingReg [I])
203
219
LastInstWritingReg[I].insert (J);
@@ -210,6 +226,7 @@ struct SrcState {
210
226
211
227
bool operator ==(const SrcState &RHS) const {
212
228
return SafeToDerefRegs == RHS.SafeToDerefRegs &&
229
+ TrustedRegs == RHS.TrustedRegs &&
213
230
LastInstWritingReg == RHS.LastInstWritingReg ;
214
231
}
215
232
bool operator !=(const SrcState &RHS) const { return !((*this ) == RHS); }
@@ -234,6 +251,7 @@ raw_ostream &operator<<(raw_ostream &OS, const SrcState &S) {
234
251
OS << " empty" ;
235
252
} else {
236
253
OS << " SafeToDerefRegs: " << S.SafeToDerefRegs << " , " ;
254
+ OS << " TrustedRegs: " << S.TrustedRegs << " , " ;
237
255
printLastInsts (OS, S.LastInstWritingReg );
238
256
}
239
257
OS << " >" ;
@@ -254,18 +272,22 @@ void SrcStatePrinter::print(raw_ostream &OS, const SrcState &S) const {
254
272
OS << " src-state<" ;
255
273
if (S.empty ()) {
256
274
assert (S.SafeToDerefRegs .empty ());
275
+ assert (S.TrustedRegs .empty ());
257
276
assert (S.LastInstWritingReg .empty ());
258
277
OS << " empty" ;
259
278
} else {
260
279
OS << " SafeToDerefRegs: " ;
261
280
RegStatePrinter.print (OS, S.SafeToDerefRegs );
281
+ OS << " , TrustedRegs: " ;
282
+ RegStatePrinter.print (OS, S.TrustedRegs );
262
283
OS << " , " ;
263
284
printLastInsts (OS, S.LastInstWritingReg );
264
285
}
265
286
OS << " >" ;
266
287
}
267
288
268
- // / Computes which registers are safe to be used by control flow instructions.
289
+ // / Computes which registers are safe to be used by control flow and signing
290
+ // / instructions.
269
291
// /
270
292
// / This is the base class for two implementations: a dataflow-based analysis
271
293
// / which is intended to be used for most functions and a simplified CFG-unaware
@@ -293,6 +315,17 @@ class SrcSafetyAnalysis {
293
315
// / RegToTrackInstsFor is the set of registers for which the dataflow analysis
294
316
// / must compute which the last set of instructions writing to it are.
295
317
const TrackedRegisters RegsToTrackInstsFor;
318
+ // / Stores information about the detected instruction sequences emitted to
319
+ // / check an authenticated pointer. Specifically, if such sequence is detected
320
+ // / in a basic block, it maps the last instruction of that basic block to
321
+ // / (CheckedRegister, FirstInstOfTheSequence) pair, see the description of
322
+ // / MCPlusBuilder::getAuthCheckedReg(BB) method.
323
+ // /
324
+ // / As the detection of such sequences requires iterating over the adjacent
325
+ // / instructions, it should be done before calling computeNext(), which
326
+ // / operates on separate instructions.
327
+ DenseMap<const MCInst *, std::pair<MCPhysReg, const MCInst *>>
328
+ CheckerSequenceInfo;
296
329
297
330
SmallPtrSet<const MCInst *, 4 > &lastWritingInsts (SrcState &S,
298
331
MCPhysReg Reg) const {
@@ -307,8 +340,10 @@ class SrcSafetyAnalysis {
307
340
308
341
SrcState createEntryState () {
309
342
SrcState S (NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters ());
310
- for (MCPhysReg Reg : BC.MIB ->getTrustedLiveInRegs ())
311
- S.SafeToDerefRegs |= BC.MIB ->getAliases (Reg, /* OnlySmaller=*/ true );
343
+ for (MCPhysReg Reg : BC.MIB ->getTrustedLiveInRegs ()) {
344
+ S.TrustedRegs |= BC.MIB ->getAliases (Reg, /* OnlySmaller=*/ true );
345
+ S.SafeToDerefRegs = S.TrustedRegs ;
346
+ }
312
347
return S;
313
348
}
314
349
@@ -355,6 +390,46 @@ class SrcSafetyAnalysis {
355
390
return Regs;
356
391
}
357
392
393
+ // Returns all registers made trusted by this instruction.
394
+ SmallVector<MCPhysReg> getRegsMadeTrusted (const MCInst &Point ,
395
+ const SrcState &Cur) const {
396
+ SmallVector<MCPhysReg> Regs;
397
+ const MCPhysReg NoReg = BC.MIB ->getNoRegister ();
398
+
399
+ // An authenticated pointer can be checked, or
400
+ MCPhysReg CheckedReg =
401
+ BC.MIB ->getAuthCheckedReg (Point , /* MayOverwrite=*/ false );
402
+ if (CheckedReg != NoReg && Cur.SafeToDerefRegs [CheckedReg])
403
+ Regs.push_back (CheckedReg);
404
+
405
+ if (CheckerSequenceInfo.contains (&Point )) {
406
+ MCPhysReg CheckedReg;
407
+ const MCInst *FirstCheckerInst;
408
+ std::tie (CheckedReg, FirstCheckerInst) = CheckerSequenceInfo.at (&Point );
409
+
410
+ // FirstCheckerInst should belong to the same basic block, meaning
411
+ // it was deterministically processed a few steps before this instruction.
412
+ const SrcState &StateBeforeChecker =
413
+ getStateBefore (*FirstCheckerInst).get ();
414
+ if (StateBeforeChecker.SafeToDerefRegs [CheckedReg])
415
+ Regs.push_back (CheckedReg);
416
+ }
417
+
418
+ // ... a safe address can be materialized, or
419
+ MCPhysReg NewAddrReg = BC.MIB ->getMaterializedAddressRegForPtrAuth (Point );
420
+ if (NewAddrReg != NoReg)
421
+ Regs.push_back (NewAddrReg);
422
+
423
+ // ... an address can be updated in a safe manner, producing the result
424
+ // which is as trusted as the input address.
425
+ if (auto DstAndSrc = BC.MIB ->analyzeAddressArithmeticsForPtrAuth (Point )) {
426
+ if (Cur.TrustedRegs [DstAndSrc->second ])
427
+ Regs.push_back (DstAndSrc->first );
428
+ }
429
+
430
+ return Regs;
431
+ }
432
+
358
433
SrcState computeNext (const MCInst &Point , const SrcState &Cur) {
359
434
SrcStatePrinter P (BC);
360
435
LLVM_DEBUG ({
@@ -381,11 +456,34 @@ class SrcSafetyAnalysis {
381
456
BitVector Clobbered = getClobberedRegs (Point );
382
457
SmallVector<MCPhysReg> NewSafeToDerefRegs =
383
458
getRegsMadeSafeToDeref (Point , Cur);
459
+ SmallVector<MCPhysReg> NewTrustedRegs = getRegsMadeTrusted (Point , Cur);
460
+
461
+ // Ideally, being trusted is a strictly stronger property than being
462
+ // safe-to-dereference. To simplify the computation of Next state, enforce
463
+ // this for NewSafeToDerefRegs and NewTrustedRegs. Additionally, this
464
+ // fixes the properly for "cumulative" register states in tricky cases
465
+ // like the following:
466
+ //
467
+ // ; LR is safe to dereference here
468
+ // mov x16, x30 ; start of the sequence, LR is s-t-d right before
469
+ // xpaclri ; clobbers LR, LR is not safe anymore
470
+ // cmp x30, x16
471
+ // b.eq 1f ; end of the sequence: LR is marked as trusted
472
+ // brk 0x1234
473
+ // 1:
474
+ // ; at this point LR would be marked as trusted,
475
+ // ; but not safe-to-dereference
476
+ //
477
+ for (auto TrustedReg : NewTrustedRegs) {
478
+ if (!is_contained (NewSafeToDerefRegs, TrustedReg))
479
+ NewSafeToDerefRegs.push_back (TrustedReg);
480
+ }
384
481
385
482
// Then, compute the state after this instruction is executed.
386
483
SrcState Next = Cur;
387
484
388
485
Next.SafeToDerefRegs .reset (Clobbered);
486
+ Next.TrustedRegs .reset (Clobbered);
389
487
// Keep track of this instruction if it writes to any of the registers we
390
488
// need to track that for:
391
489
for (MCPhysReg Reg : RegsToTrackInstsFor.getRegisters ())
@@ -406,6 +504,10 @@ class SrcSafetyAnalysis {
406
504
lastWritingInsts (Next, Reg).clear ();
407
505
}
408
506
507
+ // Process new trusted registers.
508
+ for (MCPhysReg TrustedReg : NewTrustedRegs)
509
+ Next.TrustedRegs |= BC.MIB ->getAliases (TrustedReg, /* OnlySmaller=*/ true );
510
+
409
511
LLVM_DEBUG ({
410
512
dbgs () << " .. result: (" ;
411
513
P.print (dbgs (), Next);
@@ -462,7 +564,22 @@ class DataflowSrcSafetyAnalysis
462
564
return DFParent::getStateBefore (Inst);
463
565
}
464
566
465
- void run () override { DFParent::run (); }
567
+ void run () override {
568
+ for (BinaryBasicBlock &BB : Func) {
569
+ if (auto CheckerInfo = BC.MIB ->getAuthCheckedReg (BB)) {
570
+ MCInst *LastInstOfChecker = BB.getLastNonPseudoInstr ();
571
+ LLVM_DEBUG ({
572
+ dbgs () << " Found pointer checking sequence in " << BB.getName ()
573
+ << " :\n " ;
574
+ traceReg (BC, " Checked register" , CheckerInfo->first );
575
+ traceInst (BC, " First instruction" , *CheckerInfo->second );
576
+ traceInst (BC, " Last instruction" , *LastInstOfChecker);
577
+ });
578
+ CheckerSequenceInfo[LastInstOfChecker] = *CheckerInfo;
579
+ }
580
+ }
581
+ DFParent::run ();
582
+ }
466
583
467
584
protected:
468
585
void preflight () {}
@@ -658,6 +775,26 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
658
775
return std::make_shared<GadgetReport>(CallKind, Inst, DestReg);
659
776
}
660
777
778
+ static std::shared_ptr<Report>
779
+ shouldReportSigningOracle (const BinaryContext &BC, const MCInstReference &Inst,
780
+ const SrcState &S) {
781
+ static const GadgetKind SigningOracleKind (" signing oracle found" );
782
+
783
+ MCPhysReg SignedReg = BC.MIB ->getSignedReg (Inst);
784
+ if (SignedReg == BC.MIB ->getNoRegister ())
785
+ return nullptr ;
786
+
787
+ LLVM_DEBUG ({
788
+ traceInst (BC, " Found sign inst" , Inst);
789
+ traceReg (BC, " Signed reg" , SignedReg);
790
+ traceRegMask (BC, " TrustedRegs" , S.TrustedRegs );
791
+ });
792
+ if (S.TrustedRegs [SignedReg])
793
+ return nullptr ;
794
+
795
+ return std::make_shared<GadgetReport>(SigningOracleKind, Inst, SignedReg);
796
+ }
797
+
661
798
template <typename T> static void iterateOverInstrs (BinaryFunction &BF, T Fn) {
662
799
if (BF.hasCFG ()) {
663
800
for (BinaryBasicBlock &BB : BF)
@@ -702,6 +839,8 @@ Analysis::findGadgets(BinaryFunction &BF,
702
839
703
840
if (auto Report = shouldReportCallGadget (BC, Inst, S))
704
841
Result.Diagnostics .push_back (Report);
842
+ if (auto Report = shouldReportSigningOracle (BC, Inst, S))
843
+ Result.Diagnostics .push_back (Report);
705
844
});
706
845
return Result;
707
846
}
0 commit comments