結論と決定条件
- すべての SO について、直接および推移的な依存関係、ロードエントリーポイント、初期化タイミング、最小ターゲット API、および呼び出し元を記録します。
- 静的な JNI 名検出と RegisterNatives による動的登録では制約が異なるため、名前を処理する前に実際のバインド方法を検証してください。
- arm64-v8a、armeabi-v7a、x86_64 はそれぞれ異なるアーティファクトと互換性マトリックスを表しており、あるアーキテクチャでの成功は他のアーキテクチャにも通用するとはいえません。
- 保護強度の承認とランタイム安定性の承認は、同じ最終 APK、署名、および SO セットに紐付ける必要があります。
まず ELF 依存グラフと実際のロードタイムラインをマッピングする
Android アプリケーションは、メインライブラリ、ビジネスロジックライブラリ、アルゴリズムライブラリ、サードパーティ SDK、システムライブラリで構成される場合があります。Java または Kotlin コードは System.loadLibrary を直接呼び出せる一方、メインライブラリは ELF 依存関係やランタイムローディングを通じてさらに他のライブラリをロードする可能性があります。最終的なクラッシュ地点は保護されたライブラリの外部にある可能性があり、シンボル解決と初期化順序は依存チェーン全体によって決定されます。
依存グラフには、ABI ごとのライブラリファイル、DT_NEEDED 関係、最小システム API レベル、ロードエントリーポイント、初期化関数、エクスポートされたインターフェース、および呼び出し元を記録する必要があります。さらに、ライブラリが自社開発、オープンソース依存、またはクローズドソース SDK のいずれかを明示してください。これにより、変更の境界線と回帰テストの責任範囲が定義されます。
Android NDK のドキュメントによると、ネイティブシンボルは通常ライブラリ読み込み時に解決されます。ターゲットシステムに存在しない API をコードが参照している場合、実行時の分岐によってそのコードパスが実行されないことが示唆されていても、dlopen は即座に失敗する可能性があります。したがって、依存関係グラフには NDK API フロアとデバイスのシステムバージョンを含める必要があります。
| 項目 | 記録内容 | ハードニングへの影響 | 検証方法 |
|---|---|---|---|
| ライブラリおよびソース | メインライブラリ、推移的依存関係、サードパーティ製ライブラリ、システムライブラリ | 変更可能な範囲と回帰テストの責任範囲を決定します | 最終 APK 内の各 ABI に対して展開し、検証を行います |
| 読み込みエントリーポイント | 静的依存関係、System.loadLibrary、または実行時 dlopen | 最も早期の失敗段階とログ出力場所を決定します | 起動タイムラインと読み込み結果を記録します |
| API フロア | ビルド時の APP_PLATFORM と使用されたシステムシンボル | デバイスの API レベルより高い場合、欠落したシンボルにより読み込み時に失敗する可能性があります | ターゲットシステムでの実機起動とシンボル検証 |
| 初期化順序 | JNI_OnLoad、コンストラクタ、ビジネスロジックの初期化 | コードレイアウトやタイミングの変更が、暗黙的な依存関係を顕在化させる可能性があります | 保護されていないビルドとリリース候補ビルドの間でタイムラインを比較します |
| 例外およびスレッド | C++ 例外、スレッド所有権、JNIEnv の使用 | ライブラリ間およびスレッド間のエラーは、多くの場合即時クラッシュを引き起こします | CheckJNI、シンボル化されたスタックトレース、およびビジネス回帰テストを実施します |
静的登録と動的登録はシンボル処理の境界を定義します
JNI は命名規則を通じてネイティブメソッドを検出するか、初期化時に RegisterNatives を使用して Java メソッドと関数アドレスの紐付けを確立できます。静的登録は予測可能なエクスポート名に依存しますが、動的登録は通常少数の初期化エントリーポイントのみを公開すれば済む一方、クラス検索、メソッドシグネチャ、登録タイミング、および読み込みスレッドに依存します。
ハードニングやシンボル処理によって静的登録名が変更されると、システムがネイティブ実装を見つけられなくなる可能性があります。同様に、動的登録テーブルにおけるメソッドシグネチャ、クラス名保持ルール、または初期化順序の変更も、最初の関数呼び出し前に失敗を引き起こすことがあります。エクスポートされたシンボルの数を単に確認するだけでは、JNI バインディングが正しく維持されていることを証明できません。
Android 公式 JNI ガイドラインも、JNIEnv にはスレッド制約があり、保留中の例外、無効な参照、スレッド跨ぎの使用がクラッシュを招くと警告しています。ハードニング後の回帰テストでは、パラメータなしのデモメソッドを呼び出すだけでなく、実際のスレッドと例外パスを網羅する必要があります。
| 登録方式 | 依存関係 | ハードニングの感度項目 | 最小限の検証要件 |
|---|---|---|---|
| 静的名前発見 | Java クラス名、メソッド名、シグネチャ、エクスポートされたシンボル | 名前変更、隠蔽シンボル、シグネチャの不一致 | 各重要なエントリーポイントを呼び出し、リンクエラーがないか確認する |
| RegisterNatives | クラス検索、メソッドシグネチャ、登録テーブル、初期化タイミング | クラス名処理、登録順序、関数アドレスの変更 | 登録完了を確認し、実際の入出力シナリオを実行する |
| サードパーティ製ラッパー | 内部 SDK ルールとクローズドソース実装 | 自己検証、暗黙的なエクスポート、バージョン差異 | ベンダーサポートマトリクスと実環境シナリオに対して検証する |
- Java からネイティブへの主要なマッピング一覧
- 静的または動的登録方式であることを確認する
- 必要なクラス名とシグネチャのルールを維持する
- 例外、スレッド、参照ライフサイクルを検証する
ABI は独立したリリース成果物として受理されなければならない
ABI は単なるディレクトリラベルではありません。Android NDK 公式ドキュメントでは、ABI が命令セット、エンディアン、呼び出し規約、スタックとレジスタの使用法、実行ファイル形式、C++ 名前のマングリングを定義すると明記されています。arm64-v8a と armeabi-v7a では機械語コードと依存関係が異なり、x86_64 エミュレータでの成功は ARM 実機での成功を保証しません。
各 ABI について、直接および推移的な依存関係がすべて存在すること、ライブラリの API フロアがターゲットシステムと互換性があること、パッケージングツールが特定のアーキテクチャ向けファイルを誤って削除していないことを確認してください。メインライブラリのみが arm64-v8a を含み、サードパーティ依存関係に対応する成果物が欠けている場合、アプリケーションはロード時に失敗します。
製品が ABI のサブセットのみをサポートする予定であれば、リリースノート、アプリストア設定、テスト範囲にこれを明記してください。ビルド不可、インストール不可、または重要な JNI パスが実行されなかったアーキテクチャは「未カバー」としてマークする必要があり、コンパイル成功はランタイム証拠の代わりにはなりません。
| チェック項目 | arm64-v8a | armeabi-v7a | x86_64 | 判定ルール |
|---|---|---|---|---|
| すべての依存関係が揃っていること | ライブラリごとに検証 | リリーススコープごとに検証 | 必須の場合のみ検証 | 必須の依存関係が一つでも欠けている場合はブロック |
| 最小システム要件でロード可能であること | 対象 API:実機 | 対象 API:実機 | エミュレータまたは実機 | 最新システムのみで検証しない |
| 重要な JNI パス | 実際の入出力 | 実際の入出力 | ARM 版の結果を流用不可 | リリース済み ABI ごとに独立して記録 |
| クラッシュの帰属可能性 | 一致するシンボルを保持 | 一致するシンボルを保持 | 一致するシンボルを保持 | シンボルは同一のリリース候補に対応している必要がある |
保護機能に影響を与えるネイティブランタイム条件は何か
シンボル非表示化は静的な手がかりを減らし、文字列処理は平文の露出を下げ、制御フローや関数の仮想化は選択されたコードの実行形態を変化させます。これらの手法は、エクスポート済みシンボル、関数境界、例外アンワインディング、間接呼び出し、アライメント、およびパフォーマンスにそれぞれ異なる影響を与えます。処理後のシンボル数が少ないことがネイティブ保護の完了を意味するわけではなく、またライブラリを読み取れるからといって保護効果がすべて無効になるわけではありません。
起動時初期化、シグナルハンドリング、C++ 例外、コールバック、スレッドローカル状態、反射的シンボル参照、およびサードパーティ SDK の自己検証は感度の高い領域です。スコープを選定する際は、入出力が明確で、呼び出し頻度が測定可能であり、障害を分離可能なビジネス機能を優先してください。最初の PoC では初期化チェーン全体を処理対象に含めないでください。
保護設定では、各関数または関数グループについて、根拠、呼び出しフェーズ、ABI、依存関係、パフォーマンス予算、およびロールバックグループを記録する必要があります。以下は、内部シンボルや製品設定構文を除外した、公開かつ安全な検証チェックリストの書式例を示すものです。
native_group: protocol-core-v1
abis: [arm64-v8a, armeabi-v7a]
load_phase: post-authentication
jni_registration: dynamic
execution:
frequency: bounded
main_thread: false
dependencies:
- business-runtime
- system-crypto
acceptance:
- dependency-resolution
- jni-registration
- exception-path
- output-parity
- latency-budget
rollback: native-group-v0SO ハードニング後のクラッシュを最早差分で特定
トラブルシューティング時はまず、保護なしベースラインとリリース候補の双方について、ファイル識別子、署名、デバイス、システム、インストール方法、およびビジネス入力を固定してください。その後、タイムライン上で最早の差分を検索します。対象はプロセス生成、アプリケーション起動、ライブラリロード、JNI_OnLoad、登録完了、最初のネイティブ呼び出し、または主要ビジネス戻り値です。クラッシュログの最終エントリーは単なる連鎖反応に過ぎない場合があります。
UnsatisfiedLinkError は通常、ライブラリの欠落、ABI の不一致、依存関係の問題、またはシンボル解決の不備を示します。登録エラーはネイティブメソッドの欠如として現れることがあり、JNIEnv の誤用、参照の不備、または未処理の例外はビジネスロジックの関数呼び出し中にクラッシュを引き起こす可能性があります。内部での原因究明(アトリビューション)のためにリリース候補と一致するネイティブシンボルを保持してください。ただし、公開レポートに実シンボルやアドレスを暴露しないでください。
CheckJNI は JNI の誤用検出に役立ちますが、これは診断ツールであり、本番環境の実行状態を表すものではなく、すべてのネイティブメモリや並行性の問題を網羅できるわけではありません。修正後は、対象のリリース構成下で重要なパスを必ず再実行してください。
| 最初の症状 | 優先的に確認すべき項目 | 必須の証拠 | 最初に行うべきではない対応 |
|---|---|---|---|
| dlopen の失敗 | ABI、DT_NEEDED、API フロア、およびシンボル | リリース候補と一致するライブラリ一覧とロードエラー | 無関係な保護スイッチの繰り返し切り替え |
| ネイティブメソッドが見つからない | 登録メソッド、クラス名、メソッドシグネチャ、およびタイミング | 登録テーブル、保持ルール、および呼び出しエントリーポイント | エクスポートされたシンボル数のみを確認すること |
| 初回呼び出し時のクラッシュ | パラメータ、参照、スレッド、例外、および関数処理 | シンボル化されたスタックトレース、入力値、およびベースライン比較 | 別バージョンのシンボルファイルを用いて説明すること |
| 一部デバイスでの失敗 | システム API、ベンダー間の差異、ABI、およびサードパーティ製ライブラリ | デバイスのシステムマトリクスと初期の差異 | エミュレータでの成功をもって実機での結論を代用すること |
リリース結論には強度、互換性、および保守性を盛り込む必要があります
静的強度の観測では、シンボル、文字列、コード構造、および主要エントリーポイントの露出面の変化を記録できます。ランタイム受入検証では、ローディング、JNI、重要なビジネスロジック、例外、および対象 ABI を確認します。これら 2 種類の証拠は相互に補完するものであり、どちらか一方で他方を代替することはできません。
最終的なリリース候補では、インストールアップグレード、署名検証、チャネルチェック、起動テスト、クラッシュ監視、ネイティブシンボルのアーカイブ、およびロールバック訓練を完了させる必要があります。シンボルアーカイブは、ファイル識別子を通じて一致するバージョンを特定できるものでなければなりません。そうでない場合、オンラインで発生したネイティブクラッシュを正しくシンボル化できません。
本資料は特定の SO や保護構成に対するテスト結論ではなく、エンジニアリング検査のフレームワークを提供するものです。ターゲットパッケージ、ターゲット ABI、実際のコールパスがなければ、準備作業の完了性を評価することはできても、互換性やパフォーマンスを保証することはできません。
- リリースされたすべての ABI にはランタイム証拠が存在します。
- 重要な JNI エントリポイントは、通常パスと例外パスの両方を網羅しています。
- ネイティブシンボルは最終的なリリース候補のアイデンティティと一致しています。
- ロード、ビジネス処理、およびロールバックの結果が再現可能です。
- カバーされていないデバイスとシステムは明示的に制限されています。
証拠と適用可能性の境界
このセクションでは、文書化されたプラットフォームの事実、技術的な判断、および未検証の製品主張に一般化できない制限を分けて説明します。
| 条文判決 | 事実または工学的根拠 | 適用制限 |
|---|---|---|
| ABI は独立したアーティファクトの次元として承認される必要があります。 | 公式 Android NDK は、命令セット、呼び出し規約、レジスタ、スタック、ELF フォーマット、および名前マングリングを含む ABI を定義しています。 | ドキュメント上の定義だけでは、アプリケーションに完全な依存関係が含まれていること、またはターゲットデバイスで正常に実行されることを証明できません。 |
| デバイスのシステムよりも高いレベルのネイティブ API を参照すると、ロード時に失敗する可能性があります。 | NDK の一般的な問題に関するドキュメントでは、シンボルは通常ライブラリロード時に解決されると記載されており、存在しない API を参照している場合、ランタイム分岐によってこれを回避することはできません。 | 具体的な障害は、ビルドパラメータ、依存関係、およびデバイスログを通じて依然として確認する必要があります。 |
| JNI エラーについては、スレッド、参照、および例外の各レベルで検証が必要です。 | Android JNI ガイドラインには、クラッシュを引き起こす可能性のある無効な JNIEnv、参照、保留中の例外、およびメソッド登録などの問題がリストされています。 | CheckJNI は問題の一部を検出する補助ツールに過ぎず、包括的なビジネスロジックおよびメモリ安全性テストの代わりにはなりません。 |
| 単一の x86_64 エミュレータからの結果を、ARM 実機に外挿することはできません。 | 異なる ABI は異なる命令セットと呼び出し規約を使用しており、最終的なライブラリと依存関係の組み合わせも異なる可能性があります。 | 製品が特定の ABI を明示的にリリースしない場合、強制的にテストを行うのではなく、「該当なし」とマークできます。 |
| 保護強度と互換性は、同一のリリース候補内でクローズ(確定)させる必要があります。 | SO ファイルの再ビルドまたは置き換えは、コード、シンボル、依存関係、および潜在的なランタイム動作を変更します。 | これはアーティファクトガバナンスの原則であり、特定の保護レベルが合格したことを意味するものではありません。 |
エンジニアリングに関する質問
メインの SO が 1 つしかない場合でも、依存関係グラフは必要ですか?
はい。メインの SO はシステムライブラリ、C++ ランタイム、またはサードパーティ製ライブラリに依存し、JNI を介して Java レイヤーと相互作用する可能性があります。依存関係グラフは、最速のロード時点と責任範囲を確認するために使用されます。
arm64-v8a で合格した場合でも、armeabi-v7a のテストは必要ですか?
リリースパッケージが armeabi-v7a を含みサポートしている場合、独立した検証が必要です。これら 2 つの ABI は命令セット、呼び出し規約、依存アーティファクトが異なり、相互に代替することはできません。
エクスポートされたすべてのシンボルを非表示にすることは、より安全ですか?
必ずしもそうではありません。必要な静的 JNI エントリポイント、サードパーティからの呼び出し、およびシステムの規約は、エクスポートされたシンボルに依存している場合があります。まず登録と呼び出し方法を確認し、その後露出を最小限に抑えてください。
CheckJNI がエラーを報告しなければリリースできますか?
いいえ、それだけではリリースには不十分です。実際のターゲット構成、ビジネス入力、例外パス、ABI、システムスコープ、インストールアップグレード、およびロールバック機能も検証する必要があります。
SO ハードニング後にクラッシュが発生した場合、すぐにすべての保護を無効にするべきですか?
まずリリース候補の特定を行い、最も初期の変更点を見つけます。保護グループの二分探索やロールバックを行えますが、帰属不明な新しいアーティファクトの生成を防ぐため、記録可能な変数は一度に 1 つのみ変更してください。
独自のアプリでこれをテストしてみませんか?
Yudun PoC と互換性評価のために、リリース候補、ターゲット システム、重要なビジネス パスを提出します。