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