| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os; |
| |
| import android.content.Context; |
| import android.os.storage.StorageManager; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| import android.system.OsConstants; |
| import android.util.Slog; |
| |
| import libcore.io.IoUtils; |
| |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.InterruptedIOException; |
| |
| /** |
| * Variant of {@link FileDescriptor} that allows its creator to revoke all |
| * access to the underlying resource. |
| * <p> |
| * This is useful when the code that originally opened a file needs to strongly |
| * assert that any clients are completely hands-off for security purposes. |
| * |
| * @hide |
| */ |
| public class RevocableFileDescriptor { |
| private static final String TAG = "RevocableFileDescriptor"; |
| private static final boolean DEBUG = true; |
| |
| private FileDescriptor mInner; |
| private ParcelFileDescriptor mOuter; |
| |
| private volatile boolean mRevoked; |
| |
| /** {@hide} */ |
| public RevocableFileDescriptor() { |
| } |
| |
| /** |
| * Create an instance that references the given {@link File}. |
| */ |
| public RevocableFileDescriptor(Context context, File file) throws IOException { |
| try { |
| init(context, Os.open(file.getAbsolutePath(), |
| OsConstants.O_CREAT | OsConstants.O_RDWR, 0700)); |
| } catch (ErrnoException e) { |
| throw e.rethrowAsIOException(); |
| } |
| } |
| |
| /** |
| * Create an instance that references the given {@link FileDescriptor}. |
| */ |
| public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException { |
| init(context, fd); |
| } |
| |
| /** {@hide} */ |
| public void init(Context context, FileDescriptor fd) throws IOException { |
| mInner = fd; |
| mOuter = context.getSystemService(StorageManager.class) |
| .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback); |
| } |
| |
| /** |
| * Return a {@link ParcelFileDescriptor} which can safely be passed to an |
| * untrusted process. After {@link #revoke()} is called, all operations will |
| * fail with {@link OsConstants#EPERM}. |
| */ |
| public ParcelFileDescriptor getRevocableFileDescriptor() { |
| return mOuter; |
| } |
| |
| /** |
| * Revoke all future access to the {@link ParcelFileDescriptor} returned by |
| * {@link #getRevocableFileDescriptor()}. From this point forward, all |
| * operations will fail with {@link OsConstants#EPERM}. |
| */ |
| public void revoke() { |
| mRevoked = true; |
| IoUtils.closeQuietly(mInner); |
| } |
| |
| public boolean isRevoked() { |
| return mRevoked; |
| } |
| |
| private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { |
| private void checkRevoked() throws ErrnoException { |
| if (mRevoked) { |
| throw new ErrnoException(TAG, OsConstants.EPERM); |
| } |
| } |
| |
| @Override |
| public long onGetSize() throws ErrnoException { |
| checkRevoked(); |
| return Os.fstat(mInner).st_size; |
| } |
| |
| @Override |
| public int onRead(long offset, int size, byte[] data) throws ErrnoException { |
| checkRevoked(); |
| int n = 0; |
| while (n < size) { |
| try { |
| n += Os.pread(mInner, data, n, size - n, offset + n); |
| break; |
| } catch (InterruptedIOException e) { |
| n += e.bytesTransferred; |
| } |
| } |
| return n; |
| } |
| |
| @Override |
| public int onWrite(long offset, int size, byte[] data) throws ErrnoException { |
| checkRevoked(); |
| int n = 0; |
| while (n < size) { |
| try { |
| n += Os.pwrite(mInner, data, n, size - n, offset + n); |
| break; |
| } catch (InterruptedIOException e) { |
| n += e.bytesTransferred; |
| } |
| } |
| return n; |
| } |
| |
| @Override |
| public void onFsync() throws ErrnoException { |
| if (DEBUG) Slog.v(TAG, "onFsync()"); |
| checkRevoked(); |
| Os.fsync(mInner); |
| } |
| |
| @Override |
| public void onRelease() { |
| if (DEBUG) Slog.v(TAG, "onRelease()"); |
| mRevoked = true; |
| IoUtils.closeQuietly(mInner); |
| } |
| }; |
| } |