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