Home | History | Annotate | Download | only in server
      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.PowerManager;
     25 import android.os.RecoverySystem;
     26 import android.os.RemoteException;
     27 import android.os.SystemProperties;
     28 import android.system.ErrnoException;
     29 import android.system.Os;
     30 import android.util.Slog;
     31 
     32 import libcore.io.IoUtils;
     33 
     34 import java.io.DataInputStream;
     35 import java.io.DataOutputStream;
     36 import java.io.File;
     37 import java.io.FileWriter;
     38 import 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  */
     47 public 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                     byte[] cmdUtf8 = command.getBytes("UTF-8");
    289                     dos.writeInt(cmdUtf8.length);
    290                     dos.write(cmdUtf8, 0, cmdUtf8.length);
    291                     dos.flush();
    292                 }
    293 
    294                 // Read the status from the socket.
    295                 int status = dis.readInt();
    296 
    297                 // Ack receipt of the status code. uncrypt waits for the ack so
    298                 // the socket won't be destroyed before we receive the code.
    299                 dos.writeInt(0);
    300 
    301                 if (status == 100) {
    302                     Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
    303                             " bcb successfully finished.");
    304                 } else {
    305                     // Error in /system/bin/uncrypt.
    306                     Slog.e(TAG, "uncrypt failed with status: " + status);
    307                     return false;
    308                 }
    309             } catch (IOException e) {
    310                 Slog.e(TAG, "IOException when communicating with uncrypt:", e);
    311                 return false;
    312             } finally {
    313                 IoUtils.closeQuietly(dis);
    314                 IoUtils.closeQuietly(dos);
    315                 IoUtils.closeQuietly(socket);
    316             }
    317 
    318             return true;
    319         }
    320     }
    321 }
    322