1 /* 2 * Copyright (C) 2018 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.util.Slog; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 27 import libcore.io.IoUtils; 28 import libcore.util.EmptyArray; 29 30 import java.io.File; 31 import java.io.FileDescriptor; 32 import java.io.IOException; 33 import java.io.InterruptedIOException; 34 import java.util.Arrays; 35 36 /** 37 * Variant of {@link FileDescriptor} that allows its creator to specify regions 38 * that should be redacted. 39 * 40 * @hide 41 */ 42 public class RedactingFileDescriptor { 43 private static final String TAG = "RedactingFileDescriptor"; 44 private static final boolean DEBUG = true; 45 46 private volatile long[] mRedactRanges; 47 private volatile long[] mFreeOffsets; 48 49 private FileDescriptor mInner = null; 50 private ParcelFileDescriptor mOuter = null; 51 RedactingFileDescriptor( Context context, File file, int mode, long[] redactRanges, long[] freeOffsets)52 private RedactingFileDescriptor( 53 Context context, File file, int mode, long[] redactRanges, long[] freeOffsets) 54 throws IOException { 55 mRedactRanges = checkRangesArgument(redactRanges); 56 mFreeOffsets = freeOffsets; 57 58 try { 59 try { 60 mInner = Os.open(file.getAbsolutePath(), 61 FileUtils.translateModePfdToPosix(mode), 0); 62 mOuter = context.getSystemService(StorageManager.class) 63 .openProxyFileDescriptor(mode, mCallback); 64 } catch (ErrnoException e) { 65 throw e.rethrowAsIOException(); 66 } 67 } catch (IOException e) { 68 IoUtils.closeQuietly(mInner); 69 IoUtils.closeQuietly(mOuter); 70 throw e; 71 } 72 } 73 checkRangesArgument(long[] ranges)74 private static long[] checkRangesArgument(long[] ranges) { 75 if (ranges.length % 2 != 0) { 76 throw new IllegalArgumentException(); 77 } 78 for (int i = 0; i < ranges.length - 1; i += 2) { 79 if (ranges[i] > ranges[i + 1]) { 80 throw new IllegalArgumentException(); 81 } 82 } 83 return ranges; 84 } 85 86 /** 87 * Open the given {@link File} and returns a {@link ParcelFileDescriptor} 88 * that offers a redacted view of the underlying data. If a redacted region 89 * is written to, the newly written data can be read back correctly instead 90 * of continuing to be redacted. 91 * 92 * @param file The underlying file to open. 93 * @param mode The {@link ParcelFileDescriptor} mode to open with. 94 * @param redactRanges List of file ranges that should be redacted, stored 95 * as {@code [start1, end1, start2, end2, ...]}. Start values are 96 * inclusive and end values are exclusive. 97 * @param freePositions List of file offsets at which the four byte value 'free' should be 98 * written instead of zeros within parts of the file covered by {@code redactRanges}. 99 * Non-redacted bytes will not be modified even if covered by a 'free'. This is 100 * useful for overwriting boxes in ISOBMFF files with padding data. 101 */ open(Context context, File file, int mode, long[] redactRanges, long[] freePositions)102 public static ParcelFileDescriptor open(Context context, File file, int mode, 103 long[] redactRanges, long[] freePositions) throws IOException { 104 return new RedactingFileDescriptor(context, file, mode, redactRanges, freePositions).mOuter; 105 } 106 107 /** 108 * Update the given ranges argument to remove any references to the given 109 * offset and length. This is typically used when a caller has written over 110 * a previously redacted region. 111 */ 112 @VisibleForTesting removeRange(long[] ranges, long start, long end)113 public static long[] removeRange(long[] ranges, long start, long end) { 114 if (start == end) { 115 return ranges; 116 } else if (start > end) { 117 throw new IllegalArgumentException(); 118 } 119 120 long[] res = EmptyArray.LONG; 121 for (int i = 0; i < ranges.length; i += 2) { 122 if (start <= ranges[i] && end >= ranges[i + 1]) { 123 // Range entirely covered; remove it 124 } else if (start >= ranges[i] && end <= ranges[i + 1]) { 125 // Range partially covered; punch a hole 126 res = Arrays.copyOf(res, res.length + 4); 127 res[res.length - 4] = ranges[i]; 128 res[res.length - 3] = start; 129 res[res.length - 2] = end; 130 res[res.length - 1] = ranges[i + 1]; 131 } else { 132 // Range might covered; adjust edges if needed 133 res = Arrays.copyOf(res, res.length + 2); 134 if (end >= ranges[i] && end <= ranges[i + 1]) { 135 res[res.length - 2] = Math.max(ranges[i], end); 136 } else { 137 res[res.length - 2] = ranges[i]; 138 } 139 if (start >= ranges[i] && start <= ranges[i + 1]) { 140 res[res.length - 1] = Math.min(ranges[i + 1], start); 141 } else { 142 res[res.length - 1] = ranges[i + 1]; 143 } 144 } 145 } 146 return res; 147 } 148 149 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { 150 @Override 151 public long onGetSize() throws ErrnoException { 152 return Os.fstat(mInner).st_size; 153 } 154 155 @Override 156 public int onRead(long offset, int size, byte[] data) throws ErrnoException { 157 int n = 0; 158 while (n < size) { 159 try { 160 final int res = Os.pread(mInner, data, n, size - n, offset + n); 161 if (res == 0) { 162 break; 163 } else { 164 n += res; 165 } 166 } catch (InterruptedIOException e) { 167 n += e.bytesTransferred; 168 } 169 } 170 171 // Redact any relevant ranges before returning 172 final long[] ranges = mRedactRanges; 173 for (int i = 0; i < ranges.length; i += 2) { 174 final long start = Math.max(offset, ranges[i]); 175 final long end = Math.min(offset + size, ranges[i + 1]); 176 for (long j = start; j < end; j++) { 177 data[(int) (j - offset)] = 0; 178 } 179 // Overwrite data at 'free' offsets within the redaction ranges. 180 for (long freeOffset : mFreeOffsets) { 181 final long freeEnd = freeOffset + 4; 182 final long redactFreeStart = Math.max(freeOffset, start); 183 final long redactFreeEnd = Math.min(freeEnd, end); 184 for (long j = redactFreeStart; j < redactFreeEnd; j++) { 185 data[(int) (j - offset)] = (byte) "free".charAt((int) (j - freeOffset)); 186 } 187 } 188 } 189 return n; 190 } 191 192 @Override 193 public int onWrite(long offset, int size, byte[] data) throws ErrnoException { 194 int n = 0; 195 while (n < size) { 196 try { 197 final int res = Os.pwrite(mInner, data, n, size - n, offset + n); 198 if (res == 0) { 199 break; 200 } else { 201 n += res; 202 } 203 } catch (InterruptedIOException e) { 204 n += e.bytesTransferred; 205 } 206 } 207 208 // Clear any relevant redaction ranges before returning, since the 209 // writer should have access to see the data they just overwrote 210 mRedactRanges = removeRange(mRedactRanges, offset, offset + n); 211 return n; 212 } 213 214 @Override 215 public void onFsync() throws ErrnoException { 216 Os.fsync(mInner); 217 } 218 219 @Override 220 public void onRelease() { 221 if (DEBUG) Slog.v(TAG, "onRelease()"); 222 IoUtils.closeQuietly(mInner); 223 } 224 }; 225 } 226