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.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