1/* 2 * Copyright (C) 2016 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 17package com.android.server; 18 19import android.content.Context; 20import android.net.LocalSocket; 21import android.net.LocalSocketAddress; 22import android.os.IRecoverySystem; 23import android.os.IRecoverySystemProgressListener; 24import android.os.PowerManager; 25import android.os.RecoverySystem; 26import android.os.RemoteException; 27import android.os.SystemProperties; 28import android.system.ErrnoException; 29import android.system.Os; 30import android.util.Slog; 31 32import libcore.io.IoUtils; 33 34import java.io.DataInputStream; 35import java.io.DataOutputStream; 36import java.io.File; 37import java.io.FileWriter; 38import java.io.IOException; 39 40/** 41 * The recovery system service is responsible for coordinating recovery related 42 * functions on the device. It sets up (or clears) the bootloader control block 43 * (BCB), which will be read by the bootloader and the recovery image. It also 44 * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the 45 * /data partition so that it can be accessed under the recovery image. 46 */ 47public final class RecoverySystemService extends SystemService { 48 private static final String TAG = "RecoverySystemService"; 49 private static final boolean DEBUG = false; 50 51 // The socket at /dev/socket/uncrypt to communicate with uncrypt. 52 private static final String UNCRYPT_SOCKET = "uncrypt"; 53 54 // The init services that communicate with /system/bin/uncrypt. 55 private static final String INIT_SERVICE_UNCRYPT = "init.svc.uncrypt"; 56 private static final String INIT_SERVICE_SETUP_BCB = "init.svc.setup-bcb"; 57 private static final String INIT_SERVICE_CLEAR_BCB = "init.svc.clear-bcb"; 58 59 private static final int SOCKET_CONNECTION_MAX_RETRY = 30; 60 61 private static final Object sRequestLock = new Object(); 62 63 private Context mContext; 64 65 public RecoverySystemService(Context context) { 66 super(context); 67 mContext = context; 68 } 69 70 @Override 71 public void onStart() { 72 publishBinderService(Context.RECOVERY_SERVICE, new BinderService()); 73 } 74 75 private final class BinderService extends IRecoverySystem.Stub { 76 @Override // Binder call 77 public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) { 78 if (DEBUG) Slog.d(TAG, "uncrypt: " + filename); 79 80 synchronized (sRequestLock) { 81 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); 82 83 final boolean available = checkAndWaitForUncryptService(); 84 if (!available) { 85 Slog.e(TAG, "uncrypt service is unavailable."); 86 return false; 87 } 88 89 // Write the filename into UNCRYPT_PACKAGE_FILE to be read by 90 // uncrypt. 91 RecoverySystem.UNCRYPT_PACKAGE_FILE.delete(); 92 93 try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) { 94 uncryptFile.write(filename + "\n"); 95 } catch (IOException e) { 96 Slog.e(TAG, "IOException when writing \"" + 97 RecoverySystem.UNCRYPT_PACKAGE_FILE + "\":", e); 98 return false; 99 } 100 101 // Trigger uncrypt via init. 102 SystemProperties.set("ctl.start", "uncrypt"); 103 104 // Connect to the uncrypt service socket. 105 LocalSocket socket = connectService(); 106 if (socket == null) { 107 Slog.e(TAG, "Failed to connect to uncrypt socket"); 108 return false; 109 } 110 111 // Read the status from the socket. 112 DataInputStream dis = null; 113 DataOutputStream dos = null; 114 try { 115 dis = new DataInputStream(socket.getInputStream()); 116 dos = new DataOutputStream(socket.getOutputStream()); 117 int lastStatus = Integer.MIN_VALUE; 118 while (true) { 119 int status = dis.readInt(); 120 // Avoid flooding the log with the same message. 121 if (status == lastStatus && lastStatus != Integer.MIN_VALUE) { 122 continue; 123 } 124 lastStatus = status; 125 126 if (status >= 0 && status <= 100) { 127 // Update status 128 Slog.i(TAG, "uncrypt read status: " + status); 129 if (listener != null) { 130 try { 131 listener.onProgress(status); 132 } catch (RemoteException ignored) { 133 Slog.w(TAG, "RemoteException when posting progress"); 134 } 135 } 136 if (status == 100) { 137 Slog.i(TAG, "uncrypt successfully finished."); 138 // Ack receipt of the final status code. uncrypt 139 // waits for the ack so the socket won't be 140 // destroyed before we receive the code. 141 dos.writeInt(0); 142 break; 143 } 144 } else { 145 // Error in /system/bin/uncrypt. 146 Slog.e(TAG, "uncrypt failed with status: " + status); 147 // Ack receipt of the final status code. uncrypt waits 148 // for the ack so the socket won't be destroyed before 149 // we receive the code. 150 dos.writeInt(0); 151 return false; 152 } 153 } 154 } catch (IOException e) { 155 Slog.e(TAG, "IOException when reading status: ", e); 156 return false; 157 } finally { 158 IoUtils.closeQuietly(dis); 159 IoUtils.closeQuietly(dos); 160 IoUtils.closeQuietly(socket); 161 } 162 163 return true; 164 } 165 } 166 167 @Override // Binder call 168 public boolean clearBcb() { 169 if (DEBUG) Slog.d(TAG, "clearBcb"); 170 synchronized (sRequestLock) { 171 return setupOrClearBcb(false, null); 172 } 173 } 174 175 @Override // Binder call 176 public boolean setupBcb(String command) { 177 if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]"); 178 synchronized (sRequestLock) { 179 return setupOrClearBcb(true, command); 180 } 181 } 182 183 @Override // Binder call 184 public void rebootRecoveryWithCommand(String command) { 185 if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]"); 186 synchronized (sRequestLock) { 187 if (!setupOrClearBcb(true, command)) { 188 return; 189 } 190 191 // Having set up the BCB, go ahead and reboot. 192 PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 193 pm.reboot(PowerManager.REBOOT_RECOVERY); 194 } 195 } 196 197 /** 198 * Check if any of the init services is still running. If so, we cannot 199 * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise 200 * it may break the socket communication since init creates / deletes 201 * the socket (/dev/socket/uncrypt) on service start / exit. 202 */ 203 private boolean checkAndWaitForUncryptService() { 204 for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { 205 final String uncryptService = SystemProperties.get(INIT_SERVICE_UNCRYPT); 206 final String setupBcbService = SystemProperties.get(INIT_SERVICE_SETUP_BCB); 207 final String clearBcbService = SystemProperties.get(INIT_SERVICE_CLEAR_BCB); 208 final boolean busy = "running".equals(uncryptService) || 209 "running".equals(setupBcbService) || "running".equals(clearBcbService); 210 if (DEBUG) { 211 Slog.i(TAG, "retry: " + retry + " busy: " + busy + 212 " uncrypt: [" + uncryptService + "]" + 213 " setupBcb: [" + setupBcbService + "]" + 214 " clearBcb: [" + clearBcbService + "]"); 215 } 216 217 if (!busy) { 218 return true; 219 } 220 221 try { 222 Thread.sleep(1000); 223 } catch (InterruptedException e) { 224 Slog.w(TAG, "Interrupted:", e); 225 } 226 } 227 228 return false; 229 } 230 231 private LocalSocket connectService() { 232 LocalSocket socket = new LocalSocket(); 233 boolean done = false; 234 // The uncrypt socket will be created by init upon receiving the 235 // service request. It may not be ready by this point. So we will 236 // keep retrying until success or reaching timeout. 237 for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { 238 try { 239 socket.connect(new LocalSocketAddress(UNCRYPT_SOCKET, 240 LocalSocketAddress.Namespace.RESERVED)); 241 done = true; 242 break; 243 } catch (IOException ignored) { 244 try { 245 Thread.sleep(1000); 246 } catch (InterruptedException e) { 247 Slog.w(TAG, "Interrupted:", e); 248 } 249 } 250 } 251 if (!done) { 252 Slog.e(TAG, "Timed out connecting to uncrypt socket"); 253 return null; 254 } 255 return socket; 256 } 257 258 private boolean setupOrClearBcb(boolean isSetup, String command) { 259 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); 260 261 final boolean available = checkAndWaitForUncryptService(); 262 if (!available) { 263 Slog.e(TAG, "uncrypt service is unavailable."); 264 return false; 265 } 266 267 if (isSetup) { 268 SystemProperties.set("ctl.start", "setup-bcb"); 269 } else { 270 SystemProperties.set("ctl.start", "clear-bcb"); 271 } 272 273 // Connect to the uncrypt service socket. 274 LocalSocket socket = connectService(); 275 if (socket == null) { 276 Slog.e(TAG, "Failed to connect to uncrypt socket"); 277 return false; 278 } 279 280 DataInputStream dis = null; 281 DataOutputStream dos = null; 282 try { 283 dis = new DataInputStream(socket.getInputStream()); 284 dos = new DataOutputStream(socket.getOutputStream()); 285 286 // Send the BCB commands if it's to setup BCB. 287 if (isSetup) { 288 dos.writeInt(command.length()); 289 dos.writeBytes(command); 290 dos.flush(); 291 } 292 293 // Read the status from the socket. 294 int status = dis.readInt(); 295 296 // Ack receipt of the status code. uncrypt waits for the ack so 297 // the socket won't be destroyed before we receive the code. 298 dos.writeInt(0); 299 300 if (status == 100) { 301 Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") + 302 " bcb successfully finished."); 303 } else { 304 // Error in /system/bin/uncrypt. 305 Slog.e(TAG, "uncrypt failed with status: " + status); 306 return false; 307 } 308 } catch (IOException e) { 309 Slog.e(TAG, "IOException when communicating with uncrypt:", e); 310 return false; 311 } finally { 312 IoUtils.closeQuietly(dis); 313 IoUtils.closeQuietly(dos); 314 IoUtils.closeQuietly(socket); 315 } 316 317 return true; 318 } 319 } 320} 321