1 /* 2 * Copyright (C) 2017 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 17 package android.os; 18 19 import android.content.Context; 20 import android.os.storage.StorageManager; 21 import android.system.ErrnoException; 22 import android.system.Os; 23 import android.system.OsConstants; 24 import android.util.Slog; 25 26 import libcore.io.IoUtils; 27 28 import java.io.File; 29 import java.io.FileDescriptor; 30 import java.io.IOException; 31 import java.io.InterruptedIOException; 32 33 /** 34 * Variant of {@link FileDescriptor} that allows its creator to revoke all 35 * access to the underlying resource. 36 * <p> 37 * This is useful when the code that originally opened a file needs to strongly 38 * assert that any clients are completely hands-off for security purposes. 39 * 40 * @hide 41 */ 42 public class RevocableFileDescriptor { 43 private static final String TAG = "RevocableFileDescriptor"; 44 private static final boolean DEBUG = true; 45 46 private FileDescriptor mInner; 47 private ParcelFileDescriptor mOuter; 48 49 private volatile boolean mRevoked; 50 51 private ParcelFileDescriptor.OnCloseListener mOnCloseListener; 52 53 /** {@hide} */ RevocableFileDescriptor()54 public RevocableFileDescriptor() { 55 } 56 57 /** 58 * Create an instance that references the given {@link File}. 59 */ RevocableFileDescriptor(Context context, File file)60 public RevocableFileDescriptor(Context context, File file) throws IOException { 61 try { 62 init(context, Os.open(file.getAbsolutePath(), 63 OsConstants.O_CREAT | OsConstants.O_RDWR, 0700)); 64 } catch (ErrnoException e) { 65 throw e.rethrowAsIOException(); 66 } 67 } 68 69 /** 70 * Create an instance that references the given {@link FileDescriptor}. 71 */ RevocableFileDescriptor(Context context, FileDescriptor fd)72 public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException { 73 init(context, fd); 74 } 75 RevocableFileDescriptor(Context context, FileDescriptor fd, Handler handler)76 public RevocableFileDescriptor(Context context, FileDescriptor fd, Handler handler) 77 throws IOException { 78 init(context, fd, handler); 79 } 80 81 /** {@hide} */ init(Context context, FileDescriptor fd)82 public void init(Context context, FileDescriptor fd) throws IOException { 83 init(context, fd, null); 84 } 85 86 /** {@hide} */ init(Context context, FileDescriptor fd, Handler handler)87 public void init(Context context, FileDescriptor fd, Handler handler) throws IOException { 88 mInner = fd; 89 StorageManager sm = context.getSystemService(StorageManager.class); 90 if (handler != null) { 91 mOuter = sm.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback, 92 handler); 93 } else { 94 mOuter = sm.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback); 95 } 96 } 97 98 /** 99 * Return a {@link ParcelFileDescriptor} which can safely be passed to an 100 * untrusted process. After {@link #revoke()} is called, all operations will 101 * fail with {@link OsConstants#EPERM}. 102 */ getRevocableFileDescriptor()103 public ParcelFileDescriptor getRevocableFileDescriptor() { 104 return mOuter; 105 } 106 107 /** 108 * Revoke all future access to the {@link ParcelFileDescriptor} returned by 109 * {@link #getRevocableFileDescriptor()}. From this point forward, all 110 * operations will fail with {@link OsConstants#EPERM}. 111 */ revoke()112 public void revoke() { 113 mRevoked = true; 114 IoUtils.closeQuietly(mInner); 115 } 116 117 /** 118 * Callback for indicating that {@link ParcelFileDescriptor} passed to the client 119 * process ({@link #getRevocableFileDescriptor()}) has been closed. 120 */ addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener)121 public void addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener) { 122 mOnCloseListener = onCloseListener; 123 } 124 isRevoked()125 public boolean isRevoked() { 126 return mRevoked; 127 } 128 129 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { 130 private void checkRevoked() throws ErrnoException { 131 if (mRevoked) { 132 throw new ErrnoException(TAG, OsConstants.EPERM); 133 } 134 } 135 136 @Override 137 public long onGetSize() throws ErrnoException { 138 checkRevoked(); 139 return Os.fstat(mInner).st_size; 140 } 141 142 @Override 143 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 144 checkRevoked(); 145 int n = 0; 146 while (n < size) { 147 try { 148 n += Os.pread(mInner, data, n, size - n, offset + n); 149 break; 150 } catch (InterruptedIOException e) { 151 n += e.bytesTransferred; 152 } 153 } 154 return n; 155 } 156 157 @Override 158 public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 159 checkRevoked(); 160 int n = 0; 161 while (n < size) { 162 try { 163 n += Os.pwrite(mInner, data, n, size - n, offset + n); 164 break; 165 } catch (InterruptedIOException e) { 166 n += e.bytesTransferred; 167 } 168 } 169 return n; 170 } 171 172 @Override 173 public void onFsync() throws ErrnoException { 174 if (DEBUG) Slog.v(TAG, "onFsync()"); 175 checkRevoked(); 176 Os.fsync(mInner); 177 } 178 179 @Override 180 public void onRelease() { 181 if (DEBUG) Slog.v(TAG, "onRelease()"); 182 mRevoked = true; 183 IoUtils.closeQuietly(mInner); 184 if (mOnCloseListener != null) { 185 mOnCloseListener.onClose(null); 186 } 187 } 188 }; 189 } 190