1 /*
2  * Copyright (C) 2018 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.devicepolicy;
18 
19 import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback;
20 import android.app.admin.StartInstallingUpdateCallback;
21 import android.content.Context;
22 import android.os.ParcelFileDescriptor;
23 import android.os.UpdateEngine;
24 import android.os.UpdateEngineCallback;
25 import android.util.Log;
26 
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.zip.ZipEntry;
37 import java.util.zip.ZipException;
38 import java.util.zip.ZipFile;
39 
40 /**
41  * Used for installing an update on <a href="https://source.android.com/devices/tech/ota/ab">AB
42  * devices.</a>
43  * <p>This logic is specific to GOTA and should be modified by OEMs using a different AB update
44  * system.</p>
45  */
46 class AbUpdateInstaller extends UpdateInstaller {
47     private static final String PAYLOAD_BIN = "payload.bin";
48     private static final String PAYLOAD_PROPERTIES_TXT = "payload_properties.txt";
49     //https://en.wikipedia.org/wiki/Zip_(file_format)#Local_file_header
50     private static final int OFFSET_TO_FILE_NAME = 30;
51     // kDownloadStateInitializationError constant from system/update_engine/common/error_code.h.
52     private static final int DOWNLOAD_STATE_INITIALIZATION_ERROR = 20;
53     private long mSizeForUpdate;
54     private long mOffsetForUpdate;
55     private List<String> mProperties;
56     private Enumeration<? extends ZipEntry> mEntries;
57     private ZipFile mPackedUpdateFile;
58     private static final Map<Integer, Integer> errorCodesMap = buildErrorCodesMap();
59     private static final Map<Integer, String> errorStringsMap = buildErrorStringsMap();
60     public static final String UNKNOWN_ERROR = "Unknown error with error code = ";
61     private boolean mUpdateInstalled;
62 
buildErrorCodesMap()63     private static Map<Integer, Integer> buildErrorCodesMap() {
64         Map<Integer, Integer> map = new HashMap<>();
65         map.put(
66                 UpdateEngine.ErrorCodeConstants.ERROR,
67                 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
68         map.put(
69                 DOWNLOAD_STATE_INITIALIZATION_ERROR,
70                 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
71         map.put(
72                 UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
73                 InstallSystemUpdateCallback.UPDATE_ERROR_INCORRECT_OS_VERSION);
74 
75         // Error constants corresponding to errors related to bad update file.
76         map.put(
77                 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
78                 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
79         map.put(
80                 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
81                 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
82         map.put(
83                 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
84                 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
85         map.put(
86                 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
87                 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
88         // TODO(b/133396459): replace with a constant.
89         map.put(
90                 26 /* kDownloadMetadataSignatureMismatch */,
91                 InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID);
92 
93         // Error constants corresponding to errors related to devices bad state.
94         map.put(
95                 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
96                 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
97         map.put(
98                 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
99                 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
100         map.put(
101                 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
102                 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
103         map.put(
104                 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
105                 InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN);
106 
107         return map;
108     }
109 
buildErrorStringsMap()110     private static Map<Integer, String> buildErrorStringsMap() {
111         Map<Integer, String> map = new HashMap<>();
112         map.put(UpdateEngine.ErrorCodeConstants.ERROR, UNKNOWN_ERROR);
113         map.put(
114                 DOWNLOAD_STATE_INITIALIZATION_ERROR,
115                 "The delta update payload was targeted for another version or the source partition"
116                         + "was modified after it was installed");
117         map.put(
118                 UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
119                 "Failed to finish the configured postinstall works.");
120         map.put(
121                 UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
122                 "Failed to open one of the partitions it tried to write to or read data from.");
123         map.put(
124                 UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
125                 "Payload mismatch error.");
126         map.put(
127                 UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
128                 "Failed to read the payload data from the given URL.");
129         map.put(
130                 UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, "Payload hash error.");
131         map.put(
132                 UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
133                 "Payload size mismatch error.");
134         map.put(
135                 UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
136                 "Failed to verify the signature of the payload.");
137         map.put(
138                 UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
139                 "The payload has been successfully installed,"
140                         + "but the active slot was not flipped.");
141         return map;
142     }
143 
AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector, DevicePolicyConstants constants)144     AbUpdateInstaller(Context context, ParcelFileDescriptor updateFileDescriptor,
145             StartInstallingUpdateCallback callback, DevicePolicyManagerService.Injector injector,
146             DevicePolicyConstants constants) {
147         super(context, updateFileDescriptor, callback, injector, constants);
148         mUpdateInstalled = false;
149     }
150 
151     @Override
installUpdateInThread()152     public void installUpdateInThread() {
153         if (mUpdateInstalled) {
154             throw new IllegalStateException("installUpdateInThread can be called only once.");
155         }
156         try {
157             setState();
158             applyPayload(Paths.get(mCopiedUpdateFile.getAbsolutePath()).toUri().toString());
159         } catch (ZipException e) {
160             Log.w(UpdateInstaller.TAG, e);
161             notifyCallbackOnError(
162                     InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
163                     Log.getStackTraceString(e));
164         } catch (IOException e) {
165             Log.w(UpdateInstaller.TAG, e);
166             notifyCallbackOnError(
167                     InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN,
168                     Log.getStackTraceString(e));
169         }
170     }
171 
setState()172     private void setState() throws IOException {
173         mUpdateInstalled = true;
174         mPackedUpdateFile = new ZipFile(mCopiedUpdateFile);
175         mProperties = new ArrayList<>();
176         mSizeForUpdate = -1;
177         mOffsetForUpdate = 0;
178         mEntries = mPackedUpdateFile.entries();
179     }
180 
buildBoundUpdateEngine()181     private UpdateEngine buildBoundUpdateEngine() {
182         UpdateEngine updateEngine = new UpdateEngine();
183         updateEngine.bind(new DelegatingUpdateEngineCallback(this, updateEngine));
184         return updateEngine;
185     }
186 
applyPayload(String updatePath)187     private void applyPayload(String updatePath) throws IOException {
188         if (!updateStateForPayload()) {
189             return;
190         }
191         String[] headerKeyValuePairs = mProperties.stream().toArray(String[]::new);
192         if (mSizeForUpdate == -1) {
193             Log.w(UpdateInstaller.TAG, "Failed to find payload entry in the given package.");
194             notifyCallbackOnError(
195                     InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
196                     "Failed to find payload entry in the given package.");
197             return;
198         }
199 
200         UpdateEngine updateEngine = buildBoundUpdateEngine();
201         try {
202             updateEngine.applyPayload(
203                     updatePath, mOffsetForUpdate, mSizeForUpdate, headerKeyValuePairs);
204         } catch (Exception e) {
205             // Prevent an automatic restart when an update is already being processed
206             // (http://b/124106342).
207             Log.w(UpdateInstaller.TAG, "Failed to install update from file.", e);
208             notifyCallbackOnError(
209                     InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN,
210                     "Failed to install update from file.");
211         }
212     }
213 
updateStateForPayload()214     private boolean updateStateForPayload() throws IOException {
215         long offset = 0;
216         while (mEntries.hasMoreElements()) {
217             ZipEntry entry = mEntries.nextElement();
218 
219             String name = entry.getName();
220             offset += buildOffsetForEntry(entry, name);
221             if (entry.isDirectory()) {
222                 offset -= entry.getCompressedSize();
223                 continue;
224             }
225             if (PAYLOAD_BIN.equals(name)) {
226                 if (entry.getMethod() != ZipEntry.STORED) {
227                     Log.w(UpdateInstaller.TAG, "Invalid compression method.");
228                     notifyCallbackOnError(
229                             InstallSystemUpdateCallback.UPDATE_ERROR_UPDATE_FILE_INVALID,
230                             "Invalid compression method.");
231                     return false;
232                 }
233                 mSizeForUpdate = entry.getCompressedSize();
234                 mOffsetForUpdate = offset - entry.getCompressedSize();
235             } else if (PAYLOAD_PROPERTIES_TXT.equals(name)) {
236                 updatePropertiesForEntry(entry);
237             }
238         }
239         return true;
240     }
241 
buildOffsetForEntry(ZipEntry entry, String name)242     private long buildOffsetForEntry(ZipEntry entry, String name) {
243         return OFFSET_TO_FILE_NAME + name.length() + entry.getCompressedSize()
244                 + (entry.getExtra() == null ? 0 : entry.getExtra().length);
245     }
246 
updatePropertiesForEntry(ZipEntry entry)247     private void updatePropertiesForEntry(ZipEntry entry) throws IOException {
248         try (BufferedReader bufferedReader = new BufferedReader(
249                 new InputStreamReader(mPackedUpdateFile.getInputStream(entry)))) {
250             String line;
251             /* Neither @line nor @mProperties are size constraint since there is a few properties
252             with limited size. */
253             while ((line = bufferedReader.readLine()) != null) {
254                 mProperties.add(line);
255             }
256         }
257     }
258 
259     private static class DelegatingUpdateEngineCallback extends UpdateEngineCallback {
260         private UpdateInstaller mUpdateInstaller;
261         private UpdateEngine mUpdateEngine;
262 
DelegatingUpdateEngineCallback( UpdateInstaller updateInstaller, UpdateEngine updateEngine)263         DelegatingUpdateEngineCallback(
264                 UpdateInstaller updateInstaller, UpdateEngine updateEngine) {
265             mUpdateInstaller = updateInstaller;
266             mUpdateEngine = updateEngine;
267         }
268 
269         @Override
onStatusUpdate(int statusCode, float percentage)270         public void onStatusUpdate(int statusCode, float percentage) {
271             return;
272         }
273 
274         @Override
onPayloadApplicationComplete(int errorCode)275         public void onPayloadApplicationComplete(int errorCode) {
276             mUpdateEngine.unbind();
277             if (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) {
278                 mUpdateInstaller.notifyCallbackOnSuccess();
279             } else {
280                 mUpdateInstaller.notifyCallbackOnError(
281                         errorCodesMap.getOrDefault(
282                                 errorCode, InstallSystemUpdateCallback.UPDATE_ERROR_UNKNOWN),
283                         errorStringsMap.getOrDefault(errorCode, UNKNOWN_ERROR + errorCode));
284             }
285         }
286     }
287 }
288