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