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