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 <filesystem> 18 #include <fstream> 19 20 #include <asm-generic/errno-base.h> 21 #include <gmock/gmock.h> 22 #include <gtest/gtest.h> 23 #include <sched.h> 24 #include <sys/mount.h> 25 26 #include <android-base/errors.h> 27 #include <android-base/logging.h> 28 #include <android-base/macros.h> 29 #include <android-base/result.h> 30 #include <android-base/stringprintf.h> 31 #include <android-base/strings.h> 32 #include <android-base/unique_fd.h> 33 #include <android/apex/ApexInfo.h> 34 #include <android/apex/ApexSessionInfo.h> 35 #include <binder/IServiceManager.h> 36 #include <selinux/android.h> 37 38 #include "apex_file.h" 39 #include "session_state.pb.h" 40 41 #include "com_android_apex.h" 42 43 using android::base::Error; 44 using android::base::Result; 45 using apex::proto::SessionState; 46 47 namespace android { 48 namespace apex { 49 namespace testing { 50 51 using ::testing::AllOf; 52 using ::testing::Eq; 53 using ::testing::ExplainMatchResult; 54 using ::testing::Field; 55 using ::testing::Property; 56 57 template <typename T> 58 inline ::testing::AssertionResult IsOk(const android::base::Result<T>& result) { 59 if (result.ok()) { 60 return ::testing::AssertionSuccess() << " is Ok"; 61 } else { 62 return ::testing::AssertionFailure() << " failed with " << result.error(); 63 } 64 } 65 66 inline ::testing::AssertionResult IsOk(const android::binder::Status& status) { 67 if (status.isOk()) { 68 return ::testing::AssertionSuccess() << " is Ok"; 69 } else { 70 return ::testing::AssertionFailure() 71 << " failed with " << status.exceptionMessage().c_str(); 72 } 73 } 74 75 MATCHER_P(SessionInfoEq, other, "") { 76 return ExplainMatchResult( 77 AllOf( 78 Field("sessionId", &ApexSessionInfo::sessionId, Eq(other.sessionId)), 79 Field("isUnknown", &ApexSessionInfo::isUnknown, Eq(other.isUnknown)), 80 Field("isVerified", &ApexSessionInfo::isVerified, 81 Eq(other.isVerified)), 82 Field("isStaged", &ApexSessionInfo::isStaged, Eq(other.isStaged)), 83 Field("isActivated", &ApexSessionInfo::isActivated, 84 Eq(other.isActivated)), 85 Field("isRevertInProgress", &ApexSessionInfo::isRevertInProgress, 86 Eq(other.isRevertInProgress)), 87 Field("isActivationFailed", &ApexSessionInfo::isActivationFailed, 88 Eq(other.isActivationFailed)), 89 Field("isSuccess", &ApexSessionInfo::isSuccess, Eq(other.isSuccess)), 90 Field("isReverted", &ApexSessionInfo::isReverted, 91 Eq(other.isReverted)), 92 Field("isRevertFailed", &ApexSessionInfo::isRevertFailed, 93 Eq(other.isRevertFailed))), 94 arg, result_listener); 95 } 96 97 MATCHER_P(ApexInfoEq, other, "") { 98 return ExplainMatchResult( 99 AllOf(Field("moduleName", &ApexInfo::moduleName, Eq(other.moduleName)), 100 Field("modulePath", &ApexInfo::modulePath, Eq(other.modulePath)), 101 Field("preinstalledModulePath", &ApexInfo::preinstalledModulePath, 102 Eq(other.preinstalledModulePath)), 103 Field("versionCode", &ApexInfo::versionCode, Eq(other.versionCode)), 104 Field("isFactory", &ApexInfo::isFactory, Eq(other.isFactory)), 105 Field("isActive", &ApexInfo::isActive, Eq(other.isActive))), 106 arg, result_listener); 107 } 108 109 MATCHER_P(ApexFileEq, other, "") { 110 return ExplainMatchResult( 111 AllOf(Property("path", &ApexFile::GetPath, Eq(other.get().GetPath())), 112 Property("image_offset", &ApexFile::GetImageOffset, 113 Eq(other.get().GetImageOffset())), 114 Property("image_size", &ApexFile::GetImageSize, 115 Eq(other.get().GetImageSize())), 116 Property("fs_type", &ApexFile::GetFsType, 117 Eq(other.get().GetFsType())), 118 Property("public_key", &ApexFile::GetBundledPublicKey, 119 Eq(other.get().GetBundledPublicKey())), 120 Property("is_compressed", &ApexFile::IsCompressed, 121 Eq(other.get().IsCompressed()))), 122 arg, result_listener); 123 } 124 125 inline ApexSessionInfo CreateSessionInfo(int session_id) { 126 ApexSessionInfo info; 127 info.sessionId = session_id; 128 info.isUnknown = false; 129 info.isVerified = false; 130 info.isStaged = false; 131 info.isActivated = false; 132 info.isRevertInProgress = false; 133 info.isActivationFailed = false; 134 info.isSuccess = false; 135 info.isReverted = false; 136 info.isRevertFailed = false; 137 return info; 138 } 139 140 } // namespace testing 141 142 // Must be in apex::android namespace, otherwise gtest won't be able to find it. 143 inline void PrintTo(const ApexSessionInfo& session, std::ostream* os) { 144 *os << "apex_session: {\n"; 145 *os << " sessionId : " << session.sessionId << "\n"; 146 *os << " isUnknown : " << session.isUnknown << "\n"; 147 *os << " isVerified : " << session.isVerified << "\n"; 148 *os << " isStaged : " << session.isStaged << "\n"; 149 *os << " isActivated : " << session.isActivated << "\n"; 150 *os << " isActivationFailed : " << session.isActivationFailed << "\n"; 151 *os << " isSuccess : " << session.isSuccess << "\n"; 152 *os << " isReverted : " << session.isReverted << "\n"; 153 *os << " isRevertFailed : " << session.isRevertFailed << "\n"; 154 *os << "}"; 155 } 156 157 inline void PrintTo(const ApexInfo& apex, std::ostream* os) { 158 *os << "apex_info: {\n"; 159 *os << " moduleName : " << apex.moduleName << "\n"; 160 *os << " modulePath : " << apex.modulePath << "\n"; 161 *os << " preinstalledModulePath : " << apex.preinstalledModulePath << "\n"; 162 *os << " versionCode : " << apex.versionCode << "\n"; 163 *os << " isFactory : " << apex.isFactory << "\n"; 164 *os << " isActive : " << apex.isActive << "\n"; 165 *os << "}"; 166 } 167 168 inline Result<bool> CompareFiles(const std::string& filename1, 169 const std::string& filename2) { 170 std::ifstream file1(filename1, std::ios::binary); 171 std::ifstream file2(filename2, std::ios::binary); 172 173 if (file1.bad() || file2.bad()) { 174 return Error() << "Could not open one of the file"; 175 } 176 177 std::istreambuf_iterator<char> begin1(file1); 178 std::istreambuf_iterator<char> begin2(file2); 179 180 return std::equal(begin1, std::istreambuf_iterator<char>(), begin2); 181 } 182 183 inline android::base::Result<std::string> GetCurrentMountNamespace() { 184 std::string result; 185 if (!android::base::Readlink("/proc/self/ns/mnt", &result)) { 186 return android::base::ErrnoError() << "Failed to read /proc/self/ns/mnt"; 187 } 188 return result; 189 } 190 191 // A helper class to switch back to the original mount namespace of a process 192 // upon exiting current scope. 193 class MountNamespaceRestorer final { 194 public: 195 explicit MountNamespaceRestorer() { 196 original_namespace_.reset(open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC)); 197 if (original_namespace_.get() < 0) { 198 PLOG(ERROR) << "Failed to open /proc/self/ns/mnt"; 199 } 200 } 201 202 ~MountNamespaceRestorer() { 203 if (original_namespace_.get() != -1) { 204 if (setns(original_namespace_.get(), CLONE_NEWNS) == -1) { 205 PLOG(ERROR) << "Failed to switch back to " << original_namespace_.get(); 206 } 207 } 208 } 209 210 private: 211 android::base::unique_fd original_namespace_; 212 DISALLOW_COPY_AND_ASSIGN(MountNamespaceRestorer); 213 }; 214 215 inline std::vector<std::string> GetApexMounts() { 216 std::vector<std::string> apex_mounts; 217 std::string mount_info; 218 if (!android::base::ReadFileToString("/proc/self/mountinfo", &mount_info)) { 219 return apex_mounts; 220 } 221 for (const auto& line : android::base::Split(mount_info, "\n")) { 222 std::vector<std::string> tokens = android::base::Split(line, " "); 223 // line format: 224 // mnt_id parent_mnt_id major:minor source target option propagation_type 225 // ex) 33 260:19 / /apex rw,nosuid,nodev - 226 if (tokens.size() >= 7 && android::base::StartsWith(tokens[4], "/apex/")) { 227 apex_mounts.push_back(tokens[4]); 228 } 229 } 230 return apex_mounts; 231 } 232 233 // Sets up a test environment for unit testing logic around mounting/unmounting 234 // apexes. For examples of usage see apexd_test.cpp 235 inline android::base::Result<void> SetUpApexTestEnvironment() { 236 using android::base::ErrnoError; 237 238 // 1. Switch to new mount namespace. 239 if (unshare(CLONE_NEWNS) != 0) { 240 return ErrnoError() << "Failed to unshare"; 241 } 242 243 // 2. Make everything private, so that changes don't propagate. 244 if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) { 245 return ErrnoError() << "Failed to mount / as private"; 246 } 247 248 // 3. Unmount all apexes. This needs to happen in two phases: 249 // Note: unlike regular unmount flow in apexd, we don't destroy dm and loop 250 // devices, since that would've propagated outside of the test environment. 251 std::vector<std::string> apex_mounts = GetApexMounts(); 252 253 // 3a. First unmount all bind mounds (without @version_code). 254 for (const auto& mount : apex_mounts) { 255 if (mount.find('@') == std::string::npos) { 256 if (umount2(mount.c_str(), 0) != 0) { 257 return ErrnoError() << "Failed to unmount " << mount; 258 } 259 } 260 } 261 262 // 3.b Now unmount versioned mounts. 263 for (const auto& mount : apex_mounts) { 264 if (mount.find('@') != std::string::npos) { 265 if (umount2(mount.c_str(), 0) != 0) { 266 return ErrnoError() << "Failed to unmount " << mount; 267 } 268 } 269 } 270 271 static constexpr const char* kApexMountForTest = "/mnt/scratch/apex"; 272 273 // Clean up in case previous test left directory behind. 274 if (access(kApexMountForTest, F_OK) == 0) { 275 if (umount2(kApexMountForTest, MNT_FORCE | UMOUNT_NOFOLLOW) != 0) { 276 PLOG(WARNING) << "Failed to unmount " << kApexMountForTest; 277 } 278 if (rmdir(kApexMountForTest) != 0) { 279 return ErrnoError() << "Failed to rmdir " << kApexMountForTest; 280 } 281 } 282 283 // 4. Create an empty tmpfs that will substitute /apex in tests. 284 if (mkdir(kApexMountForTest, 0755) != 0) { 285 return ErrnoError() << "Failed to mkdir " << kApexMountForTest; 286 } 287 288 if (mount("tmpfs", kApexMountForTest, "tmpfs", 0, nullptr) == -1) { 289 return ErrnoError() << "Failed to mount " << kApexMountForTest; 290 } 291 292 // 5. Overlay it over /apex via bind mount. 293 if (mount(kApexMountForTest, "/apex", nullptr, MS_BIND, nullptr) == -1) { 294 return ErrnoError() << "Failed to bind mount " << kApexMountForTest 295 << " over /apex"; 296 } 297 298 // Just in case, run restorecon -R on /apex. 299 if (selinux_android_restorecon("/apex", SELINUX_ANDROID_RESTORECON_RECURSE) < 300 0) { 301 return android::base::ErrnoError() << "Failed to restorecon /apex"; 302 } 303 304 return {}; 305 } 306 307 } // namespace apex 308 } // namespace android 309 310 namespace com { 311 namespace android { 312 namespace apex { 313 314 namespace testing { 315 MATCHER_P(ApexInfoXmlEq, other, "") { 316 using ::testing::AllOf; 317 using ::testing::Eq; 318 using ::testing::ExplainMatchResult; 319 using ::testing::Field; 320 using ::testing::Property; 321 322 return ExplainMatchResult( 323 AllOf( 324 Property("moduleName", &ApexInfo::getModuleName, 325 Eq(other.getModuleName())), 326 Property("modulePath", &ApexInfo::getModulePath, 327 Eq(other.getModulePath())), 328 Property("preinstalledModulePath", 329 &ApexInfo::getPreinstalledModulePath, 330 Eq(other.getPreinstalledModulePath())), 331 Property("versionCode", &ApexInfo::getVersionCode, 332 Eq(other.getVersionCode())), 333 Property("isFactory", &ApexInfo::getIsFactory, 334 Eq(other.getIsFactory())), 335 Property("isActive", &ApexInfo::getIsActive, Eq(other.getIsActive())), 336 Property("lastUpdateMillis", &ApexInfo::getLastUpdateMillis, 337 Eq(other.getLastUpdateMillis()))), 338 arg, result_listener); 339 } 340 341 } // namespace testing 342 343 // Must be in com::android::apex namespace for gtest to pick it up. 344 inline void PrintTo(const ApexInfo& apex, std::ostream* os) { 345 *os << "apex_info: {\n"; 346 *os << " moduleName : " << apex.getModuleName() << "\n"; 347 *os << " modulePath : " << apex.getModulePath() << "\n"; 348 *os << " preinstalledModulePath : " << apex.getPreinstalledModulePath() 349 << "\n"; 350 *os << " versionCode : " << apex.getVersionCode() << "\n"; 351 *os << " isFactory : " << apex.getIsFactory() << "\n"; 352 *os << " isActive : " << apex.getIsActive() << "\n"; 353 *os << "}"; 354 } 355 356 } // namespace apex 357 } // namespace android 358 } // namespace com 359