この記事では、競合状態が Android ランタイム権限システムにどのような影響を与えるかを示します。
開発者であれば、おそらく競合状態について聞いたことがあるでしょう。これらは多くの場合、数秒以内に実行される同時バックグラウンド操作に関連付けられます。ただし、特定の競合状態が UI に表示され、無限に続く場合もあります。この記事では、競合状態が Android ランタイム権限システムにどのような影響を与えるかを説明します。
まず、いくつかの基本的な用語を説明する必要があります。
競合状態は、複数の操作が同時に発生し、その順序が結果に影響を与える場合に発生します。教科書的な例は、同じ変数をインクリメントする 2 つのスレッドです。簡単なことのように思えますが、通常、これを適切に実装するには、特別なスレッドセーフな要素を使用する必要があります。
チェックから使用までの時間 (TOCTTOU または TOCTOU、トックもと発音します) は、実行された操作の前に状態チェックが行われる特定の種類の競合状態です。そしてその状態はチェックと実際の実行の間に変更されます。多くの場合、ログイン時のみにユーザー権限をチェックすることで説明されます。たとえば、サインインした時点で管理者である場合、サインアウトするまでは、管理者アクセスが取り消されてもその権限を使用できます。
Android ランタイム権限の基本についてもまとめてみましょう。
Android 6.0 (API レベル 23) 以降、最も危険な権限は、アプリのインストール時に一度にすべてではなく、実行時にユーザーによって明示的に付与される必要があります。ここで最も注目すべき要素は、図 1 に示すような、DENY ボタンと ALLOW ボタンを備えたシステム ダイアログです。
図 1. 実行時権限ダイアログ
DENY ボタンをクリックすると、onRequestPermissionsResult コールバックで PERMISSION_DENIED を受け取るため、この権限に依存する機能を無効にする必要があります。公式スニペットによると。
さらに、ユーザーはアプリケーション設定の アプリの権限 画面を使用して権限を付与または拒否することもできます。その画面を図 2 に示します。
図 2. アプリの権限画面
ほとんどの人は、実行時のアクセス許可の拒否は非常に単純な機能であり、破壊できる要素はないと考えているかもしれません。まあ、これほど真実からかけ離れたものはありません!
許可が与えられていない場合にのみダイアログが表示されます。そのため、ダイアログを表示する直前にチェックの時間を設けています。そして、「拒否」ボタンをクリックしたときの使用時間。それらの間の期間は永遠に続く可能性があります。ユーザーはダイアログを開いてからホームボタンまたは最近のボタンを押して、アプリのタスクをバックグラウンドに移動し、後でいつでも戻ることができます。
実行時の権限ダイアログが TOCTTOU に対して脆弱かどうかを確認してみましょう。これを行うには、ダイアログから戻った後に実際に付与されたアクセス許可をチェックする非常に単純なアクティビティを作成できます。標準の onRequestPermissionsResult 引数チェックとは別に、Context#checkSelfPermission() を呼び出して現在の権限付与ステータスを取得することに注意してください。 targetSdkVersion を 23 以上に設定することを忘れないでください。コードは次のようになります:
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), 1) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) val checkResultTextView = findViewById<TextView>(R.id.grantResultTextView) val grantResultTextView = findViewById<TextView>(R.id.checkResultTextView) val checkPermissionResult = checkSelfPermission(WRITE_EXTERNAL_STORAGE).toPermissionResult() val grantPermissionResult = grantResults.firstOrNull()?.toPermissionResult() checkResultTextView.text = "checkSelfPermission: $checkPermissionResult" grantResultTextView.text = "onRequestPermissionsResult: $grantPermissionResult" } private fun Int.toPermissionResult() = when (this) { PERMISSION_GRANTED -> "granted" PERMISSION_DENIED -> "denied" else -> "unknown" } }
これでテストを実行できます。これを行うには、Android 6.0 (API 23) 以降を搭載したデバイスまたは AVD が必要です。テスト結果を図 3 に示します。
図 3. 捕獲された TOCTTOU
結果が異なることがわかります。 onRequestPermissionsResult 引数が無効です。つまり、DENYボタンは何も拒否しません。許可ステータスに関しては何も行わず、拒否された結果をアプリに返します。
コード内のさまざまなことをチェックするときは、タイミングを考慮することが重要です。チェック結果をキャッシュすると、バグや奇妙な影響が発生する可能性があります。 TOCTTOU の脆弱性はプラットフォームやプログラミング言語に依存しないため、CWE-367 として分類されています。
完全なソース コードは GitHub で確認できます。
このプロジェクトには、問題を実証する自動化された UI テストも含まれています。
元々は、2017 年 12 月 14 日に www.thedroidsonroids.com で公開されました。
以上が留意すべきエッジケース。 Android UI のチェック時から使用時までの競合状態の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。