/* * 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. *

* 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); } }; }