1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm.dex; 18 19 import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; 20 import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE; 21 22 import android.annotation.NonNull; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.IPackageManager; 25 import android.content.pm.PackageInfo; 26 import android.os.FileUtils; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.os.UserHandle; 30 import android.os.storage.StorageManager; 31 import android.util.EventLog; 32 import android.util.PackageUtils; 33 import android.util.Slog; 34 import android.util.SparseArray; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.server.pm.Installer; 38 import com.android.server.pm.Installer.InstallerException; 39 import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; 40 import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; 41 42 import libcore.util.HexEncoding; 43 44 import java.io.File; 45 import java.io.IOException; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Set; 51 52 /** 53 * This class is responsible for logging data about secondary dex files and native code executed 54 * from an app's private directory. The data logged includes hashes of the name and content of each 55 * file. 56 */ 57 public class DynamicCodeLogger { 58 private static final String TAG = "DynamicCodeLogger"; 59 60 // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) - 61 // see b/63927552. 62 private static final int SNET_TAG = 0x534e4554; 63 private static final String DCL_DEX_SUBTAG = "dcl"; 64 private static final String DCL_NATIVE_SUBTAG = "dcln"; 65 66 private IPackageManager mPackageManager; 67 private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; 68 private final Installer mInstaller; 69 DynamicCodeLogger(Installer installer)70 public DynamicCodeLogger(Installer installer) { 71 mInstaller = installer; 72 mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); 73 } 74 75 @VisibleForTesting DynamicCodeLogger(@onNull IPackageManager packageManager, @NonNull Installer installer, @NonNull PackageDynamicCodeLoading packageDynamicCodeLoading)76 DynamicCodeLogger(@NonNull IPackageManager packageManager, @NonNull Installer installer, 77 @NonNull PackageDynamicCodeLoading packageDynamicCodeLoading) { 78 mPackageManager = packageManager; 79 mInstaller = installer; 80 mPackageDynamicCodeLoading = packageDynamicCodeLoading; 81 } 82 83 @NonNull getPackageManager()84 private IPackageManager getPackageManager() { 85 if (mPackageManager == null) { 86 mPackageManager = IPackageManager.Stub.asInterface( 87 ServiceManager.getService("package")); 88 } 89 return mPackageManager; 90 } 91 getAllPackagesWithDynamicCodeLoading()92 public Set<String> getAllPackagesWithDynamicCodeLoading() { 93 return mPackageDynamicCodeLoading.getAllPackagesWithDynamicCodeLoading(); 94 } 95 96 /** 97 * Write information about code dynamically loaded by {@code packageName} to the event log. 98 */ logDynamicCodeLoading(String packageName)99 public void logDynamicCodeLoading(String packageName) { 100 PackageDynamicCode info = getPackageDynamicCodeInfo(packageName); 101 if (info == null) { 102 return; 103 } 104 105 SparseArray<ApplicationInfo> appInfoByUser = new SparseArray<>(); 106 boolean needWrite = false; 107 108 for (Map.Entry<String, DynamicCodeFile> fileEntry : info.mFileUsageMap.entrySet()) { 109 String filePath = fileEntry.getKey(); 110 DynamicCodeFile fileInfo = fileEntry.getValue(); 111 int userId = fileInfo.mUserId; 112 113 int index = appInfoByUser.indexOfKey(userId); 114 ApplicationInfo appInfo; 115 if (index >= 0) { 116 appInfo = appInfoByUser.get(userId); 117 } else { 118 appInfo = null; 119 120 try { 121 PackageInfo ownerInfo = 122 getPackageManager().getPackageInfo(packageName, /*flags*/ 0, userId); 123 appInfo = ownerInfo == null ? null : ownerInfo.applicationInfo; 124 } catch (RemoteException ignored) { 125 // Can't happen, we're local. 126 } 127 appInfoByUser.put(userId, appInfo); 128 if (appInfo == null) { 129 Slog.d(TAG, "Could not find package " + packageName + " for user " + userId); 130 // Package has probably been uninstalled for user. 131 needWrite |= mPackageDynamicCodeLoading.removeUserPackage(packageName, userId); 132 } 133 } 134 135 if (appInfo == null) { 136 continue; 137 } 138 139 int storageFlags; 140 141 if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) { 142 storageFlags = StorageManager.FLAG_STORAGE_CE; 143 } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) { 144 storageFlags = StorageManager.FLAG_STORAGE_DE; 145 } else { 146 Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); 147 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); 148 continue; 149 } 150 151 byte[] hash = null; 152 try { 153 // Note that we do not take the install lock here. Hashing should never interfere 154 // with app update/compilation/removal. We may get anomalous results if a file 155 // changes while we hash it, but that can happen anyway and is harmless for our 156 // purposes. 157 hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid, 158 appInfo.volumeUuid, storageFlags); 159 } catch (InstallerException e) { 160 Slog.e(TAG, "Got InstallerException when hashing file " + filePath 161 + ": " + e.getMessage()); 162 } 163 164 String subtag = fileInfo.mFileType == FILE_TYPE_DEX 165 ? DCL_DEX_SUBTAG 166 : DCL_NATIVE_SUBTAG; 167 String fileName = new File(filePath).getName(); 168 String message = PackageUtils.computeSha256Digest(fileName.getBytes()); 169 170 // Valid SHA256 will be 256 bits, 32 bytes. 171 if (hash != null && hash.length == 32) { 172 message = message + ' ' + HexEncoding.encodeToString(hash); 173 } else { 174 Slog.d(TAG, "Got no hash for " + filePath); 175 // File has probably been deleted. 176 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); 177 } 178 179 for (String loadingPackageName : fileInfo.mLoadingPackages) { 180 int loadingUid = -1; 181 if (loadingPackageName.equals(packageName)) { 182 loadingUid = appInfo.uid; 183 } else { 184 try { 185 loadingUid = getPackageManager().getPackageUid(loadingPackageName, /*flags*/ 0, 186 userId); 187 } catch (RemoteException ignored) { 188 // Can't happen, we're local. 189 } 190 } 191 192 if (loadingUid != -1) { 193 writeDclEvent(subtag, loadingUid, message); 194 } 195 } 196 } 197 198 if (needWrite) { 199 mPackageDynamicCodeLoading.maybeWriteAsync(); 200 } 201 } 202 fileIsUnder(String filePath, String directoryPath)203 private boolean fileIsUnder(String filePath, String directoryPath) { 204 if (directoryPath == null) { 205 return false; 206 } 207 208 try { 209 return FileUtils.contains(new File(directoryPath).getCanonicalPath(), 210 new File(filePath).getCanonicalPath()); 211 } catch (IOException e) { 212 return false; 213 } 214 } 215 216 @VisibleForTesting getPackageDynamicCodeInfo(String packageName)217 PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { 218 return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); 219 } 220 221 @VisibleForTesting writeDclEvent(String subtag, int uid, String message)222 void writeDclEvent(String subtag, int uid, String message) { 223 EventLog.writeEvent(SNET_TAG, subtag, uid, message); 224 } 225 226 /** 227 * Records that an app running in the specified uid has executed dex code from the file at 228 * {@code path}. 229 */ recordDex( int loaderUserId, String dexPath, String owningPackageName, String loadingPackageName)230 public void recordDex( 231 int loaderUserId, String dexPath, String owningPackageName, String loadingPackageName) { 232 if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, 233 FILE_TYPE_DEX, loaderUserId, loadingPackageName)) { 234 mPackageDynamicCodeLoading.maybeWriteAsync(); 235 } 236 } 237 238 /** 239 * Records that an app running in the specified uid has executed native code from the file at 240 * {@code path}. 241 */ recordNative(int loadingUid, String path)242 public void recordNative(int loadingUid, String path) { 243 String[] packages; 244 try { 245 packages = getPackageManager().getPackagesForUid(loadingUid); 246 if (packages == null || packages.length == 0) { 247 return; 248 } 249 } catch (RemoteException e) { 250 // Can't happen, we're local. 251 return; 252 } 253 254 String loadingPackageName = packages[0]; 255 int loadingUserId = UserHandle.getUserId(loadingUid); 256 257 if (mPackageDynamicCodeLoading.record(loadingPackageName, path, 258 FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) { 259 mPackageDynamicCodeLoading.maybeWriteAsync(); 260 } 261 } 262 clear()263 void clear() { 264 mPackageDynamicCodeLoading.clear(); 265 } 266 removePackage(String packageName)267 void removePackage(String packageName) { 268 if (mPackageDynamicCodeLoading.removePackage(packageName)) { 269 mPackageDynamicCodeLoading.maybeWriteAsync(); 270 } 271 } 272 removeUserPackage(String packageName, int userId)273 void removeUserPackage(String packageName, int userId) { 274 if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { 275 mPackageDynamicCodeLoading.maybeWriteAsync(); 276 } 277 } 278 readAndSync(Map<String, Set<Integer>> packageToUsersMap)279 void readAndSync(Map<String, Set<Integer>> packageToUsersMap) { 280 mPackageDynamicCodeLoading.read(); 281 mPackageDynamicCodeLoading.syncData(packageToUsersMap); 282 } 283 284 /** Writes the in-memory dynamic code information to disk right away. */ writeNow()285 public void writeNow() { 286 mPackageDynamicCodeLoading.writeNow(); 287 } 288 289 /** Reads the dynamic code information from disk. */ load(Map<Integer, List<PackageInfo>> userToPackagesMap)290 public void load(Map<Integer, List<PackageInfo>> userToPackagesMap) { 291 // Compute a reverse map. 292 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 293 for (Map.Entry<Integer, List<PackageInfo>> entry : userToPackagesMap.entrySet()) { 294 List<PackageInfo> packageInfoList = entry.getValue(); 295 int userId = entry.getKey(); 296 for (PackageInfo pi : packageInfoList) { 297 Set<Integer> users = 298 packageToUsersMap.computeIfAbsent(pi.packageName, k -> new HashSet<>()); 299 users.add(userId); 300 } 301 } 302 303 readAndSync(packageToUsersMap); 304 } 305 306 /** 307 * Notifies that the user {@code userId} data for package {@code packageName} was destroyed. 308 * This will remove all dynamic code information associated with the package for the given user. 309 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case 310 * all dynamic code information for the package will be removed. 311 */ notifyPackageDataDestroyed(String packageName, int userId)312 public void notifyPackageDataDestroyed(String packageName, int userId) { 313 if (userId == UserHandle.USER_ALL) { 314 removePackage(packageName); 315 } else { 316 removeUserPackage(packageName, userId); 317 } 318 } 319 } 320