
CVE-2025-22441のライトアップとエクスプロイト:Androidでインストール済みアプリからSystemUIプロセスへの権限昇格。信頼されていないApplicationInfoがLoadedApkに渡されることが原因。
この問題の修正はCVE-2025-22441として公開されています: bulletin patch follow up
ApplicationInfoの受け渡しApplicationInfoは、インストールされたアプリに関するさまざまな情報を定義する構造体であり、特にリソースとコードが読み込まれるAPKファイルへのパスが最も重要です。
通常はシステムからアプリケーションへ渡されますが、システム以外の呼び出し元が独自のものを提供できるケースが存在することもあります。例えば過去には、bindBackupAgent()メソッドの脆弱性があり、攻撃者がuidとsourceDirの値を持つ独自のApplicationInfoオブジェクトをパラメータとして渡せたものの、それらがシステムに実際にインストールされているアプリと照合されていませんでした。このメソッドはsystem_serverによって内部的に呼び出されることを想定していましたが、adb shellに公開されていました。
今回は、RemoteViews内のApplicationInfoフィールドを詳しく調査しました。
RemoteViewsは、別のプロセスから取得できるビューを記述するオブジェクトです。これは特にホーム画面ウィジェットに使用され、ウィジェットを提供するアプリがRemoteViewsを構築し、それがホーム画面プロセス内で"適用"されます。
RemoteViewsが使用される他の場所としては、通知(SystemUIプロセスによって適用)やオートフィルダイアログ(オートフィルサービスによって提供され、system_serverによって適用)があります。
RemoteViews.mApplicationフィールドはParcelを通じてシリアライズされるため、リモートプロセスから取得される可能性があり、RemoteViewsが適用されるたびに以下のメソッドで使用されます(ソーススニペット):```java
private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
&& context.getPackageName().equals(mApplication.packageName)) {
return context;
}
try {
LoadedApk.checkAndUpdateApkPaths(mApplication);
return context.createApplicationContext(mApplication,
Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found");
}
}
return context;
}
ここで最も興味深いのは、`LoadedApk.checkAndUpdateApkPaths()` の呼び出しです。これは静的メソッドであり、いくつかのグローバル状態を変更します [(スニペットのソース)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=2275-2302;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)```java
public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
// Get the LoadedApk from the cache
ActivityThread activityThread = ActivityThread.currentActivityThread();
if (activityThread == null) {
Log.e(TAG, "Cannot find activity thread");
return;
}
checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
}
private static void checkAndUpdateApkPaths(ActivityThread activityThread,
ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
String expectedCodePath = expectedAppInfo.getCodePath();
LoadedApk loadedApk = activityThread.peekPackageInfo(
expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
// If there is load apk cached, or if the cache is valid, don't do anything.
if (loadedApk == null || loadedApk.getApplicationInfo() == null
|| loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
return;
}
// Duplicate framework logic
List<String> oldPaths = new ArrayList<>();
LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);
// Force update the LoadedApk instance, which should update the reference in the cache
loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
}
これらのメソッドで何が行われているのか説明しましょう
まず、cacheWithCode をtrueとfalseの両方に設定して、3パラメータのcheckAndUpdateApkPathsを呼び出しています。LoadedApkオブジェクトは2つのモードで構築でき、mIncludeCodeがtrueかfalseのいずれかになります。これはSDK的には、CONTEXT_INCLUDE_CODEフラグが設定されたContextかどうかに対応します
そのメソッドはまずActivityThread.peekPackageInfo()を使用します。これはすでにキャッシュされたLoadedApkインスタンスを返すため、アプリが以前に一致するpackageNameとincludeCodeでLoadedApkを構築していなければ、peekPackageInfo()はnullを返し、checkAndUpdateApkPaths()は何も行いません
RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths()に戻ると、そこにはcontext.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED)があります。CONTEXT_INCLUDE_CODEフラグは指定されておらず(そのメソッドに引数として渡されるコンテキストも同様)、そのためそのメソッドは常にmIncludeCode=falseのLoadedApkを使用します
したがって、RemoteViewsの更新バージョンにコードを含める必要はありません。RemoteViewsは常にコードなしのContextを使用するためです。コミットメッセージには、ApplicationInfoがキャッシュされる場所が2つあるとだけ記載されており、コード付きでバージョンを更新する処理を削除してもここでは問題は発生しないと思います(checkAndUpdateApkPaths()はAppWidgetHostViewとRemoteViewsでのみ使用されるため)。ただし、これに対する反論としては、さまざまなキャッシュが同期されなくなる可能性を回避できるという点があり、cacheWithCode=trueでの呼び出しを削除することでこのバグの最も深刻な影響は取り除かれますが、コードではなくリソースのみの変更が攻撃者にとって依然として価値がある可能性があるため、問題を完全に修正するわけではありません
LoadedApk.updateApplicationInfo()これまでに示したコードはRemoteViews(ウィジェット、通知など)でのみ使用されますが、ここからは新しいスプリットがインストールされた後に実行中のアプリプロセスを更新するためにも使用されるLoadedApk.updateApplicationInfo()に入ります(たとえば、Play Feature Deliveryのオンデマンド配信を使用する場合など)