一、问题描述:
常常跟OTA打交道的朋友可能会经常遇到客户root后的手机OTA升级失败的问题,如果用户取得root权限,手误破坏了system分区数据,是很有可能导致OTA升级失败的,甚至导致手机无法正常开机,因此为了避免取得root权限的手机ota升级失败导致无法开机的情况,我们在OTA升级前对设备进行root检测,如果发现设备已经root,则禁止手机安装更新。
二、实现方案:
1.修改update_script打包脚本:
/* /bootable/recovery/ota_from_target_files.py */
def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
......
script.AssertRootCheck()
script.AssertSomeFingerprint(source_fp, target_fp)
......
/* Edify_generator.py */
def AssertRootCheck(self):
cmd = ('root_check() ||abort("This phone has been root or an error occurred, can not upgrade.");')
cmd = ('root_check() ||abort("This phone has been root or an error occurred, can not upgrade.");')
self.script.append(cmd)
下面是升级过程中recovery相关log:
====== Updater-Script:
mount("ext4", "EMMC", "/dev/block/mmcblk0p6", "/system");
root_check() ||abort("This phone has been root or an error occurred, can not upgrade.");
file_getprop("/system/build.prop", "ro.build.fingerprint") == "***" || file_getprop("/system/build.prop", "ro.build.fingerprint") == "***" || abort("Package expects build fingerprint of *** or ***; this device has " + getprop("ro.build.fingerprint") + ".");
getprop("ro.product.device") == "***" || abort("This package is for \"***\" devices; this is a \"" + getprop("ro.product.device") + "\".");
ui_print("Verifying current system...");
......
如上,脚本第一句是挂载system分区,这是升级前必须要做的动作。之后,在校验fingerprint之前增加了root_check()的动作。这里root_check()是一个接口,该函数的函数体以二进制代码的形式也保存在HOTA包中,供升级时调用。
2.修改安装更新时对应root检测接口root_check()函数:
#define ROOT_SAVE_MAGIC 0x00071029
#define ROOT_UNIT_MAGIC 0x00071029
#define ROOT_UNIT_MAGIC 0x00071029
#define ROOT_MAX_RECORD 1024
#define MAX_TIME_LEN 32
#define MAX_ACTION_LEN 32
#define TASK_COMM_LEN 16
struct root_save_head{
int magic;
int count;
char reserved[512-8];
};
struct root_save_unit{
int magic;
pid_t ppid;
pid_t pid;
pid_t tid;
int cap;
int fork_by_sh;
char parent_name[TASK_COMM_LEN];
char process_name[TASK_COMM_LEN];
char thread_name[TASK_COMM_LEN];
char action[MAX_ACTION_LEN];
char time[MAX_TIME_LEN];
char reserved[512-136];
};
static struct root_save_head save_head_tmp;
static struct root_save_unit save_unit_tmp;
#define MAX_ACTION_LEN 32
#define TASK_COMM_LEN 16
struct root_save_head{
int magic;
int count;
char reserved[512-8];
};
struct root_save_unit{
int magic;
pid_t ppid;
pid_t pid;
pid_t tid;
int cap;
int fork_by_sh;
char parent_name[TASK_COMM_LEN];
char process_name[TASK_COMM_LEN];
char thread_name[TASK_COMM_LEN];
char action[MAX_ACTION_LEN];
char time[MAX_TIME_LEN];
char reserved[512-136];
};
static struct root_save_head save_head_tmp;
static struct root_save_unit save_unit_tmp;
static const char *ROOTM_PATCH = "/dev/rootm";
Value* RootCheckFN(const char* name, State* state, int argc, Expr* argv[]) {
if (argc != 0) {
return ErrorAbort(state, "%s() expects no args, got %d", name, argc);
}
int result = 1;
int fd;
int magic = 0;
int count = 0;
if (argc != 0) {
return ErrorAbort(state, "%s() expects no args, got %d", name, argc);
}
int result = 1;
int fd;
int magic = 0;
int count = 0;
int index = 0;
int result_check=1;
fd = open(ROOTM_PATCH, O_RDONLY, 0);
if(fd == -1){
fprintf(stderr, "Can not open %s, errno=%d\n", ROOTM_PATCH, errno);
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
if(sizeof(struct root_save_head) != read(fd, &save_head_tmp, sizeof(struct root_save_head))){
fprintf(stderr, "Can not read the header of records\n");
close(fd);
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
magic = save_head_tmp.magic;
count = save_head_tmp.count;
if(magic != ROOT_SAVE_MAGIC || count > ROOT_MAX_RECORD || count <= 0){
close(fd);
fprintf(stderr, "System partition check Pass!\n");
result = 0;
return StringValue(strdup(result == 0 ? "t" : ""));
fd = open(ROOTM_PATCH, O_RDONLY, 0);
if(fd == -1){
fprintf(stderr, "Can not open %s, errno=%d\n", ROOTM_PATCH, errno);
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
if(sizeof(struct root_save_head) != read(fd, &save_head_tmp, sizeof(struct root_save_head))){
fprintf(stderr, "Can not read the header of records\n");
close(fd);
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
magic = save_head_tmp.magic;
count = save_head_tmp.count;
if(magic != ROOT_SAVE_MAGIC || count > ROOT_MAX_RECORD || count <= 0){
close(fd);
fprintf(stderr, "System partition check Pass!\n");
result = 0;
return StringValue(strdup(result == 0 ? "t" : ""));
}else{
fprintf(stderr,"System partition check Fail!\n");
fprintf(stderr, "System have been Root.\n");
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
return StringValue(strdup(result == 0 ? "t" : ""));
fprintf(stderr, "System have been Root.\n");
result = 1;
return StringValue(strdup(result == 0 ? "t" : ""));
}
return StringValue(strdup(result == 0 ? "t" : ""));
}
void RegisterInstallFunctions() {
RegisterFunction("root_check", RootCheckFN);
这部分代码逻辑在项目的updater/install.c中,增加了一个root_check()的函数,该函数主要用来进行root检测。之后在/tools/releasetools/edify_generator.py脚本中增加对root_check()的调用。这个脚本的作用就是在制作升级包时生成升级包中的updater-script脚本。上述修改都是在该项目的代码中修改,专门用于制作升级包时生成脚本文件使用的,并不参与实际的软件版本编译,所以对这部分的修改只在生成HOTA升级包时生效,最终在HOTA包中的脚本updater-script中添加了root检测的逻辑调用。 而对已经发布的版本没有任何影响。
经过修改后,用户在进行HOTA升级时,实际的流程是:
主系统下获取升级包 (差分包)---> 进行HOTA升级 ---> 手机重启后进入recovery模式 ---> 在recovery下解析HOTA升级包中的updater-script脚本 ---> 调用root_chek()函数检查root标志是否存在 ----> 如果存在,则执行“abort”退出升级流程并给出手机被root的提示语。 如果不存在,继续执行脚本的下一行,直到脚本执行完毕,开始对版本进行差分升级。【如果root检查没有通过,升级流程abort,那么手机停止在recovery模式,用户点击power键后选中reboot选项,手机重启回到正常模式。】
下面是root过的设备禁止OTA所记录的日志片段:
mount /system /dev/block/platform/mtk-msdc.0/by-name/system
System partition check Fail!
System have been Root.
script aborted: This phone has been root or an error occurred, can not upgrade.
This phone has been root or an error occurred, can not upgrade.
E:Error in /sdcard/dload/update.zip
(Status 7)
Installation aborted.
I:no boot messages recovery
I:[1]check the otaupdate is done!
factory_poweroff_flag=0
demo_mode_flag=0