• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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