目录

前言

1.android默认的timestamp降级限制

1.1. recovery模式下的降级限制

1.2. update_engine的降级限制

2.通用的绕开android降级机制方式

2.1 为何降级需要擦除userdata

3.虚拟A/B与A/B系统降级的区别

3.1 Virtual A/B的概念

3.2 A/B提前擦除userdata可以降级,为何virtual A/B就不行

4.如何优雅的完成虚拟A/B系统的降级

4.1 虚拟A/B降级之--- 降级差分包

4.2 虚拟A/B OTA --- 全包降级实现方式


前言

因为公司产品的特性,客户会有一些降级版本的需求,但是这一规则其实是违背Google的安全机制的,就个人肤浅的理解,一方面低版本的通常来说,我们常说的安全补丁(Security Patch Level)回退就意味着风险,另外一方面,Google当然也希望终端的版本越新也越能增加高版本android终端的比例,这不就说明它一年一更新android大版本的“价值”是存在的吗? 当然,后面一点是我瞎掰扯的,请忽略,毕竟android不断更新,我们吃着这碗饭的手艺还能多持续一天,感谢如此伟大的贡献。

1.android默认的timestamp降级限制

一般来说,android默认的机制,都会以编译版本的时间戳timestamp作为限制条件,这使得OTA的随意升降级变得不那么丝滑,如果不是特殊行业的android设备,建议还是保持它最初的模样,毕竟还是一种简单有效又粗暴的管控手段。不过,我们公司就是特殊行业,来不及解释,不管3721,以暴制暴,干掉就干掉。

1.1. recovery模式下的降级限制

先来看看recovery的限制,A/B系统的降级,注释也说得很清楚了,读一读OTA包里的metadata的timestamp,除非是特殊情况下的差分包. 一段优美的代码被#if 0 加上#endif打破了原有的清净。

// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
// Downgrading is not allowed unless explicitly enabled in the package and only for
// incremental packages.
static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {// Incremental updates should match the current build.auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");auto pkg_pre_build = get_value(metadata, "pre-build-incremental");if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "<< device_pre_build;return false;}auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "<< device_fingerprint;return false;}
#if 0 //[Recovery]Allow downgrade for build utc// Check for downgrade version.int64_t build_timestamp =android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());int64_t pkg_post_timestamp = 0;// We allow to full update to the same version we are running, in case there// is a problem with the current copy of that version.auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");if (pkg_post_timestamp_string.empty() ||!android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||pkg_post_timestamp < build_timestamp) {if (get_value(metadata, "ota-downgrade") != "yes") {LOG(ERROR) << "Update package is older than the current build, expected a build ""newer than timestamp "<< build_timestamp << " but package has timestamp " << pkg_post_timestamp<< " and downgrade not allowed.";return false;}if (pkg_pre_build_fingerprint.empty()) {LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";return false;}}
#endif//[Recovery]Allow downgrade for build utcreturn true;
}

1.2. update_engine的降级限制降级

google默认的机制读属性限制降级system/update_engine/hardware_android.cc

// Returns true if the device runs an userdebug build, and explicitly allows OTA
// downgrade.
bool HardwareAndroid::AllowDowngrade() const {return GetBoolProperty("ro.ota.allow_downgrade", false) &&GetBoolProperty("ro.debuggable", false);
}

简单粗暴就是直接在调用处注释掉限制

system/update_engine/payload_consumer/delta_performer.cc

ErrorCode DeltaPerformer::ValidateManifest() {// Perform assorted checks to sanity check the manifest, make sure it// matches data from other sources, and that it is a supported version.bool has_old_fields =(manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info());for (const PartitionUpdate& partition : manifest_.partitions()) {has_old_fields = has_old_fields || partition.has_old_partition_info();}...#if 0if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) {LOG(ERROR) << "The current OS build timestamp ("<< hardware_->GetBuildTimestamp()<< ") is newer than the maximum timestamp in the manifest ("<< manifest_.max_timestamp() << ")";if (!hardware_->AllowDowngrade()) {return ErrorCode::kPayloadTimestampError;}LOG(INFO) << "The current OS build allows downgrade, continuing to apply"" the payload with an older timestamp.";}#endif
...}

