• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.sdksandbox.verifier;
18 
19 import android.content.Context;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.os.Handler;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.sdksandbox.verifier.DexParser.DexEntry;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.stream.Collectors;
35 
36 /**
37  * Handles the loading of dex files for multiple apks to be verified, ensures that a single dex file
38  * is loaded at any time.
39  *
40  * @hide
41  */
42 public class SerialDexLoader {
43     private static final String TAG = "SdkSandboxVerifier";
44 
45     private DexParser mParser;
46     private Handler mHandler;
47     private DexSymbols mDexSymbols;
48 
SerialDexLoader(DexParser parser, Handler handler)49     public SerialDexLoader(DexParser parser, Handler handler) {
50         mParser = parser;
51         mHandler = handler;
52         mDexSymbols = new DexSymbols();
53     }
54 
55     /**
56      * Queues all dex files found for an apk for serially loading and analyzing.
57      *
58      * @param apkPathFile path to apk containing one or more dex files
59      * @param packagename packagename associated with the apk
60      * @param verificationHandler object to handle the verification of the loaded dex
61      */
queueApkToLoad( File apkPathFile, String packagename, Context context, VerificationHandler verificationHandler)62     public void queueApkToLoad(
63             File apkPathFile,
64             String packagename,
65             Context context,
66             VerificationHandler verificationHandler) {
67 
68         mHandler.post(
69                 () -> {
70                     List<DexEntry> dexEntries = null;
71                     try {
72                         dexEntries = mParser.getDexFilePaths(apkPathFile);
73                     } catch (IOException e) {
74                         verificationHandler.onVerificationErrorForPackage(e);
75                         return;
76                     }
77 
78                     File installedPackageFile = null;
79                     List<String> verifiedEntries = new ArrayList<>();
80                     boolean passedVerification = false;
81                     try {
82                         passedVerification =
83                                 verifyDexEntries(dexEntries, verifiedEntries, verificationHandler);
84                         verificationHandler.onVerificationCompleteForPackage(passedVerification);
85                         return;
86                     } catch (IOException e) {
87                         // tmp dex files were deleted while verifying, this happens when
88                         // installation completes, try fetching installed apk to continue verifying
89                         installedPackageFile = getInstalledPackageFile(packagename, context);
90                         if (installedPackageFile == null) {
91                             verificationHandler.onVerificationErrorForPackage(
92                                     new Exception("apk files not found for " + packagename));
93                             return;
94                         }
95                     }
96 
97                     // verify installed dex entries if we ran out of time to verify the tmp files
98                     List<DexEntry> installedDexEntries = null;
99                     try {
100                         installedDexEntries = mParser.getDexFilePaths(installedPackageFile);
101                     } catch (IOException e) {
102                         verificationHandler.onVerificationErrorForPackage(e);
103                         return;
104                     }
105 
106                     // avoid verifying the same dex file twice
107                     List<DexEntry> pendingDexEntries =
108                             installedDexEntries.stream()
109                                     .filter(
110                                             entry ->
111                                                     !verifiedEntries.contains(
112                                                             entry.getEntryFilename()))
113                                     .collect(Collectors.toList());
114 
115                     try {
116                         passedVerification =
117                                 verifyDexEntries(
118                                         pendingDexEntries, verifiedEntries, verificationHandler);
119                         verificationHandler.onVerificationCompleteForPackage(passedVerification);
120                         return;
121                     } catch (IOException e) {
122                         verificationHandler.onVerificationErrorForPackage(e);
123                         return;
124                     }
125                 });
126     }
127 
verifyDexEntries( List<DexEntry> dexEntries, List<String> verifiedEntries, VerificationHandler verificationHandler)128     private boolean verifyDexEntries(
129             List<DexEntry> dexEntries,
130             List<String> verifiedEntries,
131             VerificationHandler verificationHandler)
132             throws IOException {
133         for (DexEntry entry : dexEntries) {
134             mParser.loadDexSymbols(entry.getApkFile(), entry.getDexEntry(), mDexSymbols);
135             if (!verificationHandler.verify(mDexSymbols)) {
136                 return false;
137             }
138             verifiedEntries.add(entry.getEntryFilename());
139         }
140         return true;
141     }
142 
getInstalledPackageFile(String packagename, Context context)143     private File getInstalledPackageFile(String packagename, Context context) {
144         try {
145             ApplicationInfo applicationInfo =
146                     context.getPackageManager()
147                             .getPackageInfo(
148                                     packagename,
149                                     PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES
150                                             | PackageManager.MATCH_ANY_USER)
151                             .applicationInfo;
152             return new File(applicationInfo.sourceDir);
153         } catch (NameNotFoundException e) {
154             return null;
155         }
156     }
157 
158     /** Interface for handling processing of the loaded dex contents */
159     public interface VerificationHandler {
160 
161         /**
162          * Takes in the DexSymbols and verifies its contents.
163          *
164          * @param result object contains the symbols parsed from the loaded dex file
165          */
verify(DexSymbols result)166         boolean verify(DexSymbols result);
167 
168         /**
169          * Called when all the loaded dex files have passed verification, or when one has failed.
170          *
171          * @param passed is false if the last loaded dex failed verification, or true if all dexes
172          *     passed.
173          */
onVerificationCompleteForPackage(boolean result)174         void onVerificationCompleteForPackage(boolean result);
175 
176         /**
177          * Error occurred on verifying.
178          *
179          * @param e exception thrown while attempting to load and verify the apk.
180          */
onVerificationErrorForPackage(Exception e)181         void onVerificationErrorForPackage(Exception e);
182     }
183 
184     /** Result class that contains symbols loaded from a DEX file */
185     public static class DexSymbols {
186 
187         private static final int DEX_MAX_METHOD_COUNT = 65536;
188 
189         private String mDexEntry;
190 
191         /** The table of classes referenced by the DEX file. */
192         private ArrayList<String> mReferencedClasses = new ArrayList<>(DEX_MAX_METHOD_COUNT);
193 
194         /** The table of methods referenced by the DEX file. */
195         private ArrayList<String> mReferencedMethods = new ArrayList<>(DEX_MAX_METHOD_COUNT);
196 
197         /** Maps referenced methods to their declaring class in the referenced classes table. */
198         private ArrayList<Integer> mClassIndex = new ArrayList<>(DEX_MAX_METHOD_COUNT);
199 
200         /**
201          * Adds a new method to the referencedMethods table and its containing class to the
202          * referenced classes table if it's different to the last seen class.
203          *
204          * <p>Referenced methods should be added in the order that they are present in the methods
205          * table from the dex file.
206          *
207          * @param classname describes the class with / as separator of its subpackages
208          * @param method the method name, parameter types and return types with ; as separator
209          */
addReferencedMethod(String classname, String method)210         public void addReferencedMethod(String classname, String method) {
211             // the method table is sorted by class, so new classnames can be stored when first
212             // encountered
213             if (mReferencedClasses.size() == 0
214                     || !mReferencedClasses.get(mReferencedClasses.size() - 1).equals(classname)) {
215                 mReferencedClasses.add(classname);
216             }
217             mReferencedMethods.add(method);
218             mClassIndex.add(mReferencedClasses.size() - 1);
219         }
220 
221         @VisibleForTesting
hasReferencedMethod(String classname, String method)222         boolean hasReferencedMethod(String classname, String method) {
223             int methodIdx = mReferencedMethods.indexOf(method);
224             return methodIdx >= 0
225                     && mReferencedClasses.get(mClassIndex.get(methodIdx)).equals(classname);
226         }
227 
228         /**
229          * Clears the internal state of DexSymbols and sets dex entry name to load next dex file.
230          */
clearAndSetDexEntry(String dexEntry)231         public void clearAndSetDexEntry(String dexEntry) {
232             this.mDexEntry = dexEntry;
233             mReferencedClasses.clear();
234             mReferencedMethods.clear();
235             mClassIndex.clear();
236         }
237 
238         /** Returns the number of referenced methods loaded for the current dex */
getReferencedMethodCount()239         public int getReferencedMethodCount() {
240             return mReferencedMethods.size();
241         }
242 
243         /**
244          * Returns the method indexed by methodIndex in the table of loaded methods from the dex
245          * file
246          */
getReferencedMethodAtIndex(int methodIndex)247         public String getReferencedMethodAtIndex(int methodIndex) {
248             if (methodIndex < 0 || methodIndex >= mReferencedMethods.size()) {
249                 throw new IndexOutOfBoundsException("Method index out of bounds: " + methodIndex);
250             }
251             return mReferencedMethods.get(methodIndex);
252         }
253 
254         /** Returns the declaring class for the method indexed by methodIndex */
getClassForMethodAtIndex(int methodIndex)255         public String getClassForMethodAtIndex(int methodIndex) {
256             if (methodIndex < 0 || methodIndex >= mReferencedMethods.size()) {
257                 throw new IndexOutOfBoundsException("Method index out of bounds: " + methodIndex);
258             }
259             return mReferencedClasses.get(mClassIndex.get(methodIndex));
260         }
261 
262         @Override
toString()263         public String toString() {
264             return "DexSymbols: " + mDexEntry;
265         }
266     }
267 }
268