目前很多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);
}

最终调用到了bootCommand中,在/cache/recovery/command写入 “--update_package=升级包的文件路径”。然后调用PowerManager, 触发REBOOT_RECOVERY

    /**
* 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升级包的的更多相关文章

  1. Java_I/O输入输出_使用输入输出流读取文件,将一段文字加密后存入文件,然后读取,将加密前与后的文件输出

    import java.io.*; public class Example { public static void main(String[] args) { char a[] = "今 ...

  2. 电信级的RSA加密后的密码的破解方法

    一直以来,电信通过HTTP劫持推送广告的方式已经存在了很多年了,这种手段至今并未停止.这种手段月光博客曾经有多次曝光,见<电信级的网络弹出广告>.<获取了电信恶意弹出广告的罪证> ...

  3. Code笔记之:对使用zend加密后的php文件进行解密

    对使用zend加密后的php文件进行解密 使用zend加密后的php文件用notpad++打开会出现类似的乱码 下面使用解密工具进行解密 http://pan.baidu.com/s/1i3n4ysX ...

  4. 关于vmware下复制linux系统虚拟机后eth0变成eth1问题解决

    在vmware虚拟机中,当我们克隆或者复制linux系统虚拟机后,再启动系统时会发现系统下不再有eth0,而变成了eth1 当我们使用/etc/init.d/network restart重启网络时, ...

  5. Web安全--使用Salt + Hash将密码加密后再存储进数据库

    转载原地址 http://www.bozhiyue.com/mianshiti/_net/2016/0728/314239.html (一) 为什么要用哈希函数来加密密码 如果你需要保存密码(比如网站 ...

  6. 使用JAVA进行MD5加密后所遇到的一些问题

    前言:这几天在研究apache shiro如何使用,这好用到了给密码加密的地方,就碰巧研究了下java的MD5加密是如何实现的,下面记录下我遇到的一些小问题. 使用java进行MD5加密非常的简单,代 ...

  7. js MD5加密后的字符串

    js MD5加密后的字符串 <script language="JavaScript"> /************************************** ...

  8. ovs2.7 在系统重启后,再次使用时提示数据库无法连接的问题。

    问题现象如下,ovs开始安装后,对ovs的操作是正常的,但是,现在系统重启后,OVS的操作第一条命令就失败,如下: 问题解决方法: 参考  http://blog.csdn.net/xyq54/art ...

  9. 系统重启后,mr程序不生成当前时间段的MRx文件问题

    系统重启后,mr程序不生成当前时间段的MRx文件问题 2019-4-2 之前使用正常的MR程序,系统重启后无法生成MRE\MRO\MRS文件. 服务器有两个时钟:硬件时钟和系统时钟 硬件时钟从根本上讲 ...

随机推荐

  1. PCIE 2.0协议概念基本科普

    PCIE的概念:是电脑总线PCI的一种,它沿用现有的PCI编程概念及通信标准,但建基于更快的串行通信系统. 英特尔是该接口的主要支持者.PCIe仅应用于内部互连.由于PCIe是基于现有的PCI系统,只 ...

  2. python中ones的含义和用法

    ones是numpy的一个内置函数,作用是生成参数为一的数组.英文解释: Return a new array of given shape and type, filled with ones. 例 ...

  3. 大数据项目相关技术栈(Hadoop周边技术)

    J2EE 框架Spring 开发框架 + SSH or SSM Lucene 索引和查询IKAnalyzer 分词Webmagic 爬虫 ETL工具:KettleSqoop 结构化数据库-hadoop ...

  4. Jenkins和gitlab集成自动构建

    Jenkins安装插件 Jenkins上需要安装如下插件 Gitlab Hook Plugin,GitLab Plugin Job配置 在需要自动触发的Job中 选择Build Triggers进行如 ...

  5. 【2019北京集训测试赛(十三)】数据(sj) 冷静分析

    题目大意:给你一个代表区间$[1,n]$的线段树,问你随机访问区间$[1,n]$中的一个子区间,覆盖到的线段树节点个数的期望(需要乘上$\frac{n(n-1)}{2}$后输出). 数据范围:$n≤1 ...

  6. web自动化测试(java)---元素定位

    和python类似,java-selenium也提供了很多种元素定位的方法,具体如下: findElement(By.id()) findElement(By.name()) findElement( ...

  7. sql server 备份与恢复系列六 文件组备份与还原

    一. 概述 文件备份是指备份一个或多个文件或文件组中的所有数据.使用文件备份能够只还原损坏的文件,而不用还原数据库的其余部份,从而加快恢复速度.例如,如果数据库由位于不同磁盘上的若干文件组成,在其中一 ...

  8. Docker 数据卷和数据卷容器

    1.本节课主要讲解如何在Docker内部及容器之间管理数据.容器中管理数据主要有两种方式:数据卷(Data volumes)数据卷容器(Data volume containers) 2.数据卷:是一 ...

  9. MVCC浅析

    在并发读写数据库时,读操作可能会不一致的数据(脏读).为了避免这种情况,需要实现数据库的并发访问控制,最简单的方式就是加锁访问.由于,加锁会将读写操作串行化,所以不会出现不一致的状态.但是,读操作会被 ...

  10. PM2来部署nodejs服务器永久开启

    pm2 日常使用   1. pm2 是什么? 日常开发中需要启动一个node项目,需要用npm run …,,如果终端被关掉,程序也就自动停止,有时候几个项目一起跑起来,好几个终端开着,个人不太喜欢, ...