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