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;
44
45/**
46 * Service for reading and writing blocks to a persistent partition.
47 * This data will live across factory resets not initiated via the Settings UI.
48 * When a device is factory reset through Settings this data is wiped.
49 *
50 * Allows writing one block at a time. Namely, each time
51 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
52 * is called, it will overwite the data that was previously written on the block.
53 *
54 * Clients can query the size of the currently written block via
55 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
56 *
57 * Clients can any number of bytes from the currently written block up to its total size by invoking
58 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
59 */
60public class PersistentDataBlockService extends SystemService {
61    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
62
63    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
64    private static final int HEADER_SIZE = 8;
65    // Magic number to mark block device as adhering to the format consumed by this service
66    private static final int PARTITION_TYPE_MARKER = 0x1990;
67    // Limit to 100k as blocks larger than this might cause strain on Binder.
68    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
69    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
70
71    private final Context mContext;
72    private final String mDataBlockFile;
73    private final Object mLock = new Object();
74
75    private int mAllowedAppId = -1;
76    /*
77     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
78     * block operations.
79     */
80    private final Object mOemLock = new Object();
81
82    private long mBlockDeviceSize;
83
84    public PersistentDataBlockService(Context context) {
85        super(context);
86        mContext = context;
87        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
88        mBlockDeviceSize = -1; // Load lazily
89        mAllowedAppId = getAllowedAppId(UserHandle.USER_OWNER);
90    }
91
92
93    private int getAllowedAppId(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 UserHandle.getAppId(allowedUid);
105    }
106
107    @Override
108    public void onStart() {
109        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
110    }
111
112    private void enforceOemUnlockPermission() {
113        mContext.enforceCallingOrSelfPermission(
114                Manifest.permission.OEM_UNLOCK_STATE,
115                "Can't access OEM unlock state");
116    }
117
118    private void enforceUid(int callingUid) {
119        if (UserHandle.getAppId(callingUid) != mAllowedAppId) {
120            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
121        }
122    }
123
124    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
125        int totalDataSize;
126        int blockId = inputStream.readInt();
127        if (blockId == PARTITION_TYPE_MARKER) {
128            totalDataSize = inputStream.readInt();
129        } else {
130            totalDataSize = 0;
131        }
132        return totalDataSize;
133    }
134
135    private long getBlockDeviceSize() {
136        synchronized (mLock) {
137            if (mBlockDeviceSize == -1) {
138                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
139            }
140        }
141
142        return mBlockDeviceSize;
143    }
144
145    private native long nativeGetBlockDeviceSize(String path);
146    private native int nativeWipe(String path);
147
148    private final IBinder mService = new IPersistentDataBlockService.Stub() {
149        @Override
150        public int write(byte[] data) throws RemoteException {
151            enforceUid(Binder.getCallingUid());
152
153            // Need to ensure we don't write over the last byte
154            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
155            if (data.length > maxBlockSize) {
156                // partition is ~500k so shouldn't be a problem to downcast
157                return (int) -maxBlockSize;
158            }
159
160            DataOutputStream outputStream;
161            try {
162                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
163            } catch (FileNotFoundException e) {
164                Slog.e(TAG, "partition not available?", e);
165                return -1;
166            }
167
168            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
169            headerAndData.putInt(PARTITION_TYPE_MARKER);
170            headerAndData.putInt(data.length);
171            headerAndData.put(data);
172
173            try {
174                synchronized (mLock) {
175                    outputStream.write(headerAndData.array());
176                    return data.length;
177                }
178            } catch (IOException e) {
179                Slog.e(TAG, "failed writing to the persistent data block", e);
180                return -1;
181            } finally {
182                try {
183                    outputStream.close();
184                } catch (IOException e) {
185                    Slog.e(TAG, "failed closing output stream", e);
186                }
187            }
188        }
189
190        @Override
191        public byte[] read() {
192            enforceUid(Binder.getCallingUid());
193
194            DataInputStream inputStream;
195            try {
196                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
197            } catch (FileNotFoundException e) {
198                Slog.e(TAG, "partition not available?", e);
199                return null;
200            }
201
202            try {
203                synchronized (mLock) {
204                    int totalDataSize = getTotalDataSizeLocked(inputStream);
205
206                    if (totalDataSize == 0) {
207                        return new byte[0];
208                    }
209
210                    byte[] data = new byte[totalDataSize];
211                    int read = inputStream.read(data, 0, totalDataSize);
212                    if (read < totalDataSize) {
213                        // something went wrong, not returning potentially corrupt data
214                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
215                                read + "/" + totalDataSize);
216                        return null;
217                    }
218                    return data;
219                }
220            } catch (IOException e) {
221                Slog.e(TAG, "failed to read data", e);
222                return null;
223            } finally {
224                try {
225                    inputStream.close();
226                } catch (IOException e) {
227                    Slog.e(TAG, "failed to close OutputStream");
228                }
229            }
230        }
231
232        @Override
233        public void wipe() {
234            enforceOemUnlockPermission();
235
236            synchronized (mLock) {
237                int ret = nativeWipe(mDataBlockFile);
238
239                if (ret < 0) {
240                    Slog.e(TAG, "failed to wipe persistent partition");
241                }
242            }
243        }
244
245        @Override
246        public void setOemUnlockEnabled(boolean enabled) {
247            // do not allow monkey to flip the flag
248            if (ActivityManager.isUserAMonkey()) {
249                return;
250            }
251            enforceOemUnlockPermission();
252            FileOutputStream outputStream;
253            try {
254                outputStream = new FileOutputStream(new File(mDataBlockFile));
255            } catch (FileNotFoundException e) {
256                Slog.e(TAG, "parition not available", e);
257                return;
258            }
259
260            try {
261                FileChannel channel = outputStream.getChannel();
262
263                channel.position(getBlockDeviceSize() - 1);
264
265                ByteBuffer data = ByteBuffer.allocate(1);
266                data.put(enabled ? (byte) 1 : (byte) 0);
267                data.flip();
268
269                synchronized (mOemLock) {
270                    channel.write(data);
271                }
272            } catch (IOException e) {
273                Slog.e(TAG, "unable to access persistent partition", e);
274            } finally {
275                IoUtils.closeQuietly(outputStream);
276            }
277        }
278
279        @Override
280        public boolean getOemUnlockEnabled() {
281            enforceOemUnlockPermission();
282            DataInputStream inputStream;
283            try {
284                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
285            } catch (FileNotFoundException e) {
286                Slog.e(TAG, "partition not available");
287                return false;
288            }
289
290            try {
291                inputStream.skip(getBlockDeviceSize() - 1);
292                synchronized (mOemLock) {
293                    return inputStream.readByte() != 0;
294                }
295            } catch (IOException e) {
296                Slog.e(TAG, "unable to access persistent partition", e);
297                return false;
298            } finally {
299                IoUtils.closeQuietly(inputStream);
300            }
301        }
302
303        @Override
304        public int getDataBlockSize() {
305            enforceUid(Binder.getCallingUid());
306
307            DataInputStream inputStream;
308            try {
309                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
310            } catch (FileNotFoundException e) {
311                Slog.e(TAG, "partition not available");
312                return 0;
313            }
314
315            try {
316                synchronized (mLock) {
317                    return getTotalDataSizeLocked(inputStream);
318                }
319            } catch (IOException e) {
320                Slog.e(TAG, "error reading data block size");
321                return 0;
322            } finally {
323                IoUtils.closeQuietly(inputStream);
324            }
325        }
326
327        @Override
328        public long getMaximumDataBlockSize() {
329            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
330            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
331        }
332
333    };
334}
335