[OTA] 系统加密后Recovery是如何读取OTA升级包的
来源:互联网 发布:网络黄金通缉了多少人 编辑:程序博客网 时间:2024/05/17 07:06
目前很多Android手机采用的FUSE方案,也就是内部SD卡不单独占用一个文件系统而实际上占用的是userdata的空间。 当系统加密后,解密需要VOLD的参于。而在Recovery模式下,是没有VOLD的启动的。因此,若是OTA升级包保存在了usrdata或内部存储器中时,Recovery是没有法子直接读取的。
那么,Android 5.0上, 是怎么处理这个问题的呢? 我来从头一一分析起来:
首先,安装升级包一般是调用
frameworks/base/core/java/android/os/RecoverySystem.java 中的installPackage来触发的。
public static void installPackage(Context context, File packageFile) throws IOException { String filename = packageFile.getCanonicalPath(); Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); final String filenameArg = "--update_package=" + filename; final String localeArg = "--locale=" + Locale.getDefault().toString(); bootCommand(context, filenameArg, localeArg); }
/** * Reboot into the recovery system with the supplied argument. * @param args to pass to the recovery utility. * @throws IOException if something goes wrong. */ private static void bootCommand(Context context, String... args) throws IOException { RECOVERY_DIR.mkdirs(); // In case we need it COMMAND_FILE.delete(); // In case it's not writable LOG_FILE.delete(); FileWriter command = new FileWriter(COMMAND_FILE); try { for (String arg : args) { if (!TextUtils.isEmpty(arg)) { command.write(arg); command.write("\n"); } } } finally { command.close(); } // Having written the command file, go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); pm.reboot(PowerManager.REBOOT_RECOVERY); throw new IOException("Reboot failed (no permissions?)"); }
至于pm.reboot, 最后会调用到PowerManagerService的lowLevelReboot:
/** * Low-level function to reboot the device. On success, this * function doesn't return. If more than 20 seconds passes from * the time a reboot is requested (120 seconds for reboot to * recovery), this method returns. * * @param reason code to pass to the kernel (e.g. "recovery"), or null. */ public static void lowLevelReboot(String reason) { if (reason == null) { reason = ""; } long duration; if (reason.equals(PowerManager.REBOOT_RECOVERY)) { // If we are rebooting to go into recovery, instead of // setting sys.powerctl directly we'll start the // pre-recovery service which will do some preparation for // recovery and then reboot for us. // // This preparation can take more than 20 seconds if // there's a very large update package, so lengthen the // timeout. We have seen 750MB packages take 3-4 minutes SystemProperties.set("ctl.start", "pre-recovery"); duration = 300 * 1000L; } else { SystemProperties.set("sys.powerctl", "reboot," + reason); duration = 20 * 1000L; } try { Thread.sleep(duration); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
当重启原因是REBOOT_RECOVERY是,居然不是直接重启,而是启动了pre-recovery这个服务。 好吧,还要继续追踪,这个pre-recovery是啥东西?
在init.rc里有定义:
service pre-recovery /system/bin/uncrypt class main disabled oneshot继续找 uncrypt,路漫漫啊。
uncrypt在这里 “bootable/recovery/uncrypt/”, 看到Recovery了吧,开心了吧,快到头了。
int main(int argc, char** argv){ const char* input_path; const char* map_file; int do_reboot = 1; if (argc != 1 && argc != 3) { fprintf(stderr, "usage: %s [<transform_path> <map_file>]\n", argv[0]); return 2; } if (argc == 3) { // when command-line args are given this binary is being used // for debugging; don't reboot to recovery at the end. input_path = argv[1]; map_file = argv[2]; do_reboot = 0; } else { input_path = parse_recovery_command_file(); if (input_path == NULL) { // if we're rebooting to recovery without a package (say, // to wipe data), then we don't need to do anything before // going to recovery. ALOGI("no recovery command file or no update package arg"); reboot_to_recovery(); return 1; } map_file = CACHE_BLOCK_MAP; } ALOGI("update package is %s", input_path); // Turn the name of the file we're supposed to convert into an // absolute path, so we can find what filesystem it's on. char path[PATH_MAX+1]; if (realpath(input_path, path) == NULL) { ALOGE("failed to convert %s to absolute path: %s", input_path, strerror(errno)); return 1; } int encryptable; int encrypted; if (read_fstab() == NULL) { return 1; } const char* blk_dev = find_block_device(path, &encryptable, &encrypted); if (blk_dev == NULL) { ALOGE("failed to find block device for %s", path); return 1; } // If the filesystem it's on isn't encrypted, we only produce the // block map, we don't rewrite the file contents (it would be // pointless to do so). ALOGI("encryptable: %s\n", encryptable ? "yes" : "no"); ALOGI(" encrypted: %s\n", encrypted ? "yes" : "no"); // Recovery supports installing packages from 3 paths: /cache, // /data, and /sdcard. (On a particular device, other locations // may work, but those are three we actually expect.) // // On /data we want to convert the file to a block map so that we // can read the package without mounting the partition. On /cache // and /sdcard we leave the file alone. if (strncmp(path, "/data/", 6) != 0) { // path does not start with "/data/"; leave it alone. unlink(RECOVERY_COMMAND_FILE_TMP); } else { ALOGI("writing block map %s", map_file); if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) { return 1; } } wipe_misc(); rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE); if (do_reboot) reboot_to_recovery(); return 0;}
看看uncrypt干吗了:
1.) 看下是不是有额外参数,如果有,就以为是debug模式。。嗯,这个不管它。只看正常模式
2.) 调用 parse_recovery_command_file(), 就是读一下/cache/recovery/command了是的update-package。 这个是RecoverySystem.java写入的
3.) 读不到update-package,重启到recovery,如果是Factory Data Reset触发的wipe-data就走到这里。
4.) 看看系统是不是加密的,我是感觉原生代码上这里少了块逻辑,如果系统没有加密,真接重启进recovery就是了,为什么还要继续转换?
5.) 看看 update-pacakge是不是以"/data"开始的。如果是的话,就调用produce_block_map
6.) 删除中间文件,然后重启。
至于 produce_block_map干什么了,也是一目了然的。
1.) 生成了一个block map,把/cache/recovery/command中的update_package的值改成 @/cache/recovery/block.map
2.) 如果系统是加密的,就解密,嗯,uncrypt.c 注释里写的很详细,我就不瞎扯了(其实是我也没细看这逻辑)
// If the filesystem is using an encrypted block device, it will also// read the file and rewrite it to the same blocks of the underlying// (unencrypted) block device, so the file contents can be read// without the need for the decryption key.不过,看这注释讲,如果加密了,这个文件等升级完系统重启后,己经是被废掉了。
先说到这吧。其实还有块,Recovery下面是怎么能识别 @/cache/recovery/block.map这样的update_package的。
必须提一下有个作死的BUG。
当uncrypt的selinux权限不够读原升级包文件时,会出错并退出,退出就退出吧,偏偏不重启进Recovery,此时会造成用户点系统升级后,能看到关机动画,然后就是黑屏卡住不动了。当升级包是内置sd卡时,无论是不是加密, 几乎是必出现的。
此时,会有selinux 的 denied的log.
04-14 09:32:50.094W/uncrypt ( 5524): type=1400 audit(0.0:25): avc: denied { getattr } forpath="/data/media" dev="mmcblk0p31" ino=567841scontext=u:r:uncrypt:s0 tcontext=u:object_r:media_rw_data_file:s0 tclass=dirpermissive=0
要怪就怪原生uncrypt没有media_rw的权限吧! (莫非google的OTA不用内卡?) 解决方法也很简单,在uncrypt.te中加所需权
allow uncrypt media_rw_data_file:dir r_dir_perms; allow uncrypt media_rw_data_file:file r_file_perms;
- [OTA] 系统加密后Recovery是如何读取OTA升级包的
- [OTA] 系统加密后Recovery是如何读取OTA升级包的---Andorid 6.0 M 的更新
- recovery image的OTA升级过程
- Android系统学习:如何制作OTA U盘升级包
- Android系统OTA升级包制作
- OTA升级包制作
- Android系统OTA升级
- OTA 差分升级包的制作
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- Android OTA升级原理和流程分析(四)---Android系统Recovery模式的工作原理
- L版本开启MTK_SHARED_SDCARD后OTA包放入内卡如何MOTA升级成功
- android OTA升级包制作
- 制作ota升级包出错
- Android OTA 升级(三):生成recovery.img
- Android OTA 升级(三):生成recovery.img
- Android OTA 升级(三):生成recovery.img .
- 30天自制操作系统day4
- Git教程:远程仓库
- 第五周项目四成员函数
- 特殊集合
- Git教程:添加远程库
- [OTA] 系统加密后Recovery是如何读取OTA升级包的
- 第五周项目四友元函数
- 浮点数在内存中的表示
- Git教程:从远程库克隆
- /etc目录的根目录下的文件作用
- Java学习笔记(3) 界面设计、项目搭建
- 黑马程序员_日记61_map扩展知识
- Git教程:分支管理
- Git学习笔记(九)---常用命令