/* * Copyright (c) 2022 Huawei Device Co., Ltd. * 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. */ #define MLOG_TAG "MediaPrivacyManager" #include "media_privacy_manager.h" #include #include #include #include #include #include #include #include "epfs.h" #include "image_source.h" #include "media_container_types.h" #include "media_file_utils.h" #include "media_log.h" #include "medialibrary_bundle_manager.h" #include "medialibrary_errno.h" #include "medialibrary_type_const.h" #include "media_exif.h" #include "media_library_manager.h" #include "medialibrary_bundle_manager.h" #include "medialibrary_urisensitive_operations.h" #include "medialibrary_tracer.h" #include "parameters.h" #include "permission_utils.h" using namespace std; using PrivacyRanges = vector>; namespace OHOS { namespace Media { constexpr uint32_t E_NO_EXIF = 1; constexpr uint32_t E_NO_PRIVACY_EXIF_TAG = 2; const std::vector ALL_SENSITIVE_EXIF = { PHOTO_DATA_IMAGE_GPS_LATITUDE, PHOTO_DATA_IMAGE_GPS_LONGITUDE, PHOTO_DATA_IMAGE_GPS_TIME_STAMP, PHOTO_DATA_IMAGE_GPS_DATE_STAMP, PHOTO_DATA_IMAGE_GPS_ALTITUDE, PHOTO_DATA_IMAGE_GPS_VERSION_ID, PHOTO_DATA_IMAGE_MAKE, PHOTO_DATA_IMAGE_MODEL, PHOTO_DATA_IMAGE_SOFTWARE, PHOTO_DATA_IMAGE_DATE_TIME, PHOTO_DATA_IMAGE_EXPOSURE_TIME, PHOTO_DATA_IMAGE_F_NUMBER, PHOTO_DATA_IMAGE_EXPOSURE_PROGRAM, PHOTO_DATA_IMAGE_STANDARD_OUTPUT_SENSITIVITY, PHOTO_DATA_IMAGE_PHOTOGRAPHIC_SENSITIVITY, PHOTO_DATA_IMAGE_DATE_TIME_ORIGINAL, PHOTO_DATA_IMAGE_DATE_TIME_ORIGINAL_FOR_MEDIA, PHOTO_DATA_IMAGE_DATE_TIME_DIGITIZED, PHOTO_DATA_IMAGE_EXPOSURE_BIAS_VALUE, PHOTO_DATA_IMAGE_METERING_MODE, PHOTO_DATA_IMAGE_LIGHT_SOURCE, PHOTO_DATA_IMAGE_FLASH, PHOTO_DATA_IMAGE_FOCAL_LENGTH, PHOTO_DATA_IMAGE_EXPOSURE_MODE, PHOTO_DATA_IMAGE_WHITE_BALANCE, PHOTO_DATA_IMAGE_DIGITAL_ZOOM_RATIO, PHOTO_DATA_IMAGE_FOCAL_LENGTH_IN_35_MM_FILM }; const std::vector GEOGRAPHIC_LOCATION_EXIF = { PHOTO_DATA_IMAGE_GPS_LATITUDE, PHOTO_DATA_IMAGE_GPS_LONGITUDE, PHOTO_DATA_IMAGE_GPS_TIME_STAMP, PHOTO_DATA_IMAGE_GPS_DATE_STAMP, PHOTO_DATA_IMAGE_GPS_ALTITUDE, PHOTO_DATA_IMAGE_GPS_VERSION_ID }; const std::vector SHOOTING_PARAM_EXIF = { PHOTO_DATA_IMAGE_MAKE, PHOTO_DATA_IMAGE_MODEL, PHOTO_DATA_IMAGE_SOFTWARE, PHOTO_DATA_IMAGE_DATE_TIME, PHOTO_DATA_IMAGE_EXPOSURE_TIME, PHOTO_DATA_IMAGE_F_NUMBER, PHOTO_DATA_IMAGE_EXPOSURE_PROGRAM, PHOTO_DATA_IMAGE_PHOTOGRAPHIC_SENSITIVITY, PHOTO_DATA_IMAGE_DATE_TIME_ORIGINAL, PHOTO_DATA_IMAGE_DATE_TIME_DIGITIZED, PHOTO_DATA_IMAGE_EXPOSURE_BIAS_VALUE, PHOTO_DATA_IMAGE_METERING_MODE, PHOTO_DATA_IMAGE_LIGHT_SOURCE, PHOTO_DATA_IMAGE_FLASH, PHOTO_DATA_IMAGE_FOCAL_LENGTH, PHOTO_DATA_IMAGE_EXPOSURE_MODE, PHOTO_DATA_IMAGE_WHITE_BALANCE, PHOTO_DATA_IMAGE_DIGITAL_ZOOM_RATIO, PHOTO_DATA_IMAGE_FOCAL_LENGTH_IN_35_MM_FILM }; MediaPrivacyManager::MediaPrivacyManager(const string &path, const string &mode, const string &fileId) : path_(path), mode_(mode), fileId_(fileId) {} MediaPrivacyManager::~MediaPrivacyManager() {} const unordered_map PRIVACY_PERMISSION_MAP = { { PrivacyType::PRIVACY_LOCATION, PERMISSION_NAME_MEDIA_LOCATION }, }; const vector EXIF_SUPPORTED_EXTENSION = { IMAGE_CONTAINER_TYPE_JPG, IMAGE_CONTAINER_TYPE_JPEG, IMAGE_CONTAINER_TYPE_JPE, IMAGE_CONTAINER_TYPE_PNG, IMAGE_CONTAINER_TYPE_WEBP, IMAGE_CONTAINER_TYPE_DNG, IMAGE_CONTAINER_TYPE_HEIC, }; static bool IsTargetExtension(const string &path) { const string ext = MediaFileUtils::GetExtensionFromPath(path); return find(EXIF_SUPPORTED_EXTENSION.begin(), EXIF_SUPPORTED_EXTENSION.end(), ext) != EXIF_SUPPORTED_EXTENSION.end(); } static bool IsWriteMode(const string &mode) { return mode.find(MEDIA_FILEMODE_WRITEONLY) != string::npos; } static bool CheckFsMounted(const string &fsType, const string &mountPoint) { struct mntent mountEntry; constexpr uint32_t mntEntrySize = 1024; char entryStr[mntEntrySize] = {0}; FILE *mountTable = setmntent("/proc/mounts", "r"); if (mountTable == nullptr) { MEDIA_ERR_LOG("Failed to get mount table, errno:%{public}d", errno); return false; } do { struct mntent *mnt = getmntent_r(mountTable, &mountEntry, entryStr, sizeof(entryStr)); if (mnt == nullptr) { endmntent(mountTable); break; } if ((mountEntry.mnt_type != nullptr) && (mountEntry.mnt_dir != nullptr) && (strcmp(mountEntry.mnt_type, fsType.c_str()) == 0) && (strcmp(mountEntry.mnt_dir, mountPoint.c_str()) == 0)) { endmntent(mountTable); return true; } } while (true); return false; } static int32_t BindFilterProxyFdToOrigin(const int32_t originFd, int32_t &proxyFd) { int ret = ioctl(proxyFd, IOC_SET_ORIGIN_FD, &originFd); if (ret < 0) { MEDIA_ERR_LOG("Failed to set origin fd: %{public}d to filter proxy fd: %{public}d, error: %{public}d", originFd, proxyFd, errno); return ret; } return ret; } static int32_t SendRangesToIoctl(const int32_t originFd, const int32_t proxyFd, const PrivacyRanges &rans) { FilterProxyRanges *ranges = (FilterProxyRanges *)malloc(sizeof(*ranges) + sizeof(ranges->range[0]) * rans.size()); if (ranges == nullptr) { MEDIA_ERR_LOG("Failed to malloc ranges, errno: %{public}d", errno); return -ENOMEM; } ranges->size = static_cast(rans.size()); ranges->reserved = 0; for (size_t i = 0; i < rans.size(); i++) { // first: offset, second: end ranges->range[i].begin = static_cast(rans[i].first); ranges->range[i].end = static_cast(rans[i].second); } int err = ioctl(proxyFd, IOC_SET_FILTER_PROXY_RANGE, ranges); if (err < 0) { MEDIA_ERR_LOG("Failed to set ranges to fd: %{public}d, error: %{public}d", proxyFd, errno); } free(ranges); return err; } /* Caller is responsible to close the returned fd */ static int32_t OpenOriginFd(const string &path, const string &mode) { MediaLibraryTracer tracer; tracer.Start("MediaPrivacyManager::OpenOriginFd"); string clientBundle = MediaLibraryBundleManager::GetInstance()->GetClientBundleName(); if (clientBundle.empty()) { MEDIA_DEBUG_LOG("clientBundleName is empty"); } return MediaFileUtils::OpenFile(path, mode, clientBundle); } /* * Read to the returned @filterProxyFd will redirect to the file specified by @path, but the privacy ranges(@ranges) in * read buffer will be filtered out and filled with 0. * * Caller is responsible to close the returned @filterProxyFd. */ static int32_t OpenFilterProxyFd(const string &path, const string &mode, const PrivacyRanges &ranges) { MediaLibraryTracer tracer; tracer.Start("MediaPrivacyManager::OpenFilterProxyFd"); if (!CheckFsMounted(FS_TYPE_EPFS, EPFS_MOUNT_POINT)) { MEDIA_INFO_LOG("Epfs is currently not supported yet"); return OpenOriginFd(path, mode); } int32_t originFd = open(path.c_str(), O_RDONLY); if (originFd < 0) { MEDIA_ERR_LOG("Failed to open file, errno: %{public}d, path: %{private}s", errno, path.c_str()); return originFd; } constexpr mode_t epfsFileMode = 0400; // filterProxyFd_ will be returned to user, so there is no need to close it here. int32_t filterProxyFd = open(EPFS_MOUNT_POINT.c_str(), O_TMPFILE | O_RDWR, epfsFileMode); if (filterProxyFd < 0) { MEDIA_ERR_LOG("Failed to open epfs, error: %{public}d", errno); close(originFd); return filterProxyFd; } int32_t ret = BindFilterProxyFdToOrigin(originFd, filterProxyFd); if (ret < 0) { close(originFd); close(filterProxyFd); return ret; } ret = SendRangesToIoctl(originFd, filterProxyFd, ranges); if (ret < 0) { close(originFd); close(filterProxyFd); return ret; } close(originFd); MEDIA_INFO_LOG("FilterProxyFd will be returned: %{private}d", filterProxyFd); return filterProxyFd; } static void ShowRanges(const PrivacyRanges &ranges) { for (auto range : ranges) { MEDIA_DEBUG_LOG("Range: [%{public}u, %{public}u)", range.first, range.second); } } static bool CmpMode(pair pairA, pair pairB) { return pairA.first < pairB.first; } static int32_t SortRangesAndCheck(PrivacyRanges &ranges) { if (ranges.empty()) { return E_SUCCESS; } uint32_t size = ranges.size(); if (size > PRIVACY_MAX_RANGES) { MEDIA_ERR_LOG("Privacy ranges size invalid: %{public}d", size); return -EINVAL; } sort(ranges.begin(), ranges.end(), CmpMode); const auto u_idx = unique(ranges.begin(), ranges.end()); ranges.erase(u_idx, ranges.end()); size = ranges.size(); if (ranges[0].first >= ranges[0].second) { MEDIA_ERR_LOG("Incorrect fileter ranges: begin(%{public}u) is not less than end(%{public}u)", ranges[0].first, ranges[0].second); return -EINVAL; } for (uint32_t i = 1; i < size; i++) { if ((ranges[i].first >= ranges[i].second) || (ranges[i].first < ranges[i - 1].second)) { MEDIA_ERR_LOG("Invalid ranges: [%{public}u, %{public}u), last range is [%{public}u, %{public}u)", ranges[i].first, ranges[i].second, ranges[i - 1].first, ranges[i - 1].second); return -EINVAL; } } ShowRanges(ranges); return E_SUCCESS; } static int32_t CollectRanges(const string &path, const HideSensitiveType &sensitiveType, PrivacyRanges &ranges) { if (sensitiveType == HideSensitiveType::NO_DESENSITIZE) { return E_SUCCESS; } SourceOptions opts; opts.formatHint = "image/jpeg"; uint32_t err = -1; auto imageSource = ImageSource::CreateImageSource(path, opts, err); if (imageSource == nullptr) { MEDIA_ERR_LOG("Failed to create image source, err: %{public}u", err); return -ENOMEM; } PrivacyRanges areas; std::vector exifKeys; switch (sensitiveType) { case HideSensitiveType::ALL_DESENSITIZE: err = imageSource->GetFilterArea(ALL_SENSITIVE_EXIF, areas); break; case HideSensitiveType::GEOGRAPHIC_LOCATION_DESENSITIZE: err = imageSource->GetFilterArea(GEOGRAPHIC_LOCATION_EXIF, areas); break; case HideSensitiveType::SHOOTING_PARAM_DESENSITIZE: err = imageSource->GetFilterArea(SHOOTING_PARAM_EXIF, areas); break; default: MEDIA_ERR_LOG("Invaild hide sensitive type %{public}d", sensitiveType); return E_SUCCESS; } if ((err != E_SUCCESS) && (err != E_NO_EXIF) && (err != E_NO_PRIVACY_EXIF_TAG)) { MEDIA_ERR_LOG("Failed to get privacy area with type %{public}d, err: %{public}u", sensitiveType, err); return E_ERR; } for (auto &range : areas) { ranges.insert(ranges.end(), std::make_pair(range.first, range.first + range.second)); } return E_SUCCESS; } /* * @path: [Input], the real path of the target file * @mode: [Input], the mode specified by user * @ranges: [Output], the privacy ranges of the target file * * The return value is listed below: * o Not a jpeg file: return success with empty ranges * o Write jpeg with no MEDIA_LOCATION: return permission denied * o Write jpeg with MEDIA_LOCATION: return success with empty ranges * o Read jpeg with no MEDIA_LOCATION: return success with privacy ranges if have any * o Read jpeg with MEDIA_LOCATION: return success with empty ranges * o Other cases: return negative error code. */ static int32_t GetPrivacyRanges(const string &path, const string &mode, const string &fileId, PrivacyRanges &ranges) { MediaLibraryTracer tracer; tracer.Start("MediaPrivacyManager::GetPrivacyRanges"); if (!IsTargetExtension(path)) { return E_SUCCESS; } if (fileId.empty()) { return E_SUCCESS; } if (mode.find('w') != string::npos) { return E_SUCCESS; } for (auto &item : PRIVACY_PERMISSION_MAP) { const string &perm = item.second; bool result = PermissionUtils::CheckCallerPermission(perm); if ((result == false) && (perm == PERMISSION_NAME_MEDIA_LOCATION) && IsWriteMode(mode)) { MEDIA_ERR_LOG("Write is not allowed if have no location permission"); return E_PERMISSION_DENIED; } if (result) { continue; } //collect ranges by hideSensitiveType string bundleName = MediaLibraryBundleManager::GetInstance()->GetClientBundleName(); string appId = PermissionUtils::GetAppIdByBundleName(bundleName); string appIdFile = UriSensitiveOperations::QueryAppId(fileId); if (appId == appIdFile) { continue; } HideSensitiveType sensitiveType = static_cast(UriSensitiveOperations::QuerySensitiveType(appId, fileId)); int32_t err = CollectRanges(path, sensitiveType, ranges); if (err < 0) { return err; } } return SortRangesAndCheck(ranges); } static bool IsDeveloperMediaTool() { if (!PermissionUtils::IsRootShell() && !PermissionUtils::IsHdcShell()) { MEDIA_DEBUG_LOG("Mediatool permission check failed: target is not root"); return false; } if (!OHOS::system::GetBoolParameter("const.security.developermode.state", true)) { MEDIA_DEBUG_LOG("Mediatool permission check failed: target is not in developer mode"); return false; } return true; } int32_t MediaPrivacyManager::Open() { int err = GetPrivacyRanges(path_, mode_, fileId_, ranges_); if (err < 0) { return err; } if (ranges_.size() > 0 && !IsDeveloperMediaTool()) { return OpenFilterProxyFd(path_, mode_, ranges_); } return OpenOriginFd(path_, mode_); } } // namespace Media } // namespace OHOS