ポインタ認証

はじめに

ポインタ認証は、特定のポインタが署名されるメカニズムです。ポインタが署名されると、その値と他の値(ペッパーとソルト)の暗号ハッシュが、そのポインタの未使用ビットに格納されます。

ポインタを使用する前に、認証、つまり署名の確認が必要です。これにより、署名されたポインタ値を置き換えるために、出自不明のポインタ値が使用されるのを防ぎます。

詳細については、clang のドキュメントページのポインタ認証を参照してください。

IR レベルでは、以下を使用して表現されます。

現在の実装では、AArch64 バックエンドArmv8.3-A PAuth/ポインタ認証コード命令を活用しています。このサポートは、Darwin arm64e ABI と、ELF への PAuth ABI 拡張を実装するために使用されます。

LLVM IR 表現

組み込み関数

これらの組み込み関数は、ポインタ認証操作を公開するために LLVM によって提供されます。

llvm.ptrauth.sign

構文:
declare i64 @llvm.ptrauth.sign(i64 <value>, i32 <key>, i64 <discriminator>)
概要:

llvm.ptrauth.sign’組み込み関数は、生のポインタに署名します。

引数:

value 引数は、署名される生のポインタ値です。key 引数は、署名付きの値を生成するために使用されるキーの識別子です。discriminator 引数は、識別子として使用される追加の多様性データです(整数、アドレス、またはその2つの混合)。

セマンティクス:

llvm.ptrauth.sign’組み込み関数は、sign_ 操作を実装します。署名付きの値を返します。

value が既に署名付きの値である場合、動作は未定義です。

value が、key が適切なポインタ値でない場合、動作は未定義です。

llvm.ptrauth.auth

構文:
declare i64 @llvm.ptrauth.auth(i64 <value>, i32 <key>, i64 <discriminator>)
概要:

llvm.ptrauth.auth’組み込み関数は、署名付きのポインタを認証します。

引数:

value 引数は、認証される署名付きのポインタ値です。key 引数は、署名付きの値を生成するために使用されたキーの識別子です。discriminator 引数は、識別子として使用される追加の多様性データです。

セマンティクス:

llvm.ptrauth.auth’組み込み関数は、auth_ 操作を実装します。生のポインタ値を返します。valuekey および discriminator に対して正しい署名を持っていない場合、組み込み関数はターゲット固有の方法でトラップします。

llvm.ptrauth.strip

構文:
declare i64 @llvm.ptrauth.strip(i64 <value>, i32 <key>)
概要:

llvm.ptrauth.strip’組み込み関数は、署名されている可能性のあるポインタから埋め込まれた署名を削除します。

引数:

value 引数は、削除される署名付きのポインタ値です。key 引数は、署名付きの値を生成するために使用されたキーの識別子です。

セマンティクス:

llvm.ptrauth.strip’組み込み関数は、strip_ 操作を実装します。生のポインタ値を返します。署名が有効かどうかは確認**しません**。

key は、ターゲット固有のキー)で定義されているように、value に適切なキーを識別する必要があります。

value が生のポインタ値である場合、そのまま返されます (key がポインタに適切な場合に限る)。

value が、key が適切なポインタ値でない場合、動作はターゲット固有です。

value が署名付きのポインタ値であっても、keyvalue の生成に使用されたキーと同じキーを識別しない場合、動作はターゲット固有です。

llvm.ptrauth.resign

構文:
declare i64 @llvm.ptrauth.resign(i64 <value>,
                                 i32 <old key>, i64 <old discriminator>,
                                 i32 <new key>, i64 <new discriminator>)
概要:

llvm.ptrauth.resign’組み込み関数は、異なるキーと多様性データを使用して、署名付きのポインタに再度署名します。

引数:

value 引数は、認証される署名付きのポインタ値です。old key 引数は、署名付きの値を生成するために使用されたキーの識別子です。old discriminator 引数は、auth 操作で使用される追加の多様性データです。new key 引数は、再署名された値を生成するために使用するキーの識別子です。new discriminator 引数は、sign 操作で使用される追加の多様性データです。

