Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package com.android.server.blob; |
| 17 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 18 | import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 19 | import static android.app.blob.XmlTags.ATTR_CREATION_TIME_MS; |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 20 | import static android.app.blob.XmlTags.ATTR_ID; |
| 21 | import static android.app.blob.XmlTags.ATTR_PACKAGE; |
| 22 | import static android.app.blob.XmlTags.ATTR_UID; |
| 23 | import static android.app.blob.XmlTags.TAG_ACCESS_MODE; |
| 24 | import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 25 | import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 26 | import static android.system.OsConstants.O_CREAT; |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 27 | import static android.system.OsConstants.O_RDONLY; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 28 | import static android.system.OsConstants.O_RDWR; |
| 29 | import static android.system.OsConstants.SEEK_SET; |
Sudheer Shanka | 7535154 | 2020-06-05 22:42:56 -0700 | [diff] [blame] | 30 | import static android.text.format.Formatter.FLAG_IEC_UNITS; |
| 31 | import static android.text.format.Formatter.formatFileSize; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 32 | |
| 33 | import static com.android.server.blob.BlobStoreConfig.TAG; |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 34 | import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; |
| 35 | import static com.android.server.blob.BlobStoreConfig.hasSessionExpired; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 36 | |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 37 | import android.annotation.BytesLong; |
| 38 | import android.annotation.NonNull; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 39 | import android.annotation.Nullable; |
| 40 | import android.app.blob.BlobHandle; |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 41 | import android.app.blob.IBlobCommitCallback; |
| 42 | import android.app.blob.IBlobStoreSession; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 43 | import android.content.Context; |
| 44 | import android.os.Binder; |
| 45 | import android.os.FileUtils; |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 46 | import android.os.ParcelFileDescriptor; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 47 | import android.os.RemoteException; |
| 48 | import android.os.RevocableFileDescriptor; |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 49 | import android.os.Trace; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 50 | import android.os.storage.StorageManager; |
| 51 | import android.system.ErrnoException; |
| 52 | import android.system.Os; |
| 53 | import android.util.ExceptionUtils; |
| 54 | import android.util.Slog; |
| 55 | |
| 56 | import com.android.internal.annotations.GuardedBy; |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 57 | import com.android.internal.annotations.VisibleForTesting; |
Michael Wachenschwanz | 1a7f7ab | 2020-06-09 20:16:27 -0700 | [diff] [blame] | 58 | import com.android.internal.util.FrameworkStatsLog; |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 59 | import com.android.internal.util.IndentingPrintWriter; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 60 | import com.android.internal.util.Preconditions; |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 61 | import com.android.internal.util.XmlUtils; |
Sudheer Shanka | 5caeed5 | 2020-02-01 12:54:33 -0800 | [diff] [blame] | 62 | import com.android.server.blob.BlobStoreManagerService.DumpArgs; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 63 | import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; |
| 64 | |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 65 | import libcore.io.IoUtils; |
| 66 | |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 67 | import org.xmlpull.v1.XmlPullParser; |
| 68 | import org.xmlpull.v1.XmlPullParserException; |
| 69 | import org.xmlpull.v1.XmlSerializer; |
| 70 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 71 | import java.io.File; |
| 72 | import java.io.FileDescriptor; |
| 73 | import java.io.IOException; |
| 74 | import java.security.NoSuchAlgorithmException; |
| 75 | import java.util.ArrayList; |
| 76 | import java.util.Arrays; |
Sudheer Shanka | 4824e3d | 2020-01-29 23:50:24 -0800 | [diff] [blame] | 77 | import java.util.Objects; |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 78 | |
| 79 | /** TODO: add doc */ |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 80 | @VisibleForTesting |
| 81 | class BlobStoreSession extends IBlobStoreSession.Stub { |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 82 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 83 | static final int STATE_OPENED = 1; |
| 84 | static final int STATE_CLOSED = 0; |
| 85 | static final int STATE_ABANDONED = 2; |
| 86 | static final int STATE_COMMITTED = 3; |
| 87 | static final int STATE_VERIFIED_VALID = 4; |
| 88 | static final int STATE_VERIFIED_INVALID = 5; |
| 89 | |
| 90 | private final Object mSessionLock = new Object(); |
| 91 | |
| 92 | private final Context mContext; |
| 93 | private final SessionStateChangeListener mListener; |
| 94 | |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 95 | private final BlobHandle mBlobHandle; |
| 96 | private final long mSessionId; |
| 97 | private final int mOwnerUid; |
| 98 | private final String mOwnerPackageName; |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 99 | private final long mCreationTimeMs; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 100 | |
| 101 | // Do not access this directly, instead use getSessionFile(). |
| 102 | private File mSessionFile; |
| 103 | |
| 104 | @GuardedBy("mRevocableFds") |
Sudheer Shanka | 36b25a1 | 2020-06-13 22:27:28 -0700 | [diff] [blame] | 105 | private final ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>(); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 106 | |
Sudheer Shanka | b76f766 | 2020-02-27 15:17:43 -0800 | [diff] [blame] | 107 | // This will be accessed from only one thread at any point of time, so no need to grab |
| 108 | // a lock for this. |
| 109 | private byte[] mDataDigest; |
| 110 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 111 | @GuardedBy("mSessionLock") |
| 112 | private int mState = STATE_CLOSED; |
| 113 | |
| 114 | @GuardedBy("mSessionLock") |
| 115 | private final BlobAccessMode mBlobAccessMode = new BlobAccessMode(); |
| 116 | |
| 117 | @GuardedBy("mSessionLock") |
| 118 | private IBlobCommitCallback mBlobCommitCallback; |
| 119 | |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 120 | private BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, |
| 121 | int ownerUid, String ownerPackageName, long creationTimeMs, |
| 122 | SessionStateChangeListener listener) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 123 | this.mContext = context; |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 124 | this.mBlobHandle = blobHandle; |
| 125 | this.mSessionId = sessionId; |
| 126 | this.mOwnerUid = ownerUid; |
| 127 | this.mOwnerPackageName = ownerPackageName; |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 128 | this.mCreationTimeMs = creationTimeMs; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 129 | this.mListener = listener; |
| 130 | } |
| 131 | |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 132 | BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, |
| 133 | int ownerUid, String ownerPackageName, SessionStateChangeListener listener) { |
| 134 | this(context, sessionId, blobHandle, ownerUid, ownerPackageName, |
| 135 | System.currentTimeMillis(), listener); |
| 136 | } |
| 137 | |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 138 | public BlobHandle getBlobHandle() { |
| 139 | return mBlobHandle; |
| 140 | } |
| 141 | |
| 142 | public long getSessionId() { |
| 143 | return mSessionId; |
| 144 | } |
| 145 | |
| 146 | public int getOwnerUid() { |
| 147 | return mOwnerUid; |
| 148 | } |
| 149 | |
| 150 | public String getOwnerPackageName() { |
| 151 | return mOwnerPackageName; |
| 152 | } |
| 153 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 154 | boolean hasAccess(int callingUid, String callingPackageName) { |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 155 | return mOwnerUid == callingUid && mOwnerPackageName.equals(callingPackageName); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | void open() { |
| 159 | synchronized (mSessionLock) { |
| 160 | if (isFinalized()) { |
| 161 | throw new IllegalStateException("Not allowed to open session with state: " |
| 162 | + stateToString(mState)); |
| 163 | } |
| 164 | mState = STATE_OPENED; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | int getState() { |
| 169 | synchronized (mSessionLock) { |
| 170 | return mState; |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | void sendCommitCallbackResult(int result) { |
| 175 | synchronized (mSessionLock) { |
| 176 | try { |
| 177 | mBlobCommitCallback.onResult(result); |
| 178 | } catch (RemoteException e) { |
| 179 | Slog.d(TAG, "Error sending the callback result", e); |
| 180 | } |
| 181 | mBlobCommitCallback = null; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | BlobAccessMode getBlobAccessMode() { |
| 186 | synchronized (mSessionLock) { |
| 187 | return mBlobAccessMode; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | boolean isFinalized() { |
| 192 | synchronized (mSessionLock) { |
| 193 | return mState == STATE_COMMITTED || mState == STATE_ABANDONED; |
| 194 | } |
| 195 | } |
| 196 | |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 197 | boolean isExpired() { |
| 198 | final long lastModifiedTimeMs = getSessionFile().lastModified(); |
| 199 | return hasSessionExpired(lastModifiedTimeMs == 0 |
| 200 | ? mCreationTimeMs : lastModifiedTimeMs); |
| 201 | } |
| 202 | |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 203 | @Override |
| 204 | @NonNull |
| 205 | public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, |
| 206 | @BytesLong long lengthBytes) { |
Sudheer Shanka | 4824e3d | 2020-01-29 23:50:24 -0800 | [diff] [blame] | 207 | Preconditions.checkArgumentNonnegative(offsetBytes, "offsetBytes must not be negative"); |
| 208 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 209 | assertCallerIsOwner(); |
| 210 | synchronized (mSessionLock) { |
| 211 | if (mState != STATE_OPENED) { |
| 212 | throw new IllegalStateException("Not allowed to write in state: " |
| 213 | + stateToString(mState)); |
| 214 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 215 | } |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 216 | |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 217 | FileDescriptor fd = null; |
| 218 | try { |
| 219 | fd = openWriteInternal(offsetBytes, lengthBytes); |
| 220 | final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); |
| 221 | synchronized (mSessionLock) { |
| 222 | if (mState != STATE_OPENED) { |
| 223 | IoUtils.closeQuietly(fd); |
| 224 | throw new IllegalStateException("Not allowed to write in state: " |
| 225 | + stateToString(mState)); |
| 226 | } |
| 227 | trackRevocableFdLocked(revocableFd); |
| 228 | return revocableFd.getRevocableFileDescriptor(); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 229 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 230 | } catch (IOException e) { |
| 231 | IoUtils.closeQuietly(fd); |
| 232 | throw ExceptionUtils.wrap(e); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 233 | } |
| 234 | } |
| 235 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 236 | @NonNull |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 237 | private FileDescriptor openWriteInternal(@BytesLong long offsetBytes, |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 238 | @BytesLong long lengthBytes) throws IOException { |
| 239 | // TODO: Add limit on active open sessions/writes/reads |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 240 | try { |
| 241 | final File sessionFile = getSessionFile(); |
| 242 | if (sessionFile == null) { |
| 243 | throw new IllegalStateException("Couldn't get the file for this session"); |
| 244 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 245 | final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 246 | if (offsetBytes > 0) { |
| 247 | final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET); |
| 248 | if (curOffset != offsetBytes) { |
| 249 | throw new IllegalStateException("Failed to seek " + offsetBytes |
| 250 | + "; curOffset=" + offsetBytes); |
| 251 | } |
| 252 | } |
| 253 | if (lengthBytes > 0) { |
| 254 | mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes); |
| 255 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 256 | return fd; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 257 | } catch (ErrnoException e) { |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 258 | throw e.rethrowAsIOException(); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 259 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | @Override |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 263 | @NonNull |
| 264 | public ParcelFileDescriptor openRead() { |
| 265 | assertCallerIsOwner(); |
| 266 | synchronized (mSessionLock) { |
| 267 | if (mState != STATE_OPENED) { |
| 268 | throw new IllegalStateException("Not allowed to read in state: " |
| 269 | + stateToString(mState)); |
| 270 | } |
Sudheer Shanka | d6f4794 | 2020-06-18 16:46:36 -0700 | [diff] [blame] | 271 | if (!BlobStoreConfig.shouldUseRevocableFdForReads()) { |
| 272 | try { |
| 273 | return new ParcelFileDescriptor(openReadInternal()); |
| 274 | } catch (IOException e) { |
| 275 | throw ExceptionUtils.wrap(e); |
| 276 | } |
| 277 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 278 | } |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 279 | |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 280 | FileDescriptor fd = null; |
| 281 | try { |
| 282 | fd = openReadInternal(); |
| 283 | final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); |
| 284 | synchronized (mSessionLock) { |
| 285 | if (mState != STATE_OPENED) { |
| 286 | IoUtils.closeQuietly(fd); |
| 287 | throw new IllegalStateException("Not allowed to read in state: " |
| 288 | + stateToString(mState)); |
| 289 | } |
| 290 | trackRevocableFdLocked(revocableFd); |
| 291 | return revocableFd.getRevocableFileDescriptor(); |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 292 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 293 | } catch (IOException e) { |
| 294 | IoUtils.closeQuietly(fd); |
| 295 | throw ExceptionUtils.wrap(e); |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 296 | } |
| 297 | } |
| 298 | |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 299 | @NonNull |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 300 | private FileDescriptor openReadInternal() throws IOException { |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 301 | try { |
| 302 | final File sessionFile = getSessionFile(); |
| 303 | if (sessionFile == null) { |
| 304 | throw new IllegalStateException("Couldn't get the file for this session"); |
| 305 | } |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 306 | final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); |
| 307 | return fd; |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 308 | } catch (ErrnoException e) { |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 309 | throw e.rethrowAsIOException(); |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 310 | } |
Sudheer Shanka | db483fa | 2020-01-22 12:31:33 -0800 | [diff] [blame] | 311 | } |
| 312 | |
| 313 | @Override |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 314 | @BytesLong |
| 315 | public long getSize() { |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 316 | return getSessionFile().length(); |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 317 | } |
| 318 | |
| 319 | @Override |
| 320 | public void allowPackageAccess(@NonNull String packageName, |
| 321 | @NonNull byte[] certificate) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 322 | assertCallerIsOwner(); |
Sudheer Shanka | 4824e3d | 2020-01-29 23:50:24 -0800 | [diff] [blame] | 323 | Objects.requireNonNull(packageName, "packageName must not be null"); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 324 | synchronized (mSessionLock) { |
| 325 | if (mState != STATE_OPENED) { |
| 326 | throw new IllegalStateException("Not allowed to change access type in state: " |
| 327 | + stateToString(mState)); |
| 328 | } |
| 329 | mBlobAccessMode.allowPackageAccess(packageName, certificate); |
| 330 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 331 | } |
| 332 | |
| 333 | @Override |
| 334 | public void allowSameSignatureAccess() { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 335 | assertCallerIsOwner(); |
| 336 | synchronized (mSessionLock) { |
| 337 | if (mState != STATE_OPENED) { |
| 338 | throw new IllegalStateException("Not allowed to change access type in state: " |
| 339 | + stateToString(mState)); |
| 340 | } |
| 341 | mBlobAccessMode.allowSameSignatureAccess(); |
| 342 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 343 | } |
| 344 | |
| 345 | @Override |
| 346 | public void allowPublicAccess() { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 347 | assertCallerIsOwner(); |
| 348 | synchronized (mSessionLock) { |
| 349 | if (mState != STATE_OPENED) { |
| 350 | throw new IllegalStateException("Not allowed to change access type in state: " |
| 351 | + stateToString(mState)); |
| 352 | } |
| 353 | mBlobAccessMode.allowPublicAccess(); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | @Override |
| 358 | public boolean isPackageAccessAllowed(@NonNull String packageName, |
| 359 | @NonNull byte[] certificate) { |
| 360 | assertCallerIsOwner(); |
Sudheer Shanka | 4824e3d | 2020-01-29 23:50:24 -0800 | [diff] [blame] | 361 | Objects.requireNonNull(packageName, "packageName must not be null"); |
| 362 | Preconditions.checkByteArrayNotEmpty(certificate, "certificate"); |
| 363 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 364 | synchronized (mSessionLock) { |
| 365 | if (mState != STATE_OPENED) { |
| 366 | throw new IllegalStateException("Not allowed to get access type in state: " |
| 367 | + stateToString(mState)); |
| 368 | } |
| 369 | return mBlobAccessMode.isPackageAccessAllowed(packageName, certificate); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | @Override |
| 374 | public boolean isSameSignatureAccessAllowed() { |
| 375 | assertCallerIsOwner(); |
| 376 | synchronized (mSessionLock) { |
| 377 | if (mState != STATE_OPENED) { |
| 378 | throw new IllegalStateException("Not allowed to get access type in state: " |
| 379 | + stateToString(mState)); |
| 380 | } |
| 381 | return mBlobAccessMode.isSameSignatureAccessAllowed(); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | @Override |
| 386 | public boolean isPublicAccessAllowed() { |
| 387 | assertCallerIsOwner(); |
| 388 | synchronized (mSessionLock) { |
| 389 | if (mState != STATE_OPENED) { |
| 390 | throw new IllegalStateException("Not allowed to get access type in state: " |
| 391 | + stateToString(mState)); |
| 392 | } |
| 393 | return mBlobAccessMode.isPublicAccessAllowed(); |
| 394 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 395 | } |
| 396 | |
| 397 | @Override |
| 398 | public void close() { |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 399 | closeSession(STATE_CLOSED, false /* sendCallback */); |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 400 | } |
| 401 | |
| 402 | @Override |
| 403 | public void abandon() { |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 404 | closeSession(STATE_ABANDONED, true /* sendCallback */); |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 405 | } |
| 406 | |
| 407 | @Override |
| 408 | public void commit(IBlobCommitCallback callback) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 409 | synchronized (mSessionLock) { |
| 410 | mBlobCommitCallback = callback; |
| 411 | |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 412 | closeSession(STATE_COMMITTED, true /* sendCallback */); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 413 | } |
| 414 | } |
| 415 | |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 416 | private void closeSession(int state, boolean sendCallback) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 417 | assertCallerIsOwner(); |
| 418 | synchronized (mSessionLock) { |
| 419 | if (mState != STATE_OPENED) { |
| 420 | if (state == STATE_CLOSED) { |
| 421 | // Just trying to close the session which is already deleted or abandoned, |
| 422 | // ignore. |
| 423 | return; |
| 424 | } else { |
| 425 | throw new IllegalStateException("Not allowed to delete or abandon a session" |
| 426 | + " with state: " + stateToString(mState)); |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | mState = state; |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 431 | revokeAllFds(); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 432 | |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 433 | if (sendCallback) { |
| 434 | mListener.onStateChanged(this); |
| 435 | } |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 436 | } |
| 437 | } |
| 438 | |
Sudheer Shanka | b76f766 | 2020-02-27 15:17:43 -0800 | [diff] [blame] | 439 | void computeDigest() { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 440 | try { |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 441 | Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, |
| 442 | "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length()); |
Sudheer Shanka | b76f766 | 2020-02-27 15:17:43 -0800 | [diff] [blame] | 443 | mDataDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 444 | } catch (IOException | NoSuchAlgorithmException e) { |
| 445 | Slog.e(TAG, "Error computing the digest", e); |
Sudheer Shanka | d4ea5e1 | 2020-02-04 16:42:17 -0800 | [diff] [blame] | 446 | } finally { |
| 447 | Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 448 | } |
Sudheer Shanka | b76f766 | 2020-02-27 15:17:43 -0800 | [diff] [blame] | 449 | } |
| 450 | |
| 451 | void verifyBlobData() { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 452 | synchronized (mSessionLock) { |
Sudheer Shanka | b76f766 | 2020-02-27 15:17:43 -0800 | [diff] [blame] | 453 | if (mDataDigest != null && Arrays.equals(mDataDigest, mBlobHandle.digest)) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 454 | mState = STATE_VERIFIED_VALID; |
| 455 | // Commit callback will be sent once the data is persisted. |
| 456 | } else { |
Sudheer Shanka | 1680642 | 2020-06-05 15:36:37 -0700 | [diff] [blame] | 457 | Slog.d(TAG, "Digest of the data (" |
| 458 | + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest)) |
| 459 | + ") didn't match the given BlobHandle.digest (" |
| 460 | + BlobHandle.safeDigest(mBlobHandle.digest) + ")"); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 461 | mState = STATE_VERIFIED_INVALID; |
Michael Wachenschwanz | 1a7f7ab | 2020-06-09 20:16:27 -0700 | [diff] [blame] | 462 | |
| 463 | FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId, |
| 464 | getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 465 | sendCommitCallbackResult(COMMIT_RESULT_ERROR); |
| 466 | } |
| 467 | mListener.onStateChanged(this); |
| 468 | } |
| 469 | } |
| 470 | |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 471 | private void revokeAllFds() { |
| 472 | synchronized (mRevocableFds) { |
| 473 | for (int i = mRevocableFds.size() - 1; i >= 0; --i) { |
| 474 | mRevocableFds.get(i).revoke(); |
| 475 | } |
| 476 | mRevocableFds.clear(); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 477 | } |
| 478 | } |
| 479 | |
| 480 | @GuardedBy("mSessionLock") |
Sudheer Shanka | 300d9cb | 2020-06-04 23:33:13 -0700 | [diff] [blame] | 481 | private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) { |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 482 | synchronized (mRevocableFds) { |
| 483 | mRevocableFds.add(revocableFd); |
| 484 | } |
| 485 | revocableFd.addOnCloseListener((e) -> { |
| 486 | synchronized (mRevocableFds) { |
| 487 | mRevocableFds.remove(revocableFd); |
| 488 | } |
| 489 | }); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 490 | } |
| 491 | |
| 492 | @Nullable |
| 493 | File getSessionFile() { |
| 494 | if (mSessionFile == null) { |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 495 | mSessionFile = BlobStoreConfig.prepareBlobFile(mSessionId); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 496 | } |
| 497 | return mSessionFile; |
| 498 | } |
| 499 | |
| 500 | @NonNull |
| 501 | static String stateToString(int state) { |
| 502 | switch (state) { |
| 503 | case STATE_OPENED: |
| 504 | return "<opened>"; |
| 505 | case STATE_CLOSED: |
| 506 | return "<closed>"; |
| 507 | case STATE_ABANDONED: |
| 508 | return "<abandoned>"; |
| 509 | case STATE_COMMITTED: |
| 510 | return "<committed>"; |
Sudheer Shanka | b5239e4 | 2020-03-04 23:23:54 -0800 | [diff] [blame] | 511 | case STATE_VERIFIED_VALID: |
| 512 | return "<verified_valid>"; |
| 513 | case STATE_VERIFIED_INVALID: |
| 514 | return "<verified_invalid>"; |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 515 | default: |
| 516 | Slog.wtf(TAG, "Unknown state: " + state); |
| 517 | return "<unknown>"; |
| 518 | } |
| 519 | } |
| 520 | |
Sudheer Shanka | e53e1ed | 2020-02-03 17:15:24 -0800 | [diff] [blame] | 521 | @Override |
| 522 | public String toString() { |
| 523 | return "BlobStoreSession {" |
| 524 | + "id:" + mSessionId |
| 525 | + ",handle:" + mBlobHandle |
| 526 | + ",uid:" + mOwnerUid |
| 527 | + ",pkg:" + mOwnerPackageName |
| 528 | + "}"; |
| 529 | } |
| 530 | |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 531 | private void assertCallerIsOwner() { |
| 532 | final int callingUid = Binder.getCallingUid(); |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 533 | if (callingUid != mOwnerUid) { |
| 534 | throw new SecurityException(mOwnerUid + " is not the session owner"); |
Sudheer Shanka | ab1d416 | 2020-01-07 10:37:50 -0800 | [diff] [blame] | 535 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 536 | } |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 537 | |
Sudheer Shanka | 5caeed5 | 2020-02-01 12:54:33 -0800 | [diff] [blame] | 538 | void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 539 | synchronized (mSessionLock) { |
| 540 | fout.println("state: " + stateToString(mState)); |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 541 | fout.println("ownerUid: " + mOwnerUid); |
| 542 | fout.println("ownerPkg: " + mOwnerPackageName); |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 543 | fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs)); |
Sudheer Shanka | 7535154 | 2020-06-05 22:42:56 -0700 | [diff] [blame] | 544 | fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 545 | |
| 546 | fout.println("blobHandle:"); |
| 547 | fout.increaseIndent(); |
Sudheer Shanka | 5caeed5 | 2020-02-01 12:54:33 -0800 | [diff] [blame] | 548 | mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 549 | fout.decreaseIndent(); |
| 550 | |
| 551 | fout.println("accessMode:"); |
| 552 | fout.increaseIndent(); |
| 553 | mBlobAccessMode.dump(fout); |
| 554 | fout.decreaseIndent(); |
| 555 | |
| 556 | fout.println("Open fds: #" + mRevocableFds.size()); |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | void writeToXml(@NonNull XmlSerializer out) throws IOException { |
| 561 | synchronized (mSessionLock) { |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 562 | XmlUtils.writeLongAttribute(out, ATTR_ID, mSessionId); |
| 563 | XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, mOwnerPackageName); |
| 564 | XmlUtils.writeIntAttribute(out, ATTR_UID, mOwnerUid); |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 565 | XmlUtils.writeLongAttribute(out, ATTR_CREATION_TIME_MS, mCreationTimeMs); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 566 | |
| 567 | out.startTag(null, TAG_BLOB_HANDLE); |
Sudheer Shanka | bda89c1 | 2020-01-30 15:19:24 -0800 | [diff] [blame] | 568 | mBlobHandle.writeToXml(out); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 569 | out.endTag(null, TAG_BLOB_HANDLE); |
| 570 | |
| 571 | out.startTag(null, TAG_ACCESS_MODE); |
| 572 | mBlobAccessMode.writeToXml(out); |
| 573 | out.endTag(null, TAG_ACCESS_MODE); |
| 574 | } |
| 575 | } |
| 576 | |
| 577 | @Nullable |
Sudheer Shanka | 1406bc8 | 2020-02-03 12:27:24 -0800 | [diff] [blame] | 578 | static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version, |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 579 | @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) |
| 580 | throws IOException, XmlPullParserException { |
Sudheer Shanka | 34c24fb | 2020-02-18 14:50:40 -0800 | [diff] [blame] | 581 | final long sessionId = XmlUtils.readLongAttribute(in, ATTR_ID); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 582 | final String ownerPackageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); |
| 583 | final int ownerUid = XmlUtils.readIntAttribute(in, ATTR_UID); |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 584 | final long creationTimeMs = version >= XML_VERSION_ADD_SESSION_CREATION_TIME |
| 585 | ? XmlUtils.readLongAttribute(in, ATTR_CREATION_TIME_MS) |
| 586 | : System.currentTimeMillis(); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 587 | |
| 588 | final int depth = in.getDepth(); |
| 589 | BlobHandle blobHandle = null; |
| 590 | BlobAccessMode blobAccessMode = null; |
| 591 | while (XmlUtils.nextElementWithin(in, depth)) { |
| 592 | if (TAG_BLOB_HANDLE.equals(in.getName())) { |
| 593 | blobHandle = BlobHandle.createFromXml(in); |
| 594 | } else if (TAG_ACCESS_MODE.equals(in.getName())) { |
| 595 | blobAccessMode = BlobAccessMode.createFromXml(in); |
| 596 | } |
| 597 | } |
| 598 | |
| 599 | if (blobHandle == null) { |
| 600 | Slog.wtf(TAG, "blobHandle should be available"); |
| 601 | return null; |
| 602 | } |
| 603 | if (blobAccessMode == null) { |
| 604 | Slog.wtf(TAG, "blobAccessMode should be available"); |
| 605 | return null; |
| 606 | } |
| 607 | |
| 608 | final BlobStoreSession blobStoreSession = new BlobStoreSession(context, sessionId, |
Sudheer Shanka | b2a17b7 | 2020-05-28 01:14:10 -0700 | [diff] [blame] | 609 | blobHandle, ownerUid, ownerPackageName, creationTimeMs, stateChangeListener); |
Sudheer Shanka | f6e23b9 | 2020-01-15 01:51:15 -0800 | [diff] [blame] | 610 | blobStoreSession.mBlobAccessMode.allow(blobAccessMode); |
| 611 | return blobStoreSession; |
| 612 | } |
Sudheer Shanka | d2da067 | 2019-12-20 15:51:20 -0800 | [diff] [blame] | 613 | } |