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 19 #include <android-base/file.h> 20 #include <android-base/logging.h> 21 #include <android-base/stringprintf.h> 22 #include <android-base/strings.h> 23 #include <openssl/sha.h> 24 #include <filesystem> 25 #include <fstream> 26 #include <sstream> 27 #include <unordered_set> 28 29 #include "apex_constants.h" 30 #include "apex_file.h" 31 #include "string_log.h" 32 33 using android::base::ErrnoError; 34 using android::base::Error; 35 using android::base::Result; 36 using ::apex::proto::ApexManifest; 37 38 namespace android { 39 namespace apex { 40 namespace shim { 41 42 namespace fs = std::filesystem; 43 44 namespace { 45 46 static constexpr const char* kApexCtsShimPackage = "com.android.apex.cts.shim"; 47 static constexpr const char* kHashFilePath = "etc/hash.txt"; 48 static constexpr const int kBufSize = 1024; 49 static constexpr const fs::perms kForbiddenFilePermissions = 50 fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec; 51 static constexpr const char* kExpectedCtsShimFiles[] = { 52 "apex_manifest.json", 53 "apex_manifest.pb", 54 "etc/hash.txt", 55 "app/CtsShim/CtsShim.apk", 56 "app/CtsShimTargetPSdk/CtsShimTargetPSdk.apk", 57 "priv-app/CtsShimPriv/CtsShimPriv.apk", 58 }; 59 60 Result<std::string> CalculateSha512(const std::string& path) { 61 LOG(DEBUG) << "Calculating SHA512 of " << path; 62 SHA512_CTX ctx; 63 SHA512_Init(&ctx); 64 std::ifstream apex(path, std::ios::binary); 65 if (apex.bad()) { 66 return Error() << "Failed to open " << path; 67 } 68 char buf[kBufSize]; 69 while (!apex.eof()) { 70 apex.read(buf, kBufSize); 71 if (apex.bad()) { 72 return Error() << "Failed to read " << path; 73 } 74 int bytes_read = apex.gcount(); 75 SHA512_Update(&ctx, buf, bytes_read); 76 } 77 uint8_t hash[SHA512_DIGEST_LENGTH]; 78 SHA512_Final(hash, &ctx); 79 std::stringstream ss; 80 ss << std::hex; 81 for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { 82 ss << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]); 83 } 84 return ss.str(); 85 } 86 87 Result<std::vector<std::string>> GetAllowedHashes(const std::string& path) { 88 using android::base::ReadFileToString; 89 using android::base::StringPrintf; 90 const std::string& file_path = 91 StringPrintf("%s/%s", path.c_str(), kHashFilePath); 92 LOG(DEBUG) << "Reading SHA512 from " << file_path; 93 std::string hash; 94 if (!ReadFileToString(file_path, &hash, false /* follows symlinks */)) { 95 return ErrnoError() << "Failed to read " << file_path; 96 } 97 std::vector<std::string> allowed_hashes = android::base::Split(hash, "\n"); 98 auto system_shim_hash = CalculateSha512( 99 StringPrintf("%s/%s", kApexPackageSystemDir, shim::kSystemShimApexName)); 100 if (!system_shim_hash.ok()) { 101 return system_shim_hash.error(); 102 } 103 allowed_hashes.push_back(std::move(*system_shim_hash)); 104 return allowed_hashes; 105 } 106 } // namespace 107 108 bool IsShimApex(const ApexFile& apex_file) { 109 return apex_file.GetManifest().name() == kApexCtsShimPackage; 110 } 111 112 Result<void> ValidateShimApex(const std::string& mount_point, 113 const ApexFile& apex_file) { 114 LOG(DEBUG) << "Validating shim apex " << mount_point; 115 const ApexManifest& manifest = apex_file.GetManifest(); 116 if (!manifest.preinstallhook().empty() || 117 !manifest.postinstallhook().empty()) { 118 return Errorf("Shim apex is not allowed to have pre or post install hooks"); 119 } 120 std::error_code ec; 121 std::unordered_set<std::string> expected_files; 122 for (auto file : kExpectedCtsShimFiles) { 123 expected_files.insert(file); 124 } 125 126 auto iter = fs::recursive_directory_iterator(mount_point, ec); 127 // Unfortunately fs::recursive_directory_iterator::operator++ can throw an 128 // exception, which means that it's impossible to use range-based for loop 129 // here. 130 while (iter != fs::end(iter)) { 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(); 155 } 156 iter = iter.increment(ec); 157 if (ec) { 158 return Error() << "Failed to scan " << mount_point << " : " 159 << ec.message(); 160 } 161 } 162 163 return {}; 164 } 165 166 Result<void> ValidateUpdate(const std::string& system_apex_path, 167 const std::string& new_apex_path) { 168 LOG(DEBUG) << "Validating update of shim apex to " << new_apex_path 169 << " using system shim apex " << system_apex_path; 170 auto allowed = GetAllowedHashes(system_apex_path); 171 if (!allowed.ok()) { 172 return allowed.error(); 173 } 174 auto actual = CalculateSha512(new_apex_path); 175 if (!actual.ok()) { 176 return actual.error(); 177 } 178 auto it = std::find(allowed->begin(), allowed->end(), *actual); 179 if (it == allowed->end()) { 180 return Error() << new_apex_path << " has unexpected SHA512 hash " 181 << *actual; 182 } 183 return {}; 184 } 185 186 } // namespace shim 187 } // namespace apex 188 } // namespace android 189