We've been informed it's possible for an app to create a world readable hardlink in L in its own /data directory that links to another apps private data, including Messenger data. The hardlink bypassed our existing checks. So now we simply refuse all file: uris in the /data/ directory. Other apps shouldn't be sending file uris anyway, and we dont know of any that send file:///data/ uris. Bug: 28793303 Change-Id: I778bb2bcb9e11185357093c59fc1fa3f6caa26a1
160 lines
6.2 KiB
Java
160 lines
6.2 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.ContentResolver;
|
|
import android.content.Context;
|
|
import android.net.Uri;
|
|
import android.os.Environment;
|
|
import android.text.TextUtils;
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean isFileUri(final Uri uri) {
|
|
return TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_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.
|
|
public static boolean isInPrivateDir(Uri uri) {
|
|
if (!isFileUri(uri)) {
|
|
return false;
|
|
}
|
|
final File file = new File(uri.getPath());
|
|
return FileUtil.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;
|
|
}
|
|
}
|
|
}
|