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