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.Map; 47 import java.util.Set; 48 49 /** 50 * This class is responsible for logging data about secondary dex files and native code executed 51 * from an app's private directory. The data logged includes hashes of the name and content of each 52 * file. 53 */ 54 public class DynamicCodeLogger { 55 private static final String TAG = "DynamicCodeLogger"; 56 57 // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) - 58 // see b/63927552. 59 private static final int SNET_TAG = 0x534e4554; 60 private static final String DCL_DEX_SUBTAG = "dcl"; 61 private static final String DCL_NATIVE_SUBTAG = "dcln"; 62 63 private IPackageManager mPackageManager; 64 private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; 65 private final Installer mInstaller; 66 DynamicCodeLogger(Installer installer)67 DynamicCodeLogger(Installer installer) { 68 mInstaller = installer; 69 mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); 70 } 71 72 @VisibleForTesting DynamicCodeLogger(@onNull IPackageManager packageManager, @NonNull Installer installer, @NonNull PackageDynamicCodeLoading packageDynamicCodeLoading)73 DynamicCodeLogger(@NonNull IPackageManager packageManager, @NonNull Installer installer, 74 @NonNull PackageDynamicCodeLoading packageDynamicCodeLoading) { 75 mPackageManager = packageManager; 76 mInstaller = installer; 77 mPackageDynamicCodeLoading = packageDynamicCodeLoading; 78 } 79 80 @NonNull getPackageManager()81 private IPackageManager getPackageManager() { 82 if (mPackageManager == null) { 83 mPackageManager = IPackageManager.Stub.asInterface( 84 ServiceManager.getService("package")); 85 } 86 return mPackageManager; 87 } 88 getAllPackagesWithDynamicCodeLoading()89 public Set<String> getAllPackagesWithDynamicCodeLoading() { 90 return mPackageDynamicCodeLoading.getAllPackagesWithDynamicCodeLoading(); 91 } 92 93 /** 94 * Write information about code dynamically loaded by {@code packageName} to the event log. 95 */ logDynamicCodeLoading(String packageName)96 public void logDynamicCodeLoading(String packageName) { 97 PackageDynamicCode info = getPackageDynamicCodeInfo(packageName); 98 if (info == null) { 99 return; 100 } 101 102 SparseArray<ApplicationInfo> appInfoByUser = new SparseArray<>(); 103 boolean needWrite = false; 104 105 for (Map.Entry<String, DynamicCodeFile> fileEntry : info.mFileUsageMap.entrySet()) { 106 String filePath = fileEntry.getKey(); 107 DynamicCodeFile fileInfo = fileEntry.getValue(); 108 int userId = fileInfo.mUserId; 109 110 int index = appInfoByUser.indexOfKey(userId); 111 ApplicationInfo appInfo; 112 if (index >= 0) { 113 appInfo = appInfoByUser.get(userId); 114 } else { 115 appInfo = null; 116 117 try { 118 PackageInfo ownerInfo = 119 getPackageManager().getPackageInfo(packageName, /*flags*/ 0, userId); 120 appInfo = ownerInfo == null ? null : ownerInfo.applicationInfo; 121 } catch (RemoteException ignored) { 122 // Can't happen, we're local. 123 } 124 appInfoByUser.put(userId, appInfo); 125 if (appInfo == null) { 126 Slog.d(TAG, "Could not find package " + packageName + " for user " + userId); 127 // Package has probably been uninstalled for user. 128 needWrite |= mPackageDynamicCodeLoading.removeUserPackage(packageName, userId); 129 } 130 } 131 132 if (appInfo == null) { 133 continue; 134 } 135 136 int storageFlags; 137 138 if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) { 139 storageFlags = StorageManager.FLAG_STORAGE_CE; 140 } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) { 141 storageFlags = StorageManager.FLAG_STORAGE_DE; 142 } else { 143 Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); 144 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); 145 continue; 146 } 147 148 byte[] hash = null; 149 try { 150 // Note that we do not take the install lock here. Hashing should never interfere 151 // with app update/compilation/removal. We may get anomalous results if a file 152 // changes while we hash it, but that can happen anyway and is harmless for our 153 // purposes. 154 hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid, 155 appInfo.volumeUuid, storageFlags); 156 } catch (InstallerException e) { 157 Slog.e(TAG, "Got InstallerException when hashing file " + filePath 158 + ": " + e.getMessage()); 159 } 160 161 String subtag = fileInfo.mFileType == FILE_TYPE_DEX 162 ? DCL_DEX_SUBTAG 163 : DCL_NATIVE_SUBTAG; 164 String fileName = new File(filePath).getName(); 165 String message = PackageUtils.computeSha256Digest(fileName.getBytes()); 166 167 // Valid SHA256 will be 256 bits, 32 bytes. 168 if (hash != null && hash.length == 32) { 169 message = message + ' ' + HexEncoding.encodeToString(hash); 170 } else { 171 Slog.d(TAG, "Got no hash for " + filePath); 172 // File has probably been deleted. 173 needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); 174 } 175 176 for (String loadingPackageName : fileInfo.mLoadingPackages) { 177 int loadingUid = -1; 178 if (loadingPackageName.equals(packageName)) { 179 loadingUid = appInfo.uid; 180 } else { 181 try { 182 loadingUid = getPackageManager().getPackageUid(loadingPackageName, /*flags*/ 0, 183 userId); 184 } catch (RemoteException ignored) { 185 // Can't happen, we're local. 186 } 187 } 188 189 if (loadingUid != -1) { 190 writeDclEvent(subtag, loadingUid, message); 191 } 192 } 193 } 194 195 if (needWrite) { 196 mPackageDynamicCodeLoading.maybeWriteAsync(); 197 } 198 } 199 fileIsUnder(String filePath, String directoryPath)200 private boolean fileIsUnder(String filePath, String directoryPath) { 201 if (directoryPath == null) { 202 return false; 203 } 204 205 try { 206 return FileUtils.contains(new File(directoryPath).getCanonicalPath(), 207 new File(filePath).getCanonicalPath()); 208 } catch (IOException e) { 209 return false; 210 } 211 } 212 213 @VisibleForTesting getPackageDynamicCodeInfo(String packageName)214 PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { 215 return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); 216 } 217 218 @VisibleForTesting writeDclEvent(String subtag, int uid, String message)219 void writeDclEvent(String subtag, int uid, String message) { 220 EventLog.writeEvent(SNET_TAG, subtag, uid, message); 221 } 222 recordDex(int loaderUserId, String dexPath, String owningPackageName, String loadingPackageName)223 void recordDex(int loaderUserId, String dexPath, String owningPackageName, 224 String loadingPackageName) { 225 if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, 226 FILE_TYPE_DEX, loaderUserId, loadingPackageName)) { 227 mPackageDynamicCodeLoading.maybeWriteAsync(); 228 } 229 } 230 231 /** 232 * Record that an app running in the specified uid has executed native code from the file at 233 * {@param path}. 234 */ recordNative(int loadingUid, String path)235 public void recordNative(int loadingUid, String path) { 236 String[] packages; 237 try { 238 packages = getPackageManager().getPackagesForUid(loadingUid); 239 if (packages == null || packages.length == 0) { 240 return; 241 } 242 } catch (RemoteException e) { 243 // Can't happen, we're local. 244 return; 245 } 246 247 String loadingPackageName = packages[0]; 248 int loadingUserId = UserHandle.getUserId(loadingUid); 249 250 if (mPackageDynamicCodeLoading.record(loadingPackageName, path, 251 FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) { 252 mPackageDynamicCodeLoading.maybeWriteAsync(); 253 } 254 } 255 clear()256 void clear() { 257 mPackageDynamicCodeLoading.clear(); 258 } 259 removePackage(String packageName)260 void removePackage(String packageName) { 261 if (mPackageDynamicCodeLoading.removePackage(packageName)) { 262 mPackageDynamicCodeLoading.maybeWriteAsync(); 263 } 264 } 265 removeUserPackage(String packageName, int userId)266 void removeUserPackage(String packageName, int userId) { 267 if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { 268 mPackageDynamicCodeLoading.maybeWriteAsync(); 269 } 270 } 271 readAndSync(Map<String, Set<Integer>> packageToUsersMap)272 void readAndSync(Map<String, Set<Integer>> packageToUsersMap) { 273 mPackageDynamicCodeLoading.read(); 274 mPackageDynamicCodeLoading.syncData(packageToUsersMap); 275 } 276 writeNow()277 void writeNow() { 278 mPackageDynamicCodeLoading.writeNow(); 279 } 280 } 281