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 76 /** {@hide} */ init(Context context, FileDescriptor fd)77 public void init(Context context, FileDescriptor fd) throws IOException { 78 mInner = fd; 79 mOuter = context.getSystemService(StorageManager.class) 80 .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback); 81 } 82 83 /** 84 * Return a {@link ParcelFileDescriptor} which can safely be passed to an 85 * untrusted process. After {@link #revoke()} is called, all operations will 86 * fail with {@link OsConstants#EPERM}. 87 */ getRevocableFileDescriptor()88 public ParcelFileDescriptor getRevocableFileDescriptor() { 89 return mOuter; 90 } 91 92 /** 93 * Revoke all future access to the {@link ParcelFileDescriptor} returned by 94 * {@link #getRevocableFileDescriptor()}. From this point forward, all 95 * operations will fail with {@link OsConstants#EPERM}. 96 */ revoke()97 public void revoke() { 98 mRevoked = true; 99 IoUtils.closeQuietly(mInner); 100 } 101 102 /** 103 * Callback for indicating that {@link ParcelFileDescriptor} passed to the client 104 * process ({@link #getRevocableFileDescriptor()}) has been closed. 105 */ addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener)106 public void addOnCloseListener(ParcelFileDescriptor.OnCloseListener onCloseListener) { 107 mOnCloseListener = onCloseListener; 108 } 109 isRevoked()110 public boolean isRevoked() { 111 return mRevoked; 112 } 113 114 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { 115 private void checkRevoked() throws ErrnoException { 116 if (mRevoked) { 117 throw new ErrnoException(TAG, OsConstants.EPERM); 118 } 119 } 120 121 @Override 122 public long onGetSize() throws ErrnoException { 123 checkRevoked(); 124 return Os.fstat(mInner).st_size; 125 } 126 127 @Override 128 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 129 checkRevoked(); 130 int n = 0; 131 while (n < size) { 132 try { 133 n += Os.pread(mInner, data, n, size - n, offset + n); 134 break; 135 } catch (InterruptedIOException e) { 136 n += e.bytesTransferred; 137 } 138 } 139 return n; 140 } 141 142 @Override 143 public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 144 checkRevoked(); 145 int n = 0; 146 while (n < size) { 147 try { 148 n += Os.pwrite(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 void onFsync() throws ErrnoException { 159 if (DEBUG) Slog.v(TAG, "onFsync()"); 160 checkRevoked(); 161 Os.fsync(mInner); 162 } 163 164 @Override 165 public void onRelease() { 166 if (DEBUG) Slog.v(TAG, "onRelease()"); 167 mRevoked = true; 168 IoUtils.closeQuietly(mInner); 169 if (mOnCloseListener != null) { 170 mOnCloseListener.onClose(null); 171 } 172 } 173 }; 174 } 175