1/*
2 * Copyright (C) 2014 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.Manifest;
20import android.app.ActivityManager;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.SystemProperties;
27import android.os.UserHandle;
28import android.service.persistentdata.IPersistentDataBlockService;
29import android.util.Slog;
30
31import com.android.internal.R;
32
33import libcore.io.IoUtils;
34
35import java.io.DataInputStream;
36import java.io.DataOutputStream;
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.nio.ByteBuffer;
43import java.nio.channels.FileChannel;
44import java.security.MessageDigest;
45import java.security.NoSuchAlgorithmException;
46import java.util.Arrays;
47
48/**
49 * Service for reading and writing blocks to a persistent partition.
50 * This data will live across factory resets not initiated via the Settings UI.
51 * When a device is factory reset through Settings this data is wiped.
52 *
53 * Allows writing one block at a time. Namely, each time
54 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
55 * is called, it will overwite the data that was previously written on the block.
56 *
57 * Clients can query the size of the currently written block via
58 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
59 *
60 * Clients can any number of bytes from the currently written block up to its total size by invoking
61 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
62 */
63public class PersistentDataBlockService extends SystemService {
64    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
65
66    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
67    private static final int HEADER_SIZE = 8;
68    // Magic number to mark block device as adhering to the format consumed by this service
69    private static final int PARTITION_TYPE_MARKER = 0x19901873;
70    // Limit to 100k as blocks larger than this might cause strain on Binder.
71    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
72    public static final int DIGEST_SIZE_BYTES = 32;
73
74    private final Context mContext;
75    private final String mDataBlockFile;
76    private final Object mLock = new Object();
77
78    private int mAllowedUid = -1;
79    private long mBlockDeviceSize;
80
81    public PersistentDataBlockService(Context context) {
82        super(context);
83        mContext = context;
84        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
85        mBlockDeviceSize = -1; // Load lazily
86        mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
87    }
88
89    private int getAllowedUid(int userHandle) {
90        String allowedPackage = mContext.getResources()
91                .getString(R.string.config_persistentDataPackageName);
92        PackageManager pm = mContext.getPackageManager();
93        int allowedUid = -1;
94        try {
95            allowedUid = pm.getPackageUid(allowedPackage, userHandle);
96        } catch (PackageManager.NameNotFoundException e) {
97            // not expected
98            Slog.e(TAG, "not able to find package " + allowedPackage, e);
99        }
100        return allowedUid;
101    }
102
103    @Override
104    public void onStart() {
105        enforceChecksumValidity();
106        formatIfOemUnlockEnabled();
107        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
108    }
109
110    private void formatIfOemUnlockEnabled() {
111        if (doGetOemUnlockEnabled()) {
112            synchronized (mLock) {
113                formatPartitionLocked(true);
114            }
115        }
116    }
117
118    private void enforceOemUnlockPermission() {
119        mContext.enforceCallingOrSelfPermission(
120                Manifest.permission.OEM_UNLOCK_STATE,
121                "Can't access OEM unlock state");
122    }
123
124    private void enforceUid(int callingUid) {
125        if (callingUid != mAllowedUid) {
126            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
127        }
128    }
129
130    private void enforceIsOwner() {
131        if (!Binder.getCallingUserHandle().isOwner()) {
132            throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
133        }
134    }
135
136    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
137        // skip over checksum
138        inputStream.skipBytes(DIGEST_SIZE_BYTES);
139
140        int totalDataSize;
141        int blockId = inputStream.readInt();
142        if (blockId == PARTITION_TYPE_MARKER) {
143            totalDataSize = inputStream.readInt();
144        } else {
145            totalDataSize = 0;
146        }
147        return totalDataSize;
148    }
149
150    private long getBlockDeviceSize() {
151        synchronized (mLock) {
152            if (mBlockDeviceSize == -1) {
153                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
154            }
155        }
156
157        return mBlockDeviceSize;
158    }
159
160    private boolean enforceChecksumValidity() {
161        byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
162
163        synchronized (mLock) {
164            byte[] digest = computeDigestLocked(storedDigest);
165            if (digest == null || !Arrays.equals(storedDigest, digest)) {
166                Slog.i(TAG, "Formatting FRP partition...");
167                formatPartitionLocked(false);
168                return false;
169            }
170        }
171
172        return true;
173    }
174
175    private boolean computeAndWriteDigestLocked() {
176        byte[] digest = computeDigestLocked(null);
177        if (digest != null) {
178            DataOutputStream outputStream;
179            try {
180                outputStream = new DataOutputStream(
181                        new FileOutputStream(new File(mDataBlockFile)));
182            } catch (FileNotFoundException e) {
183                Slog.e(TAG, "partition not available?", e);
184                return false;
185            }
186
187            try {
188                outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
189                outputStream.flush();
190            } catch (IOException e) {
191                Slog.e(TAG, "failed to write block checksum", e);
192                return false;
193            } finally {
194                IoUtils.closeQuietly(outputStream);
195            }
196            return true;
197        } else {
198            return false;
199        }
200    }
201
202    private byte[] computeDigestLocked(byte[] storedDigest) {
203        DataInputStream inputStream;
204        try {
205            inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
206        } catch (FileNotFoundException e) {
207            Slog.e(TAG, "partition not available?", e);
208            return null;
209        }
210
211        MessageDigest md;
212        try {
213            md = MessageDigest.getInstance("SHA-256");
214        } catch (NoSuchAlgorithmException e) {
215            // won't ever happen -- every implementation is required to support SHA-256
216            Slog.e(TAG, "SHA-256 not supported?", e);
217            IoUtils.closeQuietly(inputStream);
218            return null;
219        }
220
221        try {
222            if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
223                inputStream.read(storedDigest);
224            } else {
225                inputStream.skipBytes(DIGEST_SIZE_BYTES);
226            }
227
228            int read;
229            byte[] data = new byte[1024];
230            md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
231            while ((read = inputStream.read(data)) != -1) {
232                md.update(data, 0, read);
233            }
234        } catch (IOException e) {
235            Slog.e(TAG, "failed to read partition", e);
236            return null;
237        } finally {
238            IoUtils.closeQuietly(inputStream);
239        }
240
241        return md.digest();
242    }
243
244    private void formatPartitionLocked(boolean setOemUnlockEnabled) {
245        DataOutputStream outputStream;
246        try {
247            outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
248        } catch (FileNotFoundException e) {
249            Slog.e(TAG, "partition not available?", e);
250            return;
251        }
252
253        byte[] data = new byte[DIGEST_SIZE_BYTES];
254        try {
255            outputStream.write(data, 0, DIGEST_SIZE_BYTES);
256            outputStream.writeInt(PARTITION_TYPE_MARKER);
257            outputStream.writeInt(0); // data size
258            outputStream.flush();
259        } catch (IOException e) {
260            Slog.e(TAG, "failed to format block", e);
261            return;
262        } finally {
263            IoUtils.closeQuietly(outputStream);
264        }
265
266        doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
267        computeAndWriteDigestLocked();
268    }
269
270    private void doSetOemUnlockEnabledLocked(boolean enabled) {
271        FileOutputStream outputStream;
272        try {
273            outputStream = new FileOutputStream(new File(mDataBlockFile));
274        } catch (FileNotFoundException e) {
275            Slog.e(TAG, "partition not available", e);
276            return;
277        }
278
279        try {
280            FileChannel channel = outputStream.getChannel();
281
282            channel.position(getBlockDeviceSize() - 1);
283
284            ByteBuffer data = ByteBuffer.allocate(1);
285            data.put(enabled ? (byte) 1 : (byte) 0);
286            data.flip();
287            channel.write(data);
288            outputStream.flush();
289        } catch (IOException e) {
290            Slog.e(TAG, "unable to access persistent partition", e);
291            return;
292        } finally {
293            IoUtils.closeQuietly(outputStream);
294        }
295    }
296
297    private boolean doGetOemUnlockEnabled() {
298        DataInputStream inputStream;
299        try {
300            inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
301        } catch (FileNotFoundException e) {
302            Slog.e(TAG, "partition not available");
303            return false;
304        }
305
306        try {
307            synchronized (mLock) {
308                inputStream.skip(getBlockDeviceSize() - 1);
309                return inputStream.readByte() != 0;
310            }
311        } catch (IOException e) {
312            Slog.e(TAG, "unable to access persistent partition", e);
313            return false;
314        } finally {
315            IoUtils.closeQuietly(inputStream);
316        }
317    }
318
319    private native long nativeGetBlockDeviceSize(String path);
320    private native int nativeWipe(String path);
321
322    private final IBinder mService = new IPersistentDataBlockService.Stub() {
323        @Override
324        public int write(byte[] data) throws RemoteException {
325            enforceUid(Binder.getCallingUid());
326
327            // Need to ensure we don't write over the last byte
328            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
329            if (data.length > maxBlockSize) {
330                // partition is ~500k so shouldn't be a problem to downcast
331                return (int) -maxBlockSize;
332            }
333
334            DataOutputStream outputStream;
335            try {
336                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
337            } catch (FileNotFoundException e) {
338                Slog.e(TAG, "partition not available?", e);
339                return -1;
340            }
341
342            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
343            headerAndData.putInt(PARTITION_TYPE_MARKER);
344            headerAndData.putInt(data.length);
345            headerAndData.put(data);
346
347            synchronized (mLock) {
348                try {
349                    byte[] checksum = new byte[DIGEST_SIZE_BYTES];
350                    outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
351                    outputStream.write(headerAndData.array());
352                    outputStream.flush();
353                } catch (IOException e) {
354                    Slog.e(TAG, "failed writing to the persistent data block", e);
355                    return -1;
356                } finally {
357                    IoUtils.closeQuietly(outputStream);
358                }
359
360                if (computeAndWriteDigestLocked()) {
361                    return data.length;
362                } else {
363                    return -1;
364                }
365            }
366        }
367
368        @Override
369        public byte[] read() {
370            enforceUid(Binder.getCallingUid());
371            if (!enforceChecksumValidity()) {
372                return new byte[0];
373            }
374
375            DataInputStream inputStream;
376            try {
377                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
378            } catch (FileNotFoundException e) {
379                Slog.e(TAG, "partition not available?", e);
380                return null;
381            }
382
383            try {
384                synchronized (mLock) {
385                    int totalDataSize = getTotalDataSizeLocked(inputStream);
386
387                    if (totalDataSize == 0) {
388                        return new byte[0];
389                    }
390
391                    byte[] data = new byte[totalDataSize];
392                    int read = inputStream.read(data, 0, totalDataSize);
393                    if (read < totalDataSize) {
394                        // something went wrong, not returning potentially corrupt data
395                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
396                                read + "/" + totalDataSize);
397                        return null;
398                    }
399                    return data;
400                }
401            } catch (IOException e) {
402                Slog.e(TAG, "failed to read data", e);
403                return null;
404            } finally {
405                try {
406                    inputStream.close();
407                } catch (IOException e) {
408                    Slog.e(TAG, "failed to close OutputStream");
409                }
410            }
411        }
412
413        @Override
414        public void wipe() {
415            enforceOemUnlockPermission();
416
417            synchronized (mLock) {
418                int ret = nativeWipe(mDataBlockFile);
419
420                if (ret < 0) {
421                    Slog.e(TAG, "failed to wipe persistent partition");
422                }
423            }
424        }
425
426        @Override
427        public void setOemUnlockEnabled(boolean enabled) {
428            // do not allow monkey to flip the flag
429            if (ActivityManager.isUserAMonkey()) {
430                return;
431            }
432            enforceOemUnlockPermission();
433            enforceIsOwner();
434
435            synchronized (mLock) {
436                doSetOemUnlockEnabledLocked(enabled);
437                computeAndWriteDigestLocked();
438            }
439        }
440
441        @Override
442        public boolean getOemUnlockEnabled() {
443            enforceOemUnlockPermission();
444            return doGetOemUnlockEnabled();
445        }
446
447        @Override
448        public int getDataBlockSize() {
449            if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
450                    != PackageManager.PERMISSION_GRANTED) {
451                enforceUid(Binder.getCallingUid());
452            }
453
454            DataInputStream inputStream;
455            try {
456                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
457            } catch (FileNotFoundException e) {
458                Slog.e(TAG, "partition not available");
459                return 0;
460            }
461
462            try {
463                synchronized (mLock) {
464                    return getTotalDataSizeLocked(inputStream);
465                }
466            } catch (IOException e) {
467                Slog.e(TAG, "error reading data block size");
468                return 0;
469            } finally {
470                IoUtils.closeQuietly(inputStream);
471            }
472        }
473
474        @Override
475        public long getMaximumDataBlockSize() {
476            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
477            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
478        }
479    };
480}
481