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