blob: 2f83be1e0370ad60b62e0c97aa565a517472b57b [file] [log] [blame]
Sudheer Shankad2da0672019-12-20 15:51:20 -08001/*
2 * Copyright 2020 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 */
16package com.android.server.blob;
17
Sudheer Shankaab1d4162020-01-07 10:37:50 -080018import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR;
Sudheer Shankab2a17b72020-05-28 01:14:10 -070019import static android.app.blob.XmlTags.ATTR_CREATION_TIME_MS;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080020import static android.app.blob.XmlTags.ATTR_ID;
21import static android.app.blob.XmlTags.ATTR_PACKAGE;
22import static android.app.blob.XmlTags.ATTR_UID;
23import static android.app.blob.XmlTags.TAG_ACCESS_MODE;
24import static android.app.blob.XmlTags.TAG_BLOB_HANDLE;
Sudheer Shankad4ea5e12020-02-04 16:42:17 -080025import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080026import static android.system.OsConstants.O_CREAT;
Sudheer Shankadb483fa2020-01-22 12:31:33 -080027import static android.system.OsConstants.O_RDONLY;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080028import static android.system.OsConstants.O_RDWR;
29import static android.system.OsConstants.SEEK_SET;
Sudheer Shanka75351542020-06-05 22:42:56 -070030import static android.text.format.Formatter.FLAG_IEC_UNITS;
31import static android.text.format.Formatter.formatFileSize;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080032
33import static com.android.server.blob.BlobStoreConfig.TAG;
Sudheer Shankab2a17b72020-05-28 01:14:10 -070034import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME;
Sudheer Shanka07717c92020-06-23 14:06:47 -070035import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages;
Sudheer Shankab2a17b72020-05-28 01:14:10 -070036import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080037
Sudheer Shankad2da0672019-12-20 15:51:20 -080038import android.annotation.BytesLong;
39import android.annotation.NonNull;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080040import android.annotation.Nullable;
41import android.app.blob.BlobHandle;
Sudheer Shankad2da0672019-12-20 15:51:20 -080042import android.app.blob.IBlobCommitCallback;
43import android.app.blob.IBlobStoreSession;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080044import android.content.Context;
45import android.os.Binder;
46import android.os.FileUtils;
Sudheer Shanka07717c92020-06-23 14:06:47 -070047import android.os.LimitExceededException;
Sudheer Shankad2da0672019-12-20 15:51:20 -080048import android.os.ParcelFileDescriptor;
Sudheer Shanka07717c92020-06-23 14:06:47 -070049import android.os.ParcelableException;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080050import android.os.RemoteException;
51import android.os.RevocableFileDescriptor;
Sudheer Shankad4ea5e12020-02-04 16:42:17 -080052import android.os.Trace;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080053import android.os.storage.StorageManager;
54import android.system.ErrnoException;
55import android.system.Os;
56import android.util.ExceptionUtils;
57import android.util.Slog;
58
59import com.android.internal.annotations.GuardedBy;
Sudheer Shankabda89c12020-01-30 15:19:24 -080060import com.android.internal.annotations.VisibleForTesting;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070061import com.android.internal.util.FrameworkStatsLog;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080062import com.android.internal.util.IndentingPrintWriter;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080063import com.android.internal.util.Preconditions;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080064import com.android.internal.util.XmlUtils;
Sudheer Shanka5caeed52020-02-01 12:54:33 -080065import com.android.server.blob.BlobStoreManagerService.DumpArgs;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080066import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener;
67
Sudheer Shanka300d9cb2020-06-04 23:33:13 -070068import libcore.io.IoUtils;
69
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080070import org.xmlpull.v1.XmlPullParser;
71import org.xmlpull.v1.XmlPullParserException;
72import org.xmlpull.v1.XmlSerializer;
73
Sudheer Shankaab1d4162020-01-07 10:37:50 -080074import java.io.File;
75import java.io.FileDescriptor;
76import java.io.IOException;
77import java.security.NoSuchAlgorithmException;
78import java.util.ArrayList;
79import java.util.Arrays;
Sudheer Shanka4824e3d2020-01-29 23:50:24 -080080import java.util.Objects;
Sudheer Shankad2da0672019-12-20 15:51:20 -080081
Sudheer Shanka07717c92020-06-23 14:06:47 -070082/**
83 * Class to represent the state corresponding to an ongoing
84 * {@link android.app.blob.BlobStoreManager.Session}
85 */
Sudheer Shankabda89c12020-01-30 15:19:24 -080086@VisibleForTesting
87class BlobStoreSession extends IBlobStoreSession.Stub {
Sudheer Shankad2da0672019-12-20 15:51:20 -080088
Sudheer Shankaab1d4162020-01-07 10:37:50 -080089 static final int STATE_OPENED = 1;
90 static final int STATE_CLOSED = 0;
91 static final int STATE_ABANDONED = 2;
92 static final int STATE_COMMITTED = 3;
93 static final int STATE_VERIFIED_VALID = 4;
94 static final int STATE_VERIFIED_INVALID = 5;
95
96 private final Object mSessionLock = new Object();
97
98 private final Context mContext;
99 private final SessionStateChangeListener mListener;
100
Sudheer Shankabda89c12020-01-30 15:19:24 -0800101 private final BlobHandle mBlobHandle;
102 private final long mSessionId;
103 private final int mOwnerUid;
104 private final String mOwnerPackageName;
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700105 private final long mCreationTimeMs;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800106
107 // Do not access this directly, instead use getSessionFile().
108 private File mSessionFile;
109
110 @GuardedBy("mRevocableFds")
Sudheer Shanka36b25a12020-06-13 22:27:28 -0700111 private final ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800112
Sudheer Shankab76f7662020-02-27 15:17:43 -0800113 // This will be accessed from only one thread at any point of time, so no need to grab
114 // a lock for this.
115 private byte[] mDataDigest;
116
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800117 @GuardedBy("mSessionLock")
118 private int mState = STATE_CLOSED;
119
120 @GuardedBy("mSessionLock")
121 private final BlobAccessMode mBlobAccessMode = new BlobAccessMode();
122
123 @GuardedBy("mSessionLock")
124 private IBlobCommitCallback mBlobCommitCallback;
125
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700126 private BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle,
127 int ownerUid, String ownerPackageName, long creationTimeMs,
128 SessionStateChangeListener listener) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800129 this.mContext = context;
Sudheer Shankabda89c12020-01-30 15:19:24 -0800130 this.mBlobHandle = blobHandle;
131 this.mSessionId = sessionId;
132 this.mOwnerUid = ownerUid;
133 this.mOwnerPackageName = ownerPackageName;
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700134 this.mCreationTimeMs = creationTimeMs;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800135 this.mListener = listener;
136 }
137
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700138 BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle,
139 int ownerUid, String ownerPackageName, SessionStateChangeListener listener) {
140 this(context, sessionId, blobHandle, ownerUid, ownerPackageName,
141 System.currentTimeMillis(), listener);
142 }
143
Sudheer Shankabda89c12020-01-30 15:19:24 -0800144 public BlobHandle getBlobHandle() {
145 return mBlobHandle;
146 }
147
148 public long getSessionId() {
149 return mSessionId;
150 }
151
152 public int getOwnerUid() {
153 return mOwnerUid;
154 }
155
156 public String getOwnerPackageName() {
157 return mOwnerPackageName;
158 }
159
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800160 boolean hasAccess(int callingUid, String callingPackageName) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800161 return mOwnerUid == callingUid && mOwnerPackageName.equals(callingPackageName);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800162 }
163
164 void open() {
165 synchronized (mSessionLock) {
166 if (isFinalized()) {
167 throw new IllegalStateException("Not allowed to open session with state: "
168 + stateToString(mState));
169 }
170 mState = STATE_OPENED;
171 }
172 }
173
174 int getState() {
175 synchronized (mSessionLock) {
176 return mState;
177 }
178 }
179
180 void sendCommitCallbackResult(int result) {
181 synchronized (mSessionLock) {
182 try {
183 mBlobCommitCallback.onResult(result);
184 } catch (RemoteException e) {
185 Slog.d(TAG, "Error sending the callback result", e);
186 }
187 mBlobCommitCallback = null;
188 }
189 }
190
191 BlobAccessMode getBlobAccessMode() {
192 synchronized (mSessionLock) {
193 return mBlobAccessMode;
194 }
195 }
196
197 boolean isFinalized() {
198 synchronized (mSessionLock) {
199 return mState == STATE_COMMITTED || mState == STATE_ABANDONED;
200 }
201 }
202
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700203 boolean isExpired() {
204 final long lastModifiedTimeMs = getSessionFile().lastModified();
205 return hasSessionExpired(lastModifiedTimeMs == 0
206 ? mCreationTimeMs : lastModifiedTimeMs);
207 }
208
Sudheer Shankad2da0672019-12-20 15:51:20 -0800209 @Override
210 @NonNull
211 public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
212 @BytesLong long lengthBytes) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -0800213 Preconditions.checkArgumentNonnegative(offsetBytes, "offsetBytes must not be negative");
214
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800215 assertCallerIsOwner();
216 synchronized (mSessionLock) {
217 if (mState != STATE_OPENED) {
218 throw new IllegalStateException("Not allowed to write in state: "
219 + stateToString(mState));
220 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700221 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800222
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700223 FileDescriptor fd = null;
224 try {
225 fd = openWriteInternal(offsetBytes, lengthBytes);
226 final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd);
227 synchronized (mSessionLock) {
228 if (mState != STATE_OPENED) {
229 IoUtils.closeQuietly(fd);
230 throw new IllegalStateException("Not allowed to write in state: "
231 + stateToString(mState));
232 }
233 trackRevocableFdLocked(revocableFd);
234 return revocableFd.getRevocableFileDescriptor();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800235 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700236 } catch (IOException e) {
237 IoUtils.closeQuietly(fd);
238 throw ExceptionUtils.wrap(e);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800239 }
240 }
241
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800242 @NonNull
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700243 private FileDescriptor openWriteInternal(@BytesLong long offsetBytes,
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800244 @BytesLong long lengthBytes) throws IOException {
245 // TODO: Add limit on active open sessions/writes/reads
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800246 try {
247 final File sessionFile = getSessionFile();
248 if (sessionFile == null) {
249 throw new IllegalStateException("Couldn't get the file for this session");
250 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700251 final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800252 if (offsetBytes > 0) {
253 final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET);
254 if (curOffset != offsetBytes) {
255 throw new IllegalStateException("Failed to seek " + offsetBytes
256 + "; curOffset=" + offsetBytes);
257 }
258 }
259 if (lengthBytes > 0) {
260 mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes);
261 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700262 return fd;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800263 } catch (ErrnoException e) {
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700264 throw e.rethrowAsIOException();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800265 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800266 }
267
268 @Override
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800269 @NonNull
270 public ParcelFileDescriptor openRead() {
271 assertCallerIsOwner();
272 synchronized (mSessionLock) {
273 if (mState != STATE_OPENED) {
274 throw new IllegalStateException("Not allowed to read in state: "
275 + stateToString(mState));
276 }
Sudheer Shankad6f47942020-06-18 16:46:36 -0700277 if (!BlobStoreConfig.shouldUseRevocableFdForReads()) {
278 try {
279 return new ParcelFileDescriptor(openReadInternal());
280 } catch (IOException e) {
281 throw ExceptionUtils.wrap(e);
282 }
283 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700284 }
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800285
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700286 FileDescriptor fd = null;
287 try {
288 fd = openReadInternal();
289 final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd);
290 synchronized (mSessionLock) {
291 if (mState != STATE_OPENED) {
292 IoUtils.closeQuietly(fd);
293 throw new IllegalStateException("Not allowed to read in state: "
294 + stateToString(mState));
295 }
296 trackRevocableFdLocked(revocableFd);
297 return revocableFd.getRevocableFileDescriptor();
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800298 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700299 } catch (IOException e) {
300 IoUtils.closeQuietly(fd);
301 throw ExceptionUtils.wrap(e);
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800302 }
303 }
304
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800305 @NonNull
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700306 private FileDescriptor openReadInternal() throws IOException {
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800307 try {
308 final File sessionFile = getSessionFile();
309 if (sessionFile == null) {
310 throw new IllegalStateException("Couldn't get the file for this session");
311 }
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700312 final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0);
313 return fd;
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800314 } catch (ErrnoException e) {
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700315 throw e.rethrowAsIOException();
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800316 }
Sudheer Shankadb483fa2020-01-22 12:31:33 -0800317 }
318
319 @Override
Sudheer Shankad2da0672019-12-20 15:51:20 -0800320 @BytesLong
321 public long getSize() {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800322 return getSessionFile().length();
Sudheer Shankad2da0672019-12-20 15:51:20 -0800323 }
324
325 @Override
326 public void allowPackageAccess(@NonNull String packageName,
327 @NonNull byte[] certificate) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800328 assertCallerIsOwner();
Sudheer Shanka4824e3d2020-01-29 23:50:24 -0800329 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800330 synchronized (mSessionLock) {
331 if (mState != STATE_OPENED) {
332 throw new IllegalStateException("Not allowed to change access type in state: "
333 + stateToString(mState));
334 }
Sudheer Shanka07717c92020-06-23 14:06:47 -0700335 if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) {
336 throw new ParcelableException(new LimitExceededException(
337 "Too many packages permitted to access the blob: "
338 + mBlobAccessMode.getNumWhitelistedPackages()));
339 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800340 mBlobAccessMode.allowPackageAccess(packageName, certificate);
341 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800342 }
343
344 @Override
345 public void allowSameSignatureAccess() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800346 assertCallerIsOwner();
347 synchronized (mSessionLock) {
348 if (mState != STATE_OPENED) {
349 throw new IllegalStateException("Not allowed to change access type in state: "
350 + stateToString(mState));
351 }
352 mBlobAccessMode.allowSameSignatureAccess();
353 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800354 }
355
356 @Override
357 public void allowPublicAccess() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800358 assertCallerIsOwner();
359 synchronized (mSessionLock) {
360 if (mState != STATE_OPENED) {
361 throw new IllegalStateException("Not allowed to change access type in state: "
362 + stateToString(mState));
363 }
364 mBlobAccessMode.allowPublicAccess();
365 }
366 }
367
368 @Override
369 public boolean isPackageAccessAllowed(@NonNull String packageName,
370 @NonNull byte[] certificate) {
371 assertCallerIsOwner();
Sudheer Shanka4824e3d2020-01-29 23:50:24 -0800372 Objects.requireNonNull(packageName, "packageName must not be null");
373 Preconditions.checkByteArrayNotEmpty(certificate, "certificate");
374
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800375 synchronized (mSessionLock) {
376 if (mState != STATE_OPENED) {
377 throw new IllegalStateException("Not allowed to get access type in state: "
378 + stateToString(mState));
379 }
380 return mBlobAccessMode.isPackageAccessAllowed(packageName, certificate);
381 }
382 }
383
384 @Override
385 public boolean isSameSignatureAccessAllowed() {
386 assertCallerIsOwner();
387 synchronized (mSessionLock) {
388 if (mState != STATE_OPENED) {
389 throw new IllegalStateException("Not allowed to get access type in state: "
390 + stateToString(mState));
391 }
392 return mBlobAccessMode.isSameSignatureAccessAllowed();
393 }
394 }
395
396 @Override
397 public boolean isPublicAccessAllowed() {
398 assertCallerIsOwner();
399 synchronized (mSessionLock) {
400 if (mState != STATE_OPENED) {
401 throw new IllegalStateException("Not allowed to get access type in state: "
402 + stateToString(mState));
403 }
404 return mBlobAccessMode.isPublicAccessAllowed();
405 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800406 }
407
408 @Override
409 public void close() {
Sudheer Shankab5239e42020-03-04 23:23:54 -0800410 closeSession(STATE_CLOSED, false /* sendCallback */);
Sudheer Shankad2da0672019-12-20 15:51:20 -0800411 }
412
413 @Override
414 public void abandon() {
Sudheer Shankab5239e42020-03-04 23:23:54 -0800415 closeSession(STATE_ABANDONED, true /* sendCallback */);
Sudheer Shankad2da0672019-12-20 15:51:20 -0800416 }
417
418 @Override
419 public void commit(IBlobCommitCallback callback) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800420 synchronized (mSessionLock) {
421 mBlobCommitCallback = callback;
422
Sudheer Shankab5239e42020-03-04 23:23:54 -0800423 closeSession(STATE_COMMITTED, true /* sendCallback */);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800424 }
425 }
426
Sudheer Shankab5239e42020-03-04 23:23:54 -0800427 private void closeSession(int state, boolean sendCallback) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800428 assertCallerIsOwner();
429 synchronized (mSessionLock) {
430 if (mState != STATE_OPENED) {
431 if (state == STATE_CLOSED) {
432 // Just trying to close the session which is already deleted or abandoned,
433 // ignore.
434 return;
435 } else {
436 throw new IllegalStateException("Not allowed to delete or abandon a session"
437 + " with state: " + stateToString(mState));
438 }
439 }
440
441 mState = state;
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700442 revokeAllFds();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800443
Sudheer Shankab5239e42020-03-04 23:23:54 -0800444 if (sendCallback) {
445 mListener.onStateChanged(this);
446 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800447 }
448 }
449
Sudheer Shankab76f7662020-02-27 15:17:43 -0800450 void computeDigest() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800451 try {
Sudheer Shankad4ea5e12020-02-04 16:42:17 -0800452 Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER,
453 "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length());
Sudheer Shankab76f7662020-02-27 15:17:43 -0800454 mDataDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800455 } catch (IOException | NoSuchAlgorithmException e) {
456 Slog.e(TAG, "Error computing the digest", e);
Sudheer Shankad4ea5e12020-02-04 16:42:17 -0800457 } finally {
458 Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800459 }
Sudheer Shankab76f7662020-02-27 15:17:43 -0800460 }
461
462 void verifyBlobData() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800463 synchronized (mSessionLock) {
Sudheer Shankab76f7662020-02-27 15:17:43 -0800464 if (mDataDigest != null && Arrays.equals(mDataDigest, mBlobHandle.digest)) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800465 mState = STATE_VERIFIED_VALID;
466 // Commit callback will be sent once the data is persisted.
467 } else {
Sudheer Shanka16806422020-06-05 15:36:37 -0700468 Slog.d(TAG, "Digest of the data ("
469 + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest))
470 + ") didn't match the given BlobHandle.digest ("
471 + BlobHandle.safeDigest(mBlobHandle.digest) + ")");
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800472 mState = STATE_VERIFIED_INVALID;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700473
474 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId,
475 getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800476 sendCommitCallbackResult(COMMIT_RESULT_ERROR);
477 }
478 mListener.onStateChanged(this);
479 }
480 }
481
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700482 void destroy() {
483 revokeAllFds();
484 getSessionFile().delete();
485 }
486
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700487 private void revokeAllFds() {
488 synchronized (mRevocableFds) {
489 for (int i = mRevocableFds.size() - 1; i >= 0; --i) {
490 mRevocableFds.get(i).revoke();
491 }
492 mRevocableFds.clear();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800493 }
494 }
495
496 @GuardedBy("mSessionLock")
Sudheer Shanka300d9cb2020-06-04 23:33:13 -0700497 private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800498 synchronized (mRevocableFds) {
499 mRevocableFds.add(revocableFd);
500 }
501 revocableFd.addOnCloseListener((e) -> {
502 synchronized (mRevocableFds) {
503 mRevocableFds.remove(revocableFd);
504 }
505 });
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800506 }
507
508 @Nullable
509 File getSessionFile() {
510 if (mSessionFile == null) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800511 mSessionFile = BlobStoreConfig.prepareBlobFile(mSessionId);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800512 }
513 return mSessionFile;
514 }
515
516 @NonNull
517 static String stateToString(int state) {
518 switch (state) {
519 case STATE_OPENED:
520 return "<opened>";
521 case STATE_CLOSED:
522 return "<closed>";
523 case STATE_ABANDONED:
524 return "<abandoned>";
525 case STATE_COMMITTED:
526 return "<committed>";
Sudheer Shankab5239e42020-03-04 23:23:54 -0800527 case STATE_VERIFIED_VALID:
528 return "<verified_valid>";
529 case STATE_VERIFIED_INVALID:
530 return "<verified_invalid>";
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800531 default:
532 Slog.wtf(TAG, "Unknown state: " + state);
533 return "<unknown>";
534 }
535 }
536
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800537 @Override
538 public String toString() {
539 return "BlobStoreSession {"
540 + "id:" + mSessionId
541 + ",handle:" + mBlobHandle
542 + ",uid:" + mOwnerUid
543 + ",pkg:" + mOwnerPackageName
544 + "}";
545 }
546
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800547 private void assertCallerIsOwner() {
548 final int callingUid = Binder.getCallingUid();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800549 if (callingUid != mOwnerUid) {
550 throw new SecurityException(mOwnerUid + " is not the session owner");
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800551 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800552 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800553
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800554 void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800555 synchronized (mSessionLock) {
556 fout.println("state: " + stateToString(mState));
Sudheer Shankabda89c12020-01-30 15:19:24 -0800557 fout.println("ownerUid: " + mOwnerUid);
558 fout.println("ownerPkg: " + mOwnerPackageName);
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700559 fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs));
Sudheer Shanka75351542020-06-05 22:42:56 -0700560 fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS));
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800561
562 fout.println("blobHandle:");
563 fout.increaseIndent();
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800564 mBlobHandle.dump(fout, dumpArgs.shouldDumpFull());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800565 fout.decreaseIndent();
566
567 fout.println("accessMode:");
568 fout.increaseIndent();
569 mBlobAccessMode.dump(fout);
570 fout.decreaseIndent();
571
572 fout.println("Open fds: #" + mRevocableFds.size());
573 }
574 }
575
576 void writeToXml(@NonNull XmlSerializer out) throws IOException {
577 synchronized (mSessionLock) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800578 XmlUtils.writeLongAttribute(out, ATTR_ID, mSessionId);
579 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, mOwnerPackageName);
580 XmlUtils.writeIntAttribute(out, ATTR_UID, mOwnerUid);
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700581 XmlUtils.writeLongAttribute(out, ATTR_CREATION_TIME_MS, mCreationTimeMs);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800582
583 out.startTag(null, TAG_BLOB_HANDLE);
Sudheer Shankabda89c12020-01-30 15:19:24 -0800584 mBlobHandle.writeToXml(out);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800585 out.endTag(null, TAG_BLOB_HANDLE);
586
587 out.startTag(null, TAG_ACCESS_MODE);
588 mBlobAccessMode.writeToXml(out);
589 out.endTag(null, TAG_ACCESS_MODE);
590 }
591 }
592
593 @Nullable
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800594 static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version,
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800595 @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener)
596 throws IOException, XmlPullParserException {
Sudheer Shanka34c24fb2020-02-18 14:50:40 -0800597 final long sessionId = XmlUtils.readLongAttribute(in, ATTR_ID);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800598 final String ownerPackageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
599 final int ownerUid = XmlUtils.readIntAttribute(in, ATTR_UID);
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700600 final long creationTimeMs = version >= XML_VERSION_ADD_SESSION_CREATION_TIME
601 ? XmlUtils.readLongAttribute(in, ATTR_CREATION_TIME_MS)
602 : System.currentTimeMillis();
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800603
604 final int depth = in.getDepth();
605 BlobHandle blobHandle = null;
606 BlobAccessMode blobAccessMode = null;
607 while (XmlUtils.nextElementWithin(in, depth)) {
608 if (TAG_BLOB_HANDLE.equals(in.getName())) {
609 blobHandle = BlobHandle.createFromXml(in);
610 } else if (TAG_ACCESS_MODE.equals(in.getName())) {
611 blobAccessMode = BlobAccessMode.createFromXml(in);
612 }
613 }
614
615 if (blobHandle == null) {
616 Slog.wtf(TAG, "blobHandle should be available");
617 return null;
618 }
619 if (blobAccessMode == null) {
620 Slog.wtf(TAG, "blobAccessMode should be available");
621 return null;
622 }
623
624 final BlobStoreSession blobStoreSession = new BlobStoreSession(context, sessionId,
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700625 blobHandle, ownerUid, ownerPackageName, creationTimeMs, stateChangeListener);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800626 blobStoreSession.mBlobAccessMode.allow(blobAccessMode);
627 return blobStoreSession;
628 }
Sudheer Shankad2da0672019-12-20 15:51:20 -0800629}