セマンティクス:

llvm.ptrauth.resign’組み込み関数は、中間的な生のポインタを公開せずに、組み合わせた auth_ および sign_ 操作を実行します。署名付きのポインタ値を返します。valueold key および old discriminator に対して正しい署名を持っていない場合、組み込み関数はターゲット固有の方法でトラップします。

llvm.ptrauth.sign_generic

構文:
declare i64 @llvm.ptrauth.sign_generic(i64 <value>, i64 <discriminator>)
概要:

llvm.ptrauth.sign_generic’組み込み関数は、任意のデータの汎用署名を計算します。

引数:

value 引数は、署名される任意のデータ値です。discriminator 引数は、識別子として使用される追加の多様性データです。

セマンティクス:

llvm.ptrauth.sign_generic’組み込み関数は、指定された値と追加の多様性データの組み合わせの署名を計算します。

(埋め込み部分署名付きの署名付きポインタ値とは対照的に)完全な署名値を返します。

llvm.ptrauth.signとは対照的に、value をポインタ値として解釈しません。代わりに、任意のデータ値です。

llvm.ptrauth.blend

構文:
declare i64 @llvm.ptrauth.blend(i64 <address discriminator>, i64 <integer discriminator>)
概要:

llvm.ptrauth.blend’ 組み込み関数は、ポインターのアドレス識別子と小さな整数の識別子をブレンドして、新しい「ブレンドされた」識別子を生成します。

引数:

address discriminator 引数はポインター値です。integer discriminator 引数は、ターゲットによって指定された小さな整数です。

セマンティクス:

llvm.ptrauth.blend’ 組み込み関数は、ターゲット実装によって指定された方法で、小さな整数の識別子とポインターアドレスの識別子を組み合わせます。

定数

組み込み関数を使用して、コード内で動的に署名付きポインターを生成できますが、例えばグローバルイニシャライザなどの定数によって参照される署名付きポインターには使用できません。

後者は、署名付きポインターを生成する認証された再配置を記述するptrauth 定数を使用して表されます。

ptrauth (ptr CST, i32 KEY, i64 DISC, ptr ADDRDISC)

は、以下と同等です

  %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)

オペランドバンドル

間接呼び出しターゲットとして使用される関数ポインターは、マテリアライズされるときに署名され、呼び出し前に認証できます。これは、llvm.ptrauth.auth 組み込み関数を使用し、その結果を間接呼び出しに渡すことで実現できます。

ただし、これにより、中間的な未認証のポインターが公開されます。例えば、スタックにスピルされた場合などです。攻撃者はメモリ内のポインターを上書きし、ポインター認証によって提供されるセキュリティ上の利点を打ち消す可能性があります。それを防ぐために、ptrauth オペランドバンドルを使用できます。これにより、中間的な呼び出しターゲットがレジスタに保持され、メモリに保存されないことが保証されます。この強化の利点は、llvm.ptrauth.resign によって提供されるものと同様です。

具体的には

define void @f(void ()* %fp) {
  call void %fp() [ "ptrauth"(i32 <key>, i64 <data>) ]
  ret void
}

は、機能的には以下と同等です

define void @f(void ()* %fp) {
  %fp_i = ptrtoint void ()* %fp to i64
  %fp_auth = call i64 @llvm.ptrauth.auth(i64 %fp_i, i32 <key>, i64 <data>)
  %fp_auth_p = inttoptr i64 %fp_auth to void ()*
  call void %fp_auth_p()
  ret void
}

ただし、%fp_i%fp_auth、および%fp_auth_p がメモリに保存(および再ロード)されないという追加の保証があります。

関数属性

一部の関数属性は、IR で明示的に表現されていない他のポインター認証操作を記述するために使用されます。

ptrauth-indirect-gotos

ptrauth-indirect-gotos は、この関数内の間接 goto がターゲットを認証する必要があることを指定します。IR レベルでは、他の変更は必要ありません。blockaddress 定数indirectbr 命令を下げると、これはバックエンドにポインターにそれぞれ署名および認証するように指示します。

