目录
前言
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-downgrade和ota-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();
|