• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016, Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  * Neither the name of Google LLC nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 package com.android.tools.smali.dexlib2.util;
32 
33 import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
34 import com.android.tools.smali.dexlib2.dexbacked.DexBackedOdexFile.NotAnOdexFile;
35 import com.android.tools.smali.dexlib2.dexbacked.raw.CdexHeaderItem;
36 import com.android.tools.smali.dexlib2.dexbacked.raw.HeaderItem;
37 import com.android.tools.smali.dexlib2.dexbacked.raw.OdexHeaderItem;
38 import com.android.tools.smali.util.InputStreamUtil;
39 
40 import javax.annotation.Nonnull;
41 import java.io.EOFException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 
45 public class DexUtil {
46 
47     /**
48      * Reads in the dex header from the given input stream and verifies that it is valid and a supported version
49      *
50      * The inputStream must support mark(), and will be reset to initial position upon exiting the method
51      *
52      * @param inputStream An input stream that is positioned at a dex header
53      * @return The dex version
54      * @throws NotADexFile If the file is not a dex file
55      * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason
56      * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality
57      */
verifyDexHeader(@onnull InputStream inputStream)58     public static int verifyDexHeader(@Nonnull InputStream inputStream) throws IOException {
59         if (!inputStream.markSupported()) {
60             throw new IllegalArgumentException("InputStream must support mark");
61         }
62         inputStream.mark(44);
63         byte[] partialHeader = new byte[44];
64         try {
65             InputStreamUtil.readFully(inputStream, partialHeader);
66         } catch (EOFException ex) {
67             throw new NotADexFile("File is too short");
68         } finally {
69             inputStream.reset();
70         }
71 
72         return verifyDexHeader(partialHeader, 0);
73     }
74 
75     /**
76      * Verifies that the dex header is valid and a supported version
77      *
78      * @param buf A byte array containing at least the first 44 bytes of a dex file
79      * @param offset The offset within the array to the dex header
80      * @return The dex version
81      * @throws NotADexFile If the file is not a dex file
82      * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason
83      * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality
84      */
verifyDexHeader(@onnull byte[] buf, int offset)85     public static int verifyDexHeader(@Nonnull byte[] buf, int offset) {
86         int dexVersion = HeaderItem.getVersion(buf, offset);
87         if (dexVersion == -1) {
88             StringBuilder sb = new StringBuilder("Not a valid dex magic value:");
89             for (int i=0; i<8; i++) {
90                 sb.append(String.format(" %02x", buf[i]));
91             }
92             throw new NotADexFile(sb.toString());
93         }
94 
95         if (!HeaderItem.isSupportedDexVersion(dexVersion)) {
96             throw new UnsupportedFile(String.format("Dex version %03d is not supported", dexVersion));
97         }
98 
99         int endian = HeaderItem.getEndian(buf, offset);
100         if (endian == HeaderItem.BIG_ENDIAN_TAG) {
101             throw new UnsupportedFile("Big endian dex files are not supported");
102         }
103 
104         if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
105             throw new InvalidFile(String.format("Invalid endian tag: 0x%x", endian));
106         }
107 
108         return dexVersion;
109     }
110 
111     /**
112      * Verifies that the cdex header is valid and a supported version
113      *
114      * @param buf A byte array containing at least the first 44 bytes of a cdex file
115      * @param offset The offset within the array to the dex header
116      * @return The dex version
117      * @throws NotADexFile If the file is not a cdex file
118      * @throws InvalidFile If the header appears to be a cdex file, but is not valid for some reason
119      * @throws UnsupportedFile If the cdex header is valid, but uses unsupported functionality
120      */
verifyCdexHeader(@onnull byte[] buf, int offset)121     public static int verifyCdexHeader(@Nonnull byte[] buf, int offset) {
122         int cdexVersion = CdexHeaderItem.getVersion(buf, offset);
123         if (cdexVersion == -1) {
124             StringBuilder sb = new StringBuilder("Not a valid cdex magic value:");
125             for (int i=0; i<8; i++) {
126                 sb.append(String.format(" %02x", buf[offset + i]));
127             }
128             throw new NotADexFile(sb.toString());
129         }
130 
131         if (!CdexHeaderItem.isSupportedCdexVersion(cdexVersion)) {
132             throw new UnsupportedFile(String.format("Dex version %03d is not supported", cdexVersion));
133         }
134 
135         int endian = HeaderItem.getEndian(buf, offset);
136         if (endian == HeaderItem.BIG_ENDIAN_TAG) {
137             throw new UnsupportedFile("Big endian dex files are not supported");
138         }
139 
140         if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
141             throw new InvalidFile(String.format("Invalid endian tag: 0x%x", endian));
142         }
143 
144         return cdexVersion;
145     }
146 
147     /**
148      * Reads in the odex header from the given input stream and verifies that it is valid and a supported version
149      *
150      * The inputStream must support mark(), and will be reset to initial position upon exiting the method
151      *
152      * @param inputStream An input stream that is positioned at an odex header
153      * @throws NotAnOdexFile If the file is not an odex file
154      * @throws UnsupportedFile If the odex header is valid, but is an unsupported version
155      */
verifyOdexHeader(@onnull InputStream inputStream)156     public static void verifyOdexHeader(@Nonnull InputStream inputStream) throws IOException {
157         if (!inputStream.markSupported()) {
158             throw new IllegalArgumentException("InputStream must support mark");
159         }
160         inputStream.mark(8);
161         byte[] partialHeader = new byte[8];
162         try {
163             InputStreamUtil.readFully(inputStream, partialHeader);
164         } catch (EOFException ex) {
165             throw new NotAnOdexFile("File is too short");
166         } finally {
167             inputStream.reset();
168         }
169 
170         verifyOdexHeader(partialHeader, 0);
171     }
172 
173     /**
174      * Verifies that the odex header is valid and a supported version
175      *
176      * @param buf A byte array containing at least the first 8 bytes of an odex file
177      * @param offset The offset within the array to the odex header
178      * @throws NotAnOdexFile If the file is not an odex file
179      * @throws UnsupportedFile If the odex header is valid, but uses unsupported functionality
180      */
verifyOdexHeader(@onnull byte[] buf, int offset)181     public static void verifyOdexHeader(@Nonnull byte[] buf, int offset) {
182         int odexVersion = OdexHeaderItem.getVersion(buf, offset);
183         if (odexVersion == -1) {
184             StringBuilder sb = new StringBuilder("Not a valid odex magic value:");
185             for (int i=0; i<8; i++) {
186                 sb.append(String.format(" %02x", buf[i]));
187             }
188             throw new NotAnOdexFile(sb.toString());
189         }
190 
191         if (!OdexHeaderItem.isSupportedOdexVersion(odexVersion)) {
192             throw new UnsupportedFile(String.format("Odex version %03d is not supported", odexVersion));
193         }
194     }
195 
196     public static class InvalidFile extends RuntimeException {
InvalidFile()197         public InvalidFile() {
198         }
199 
InvalidFile(String message)200         public InvalidFile(String message) {
201             super(message);
202         }
203 
InvalidFile(String message, Throwable cause)204         public InvalidFile(String message, Throwable cause) {
205             super(message, cause);
206         }
207 
InvalidFile(Throwable cause)208         public InvalidFile(Throwable cause) {
209             super(cause);
210         }
211     }
212 
213     public static class UnsupportedFile extends RuntimeException {
UnsupportedFile()214         public UnsupportedFile() {
215         }
216 
UnsupportedFile(String message)217         public UnsupportedFile(String message) {
218             super(message);
219         }
220 
UnsupportedFile(String message, Throwable cause)221         public UnsupportedFile(String message, Throwable cause) {
222             super(message, cause);
223         }
224 
UnsupportedFile(Throwable cause)225         public UnsupportedFile(Throwable cause) {
226             super(cause);
227         }
228     }
229 }
230