特定スキームは ABI に表示されません。現在、AArch64 バックエンドは、SipHash 安定識別子を使用して、親関数の名前から派生した整数識別子を使用して、ASIA キーを使用して blockaddresses に署名します。

  ptrauth_string_discriminator("<function_name> blockaddress")

AArch64 サポート

AArch64 は、現在、Armv8.3-A 命令に基づいたポインター認証プリミティブを完全にサポートしている唯一のアーキテクチャです。

Armv8.3-A PAuth ポインター認証コード

Armv8.3-A アーキテクチャ拡張では、ポインター認証コード(PAC)を操作する命令のサポートを提供する PAuth 機能が定義されています。

キー

PAuth 機能では 5 つのキーがサポートされています。

そのうち、4 つのキーは、IR 構造で使用されるキーを指定するために、相互に使用できます。

  • ASIA/ASIB は命令キーです(それぞれ 0 と 1 としてエンコードされます)。

  • ASDA/ASDB はデータキーです(それぞれ 2 と 3 としてエンコードされます)。

ASGA は明示的に指定できない特別なキーであり、llvm.ptrauth.sign_generic 組み込み関数を実装するために暗黙的にのみ使用されます。

命令

上記の IR 組み込み関数は、次のようにこれらの命令にマップされます。

  • llvm.ptrauth.sign: PAC{I,D}{A,B}{Z,SP,}

  • llvm.ptrauth.auth: AUT{I,D}{A,B}{Z,SP,}

  • llvm.ptrauth.strip: XPAC{I,D}

  • llvm.ptrauth.blend: ブレンド操作のセマンティクスは ABI によって指定されます。ELF PAuth ABI 拡張と arm64e の両方で、これは上位 16 ビットへの MOVK です。したがって、これにより、ブレンドで使用される整数識別子の幅が 16 ビットに制限されます。

  • llvm.ptrauth.sign_generic: PACGA

  • llvm.ptrauth.resign: AUT*+PAC*。これらは、中間的な生のポインター値がスピルされて攻撃可能にならないように、バックエンドで単一の擬似命令として表されます。

アセンブリ表現

アセンブリレベルでは、認証された再配置は、@AUTH 修飾子を使用して表されます。

    .quad _target@AUTH(<key>,<discriminator>[,addr])

ここで

  • key は Armv8.3-A キー識別子(iaibdadb)です。

  • discriminator は 16 ビットの符号なし識別子値です。

  • addr は、認証されたポインターがアドレスで識別される(つまり、再配置のターゲットアドレスが署名操作で使用される前に discriminator にブレンドされる)ことを示します。

例:

  _authenticated_reference_to_sym:
    .quad _sym@AUTH(db,0)
  _authenticated_reference_to_sym_addr_disc:
    .quad _sym@AUTH(ia,12,addr)

MachO オブジェクトファイル表現

オブジェクトファイルレベルでは、認証された再配置は、ARM64_RELOC_AUTHENTICATED_POINTER 再配置の種類(値 11)を使用して表されます。

ポインター認証情報は、次のように加算にエンコードされます。

| 63 | 62 | 61-51 | 50-49 |   48   | 47     -     32 | 31  -  0 |
| -- | -- | ----- | ----- | ------ | --------------- | -------- |
|  1 |  0 |   0   |  key  |  addr  |  discriminator  |  addend  |

ELF オブジェクトファイル表現

オブジェクトファイルレベルでは、認証された再配置は、R_AARCH64_AUTH_ABS64 再配置の種類(値 0xE100)を使用して表されます。

署名スキーマは、次のように適用される再配置の場所にエンコードされます。

| 63                | 62       | 61:60    | 59:48    |  47:32        | 31:0                |
| ----------------- | -------- | -------- | -------- | ------------- | ------------------- |
| address diversity | reserved | key      | reserved | discriminator | reserved for addend |