Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 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 | #include "apex_shim.h" |
| 18 | |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 19 | #include <android-base/file.h> |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 20 | #include <android-base/logging.h> |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 21 | #include <android-base/stringprintf.h> |
| 22 | #include <android-base/strings.h> |
| 23 | #include <openssl/sha.h> |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 24 | #include <filesystem> |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 25 | #include <fstream> |
| 26 | #include <sstream> |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 27 | #include <unordered_set> |
| 28 | |
Nikita Ioffe | c367f31 | 2019-07-27 15:57:35 +0100 | [diff] [blame] | 29 | #include "apex_constants.h" |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 30 | #include "apex_file.h" |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 31 | #include "string_log.h" |
| 32 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 33 | using android::base::ErrnoError; |
| 34 | using android::base::Error; |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 35 | using android::base::Result; |
| 36 | |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 37 | namespace android { |
| 38 | namespace apex { |
| 39 | namespace shim { |
| 40 | |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 41 | namespace fs = std::filesystem; |
| 42 | |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 43 | namespace { |
| 44 | |
Nikita Ioffe | d9a25d4 | 2019-03-26 01:37:03 +0000 | [diff] [blame] | 45 | static constexpr const char* kApexCtsShimPackage = "com.android.apex.cts.shim"; |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 46 | static constexpr const char* kHashFilePath = "etc/hash.txt"; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 47 | static constexpr const int kBufSize = 1024; |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 48 | static constexpr const fs::perms kForbiddenFilePermissions = |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 49 | fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec; |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 50 | static constexpr const char* kExpectedCtsShimFiles[] = { |
| 51 | "apex_manifest.json", |
| 52 | "apex_manifest.pb", |
| 53 | "etc/hash.txt", |
Dario Freni | 3975cc7 | 2020-04-23 12:34:30 +0100 | [diff] [blame] | 54 | "app/CtsShim/CtsShim.apk", |
| 55 | "app/CtsShimTargetPSdk/CtsShimTargetPSdk.apk", |
| 56 | "priv-app/CtsShimPriv/CtsShimPriv.apk", |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 57 | }; |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 58 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 59 | Result<std::string> CalculateSha512(const std::string& path) { |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 60 | LOG(DEBUG) << "Calculating SHA512 of " << path; |
| 61 | SHA512_CTX ctx; |
| 62 | SHA512_Init(&ctx); |
| 63 | std::ifstream apex(path, std::ios::binary); |
| 64 | if (apex.bad()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 65 | return Error() << "Failed to open " << path; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 66 | } |
| 67 | char buf[kBufSize]; |
| 68 | while (!apex.eof()) { |
| 69 | apex.read(buf, kBufSize); |
| 70 | if (apex.bad()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 71 | return Error() << "Failed to read " << path; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 72 | } |
| 73 | int bytes_read = apex.gcount(); |
| 74 | SHA512_Update(&ctx, buf, bytes_read); |
| 75 | } |
| 76 | uint8_t hash[SHA512_DIGEST_LENGTH]; |
| 77 | SHA512_Final(hash, &ctx); |
| 78 | std::stringstream ss; |
| 79 | ss << std::hex; |
| 80 | for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { |
| 81 | ss << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]); |
| 82 | } |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 83 | return ss.str(); |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 84 | } |
| 85 | |
Nikita Ioffe | c367f31 | 2019-07-27 15:57:35 +0100 | [diff] [blame] | 86 | Result<std::vector<std::string>> GetAllowedHashes(const std::string& path) { |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 87 | using android::base::ReadFileToString; |
| 88 | using android::base::StringPrintf; |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 89 | const std::string& file_path = |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 90 | StringPrintf("%s/%s", path.c_str(), kHashFilePath); |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 91 | LOG(DEBUG) << "Reading SHA512 from " << file_path; |
| 92 | std::string hash; |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 93 | if (!ReadFileToString(file_path, &hash, false /* follows symlinks */)) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 94 | return ErrnoError() << "Failed to read " << file_path; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 95 | } |
Nikita Ioffe | c367f31 | 2019-07-27 15:57:35 +0100 | [diff] [blame] | 96 | std::vector<std::string> allowed_hashes = android::base::Split(hash, "\n"); |
| 97 | auto system_shim_hash = CalculateSha512( |
| 98 | StringPrintf("%s/%s", kApexPackageSystemDir, shim::kSystemShimApexName)); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 99 | if (!system_shim_hash.ok()) { |
Nikita Ioffe | c367f31 | 2019-07-27 15:57:35 +0100 | [diff] [blame] | 100 | return system_shim_hash.error(); |
| 101 | } |
| 102 | allowed_hashes.push_back(std::move(*system_shim_hash)); |
| 103 | return allowed_hashes; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 104 | } |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 105 | } // namespace |
| 106 | |
| 107 | bool IsShimApex(const ApexFile& apex_file) { |
| 108 | return apex_file.GetManifest().name() == kApexCtsShimPackage; |
| 109 | } |
| 110 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 111 | Result<void> ValidateShimApex(const std::string& mount_point, |
| 112 | const ApexFile& apex_file) { |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 113 | LOG(DEBUG) << "Validating shim apex " << mount_point; |
Nikita Ioffe | 69a1bc5 | 2019-03-29 14:55:29 +0000 | [diff] [blame] | 114 | const ApexManifest& manifest = apex_file.GetManifest(); |
| 115 | if (!manifest.preinstallhook().empty() || |
| 116 | !manifest.postinstallhook().empty()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 117 | return Errorf("Shim apex is not allowed to have pre or post install hooks"); |
Nikita Ioffe | 69a1bc5 | 2019-03-29 14:55:29 +0000 | [diff] [blame] | 118 | } |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 119 | std::error_code ec; |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 120 | std::unordered_set<std::string> expected_files; |
| 121 | for (auto file : kExpectedCtsShimFiles) { |
| 122 | expected_files.insert(file); |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 123 | } |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 124 | |
| 125 | auto iter = fs::recursive_directory_iterator(mount_point, ec); |
| 126 | // Unfortunately fs::recursive_directory_iterator::operator++ can throw an |
| 127 | // exception, which means that it's impossible to use range-based for loop |
| 128 | // here. |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 129 | // TODO: wrap into a non-throwing iterator to support range-based for loop. |
| 130 | while (iter != fs::end(iter)) { |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 131 | auto path = iter->path(); |
| 132 | // Resolve the mount point to ensure any trailing slash is removed. |
| 133 | auto resolved_mount_point = fs::path(mount_point).string(); |
| 134 | auto local_path = path.string().substr(resolved_mount_point.length() + 1); |
| 135 | fs::file_status status = iter->status(ec); |
| 136 | |
| 137 | if (fs::is_symlink(status)) { |
| 138 | return Error() |
| 139 | << "Shim apex is not allowed to contain symbolic links, found " |
| 140 | << path; |
| 141 | } else if (fs::is_regular_file(status)) { |
| 142 | if ((status.permissions() & kForbiddenFilePermissions) != |
| 143 | fs::perms::none) { |
| 144 | return Error() << path << " has illegal permissions"; |
| 145 | } |
| 146 | auto ex = expected_files.find(local_path); |
| 147 | if (ex != expected_files.end()) { |
| 148 | expected_files.erase(local_path); |
| 149 | } else { |
| 150 | return Error() << path << " is an unexpected file inside the shim apex"; |
| 151 | } |
| 152 | } else if (!fs::is_directory(status)) { |
| 153 | // If this is not a symlink, a file or a directory, fail. |
| 154 | return Error() << "Unexpected file entry in shim apex: " << iter->path(); |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 155 | } |
| 156 | iter = iter.increment(ec); |
| 157 | if (ec) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 158 | return Error() << "Failed to scan " << mount_point << " : " |
| 159 | << ec.message(); |
Nikita Ioffe | 2cbca0e | 2019-03-27 19:12:11 +0000 | [diff] [blame] | 160 | } |
| 161 | } |
Dario Freni | 9b813b5 | 2019-11-18 21:52:14 +0000 | [diff] [blame] | 162 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 163 | return {}; |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 164 | } |
| 165 | |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 166 | Result<void> ValidateUpdate(const std::string& system_apex_path, |
| 167 | const std::string& new_apex_path) { |
Nikita Ioffe | dcb9669 | 2019-04-11 16:09:48 +0100 | [diff] [blame] | 168 | LOG(DEBUG) << "Validating update of shim apex to " << new_apex_path |
| 169 | << " using system shim apex " << system_apex_path; |
Nikita Ioffe | c367f31 | 2019-07-27 15:57:35 +0100 | [diff] [blame] | 170 | auto allowed = GetAllowedHashes(system_apex_path); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 171 | if (!allowed.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 172 | return allowed.error(); |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 173 | } |
Nikita Ioffe | 98054d7 | 2019-04-03 14:18:22 +0100 | [diff] [blame] | 174 | auto actual = CalculateSha512(new_apex_path); |
Bernie Innocenti | d04d5d0 | 2020-02-06 22:01:51 +0900 | [diff] [blame] | 175 | if (!actual.ok()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 176 | return actual.error(); |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 177 | } |
Nikita Ioffe | 98054d7 | 2019-04-03 14:18:22 +0100 | [diff] [blame] | 178 | auto it = std::find(allowed->begin(), allowed->end(), *actual); |
| 179 | if (it == allowed->end()) { |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 180 | return Error() << new_apex_path << " has unexpected SHA512 hash " |
| 181 | << *actual; |
Nikita Ioffe | 6d6c74f | 2019-03-18 14:54:38 +0000 | [diff] [blame] | 182 | } |
Mohammad Samiul Islam | bd6ab0f | 2019-06-20 15:55:27 +0100 | [diff] [blame] | 183 | return {}; |
Nikita Ioffe | 4db13a5 | 2019-03-14 23:26:08 +0000 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | } // namespace shim |
| 187 | } // namespace apex |
| 188 | } // namespace android |