2.通用的绕开android降级机制方式

2.1 为何降级需要Factory Reset

android的userdata加密机制,出于安全性的考虑,对于降级回退操作,这一导致安全补丁的回退危险行为,做了严格的限制,主要是Keymaster对Security-Patch-Level的检查,回退安全补丁导致解密userdata的加密key无法正常解密userdata所致,以前的userdata采用FDE的加密方式,如果出现问题,就会出现弹出黑框,让输入密码,实则输入密码也并不起作用,因为本来就是随机密码。

加密方式Google在android P上又开始启用FBE的加密方式,到android R上又在此基础上改进为Metadata加密,原来的加密Key存放在userdata分区,而metadata加密的加密Key单独用metadata分区来保存。

3.虚拟A/B与A/B系统降级的区别

3.1 Virtual A/B的概念

Google官方针对虚拟A/B还是做了很多说明的,按照实际出发简述一下个人的理解,延续A/B系统的OTA升级失败也能回滚的优势,而且相比之下,还省掉了flash的分区空间占用。个人遣词造句功力不够,还是放上google文档链接更为靠谱。https://source.android.com/devices/tech/ota/virtual_abhttps://source.android.com/devices/tech/ota/virtual_ab

3.2 A/B提前擦除userdata可以降级,为何virtual A/B就不行

4.如何优雅的完成虚拟A/B系统的降级

4.1 虚拟A/B降级之---降级差分包

最初想启用降级差分包的方式,尝试应用在virtual A/B系统的降级过程中,主要是源于在recovery模式下,安装完ota包之后,重新开机,虽然现象还是一样block在开机动画,但是在recovery模式下执行完factory reset之后,机器降级成功,加之ota包的制作过程中总是有downgrade wipe字眼飘过,所以就有了从ota包制作的点上切入的想法。

降级差分包的制作,可以参考我临时的shell脚本:

https://blog.csdn.net/jeephao/article/details/124108372https://blog.csdn.net/jeephao/article/details/124108372降级差分包的metadata文件,在脚本降级差分包过程中传递了downgrade及wipe的参数之后,与正常的升级包对比多了两个字段的参数ota-downgradeota-wipe ,因为没有仔细追踪源码,当时认为这样能实现降级,就大意的认为这两个参数是根原因,简直就是天真,所以也导致很长一段时间内,都需要通过差分包来实现降级需求,甚是麻烦,实际recovery升级才去读取Metadata信息做校验之类,update_engine的流程才不干这事儿,

ota-downgrade=yes
ota-property-files=payload_metadata.bin:775:358290,payload.bin:775:436667020,payload_properties.txt:436667853:167,metadata:69:659
ota-required-cache=0
ota-streaming-property-files=payload.bin:775:436667020,payload_properties.txt:436667853:167,metadata:69:659
ota-type=AB
ota-wipe=yes
post-build=xxx/xxx/eda52:11/RKQ1.210107.001/218.01.07.0024:user/release-keys
post-build-incremental=218.01.07.0024
post-sdk-level=30
post-security-patch-level=2021-05-05
post-timestamp=1621431844
pre-build=xxx/xxx/xxx:11/RKQ1.210107.001/218.01.09.0048:user/release-keys
pre-build-incremental=218.01.09.0048
pre-device=eda52

update_engine直接读取payload_properties.txt,后面追踪update_engine的源码发现通过强制设置powerwash为true可以达到同样的目的,和同事讨论,才开始真正了解倒POWERWASH=1的意义所在,原来降级差分包就是设置了这个字段才实现。

FILE_HASH=5jg6Mcc11/MQ+gMkSaVaChvgidMiSG86mh5H9ZOK3Cs=
FILE_SIZE=436667020
METADATA_HASH=s7ZrZha3xmTik6roNbUAzHd8wk8Jw9f3nq0Lor41VuY=
METADATA_SIZE=358023
POWERWASH=1

