/* * Copyright (C) 2019 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 specic language governing permissions and * limitations under the License. */ #include "MediaProviderWrapper.h" #include "libfuse_jni/ReaddirHelper.h" #include #include #include #include #include #include #include #include #include namespace mediaprovider { namespace fuse { using android::base::GetBoolProperty; using std::string; namespace { constexpr const char* kPropRedactionEnabled = "persist.sys.fuse.redaction-enabled"; constexpr uid_t ROOT_UID = 0; constexpr uid_t SHELL_UID = 2000; /** Private helper functions **/ inline bool shouldBypassMediaProvider(uid_t uid) { return uid == SHELL_UID || uid == ROOT_UID; } static bool CheckForJniException(JNIEnv* env) { if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); return true; } return false; } /** * Auxiliary for caching class fields */ static jfieldID CacheField(JNIEnv* env, jclass clazz, const char field_name[], const char type[]) { jfieldID fid; string actual_field_name(field_name); fid = env->GetFieldID(clazz, actual_field_name.c_str(), type); if (!fid) { LOG(FATAL) << "Error caching field: " << field_name << type; } return fid; } int insertFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_insert_file, const string& path, uid_t uid) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); int res = env->CallIntMethod(media_provider_object, mid_insert_file, j_path.get(), uid); if (CheckForJniException(env)) { return EFAULT; } return res; } int deleteFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_delete_file, const string& path, uid_t uid) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); int res = env->CallIntMethod(media_provider_object, mid_delete_file, j_path.get(), uid); if (CheckForJniException(env)) { return EFAULT; } return res; } int isMkdirOrRmdirAllowedInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_is_mkdir_or_rmdir_allowed, const string& path, uid_t uid, bool forCreate) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); int res = env->CallIntMethod(media_provider_object, mid_is_mkdir_or_rmdir_allowed, j_path.get(), uid, forCreate); if (CheckForJniException(env)) { return EFAULT; } return res; } int isOpendirAllowedInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_is_opendir_allowed, const string& path, uid_t uid, bool forWrite) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); int res = env->CallIntMethod(media_provider_object, mid_is_opendir_allowed, j_path.get(), uid, forWrite); if (CheckForJniException(env)) { return EFAULT; } return res; } bool isUidAllowedAccessToDataOrObbPathInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_is_uid_allowed_path_access_, uid_t uid, const string& path) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); bool res = env->CallBooleanMethod(media_provider_object, mid_is_uid_allowed_path_access_, uid, j_path.get()); if (CheckForJniException(env)) { return false; } return res; } std::vector> getFilesInDirectoryInternal( JNIEnv* env, jobject media_provider_object, jmethodID mid_get_files_in_dir, uid_t uid, const string& path) { std::vector> directory_entries; ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); ScopedLocalRef files_list( env, static_cast(env->CallObjectMethod( media_provider_object, mid_get_files_in_dir, j_path.get(), uid))); if (CheckForJniException(env)) { directory_entries.push_back(std::make_shared("", EFAULT)); return directory_entries; } int de_count = env->GetArrayLength(files_list.get()); if (de_count == 1) { ScopedLocalRef j_d_name(env, (jstring)env->GetObjectArrayElement(files_list.get(), 0)); ScopedUtfChars d_name(env, j_d_name.get()); if (d_name.c_str() == nullptr) { LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << 0; directory_entries.push_back(std::make_shared("", EFAULT)); return directory_entries; } else if (d_name.c_str()[0] == '\0') { // Calling package has no storage permissions. directory_entries.push_back(std::make_shared("", EPERM)); return directory_entries; } } for (int i = 0; i < de_count; i++) { ScopedLocalRef j_d_name(env, (jstring)env->GetObjectArrayElement(files_list.get(), i)); ScopedUtfChars d_name(env, j_d_name.get()); if (d_name.c_str() == nullptr) { LOG(ERROR) << "Error reading file name returned from MediaProvider at index " << i; directory_entries.resize(0); directory_entries.push_back(std::make_shared("", EFAULT)); break; } directory_entries.push_back(std::make_shared(d_name.c_str(), DT_REG)); } return directory_entries; } int renameInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_rename, const string& old_path, const string& new_path, uid_t uid) { ScopedLocalRef j_old_path(env, env->NewStringUTF(old_path.c_str())); ScopedLocalRef j_new_path(env, env->NewStringUTF(new_path.c_str())); int res = env->CallIntMethod(media_provider_object, mid_rename, j_old_path.get(), j_new_path.get(), uid); if (CheckForJniException(env)) { return EFAULT; } return res; } void onFileCreatedInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_on_file_created, const string& path) { ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); env->CallVoidMethod(media_provider_object, mid_on_file_created, j_path.get()); CheckForJniException(env); return; } } // namespace /*****************************************************************************************/ /******************************* Public API Implementation *******************************/ /*****************************************************************************************/ JavaVM* MediaProviderWrapper::gJavaVm = nullptr; pthread_key_t MediaProviderWrapper::gJniEnvKey; void MediaProviderWrapper::OneTimeInit(JavaVM* vm) { gJavaVm = vm; CHECK(gJavaVm != nullptr); pthread_key_create(&MediaProviderWrapper::gJniEnvKey, MediaProviderWrapper::DetachThreadFunction); } MediaProviderWrapper::MediaProviderWrapper(JNIEnv* env, jobject media_provider) { if (!media_provider) { LOG(FATAL) << "MediaProvider is null!"; } media_provider_object_ = reinterpret_cast(env->NewGlobalRef(media_provider)); media_provider_class_ = env->FindClass("com/android/providers/media/MediaProvider"); if (!media_provider_class_) { LOG(FATAL) << "Could not find class MediaProvider"; } media_provider_class_ = reinterpret_cast(env->NewGlobalRef(media_provider_class_)); // Cache methods - Before calling a method, make sure you cache it here mid_insert_file_ = CacheMethod(env, "insertFileIfNecessary", "(Ljava/lang/String;I)I", /*is_static*/ false); mid_delete_file_ = CacheMethod(env, "deleteFile", "(Ljava/lang/String;I)I", /*is_static*/ false); mid_on_file_open_ = CacheMethod(env, "onFileOpen", "(Ljava/lang/String;Ljava/lang/String;IIIZZZ)Lcom/android/" "providers/media/FileOpenResult;", /*is_static*/ false); mid_is_mkdir_or_rmdir_allowed_ = CacheMethod(env, "isDirectoryCreationOrDeletionAllowed", "(Ljava/lang/String;IZ)I", /*is_static*/ false); mid_is_opendir_allowed_ = CacheMethod(env, "isOpendirAllowed", "(Ljava/lang/String;IZ)I", /*is_static*/ false); mid_get_files_in_dir_ = CacheMethod(env, "getFilesInDirectory", "(Ljava/lang/String;I)[Ljava/lang/String;", /*is_static*/ false); mid_rename_ = CacheMethod(env, "rename", "(Ljava/lang/String;Ljava/lang/String;I)I", /*is_static*/ false); mid_is_uid_allowed_access_to_data_or_obb_path_ = CacheMethod(env, "isUidAllowedAccessToDataOrObbPath", "(ILjava/lang/String;)Z", /*is_static*/ false); mid_on_file_created_ = CacheMethod(env, "onFileCreated", "(Ljava/lang/String;)V", /*is_static*/ false); mid_should_allow_lookup_ = CacheMethod(env, "shouldAllowLookup", "(II)Z", /*is_static*/ false); mid_is_app_clone_user_ = CacheMethod(env, "isAppCloneUser", "(I)Z", /*is_static*/ false); mid_transform_ = CacheMethod(env, "transform", "(Ljava/lang/String;Ljava/lang/String;IIIII)Z", /*is_static*/ false); mid_file_lookup_ = CacheMethod(env, "onFileLookup", "(Ljava/lang/String;II)Lcom/android/providers/media/FileLookupResult;", /*is_static*/ false); // FileLookupResult file_lookup_result_class_ = env->FindClass("com/android/providers/media/FileLookupResult"); if (!file_lookup_result_class_) { LOG(FATAL) << "Could not find class FileLookupResult"; } file_lookup_result_class_ = reinterpret_cast(env->NewGlobalRef(file_lookup_result_class_)); fid_file_lookup_transforms_ = CacheField(env, file_lookup_result_class_, "transforms", "I"); fid_file_lookup_transforms_reason_ = CacheField(env, file_lookup_result_class_, "transformsReason", "I"); fid_file_lookup_uid_ = CacheField(env, file_lookup_result_class_, "uid", "I"); fid_file_lookup_transforms_complete_ = CacheField(env, file_lookup_result_class_, "transformsComplete", "Z"); fid_file_lookup_transforms_supported_ = CacheField(env, file_lookup_result_class_, "transformsSupported", "Z"); fid_file_lookup_io_path_ = CacheField(env, file_lookup_result_class_, "ioPath", "Ljava/lang/String;"); // FileOpenResult file_open_result_class_ = env->FindClass("com/android/providers/media/FileOpenResult"); if (!file_open_result_class_) { LOG(FATAL) << "Could not find class FileOpenResult"; } file_open_result_class_ = reinterpret_cast(env->NewGlobalRef(file_open_result_class_)); fid_file_open_status_ = CacheField(env, file_open_result_class_, "status", "I"); fid_file_open_uid_ = CacheField(env, file_open_result_class_, "uid", "I"); fid_file_open_transforms_uid_ = CacheField(env, file_open_result_class_, "transformsUid", "I"); fid_file_open_redaction_ranges_ = CacheField(env, file_open_result_class_, "redactionRanges", "[J"); } MediaProviderWrapper::~MediaProviderWrapper() { JNIEnv* env = MaybeAttachCurrentThread(); env->DeleteGlobalRef(media_provider_object_); env->DeleteGlobalRef(media_provider_class_); } int MediaProviderWrapper::InsertFile(const string& path, uid_t uid) { if (uid == ROOT_UID) { return 0; } JNIEnv* env = MaybeAttachCurrentThread(); return insertFileInternal(env, media_provider_object_, mid_insert_file_, path, uid); } int MediaProviderWrapper::DeleteFile(const string& path, uid_t uid) { if (uid == ROOT_UID) { int res = unlink(path.c_str()); return res; } JNIEnv* env = MaybeAttachCurrentThread(); return deleteFileInternal(env, media_provider_object_, mid_delete_file_, path, uid); } std::unique_ptr MediaProviderWrapper::OnFileOpen(const string& path, const string& io_path, uid_t uid, pid_t tid, int transforms_reason, bool for_write, bool redact, bool log_transforms_metrics) { JNIEnv* env = MaybeAttachCurrentThread(); if (shouldBypassMediaProvider(uid)) { return std::make_unique(0, uid, 0 /* transforms_uid */, new RedactionInfo()); } ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); ScopedLocalRef j_io_path(env, env->NewStringUTF(io_path.c_str())); ScopedLocalRef j_res_file_open_object( env, env->CallObjectMethod(media_provider_object_, mid_on_file_open_, j_path.get(), j_io_path.get(), uid, tid, transforms_reason, for_write, redact, log_transforms_metrics)); if (CheckForJniException(env)) { return nullptr; } int status = env->GetIntField(j_res_file_open_object.get(), fid_file_open_status_); int original_uid = env->GetIntField(j_res_file_open_object.get(), fid_file_open_uid_); int transforms_uid = env->GetIntField(j_res_file_open_object.get(), fid_file_open_transforms_uid_); if (redact) { ScopedLocalRef redaction_ranges_local_ref( env, static_cast(env->GetObjectField(j_res_file_open_object.get(), fid_file_open_redaction_ranges_))); ScopedLongArrayRO redaction_ranges(env, redaction_ranges_local_ref.get()); std::unique_ptr ri; if (redaction_ranges.size() % 2) { LOG(ERROR) << "Error while calculating redaction ranges: array length is uneven"; } else if (redaction_ranges.size() > 0) { ri = std::make_unique(redaction_ranges.size() / 2, redaction_ranges.get()); } else { // No ranges to redact ri = std::make_unique(); } return std::make_unique(status, original_uid, transforms_uid, ri.release()); } else { return std::make_unique(status, original_uid, transforms_uid, new RedactionInfo()); } } int MediaProviderWrapper::IsCreatingDirAllowed(const string& path, uid_t uid) { if (shouldBypassMediaProvider(uid)) { return 0; } JNIEnv* env = MaybeAttachCurrentThread(); return isMkdirOrRmdirAllowedInternal(env, media_provider_object_, mid_is_mkdir_or_rmdir_allowed_, path, uid, /*forCreate*/ true); } int MediaProviderWrapper::IsDeletingDirAllowed(const string& path, uid_t uid) { if (shouldBypassMediaProvider(uid)) { return 0; } JNIEnv* env = MaybeAttachCurrentThread(); return isMkdirOrRmdirAllowedInternal(env, media_provider_object_, mid_is_mkdir_or_rmdir_allowed_, path, uid, /*forCreate*/ false); } std::vector> MediaProviderWrapper::GetDirectoryEntries( uid_t uid, const string& path, DIR* dirp) { // Default value in case JNI thread was being terminated std::vector> res; if (shouldBypassMediaProvider(uid)) { addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res); return res; } JNIEnv* env = MaybeAttachCurrentThread(); res = getFilesInDirectoryInternal(env, media_provider_object_, mid_get_files_in_dir_, uid, path); const int res_size = res.size(); if (res_size && res[0]->d_name[0] == '/') { // Path is unknown to MediaProvider, get files and directories from lower file system. res.resize(0); addDirectoryEntriesFromLowerFs(dirp, /* filter */ nullptr, &res); } else if (res_size == 0 || !res[0]->d_name.empty()) { // add directory names from lower file system. addDirectoryEntriesFromLowerFs(dirp, /* filter */ &isDirectory, &res); } return res; } int MediaProviderWrapper::IsOpendirAllowed(const string& path, uid_t uid, bool forWrite) { if (shouldBypassMediaProvider(uid)) { return 0; } JNIEnv* env = MaybeAttachCurrentThread(); return isOpendirAllowedInternal(env, media_provider_object_, mid_is_opendir_allowed_, path, uid, forWrite); } bool MediaProviderWrapper::isUidAllowedAccessToDataOrObbPath(uid_t uid, const string& path) { if (shouldBypassMediaProvider(uid)) { return true; } JNIEnv* env = MaybeAttachCurrentThread(); return isUidAllowedAccessToDataOrObbPathInternal( env, media_provider_object_, mid_is_uid_allowed_access_to_data_or_obb_path_, uid, path); } int MediaProviderWrapper::Rename(const string& old_path, const string& new_path, uid_t uid) { // Rename from SHELL_UID should go through MediaProvider to update database rows, so only bypass // MediaProvider for ROOT_UID. if (uid == ROOT_UID) { int res = rename(old_path.c_str(), new_path.c_str()); if (res != 0) res = -errno; return res; } JNIEnv* env = MaybeAttachCurrentThread(); return renameInternal(env, media_provider_object_, mid_rename_, old_path, new_path, uid); } void MediaProviderWrapper::OnFileCreated(const string& path) { JNIEnv* env = MaybeAttachCurrentThread(); return onFileCreatedInternal(env, media_provider_object_, mid_on_file_created_, path); } bool MediaProviderWrapper::ShouldAllowLookup(uid_t uid, int path_user_id) { JNIEnv* env = MaybeAttachCurrentThread(); bool res = env->CallBooleanMethod(media_provider_object_, mid_should_allow_lookup_, uid, path_user_id); if (CheckForJniException(env)) { return false; } return res; } bool MediaProviderWrapper::IsAppCloneUser(uid_t userId) { JNIEnv* env = MaybeAttachCurrentThread(); bool res = env->CallBooleanMethod(media_provider_object_, mid_is_app_clone_user_, userId); if (CheckForJniException(env)) { return false; } return res; } std::unique_ptr MediaProviderWrapper::FileLookup(const std::string& path, uid_t uid, pid_t tid) { JNIEnv* env = MaybeAttachCurrentThread(); ScopedLocalRef j_path(env, env->NewStringUTF(path.c_str())); ScopedLocalRef j_res_file_lookup_object( env, env->CallObjectMethod(media_provider_object_, mid_file_lookup_, j_path.get(), uid, tid)); if (CheckForJniException(env)) { return nullptr; } int transforms = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_); int transforms_reason = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_reason_); int original_uid = env->GetIntField(j_res_file_lookup_object.get(), fid_file_lookup_uid_); bool transforms_complete = env->GetBooleanField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_complete_); bool transforms_supported = env->GetBooleanField(j_res_file_lookup_object.get(), fid_file_lookup_transforms_supported_); ScopedLocalRef j_io_path( env, (jstring)env->GetObjectField(j_res_file_lookup_object.get(), fid_file_lookup_io_path_)); ScopedUtfChars j_io_path_utf(env, j_io_path.get()); std::unique_ptr file_lookup_result = std::make_unique( transforms, transforms_reason, original_uid, transforms_complete, transforms_supported, string(j_io_path_utf.c_str())); return file_lookup_result; } bool MediaProviderWrapper::Transform(const std::string& src, const std::string& dst, int transforms, int transforms_reason, uid_t read_uid, uid_t open_uid, uid_t transforms_uid) { JNIEnv* env = MaybeAttachCurrentThread(); ScopedLocalRef j_src(env, env->NewStringUTF(src.c_str())); ScopedLocalRef j_dst(env, env->NewStringUTF(dst.c_str())); bool res = env->CallBooleanMethod(media_provider_object_, mid_transform_, j_src.get(), j_dst.get(), transforms, transforms_reason, read_uid, open_uid, transforms_uid); if (CheckForJniException(env)) { return false; } return res; } /*****************************************************************************************/ /******************************** Private member functions *******************************/ /*****************************************************************************************/ /** * Finds MediaProvider method and adds it to methods map so it can be quickly called later. */ jmethodID MediaProviderWrapper::CacheMethod(JNIEnv* env, const char method_name[], const char signature[], bool is_static) { jmethodID mid; string actual_method_name(method_name); actual_method_name.append("ForFuse"); if (is_static) { mid = env->GetStaticMethodID(media_provider_class_, actual_method_name.c_str(), signature); } else { mid = env->GetMethodID(media_provider_class_, actual_method_name.c_str(), signature); } if (!mid) { LOG(FATAL) << "Error caching method: " << method_name << signature; } return mid; } void MediaProviderWrapper::DetachThreadFunction(void* unused) { int detach = gJavaVm->DetachCurrentThread(); CHECK_EQ(detach, 0); } JNIEnv* MediaProviderWrapper::MaybeAttachCurrentThread() { // We could use pthread_getspecific here as that's likely quicker but // that would result in wrong behaviour for threads that don't need to // be attached (e.g, those that were created in managed code). JNIEnv* env = nullptr; if (gJavaVm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_4) == JNI_OK) { return env; } // This thread is currently unattached, so it must not have any TLS // value. Note that we don't really care about the actual value we store // in TLS -- we only care about the value destructor being called, which // will happen as long as the key is not null. CHECK(pthread_getspecific(gJniEnvKey) == nullptr); CHECK_EQ(gJavaVm->AttachCurrentThread(&env, nullptr), 0); CHECK(env != nullptr); pthread_setspecific(gJniEnvKey, env); return env; } } // namespace fuse } // namespace mediaprovider