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 /** {@hide} */ RevocableFileDescriptor()52 public RevocableFileDescriptor() { 53 } 54 55 /** 56 * Create an instance that references the given {@link File}. 57 */ RevocableFileDescriptor(Context context, File file)58 public RevocableFileDescriptor(Context context, File file) throws IOException { 59 try { 60 init(context, Os.open(file.getAbsolutePath(), 61 OsConstants.O_CREAT | OsConstants.O_RDWR, 0700)); 62 } catch (ErrnoException e) { 63 throw e.rethrowAsIOException(); 64 } 65 } 66 67 /** 68 * Create an instance that references the given {@link FileDescriptor}. 69 */ RevocableFileDescriptor(Context context, FileDescriptor fd)70 public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException { 71 init(context, fd); 72 } 73 74 /** {@hide} */ init(Context context, FileDescriptor fd)75 public void init(Context context, FileDescriptor fd) throws IOException { 76 mInner = fd; 77 mOuter = context.getSystemService(StorageManager.class) 78 .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback); 79 } 80 81 /** 82 * Return a {@link ParcelFileDescriptor} which can safely be passed to an 83 * untrusted process. After {@link #revoke()} is called, all operations will 84 * fail with {@link OsConstants#EPERM}. 85 */ getRevocableFileDescriptor()86 public ParcelFileDescriptor getRevocableFileDescriptor() { 87 return mOuter; 88 } 89 90 /** 91 * Revoke all future access to the {@link ParcelFileDescriptor} returned by 92 * {@link #getRevocableFileDescriptor()}. From this point forward, all 93 * operations will fail with {@link OsConstants#EPERM}. 94 */ revoke()95 public void revoke() { 96 mRevoked = true; 97 IoUtils.closeQuietly(mInner); 98 } 99 isRevoked()100 public boolean isRevoked() { 101 return mRevoked; 102 } 103 104 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { 105 private void checkRevoked() throws ErrnoException { 106 if (mRevoked) { 107 throw new ErrnoException(TAG, OsConstants.EPERM); 108 } 109 } 110 111 @Override 112 public long onGetSize() throws ErrnoException { 113 checkRevoked(); 114 return Os.fstat(mInner).st_size; 115 } 116 117 @Override 118 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 119 checkRevoked(); 120 int n = 0; 121 while (n < size) { 122 try { 123 n += Os.pread(mInner, data, n, size - n, offset + n); 124 break; 125 } catch (InterruptedIOException e) { 126 n += e.bytesTransferred; 127 } 128 } 129 return n; 130 } 131 132 @Override 133 public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 134 checkRevoked(); 135 int n = 0; 136 while (n < size) { 137 try { 138 n += Os.pwrite(mInner, data, n, size - n, offset + n); 139 break; 140 } catch (InterruptedIOException e) { 141 n += e.bytesTransferred; 142 } 143 } 144 return n; 145 } 146 147 @Override 148 public void onFsync() throws ErrnoException { 149 if (DEBUG) Slog.v(TAG, "onFsync()"); 150 checkRevoked(); 151 Os.fsync(mInner); 152 } 153 154 @Override 155 public void onRelease() { 156 if (DEBUG) Slog.v(TAG, "onRelease()"); 157 mRevoked = true; 158 IoUtils.closeQuietly(mInner); 159 } 160 }; 161 } 162