4.2 虚拟A/B降级---全包降级

在update engine执行install plan的过程中,读全包的过程中追加POWERWASH参数在payload_properties.txt中,还是借鉴了降级差分包的方式,整体流程如何就生效,这个空了再补上。封装的解析OTA全包的流程补充到下面,做个记录。

diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index b7d119f..54d0f0c 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -53,6 +53,8 @@#include "update_engine/update_boot_flags_action.h"#include "update_engine/update_status_utils.h"+#include <ziparchive/zip_archive.h>
+#include <android-base/strings.h>#ifndef _UE_SIDELOAD// Do not include support for external HTTP(s) urls when building// update_engine_sideload.
@@ -167,6 +169,159 @@ void UpdateAttempterAndroid::Init() {}}+// readmetadata interface comes from recovery read package process
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
+    CHECK(metadata != nullptr);
+
+    static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
+    ZipEntry entry;
+    if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
+        LOG(ERROR) << "Failed to find " << METADATA_PATH;
+        return false;
+    }
+
+    uint32_t length = entry.uncompressed_length;
+    std::string metadata_string(length, '\0');
+    int32_t err =
+        ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
+
+    if (err != 0) {
+        LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
+        return false;
+    }
+
+    for (const std::string& line : android::base::Split(metadata_string, "\n")) {
+        size_t eq = line.find('=');
+        if (eq != std::string::npos) {
+            metadata->emplace(android::base::Trim(line.substr(0, eq)),
+                android::base::Trim(line.substr(eq + 1)));
+        }
+    }
+
+    return true;
+}
+
+std::string get_value(const std::map<std::string, std::string>& metadata,
+    const std::string& key) {
+    const auto& it = metadata.find(key);
+    return (it == metadata.end()) ? "" : it->second;
+}
+// readmetadata interface comes from recovery read package process
+
+bool isUserWithUserdebugUpgrade(std::string post_build) {
+
+    std::string type = "";
+
+    auto device_build_type = android::base::GetProperty("ro.build.type", "");
+    if (post_build.empty() || device_build_type == "") return false;
+
+    if (post_build.find("userdebug/") != std::string::npos ) {
+        type = "userdebug";
+    } else if (post_build.find("user/") != std::string::npos ) {
+        type = "user";
+    }
+
+    if (type == "" || type == device_build_type) {
+        return false;
+    }
+
+    return true;
+}
+
+bool IsResetPowerwash (const string payload_url,  ZipArchiveHandle zip_handle_) {
+
+    std::string version_rule = "xxx.xx.xx.xxxx";
+    std::map<std::string, std::string> metadata_ota;
+    /* get ota package file path */
+    string actual_filepath = payload_url.substr(strlen("file://"));
+
+    //ZipArchiveHandle zip_handle_;
+    int32_t open_status = OpenArchive(actual_filepath.c_str(), &zip_handle_);
+    LOG(INFO) << "IsResetPowerwash " << " open_status " << open_status << " path "
+        << actual_filepath.c_str();
+    if (open_status != 0) {
+        LOG(ERROR) << "Failed to open " << " open_status " << open_status;
+    }
+
+    ReadMetadataFromPackage(zip_handle_, &metadata_ota);
+    /* source build incremental version and security path */
+    std::string currentVersion = android::base::GetProperty("ro.build.version.incremental", "");
+    std::string current_secPatchLevel = android::base::GetProperty("ro.build.version.security_patch", "");
+    std::string targetVersion = get_value(metadata_ota, "post-build-incremental");
+    std::string target_secpatchlevel = get_value(metadata_ota, "post-security-patch-level");
+
+    LOG(INFO) << "IsResetPowerwash:" << " postbuild = " << targetVersion << " ; securitypatchlevel = "
+        << target_secpatchlevel ;
+    LOG(INFO) << "IsResetPowerwash:" << " currentVersion = " << currentVersion << " ; targetVersion = "
+        << targetVersion;
+
+    // check variable. The version number rule such as: A12.12.01.0004/218.01.14.0133 2022-03-05
+    if(current_secPatchLevel.empty() || target_secpatchlevel.empty()) {
+        LOG(INFO) << "Some variable(secure patch level) is empty, abort running and return false";
+        return false;
+    }
+    if(currentVersion.size() != version_rule.size() || targetVersion.size() != version_rule.size()) {
+        LOG(INFO) << "The version number does not conform to the rules.";
+        return false;
+    }
+
+    // security patch level compare
+    if (((target_secpatchlevel.compare(0, 4, current_secPatchLevel, 0, 4) == 0) &&
+            ((target_secpatchlevel.compare(5, 2, current_secPatchLevel, 5, 2) < 0 ))) ||
+        (target_secpatchlevel.compare(0, 4, current_secPatchLevel, 0, 4) < 0 ) ) {
+        LOG(INFO) << "IsResetPowerwash: process for patch-level reback , need wipe userdata.";
+        return true;
+    }
+
+//   close version compare downgrade
+//  // verify user with userdebug upgrade
+//  if(isUserWithUserdebugUpgrade(get_value(metadata_ota, "post-build"))) {
+//      LOG(INFO) << "IsResetPowerwash: user <=> userdebug upgrade, need wipe userdata.";
+//      return true;
+//  }
+//
+//  // verify gms with non-gms upgrade
+//  if(currentVersion.compare(4, 2, targetVersion , 4, 2) != 0 ) {
+//      LOG(INFO) << "IsResetPowerwash: gms <=> nongms upgrade, need wipe userdata.";
+//      return true;
+//  }
+
+    // verify downgrade by hon version number.
+    // When the security patch level is the same as before, there is no
+    // problem with upgrading, so the logic below is not processed.
+
+//   if(currentVersion.compare(7, 2, targetVersion , 7, 2) > 0) { // release version compare
+//       LOG(INFO) << "IsResetPowerwash: downgrade for honey version , need wipe userdata.";
+//       return true;
+//   } else if(currentVersion.compare(7, 2, targetVersion , 7, 2) == 0 &&
+//           currentVersion.compare(10, 4, targetVersion, 10, 4) > 0) { // dailybuild version compare
+//       LOG(INFO) << "IsResetPowerwash: downgrade for dailybuild , need wipe userdata.";
+//       return true;
+//   }
+
+    return false;
+}
+
+
+void HonUpgradeHandle(InstallPlan* install_plan, ZipArchiveHandle zip_handle_) {
+
+    LOG(INFO) << "HonUpgradeHandle >>>>";
+
+    if(install_plan == nullptr) {
+        LOG(INFO) << "install_plan is nullptr.";
+        return;
+    }
+
+    InstallPlan inplan = *install_plan;
+    if(!inplan.powerwash_required && IsResetPowerwash(inplan.download_url, zip_handle_)) {
+        LOG(INFO) << "Need re-set powerwash_required.";
+        (*install_plan).powerwash_required = true;
+    } else {
+        LOG(INFO) << "The process is normal upgrade, Skip.";
+    }
+}
+
+bool UpdateAttempterAndroid::ApplyPayload(const string& payload_url,int64_t payload_offset,
@@ -192,7 +347,7 @@ bool UpdateAttempterAndroid::ApplyPayload(// Setup the InstallPlan based on the request.install_plan_ = InstallPlan();
-
+  ZipArchiveHandle zip_handle_= nullptr;install_plan_.download_url = payload_url;install_plan_.version = "";base_offset_ = payload_offset;
@@ -277,6 +432,11 @@ bool UpdateAttempterAndroid::ApplyPayload(}}+  // porting from EDA56
+  HonUpgradeHandle(&install_plan_, zip_handle_);
+  // porting from EDA56
+  CloseArchive(zip_handle_);//open finish close open ziphandle
+LOG(INFO) << "Using this install plan:";install_plan_.Dump();

android virtual A/B OTA降级策略相关推荐

  1. Android A/B System OTA分析(一)概览

    本文为洛奇看世界(guyongqiangx)原创,转载请注明出处. 文章链接:https://blog.csdn.net/guyongqiangx/article/details/71334889 A ...

  2. Android Virtual Device(AVD)屏幕大小调整

    (1)各种常用机型的分辨率列表如下: 型号                         宽高值 WXGA800                 480X800 WVGA854            ...

  3. 修改android virtual device路径

    android virtual device默认路径指向C:\user\Thinkpad\.android\avd\ 由于C空间有限和不方便管理,想将avd路径转移到D盘. 步骤如下: 1)计算机-属 ...

  4. Android 8.0 运行时权限策略变化和适配方案

    Android8.0也就是Android O即将要发布了,有很多新特性,目前我们可以通过AndroidStudio3.0 Canary版本下载Android O最新的系统映像的Developer Pr ...

  5. android avd 使用方法,Android中Android Virtual Device(AVD)使用教程

    AVD的全称为:Android Virtual Device,就是Android运行的虚拟设备,他是Android的模拟器识别.建立的Android要运行,必须创建AVD,每个AVD上可以配置很多的运 ...

  6. 咖啡汪日志—— 回退兜底 及实用的服务降级策略

    本汪作为一名资深的哈士奇 每天除了闲逛,拆家,就是啃博客了 作为不是在戏精,就是在戏精的路上的二哈 今天就来给大家说说在实际工作中 如何进行简单的回退兜底,熔断降级 一. 开篇有益 1.什么是Hyst ...

  7. 全志T507如何在Android系统上进行OTA升级

    OTA升级是Android系统所提供的标准软件升级方式.它功能十分强大,并且提供了完全升级(完整包)和增量升级(差异包)两种模式.https://www.forlinx.com/product/133 ...

  8. Android A/B System OTA分析(三)主系统和bootloader的通信

    Android从7.0开始引入新的OTA升级方式,A/B System Updates,这里将其叫做A/B系统,涉及的内容较多,分多篇对A/B系统的各个方面进行分析.本文为第三篇,主系统和bootlo ...

  9. Android A/B System OTA分析(六)如何获取 payload 的 offset 和 size

    本文为洛奇看世界(guyongqiangx)原创,转载请注明出处. 文章链接:https://blog.csdn.net/guyongqiangx/article/details/72604355 A ...

