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