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