最新文章

  1. freeimage ubuntu安装
  2. 超级vga显示卡_VGA 显示卡简介
  3. 设计模式-创建型-单件
  4. Zookeeper的目录结构
  5. 「后端小伙伴来学前端了」Vue中Props 实现组件通信TodoList案例
  6. CMake 用法导览
  7. 他从零开始学Python,25岁拥有480000粉丝:成功就靠这3点!
  8. 随机邮箱_msgsafe - 一个处于半死不活状态的加密邮箱
  9. 华为acl怎么生效_华为ACL配置教程(一)
  10. FirstDjangoWebApp-1
  11. 如何通过Multitouch为Mac电脑添加更多手势控制?
  12. java gzip解压请求_使用 gzip 压缩请求正文
  13. 自考总结--数据结构导论
  14. opencv出现0x75BA812F 处(位于 Project1.exe 中)有未经处理的异常: Microsoft C++ 异常的一种可行解决方案
  15. servlet制作过滤器
  16. python面向对象,烤地瓜应用
  17. 山东高中学业水平考试时间2020计算机,2020年山东省高中学业水平等级考试报名时间及科目...
  18. vue中的Actions
  19. java中String转Long类型
  20. 在EntityFramework中使用 nock的方法。

热门文章

  1. Oracle列转行3种方式
  2. 如何理解计算机编程并把它学好呢? --棒喝篇
  3. 即将失传的中国传统小吃 你都吃过了吗?
  4. java计算一个多边形的重心_Java多边形重心计算
  5. 仙人掌之歌——上线运营(5)
  6. 双十二电商短信推广模板
  7. 看漫画也能学Java?大佬亲自带你进入Java的世界
  8. java版帝王三国怎么玩_帝王三国平民玩家怎么玩
  9. c语言编程小学生测验,c语言小学生测验
  10. 获取电脑当前正在连接的wifi密码