diff options
-rw-r--r-- | Android.bp | 51 | ||||
-rw-r--r-- | OWNERS | 2 | ||||
-rw-r--r-- | fuzzers/GsiServiceFuzzer.cpp | 29 | ||||
-rw-r--r-- | gsi_service.cpp | 41 | ||||
-rw-r--r-- | gsi_service.h | 2 | ||||
-rw-r--r-- | gsi_tool.cpp | 78 | ||||
-rw-r--r-- | include/libgsi/libgsi.h | 2 | ||||
-rw-r--r-- | libgsid.cpp | 2 | ||||
-rw-r--r-- | partition_installer.cpp | 3 | ||||
-rw-r--r-- | tests/Android.bp | 17 | ||||
-rw-r--r-- | tests/DSUEndtoEndTest.java | 122 | ||||
-rw-r--r-- | tests/DsuGsiIntegrationTest.java | 9 | ||||
-rw-r--r-- | tests/DsuGsiToolTest.java | 107 | ||||
-rw-r--r-- | tests/dsu_gsi_tool_test.xml | 21 |
14 files changed, 401 insertions, 85 deletions
@@ -74,21 +74,19 @@ cc_library_headers { recovery_available: true, vendor_available: true, export_include_dirs: ["include"], + apex_available: [ + "//apex_available:anyapex", + "//apex_available:platform", + ], + min_sdk_version: "31", } -cc_binary { - name: "gsid", +cc_defaults { + name: "gsid_defaults", srcs: [ - "daemon.cpp", "gsi_service.cpp", "partition_installer.cpp", ], - required: [ - "mke2fs", - ], - init_rc: [ - "gsid.rc", - ], shared_libs: [ "libbase", "libbinder", @@ -124,6 +122,22 @@ cc_binary { local_include_dirs: ["include"], } +cc_binary { + name: "gsid", + defaults: [ + "gsid_defaults", + ], + srcs: [ + "daemon.cpp", + ], + required: [ + "mke2fs", + ], + init_rc: [ + "gsid.rc", + ], +} + aidl_interface { name: "gsi_aidl_interface", unstable: true, @@ -149,3 +163,22 @@ filegroup { ], path: "aidl", } + +cc_fuzz { + name: "gsi_service_fuzzer", + defaults: [ + "gsid_defaults", + "service_fuzzer_defaults", + "fuzzer_disable_leaks", + ], + srcs: [ + "fuzzers/GsiServiceFuzzer.cpp", + ], + fuzz_config: { + triage_assignee: "waghpawan@google.com", + cc: [ + "elsk@google.com", + "yochiang@google.com", + ] + }, +} @@ -1,4 +1,4 @@ -# Bug component: 30545 +# Bug component: 322150 dvander@google.com sspatil@google.com elsk@google.com diff --git a/fuzzers/GsiServiceFuzzer.cpp b/fuzzers/GsiServiceFuzzer.cpp new file mode 100644 index 0000000..1d100d8 --- /dev/null +++ b/fuzzers/GsiServiceFuzzer.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 specific language governing permissions and + * limitations under the License. + */ + +#include <fuzzbinder/libbinder_driver.h> + +#include "gsi_service.h" + +using android::fuzzService; +using android::sp; +using android::gsi::GsiService; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + auto binder = sp<GsiService>::make(); + fuzzService(binder, FuzzedDataProvider(data, size)); + return 0; +}
\ No newline at end of file diff --git a/gsi_service.cpp b/gsi_service.cpp index 2caa445..224f520 100644 --- a/gsi_service.cpp +++ b/gsi_service.cpp @@ -109,6 +109,15 @@ void GsiService::Register(const std::string& name) { if (!status.isOk()) return status; \ } while (0) +#define ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK \ + do { \ + if (!android::base::EndsWith(GetActiveDsuSlot(), ".lock")) { \ + ENFORCE_SYSTEM_OR_SHELL; \ + } else { \ + ENFORCE_SYSTEM; \ + } \ + } while (0) + int GsiService::SaveInstallation(const std::string& installation) { auto dsu_slot = GetDsuSlot(installation); auto install_dir_file = DsuInstallDirFile(dsu_slot); @@ -314,7 +323,7 @@ binder::Status GsiService::setGsiAshmem(const ::android::os::ParcelFileDescripto binder::Status GsiService::enableGsiAsync(bool one_shot, const std::string& dsuSlot, const sp<IGsiServiceCallback>& resultCallback) { - ENFORCE_SYSTEM_OR_SHELL; + ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK; std::lock_guard<std::mutex> guard(lock_); const auto result = EnableGsi(one_shot, dsuSlot); @@ -323,7 +332,7 @@ binder::Status GsiService::enableGsiAsync(bool one_shot, const std::string& dsuS } binder::Status GsiService::enableGsi(bool one_shot, const std::string& dsuSlot, int* _aidl_return) { - ENFORCE_SYSTEM_OR_SHELL; + ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK; std::lock_guard<std::mutex> guard(lock_); *_aidl_return = EnableGsi(one_shot, dsuSlot); @@ -343,10 +352,11 @@ binder::Status GsiService::isGsiEnabled(bool* _aidl_return) { } binder::Status GsiService::removeGsiAsync(const sp<IGsiServiceCallback>& resultCallback) { - bool result = false; - auto status = removeGsi(&result); - if (!status.isOk()) { - LOG(ERROR) << "Could not removeGsi: " << status.exceptionMessage().string(); + int result = IGsiService::INSTALL_OK; + bool success = true; + auto status = removeGsi(&success); + if (!status.isOk() || !success) { + LOG(ERROR) << "Could not removeGsi: " << status.exceptionMessage().c_str(); result = IGsiService::INSTALL_ERROR_GENERIC; } resultCallback->onResult(result); @@ -354,7 +364,7 @@ binder::Status GsiService::removeGsiAsync(const sp<IGsiServiceCallback>& resultC } binder::Status GsiService::removeGsi(bool* _aidl_return) { - ENFORCE_SYSTEM_OR_SHELL; + ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK; std::lock_guard<std::mutex> guard(lock_); std::string install_dir = GetActiveInstalledImageDir(); @@ -369,7 +379,7 @@ binder::Status GsiService::removeGsi(bool* _aidl_return) { } binder::Status GsiService::disableGsi(bool* _aidl_return) { - ENFORCE_SYSTEM_OR_SHELL; + ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK; std::lock_guard<std::mutex> guard(lock_); *_aidl_return = DisableGsiInstall(); @@ -436,7 +446,7 @@ binder::Status GsiService::getInstalledDsuSlots(std::vector<std::string>* _aidl_ } binder::Status GsiService::zeroPartition(const std::string& name, int* _aidl_return) { - ENFORCE_SYSTEM_OR_SHELL; + ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK; std::lock_guard<std::mutex> guard(lock_); if (IsGsiRunning() || !IsGsiInstalled()) { @@ -496,6 +506,12 @@ binder::Status GsiService::getAvbPublicKey(AvbPublicKey* dst, int32_t* _aidl_ret return binder::Status::ok(); } int fd = installer_->GetPartitionFd(); + if (fd == -1) { + LOG(ERROR) << "Failed to get partition fd"; + *_aidl_return = INSTALL_ERROR_GENERIC; + return binder::Status::ok(); + } + if (!GetAvbPublicKeyFromFd(fd, dst)) { LOG(ERROR) << "Failed to extract AVB public key"; *_aidl_return = INSTALL_ERROR_GENERIC; @@ -610,7 +626,7 @@ binder::Status ImageService::createBackingImage(const std::string& name, int64_t auto status = on_progress->onProgress(static_cast<int64_t>(current), static_cast<int64_t>(total)); if (!status.isOk()) { - LOG(ERROR) << "progress callback returned: " << status.toString8().string(); + LOG(ERROR) << "progress callback returned: " << status.toString8().c_str(); return false; } return true; @@ -871,7 +887,8 @@ int GsiService::ValidateInstallParams(std::string& install_dir) { } if (access(install_dir.c_str(), F_OK) != 0 && (errno == ENOENT)) { - if (android::base::StartsWith(install_dir, kDsuSDPrefix)) { + if (android::base::StartsWith(install_dir, kDsuSDPrefix) || + android::base::StartsWith(install_dir, kDefaultDsuImageFolder)) { if (mkdir(install_dir.c_str(), 0755) != 0) { PLOG(ERROR) << "Failed to create " << install_dir; return INSTALL_ERROR_GENERIC; @@ -905,7 +922,7 @@ int GsiService::ValidateInstallParams(std::string& install_dir) { LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once"; return INSTALL_ERROR_GENERIC; } - } else if (install_dir != kDefaultDsuImageFolder) { + } else if (!android::base::StartsWith(install_dir, kDefaultDsuImageFolder)) { LOG(ERROR) << "cannot install DSU to " << install_dir; return INSTALL_ERROR_GENERIC; } diff --git a/gsi_service.h b/gsi_service.h index 3c4c278..0dce269 100644 --- a/gsi_service.h +++ b/gsi_service.h @@ -86,11 +86,11 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { std::string GetActiveInstalledImageDir(); static std::vector<std::string> GetInstalledDsuSlots(); + GsiService(); private: friend class ImageService; - GsiService(); static int ValidateInstallParams(std::string& install_dir); int EnableGsi(bool one_shot, const std::string& dsu_slot); bool DisableGsiInstall(); diff --git a/gsi_tool.cpp b/gsi_tool.cpp index 181452b..6aa0fb5 100644 --- a/gsi_tool.cpp +++ b/gsi_tool.cpp @@ -19,6 +19,7 @@ #include <sysexits.h> #include <unistd.h> +#include <algorithm> #include <chrono> #include <condition_variable> #include <functional> @@ -70,14 +71,50 @@ static const std::map<std::string, CommandCallback> kCommandMap = { // clang-format on }; +// Commands not allowed for locked DSU +static const std::vector<std::string> kEnforceNonLockedDsu = { + // clang-format off + "disable", + "enable", + "wipe", + // clang-format on +}; + static std::string ErrorMessage(const android::binder::Status& status, int error_code = IGsiService::INSTALL_ERROR_GENERIC) { if (!status.isOk()) { - return status.exceptionMessage().string(); + return status.exceptionMessage().c_str(); } return "error code " + std::to_string(error_code); } +static inline bool IsRoot() { + return getuid() == 0; +} + +static int EnforceNonLockedDsu(sp<IGsiService> gsid) { + bool running; + auto status = gsid->isGsiRunning(&running); + if (!status.isOk()) { + std::cerr << "Could not get DSU running status: " << ErrorMessage(status) << std::endl; + return EX_SOFTWARE; + } + if (!running) { + return 0; + } + std::string dsuSlot = {}; + status = gsid->getActiveDsuSlot(&dsuSlot); + if (!status.isOk()) { + std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << std::endl; + return EX_SOFTWARE; + } + if (android::base::EndsWith(dsuSlot, ".lock") && !IsRoot()) { + std::cerr << "Must be root to access a locked DSU" << std::endl; + return EX_NOPERM; + } + return 0; +} + class ProgressBar { public: explicit ProgressBar(sp<IGsiService> gsid) : gsid_(gsid) {} @@ -210,7 +247,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) { bool reboot = true; std::string installDir = ""; std::string partition = kDefaultPartition; - if (getuid() != 0) { + if (!IsRoot()) { std::cerr << "must be root to install a GSI" << std::endl; return EX_NOPERM; } @@ -376,7 +413,7 @@ static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv) { } } - if (getuid() != 0) { + if (!IsRoot()) { std::cerr << "must be root to install a DSU" << std::endl; return EX_NOPERM; } @@ -496,7 +533,7 @@ static int WipeData(sp<IGsiService> gsid, int argc, char** /* argv */) { bool running; auto status = gsid->isGsiRunning(&running); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } if (running) { @@ -507,7 +544,7 @@ static int WipeData(sp<IGsiService> gsid, int argc, char** /* argv */) { bool installed; status = gsid->isGsiInstalled(&installed); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } if (!installed) { @@ -532,7 +569,7 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { bool running; auto status = gsid->isGsiRunning(&running); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } else if (running) { std::cout << "running" << std::endl; @@ -540,7 +577,7 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { bool installed; status = gsid->isGsiInstalled(&installed); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } else if (installed) { std::cout << "installed" << std::endl; @@ -548,21 +585,21 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { bool enabled; status = gsid->isGsiEnabled(&enabled); if (!status.isOk()) { - std::cerr << status.exceptionMessage().string() << std::endl; + std::cerr << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } else if (running || installed) { std::cout << (enabled ? "enabled" : "disabled") << std::endl; } else { std::cout << "normal" << std::endl; } - if (getuid() != 0) { + if (!IsRoot()) { return 0; } std::vector<std::string> dsu_slots; status = gsid->getInstalledDsuSlots(&dsu_slots); if (!status.isOk()) { - std::cerr << status.exceptionMessage().string() << std::endl; + std::cerr << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } int n = 0; @@ -571,13 +608,18 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) { sp<IImageService> image_service = nullptr; status = gsid->openImageService("dsu/" + dsu_slot + "/", &image_service); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + if (running) { + // openImageService through binder (gsid) could fail if running, + // because we can't stat the "outside" userdata. + continue; + } + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } std::vector<std::string> images; status = image_service->getAllBackingImages(&images); if (!status.isOk()) { - std::cerr << "error: " << status.exceptionMessage().string() << std::endl; + std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } for (auto&& image : images) { @@ -603,7 +645,7 @@ static int Cancel(sp<IGsiService> gsid, int /* argc */, char** /* argv */) { bool cancelled = false; auto status = gsid->cancelGsiInstall(&cancelled); if (!status.isOk()) { - std::cerr << status.exceptionMessage().string() << std::endl; + std::cerr << status.exceptionMessage().c_str() << std::endl; return EX_SOFTWARE; } if (!cancelled) { @@ -671,7 +713,6 @@ static int Disable(sp<IGsiService> gsid, int argc, char** /* argv */) { std::cerr << "Unrecognized arguments to disable." << std::endl; return EX_USAGE; } - bool installing = false; gsid->isGsiInstallInProgress(&installing); if (installing) { @@ -729,12 +770,19 @@ int main(int argc, char** argv) { std::string command = argv[1]; + int rc; + const auto& vec = kEnforceNonLockedDsu; + if (std::find(vec.begin(), vec.end(), command) != vec.end() && + (rc = EnforceNonLockedDsu(service)) != 0) { + return rc; + } + auto iter = kCommandMap.find(command); if (iter == kCommandMap.end()) { std::cerr << "Unrecognized command: " << command << std::endl; return usage(argc, argv); } - int rc = iter->second(service, argc - 1, argv + 1); + rc = iter->second(service, argc - 1, argv + 1); return rc; } diff --git a/include/libgsi/libgsi.h b/include/libgsi/libgsi.h index 5318341..969ea28 100644 --- a/include/libgsi/libgsi.h +++ b/include/libgsi/libgsi.h @@ -76,6 +76,8 @@ static inline std::string GetDsuMetadataKeyDir(const std::string& dsu_slot) { // install_dir "/data/gsi/dsu/dsu2" has a slot name "dsu2" std::string GetDsuSlot(const std::string& install_dir); +static constexpr char kDsuSlotProp[] = "ro.gsid.dsu_slot"; + static constexpr char kGsiBootedProp[] = "ro.gsid.image_running"; static constexpr char kGsiInstalledProp[] = "gsid.image_installed"; diff --git a/libgsid.cpp b/libgsid.cpp index 4689dcd..fc75c90 100644 --- a/libgsid.cpp +++ b/libgsid.cpp @@ -27,7 +27,7 @@ using android::sp; sp<IGsiService> GetGsiService() { auto sm = android::defaultServiceManager(); auto name = android::String16(kGsiServiceName); - android::sp<android::IBinder> res = sm->waitForService(name); + static android::sp<android::IBinder> res = sm->waitForService(name); if (res) { return android::interface_cast<IGsiService>(res); } diff --git a/partition_installer.cpp b/partition_installer.cpp index ebd3b64..ebfb329 100644 --- a/partition_installer.cpp +++ b/partition_installer.cpp @@ -256,6 +256,9 @@ bool PartitionInstaller::CommitGsiChunk(const void* data, size_t bytes) { } int PartitionInstaller::GetPartitionFd() { + if (!system_device_) { + return -1; + } return system_device_->fd(); } diff --git a/tests/Android.bp b/tests/Android.bp index 3035c10..e7e662c 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -97,3 +97,20 @@ java_test_host { "general-tests", ], } + +java_test_host { + name: "DsuGsiToolTest", + srcs: [ + "DsuGsiToolTest.java", + ], + static_libs: [ + "DsuTestBase", + ], + libs: [ + "tradefed", + ], + test_config: "dsu_gsi_tool_test.xml", + test_suites: [ + "general-tests", + ], +} diff --git a/tests/DSUEndtoEndTest.java b/tests/DSUEndtoEndTest.java index 79799bf..8179d59 100644 --- a/tests/DSUEndtoEndTest.java +++ b/tests/DSUEndtoEndTest.java @@ -21,16 +21,20 @@ import com.android.tradefed.build.IDeviceBuildInfo; import com.android.tradefed.config.Option; import com.android.tradefed.config.Option.Importance; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.util.FileUtil; +import com.android.tradefed.util.SparseImageUtil; +import com.android.tradefed.util.StreamUtil; import com.android.tradefed.util.ZipUtil2; import org.apache.commons.compress.archivers.zip.ZipFile; +import org.junit.Before; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.util.concurrent.TimeUnit; /** @@ -40,7 +44,6 @@ import java.util.concurrent.TimeUnit; public class DSUEndtoEndTest extends DsuTestBase { private static final long kDefaultUserdataSize = 4L * 1024 * 1024 * 1024; private static final String LPUNPACK_PATH = "bin/lpunpack"; - private static final String SIMG2IMG_PATH = "bin/simg2img"; // Example: atest -v DSUEndtoEndTest -- --test-arg \ // com.android.tradefed.testtype.HostTest:set-option:system_image_path:/full/path/to/system.img @@ -51,56 +54,83 @@ public class DSUEndtoEndTest extends DsuTestBase { importance=Importance.ALWAYS) private String mSystemImagePath; - private File mUnsparseSystemImage; + private File mTempDir; - @After - public void teardown() throws Exception { - if (mUnsparseSystemImage != null) { - mUnsparseSystemImage.delete(); + private File getTempPath(String relativePath) throws IOException { + if (mTempDir == null) { + mTempDir = FileUtil.createTempDir("DSUEndtoEndTest"); } + return new File(mTempDir, relativePath); } - @Test - public void testDSU() throws Exception { - String simg2imgPath = "simg2img"; - if (mSystemImagePath == null) { - IBuildInfo buildInfo = getBuild(); - File imgs = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile(); - Assert.assertNotEquals("Failed to fetch system image. See system_image_path parameter", null, imgs); - File otaTools = buildInfo.getFile("otatools.zip"); - File tempdir = ZipUtil2.extractZipToTemp(otaTools, "otatools"); - File system = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "system.img"); - if (system == null) { - File superImg = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "super.img"); - String lpunpackPath = new File(tempdir, LPUNPACK_PATH).getAbsolutePath(); - String outputDir = superImg.getParentFile().getAbsolutePath(); - String[] cmd = {lpunpackPath, "-p", "system_a", superImg.getAbsolutePath(), outputDir}; - Process p = Runtime.getRuntime().exec(cmd); - p.waitFor(); - if (p.exitValue() == 0) { - mSystemImagePath = new File(outputDir, "system_a.img").getAbsolutePath(); - } else { - ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - int len; - byte[] buf = new byte[1024]; - while ((len = p.getErrorStream().read(buf)) != -1) { - stderr.write(buf, 0, len); - } - Assert.assertEquals("non-zero exit value (" + stderr.toString("UTF-8") + ")", 0, p.exitValue()); - } - } else { - mSystemImagePath = system.getAbsolutePath(); + /** Extract system.img from build info to a temproary file. */ + private File extractSystemImageFromBuildInfo(IBuildInfo buildInfo) + throws IOException, InterruptedException { + File imgZip = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile(); + Assert.assertNotNull( + "Failed to fetch system image. See system_image_path parameter", imgZip); + + File superImg = getTempPath("super.img"); + try (ZipFile zip = new ZipFile(imgZip)) { + File systemImg = getTempPath("system.img"); + if (ZipUtil2.extractFileFromZip(zip, "system.img", systemImg)) { + return systemImg; } - simg2imgPath = new File(tempdir, SIMG2IMG_PATH).getAbsolutePath(); + Assert.assertTrue( + "No system.img or super.img in img zip.", + ZipUtil2.extractFileFromZip(zip, "super.img", superImg)); + } + + if (SparseImageUtil.isSparse(superImg)) { + File unsparseSuperImage = getTempPath("super.raw"); + SparseImageUtil.unsparse(superImg, unsparseSuperImage); + superImg = unsparseSuperImage; } - File gsi = new File(mSystemImagePath); - Assert.assertTrue("not a valid file", gsi.isFile()); - String[] cmd = {simg2imgPath, mSystemImagePath, mSystemImagePath + ".raw"}; + + File otaTools = buildInfo.getFile("otatools.zip"); + Assert.assertNotNull("No otatools.zip in BuildInfo.", otaTools); + File otaToolsDir = getTempPath("otatools"); + ZipUtil2.extractZip(otaTools, otaToolsDir); + + String lpunpackPath = new File(otaToolsDir, LPUNPACK_PATH).getAbsolutePath(); + File outputDir = getTempPath("lpunpack_output"); + outputDir.mkdirs(); + String[] cmd = { + lpunpackPath, "-p", "system_a", superImg.getAbsolutePath(), outputDir.getAbsolutePath() + }; Process p = Runtime.getRuntime().exec(cmd); p.waitFor(); - if (p.exitValue() == 0) { - mUnsparseSystemImage = new File(mSystemImagePath + ".raw"); - gsi = mUnsparseSystemImage; + if (p.exitValue() != 0) { + String stderr = StreamUtil.getStringFromStream(p.getErrorStream()); + Assert.fail(String.format("lpunpack returned %d. (%s)", p.exitValue(), stderr)); + } + return new File(outputDir, "system_a.img"); + } + + @Before + public void setUp() { + mTempDir = null; + } + + @After + public void tearDown() { + FileUtil.recursiveDelete(mTempDir); + } + + @Test + public void testDSU() throws Exception { + File systemImage; + if (mSystemImagePath != null) { + systemImage = new File(mSystemImagePath); + } else { + systemImage = extractSystemImageFromBuildInfo(getBuild()); + } + Assert.assertTrue("not a valid file", systemImage.isFile()); + + if (SparseImageUtil.isSparse(systemImage)) { + File unsparseSystemImage = getTempPath("system.raw"); + SparseImageUtil.unsparse(systemImage, unsparseSystemImage); + systemImage = unsparseSystemImage; } boolean wasRoot = getDevice().isAdbRoot(); @@ -117,8 +147,8 @@ public class DSUEndtoEndTest extends DsuTestBase { "gsi_tool install --userdata-size %d" + " --gsi-size %d" + " && sleep 10000000", - getDsuUserdataSize(kDefaultUserdataSize), gsi.length()), - gsi, + getDsuUserdataSize(kDefaultUserdataSize), systemImage.length()), + systemImage, null, 10, TimeUnit.MINUTES, diff --git a/tests/DsuGsiIntegrationTest.java b/tests/DsuGsiIntegrationTest.java index 78d001c..bb0014a 100644 --- a/tests/DsuGsiIntegrationTest.java +++ b/tests/DsuGsiIntegrationTest.java @@ -122,6 +122,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase { getDevice().executeShellV2Command("gsi_tool wipe"); if (isDsuRunning()) { getDevice().reboot(); + getDevice().disableAdbRoot(); } } catch (DeviceNotAvailableException e) { CLog.w("Failed to clean up DSU installation on device: %s", e); @@ -140,6 +141,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase { CLog.i("Wipe existing DSU installation"); assertShellCommand("gsi_tool wipe"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuNotRunning(); } @@ -160,6 +162,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase { CLog.i("Test 'gsi_tool enable -s' and 'gsi_tool enable'"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuNotRunning(); final long freeSpaceAfterInstall = getDevice().getPartitionFreeSpace("/data") << 10; @@ -173,12 +176,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase { assertShellCommand("gsi_tool enable"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuRunning(); CLog.i("Set up 'adb remount' for testing (requires reboot)"); assertAdbRoot(); assertShellCommand("remount"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuRunning(); assertAdbRoot(); assertShellCommand("remount"); @@ -190,12 +195,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase { CLog.i("DSU and original system have separate remount overlays"); assertShellCommand("gsi_tool disable"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuNotRunning(); assertDevicePathNotExist(REMOUNT_TEST_PATH); CLog.i("Test that 'adb remount' is consistent after reboot"); assertShellCommand("gsi_tool enable"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuRunning(); assertDevicePathExist(REMOUNT_TEST_FILE); assertEquals( @@ -208,12 +215,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase { assertAdbRoot(); assertShellCommand("enable-verity"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuRunning(); assertDevicePathNotExist(REMOUNT_TEST_PATH); CLog.i("Test 'gsi_tool wipe'"); assertShellCommand("gsi_tool wipe"); getDevice().reboot(); + getDevice().disableAdbRoot(); assertDsuNotRunning(); final double dampeningCoefficient = 0.9; diff --git a/tests/DsuGsiToolTest.java b/tests/DsuGsiToolTest.java new file mode 100644 index 0000000..c6e621a --- /dev/null +++ b/tests/DsuGsiToolTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 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 specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.dsu; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.util.CommandStatus; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class DsuGsiToolTest extends DsuTestBase { + private static final long DSU_MAX_WAIT_SEC = 10 * 60; + + private String getDsuInstallCommand(String slotName) { + return String.format("am start-activity" + + " -n com.android.dynsystem/com.android.dynsystem.VerificationActivity" + + " -a android.os.image.action.START_INSTALL" + + " --el KEY_USERDATA_SIZE 2147483648" + + " --ez KEY_ENABLE_WHEN_COMPLETED true" + + " --es KEY_DSU_SLOT %s", slotName); + } + + @Before + public void setUp() throws DeviceNotAvailableException { + if (isDsuRunning()) { + assertAdbRoot(); + CLog.i("Wipe existing DSU installation"); + assertShellCommand("gsi_tool wipe"); + getDevice().reboot(); + assertDsuNotRunning(); + } + getDevice().disableAdbRoot(); + } + + @After + public void tearDown() throws DeviceNotAvailableException { + if (isDsuRunning()) { + getDevice().reboot(); + } + getDevice().executeShellCommand("gsi_tool wipe"); + } + + @Test + public void testNonLockedDsu() throws DeviceNotAvailableException { + final String slotName = "foo"; + assertShellCommand(getDsuInstallCommand(slotName)); + CLog.i("Wait for DSU installation complete and reboot"); + assertTrue( + "Timed out waiting for DSU installation complete", + getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)); + CLog.i("DSU installation is complete and device is disconnected"); + + getDevice().waitForDeviceAvailable(); + assertDsuRunning(); + CLog.i("Successfully booted with DSU"); + + // These commands should run without any error + assertShellCommand("gsi_tool enable"); + assertShellCommand("gsi_tool disable"); + assertShellCommand("gsi_tool wipe"); + } + + @Test + public void testLockedDsu() throws DeviceNotAvailableException { + final String slotName = "foo.lock"; + assertShellCommand(getDsuInstallCommand(slotName)); + CLog.i("Wait for DSU installation complete and reboot"); + assertTrue( + "Timed out waiting for DSU installation complete", + getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)); + CLog.i("DSU installation is complete and device is disconnected"); + + getDevice().waitForDeviceAvailable(); + assertDsuRunning(); + CLog.i("Successfully booted with DSU"); + + // These commands should fail on a locked DSU + var result = getDevice().executeShellV2Command("gsi_tool enable"); + assertFalse(result.getStatus() == CommandStatus.SUCCESS); + result = getDevice().executeShellV2Command("gsi_tool disable"); + assertFalse(result.getStatus() == CommandStatus.SUCCESS); + result = getDevice().executeShellV2Command("gsi_tool wipe"); + assertFalse(result.getStatus() == CommandStatus.SUCCESS); + } +} diff --git a/tests/dsu_gsi_tool_test.xml b/tests/dsu_gsi_tool_test.xml new file mode 100644 index 0000000..d893902 --- /dev/null +++ b/tests/dsu_gsi_tool_test.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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 specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs DsuGsiToolTest"> + <option name="test-suite-tag" value="dsu-gsi-tool-test" /> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.dsu.DsuGsiToolTest" /> + </test> +</configuration> |