Files
packages_apps_Messaging/src/com/android/messaging/util/FileUtil.java
Tom Taylor 69ed579fb8 32161610 Security Vulnerability - Information disclosure vulnerability
in AOSP Messaging

* Check to make sure the returned uri from the gallery picker does
not point to bugle's data directory (or any subdir).

* Test:
Manual-
* I created the test app in the bug, the one that injects the bad
uri into Bugle. I verified the bad behavior before the fix and the
good behavior after.
* I tested the gallery to make sure picking photos,
from the photos app and drive, still work.
* I verified the behavior in the debugger to be sure the code is
catching the bad uri from the test app.

Change-Id: I3393f3b886c837a49758b91945cf1e17ec9bee41
Fixes: 32161610
2016-12-05 13:57:45 -08:00

149 lines
5.9 KiB
Java

/*
* Copyright (C) 2015 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 com.android.messaging.util;
import android.content.Context;
import android.os.Environment;
import android.webkit.MimeTypeMap;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class FileUtil {
/** Returns a new file name, ensuring that such a file does not already exist. */
private static synchronized File getNewFile(File directory, String extension,
String fileNameFormat) throws IOException {
final Date date = new Date(System.currentTimeMillis());
final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat);
final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension;
for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name.
final String newName = String.format(Locale.US, numberedFileNameFormat, i);
File testFile = new File(directory, newName);
if (!testFile.exists()) {
testFile.createNewFile();
return testFile;
}
}
LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat);
return null;
}
/**
* Creates an unused name to use for creating a new file. The format happens to be similar
* to that used by the Android camera application.
*
* @param directory directory that the file should be saved to
* @param contentType of the media being saved
* @return file name to be used for creating the new file. The caller is responsible for
* actually creating the file.
*/
public static File getNewFile(File directory, String contentType) throws IOException {
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String fileExtension = mimeTypeMap.getExtensionFromMimeType(contentType);
final Context context = Factory.get().getApplicationContext();
String fileNameFormat = context.getString(ContentType.isImageType(contentType)
? R.string.new_image_file_name_format : R.string.new_file_name_format);
return getNewFile(directory, fileExtension, fileNameFormat);
}
/** Delete everything below and including root */
public static void removeFileOrDirectory(File root) {
removeFileOrDirectoryExcept(root, null);
}
/** Delete everything below and including root except for the given file */
public static void removeFileOrDirectoryExcept(File root, File exclude) {
if (root.exists()) {
if (root.isDirectory()) {
for (File file : root.listFiles()) {
if (exclude == null || !file.equals(exclude)) {
removeFileOrDirectoryExcept(file, exclude);
}
}
root.delete();
} else if (root.isFile()) {
root.delete();
}
}
}
/**
* Move all files and folders under a directory into the target.
*/
public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) {
if (sourceDir.isDirectory() && targetDir.isDirectory()) {
if (isSameOrSubDirectory(sourceDir, targetDir)) {
LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " +
"directory is a parent of the target");
return;
}
for (File file : sourceDir.listFiles()) {
if (file.isDirectory()) {
final File dirTarget = new File(targetDir, file.getName());
dirTarget.mkdirs();
moveAllContentUnderDirectory(file, dirTarget);
} else {
try {
final File fileTarget = new File(targetDir, file.getName());
Files.move(file, fileTarget);
} catch (IOException e) {
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e);
// Try proceed with the next file.
}
}
}
}
}
// Checks if the file is in /data, and don't allow any app to send personal information.
// We're told it's possible to create world readable hardlinks to other apps private data
// so we ban all /data file uris. b/28793303
public static boolean isInDataDir(File file) {
return isSameOrSubDirectory(Environment.getDataDirectory(), file);
}
/**
* Checks, whether the child directory is the same as, or a sub-directory of the base
* directory.
*/
private static boolean isSameOrSubDirectory(File base, File child) {
try {
base = base.getCanonicalFile();
child = child.getCanonicalFile();
File parentFile = child;
while (parentFile != null) {
if (base.equals(parentFile)) {
return true;
}
parentFile = parentFile.getParentFile();
}
return false;
} catch (IOException ex) {
LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex);
return false;
}
}
}