blob: a750ce6158fb0a92e84bf1669ef87e4fa8333587 [file] [log] [blame]
Jeff Sharkey02d4e342017-03-10 21:53:48 -07001/*
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
17package android.os;
18
19import android.content.Context;
20import android.os.storage.StorageManager;
21import android.system.ErrnoException;
22import android.system.Os;
23import android.system.OsConstants;
24import android.util.Slog;
25
26import libcore.io.IoUtils;
27
28import java.io.File;
29import java.io.FileDescriptor;
30import java.io.IOException;
31import 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 */
42public 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} */
52 public RevocableFileDescriptor() {
53 }
54
55 /**
56 * Create an instance that references the given {@link File}.
57 */
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 */
70 public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException {
71 init(context, fd);
72 }
73
74 /** {@hide} */
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 */
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 */
95 public void revoke() {
96 mRevoked = true;
97 IoUtils.closeQuietly(mInner);
98 }
99
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}