android virtual A/B OTA降级策略

目录

 

前言

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 utc

  return 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 0
    if (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();

 

 

 

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值