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