深入理解Android之设备加密Device Encryption
Android从4.4开始就支持一项功能,那就是对设备进行加密。加密自然是为了安全性考虑,由于/system目录是只读的,手机里那些存储设备分区中需要保护的就剩下/data/分区和sdcard了。显然,/data/和sdcard大量存储了用户数据(比如app运行时存储的数据),对他们进行加密非常非常有必要。
Android 5.0发布后,为了Android设备在企业中的使用,设备加密这个功能默认就必须启用,但是加密这玩意还是对功耗,性能有影响,而市面上大部分手机还跟不上Android进化的步伐,所以Google在5.0发布几个月后又推出了Android 5.1,在这个代号为Android Lollipop MR1的版本上,设备加密就不是默认开启的了(我个人觉得可能对于升级现有手机系统而言,Device Encryption默认不开启,而对于新上市的并且配备Android L系统的手机,这个选项很可能是开启的)。
本文目的就是分析下系统中与设备加密工作相关的流程。
代码路径:
- init源码:system/core/init
- init.rc:system/core/rootdir/
- init.flo.rc,fstab.flo等文件:device/asus/flo/
- fs_mgr.c:system/core/fs_mgr/
- vold源码:system/vold/
- device mapper代码:kernel/drivers/md
环境准备:
- 最好有Android 5.1的代码,我在百度云盘上放置了一份,位置在:http://pan.baidu.com/s/1bn4fvVT
- 下载一份Kernel的代码。搞Android系统研究,最好是买一个Nexus。我本人有一个Nexus7。设备代号叫“flo”。kernel代码可安装google官方的方法下载以及编译:http://source.android.com/source/building-kernels.html。
- 当然,Nexus设备有一些设备厂商自有的东西没有源代码。那边我们在编译设备使用的系统时,需要把这些东西(往往是一些二进制库啊,甚至事先编好kernel镜像文件)先下载过来。位置在https://developers.google.com/android/nexus/blobs-preview。在这个网页上把对应设备的私有库下载过来,然后就可以编译能烧到手机并且能正常运转的Android OS了。
一 init的工作
1.1 init配置文件
在讲解设备加密前,我们还是要先来回顾下init的工作流程。我其实在《深入理解Android 卷1》一书中曾经详细分析过init的代码。那时候的版本好像还是2.3。不过到今天5.1为止,init的代码看起来改变是很大,但其实书中所分析的init的软件结构,工作流程等在5.1里没有太大的变化。
init是Linux用户空间的天字号第一进程,但它干的工作倒是很朴实,就是忠实执行我们在init配置文件(主文件是init.rc,通过import语句加载其他的rc文件)中设置的各种动作。2.3时,我记得init.rc文件还是扁平化,无层级的。但是随着Android进化,init配置文件也变得有组织了。
下面列出的5.1中init.rc中一些常见的的结构和代码,重要的地方,我在其中有注释。
[-->init.rc]
#通过import引入其他rc文件。在flo设备中,$ro.hardware的值就是“flo”
#在init的组织结构中,import对应的是一个section。一个section从对应关键词开始,直到
#下一个section的关键词为止
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
#on xxx这种结构中,on所对应的也是一个section,early-init是这个section的名字
#一个section内可以包含很多command。比如下面的write,就属于command
on early-init
write/proc/1/oom_score_adj -1000
......
on xxx #early-initsection到xxx前结束
#除了import,on之外,service关键词也代表一个section,如下面的:
#和on section不一样的是,servicesection后面的语句描述的
#除了command外,还有这个section的属性(option)。属性也有一些关键词,比如
#下面的class,critical。
service ueventd /sbin/ueventd
classcore #描述该service属于core这种class
critical #描述该service属于critical级别
seclabelu:r:ueventd:s0 #seclabel是一种command
.......
简而言之,对Android设备来说,init.rc等一众配置文件可以说是整个系统中最重要的部分了。没有配置的设备,其实仅仅就是一台未初始化的机器!所以,当init本身的代码写好后,对我们来说剩下的事情就是写配置文件了。结合上述的init.rc实例,我们可总结5.1中init配置文件的组织结构如下:
- 配置文件由一段一段的section组成。目前section的标示有三个关键词,分别是import、on和service。
- 其中import用来导入指定的配置文件。
- on section往往包含了一组需要执行的命令(command)。
- service代表了一个服务,它其实是指系统需要启动的一个进程。以及对应需要执行的一些命令。当然,service所代表的进程还可以进一步赋予属性(option)。比如class属性将service分成对应的类,而critical属性则标示该服务的关键性。
对我们代码研究者而言,具体的工作还是由函数来执行的。想要了解init到底针对每条command或者section干了什么,我们需要关注init的关键文件keywords.h。图1所示为其部分内容:
图1 keywords.h
找到这个文件,当想了解配置文件中各种命令的处理方法时,就可以直接找到对应的函数了。
注意:关于init的代码分析我们就不在啰嗦。感兴趣的兄弟们请研究卷1。
对于设备加密来说,mount_all是一个重要的命令。我们先来看它的配置文件。对于flo设备而言,它放在init.flo.rc文件的fs section中
[-->init.flo.rc]
on fs
mount_all ./fstab.flo
restorecon_recursive/persist
setpropro.crypto.fuse_sdcard true
write/sys/kernel/boot_adsp/boot 1
如图1所示,mount_all是一个command,对应的函数是do_mount_all,这个命令有一个参数,就是linux上常见的fstab文件。对flo设备而言,它是fstab.flo。图2所示为此文件的部分内容:
图2 fstab.flo文件部分内容
根据前述内容,data分区是可以加密的,所以在图2的红色框里的最后,加载data分区的时候有一个属性:
- encryptable=/dev/block/.../metadata。encryptable表示该分区可以被加密。后面的文件名表明一些加密信息(比如password啊,生成加密密钥时使用的盐值等信息存在这个文件里)。
1.2 do_mount_all分析
1.2.1 do_mount_all代码
do_mount_all将解析fstab文件,并挂载相应的分区。其代码如下所示:
[-->builtins.c::do_mount_all]
int do_mount_all(int nargs, char **args)
{
pid_tpid; int ret = -1; int child_ret = -1;
intstatus; const char *prop; struct fstab*fstab;
//init会fork一个子进程用来执行mount
pid =fork();
if (pid> 0) {
//init等子进程的执行结果
intwp_ret = TEMP_FAILURE_RETRY(waitpid(pid,&status, 0));
......
if(WIFEXITED(status)) {
ret = WEXITSTATUS(status);
}.....
} elseif (pid == 0) {
//子进程,args[1]参数就是”/fstab.flo”,fs_mgr_read_fstab将解析fstab文件
fstab = fs_mgr_read_fstab(args[1]);
//fs_mgr_mount_all的返回值具有决定意义
child_ret = fs_mgr_mount_all(fstab);
fs_mgr_free_fstab(fstab);
_exit(child_ret);
}
//回到init进程,子进程的返回结果非常重要:
if (ret== FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
//如果fstab里设置了默认加密,则设置下面这个属性
property_set("vold.decrypt","trigger_encryption");
} elseif (ret == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
//如果设备已经加密,则我们需要做一些处理:
property_set("ro.crypto.state","encrypted");
property_set("vold.decrypt", "trigger_default_encryption");
} elseif (ret == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED){
//如果设备没有加密,则不需要做什么处理,和以前一样
property_set("ro.crypto.state","unencrypted");
action_for_each_trigger("nonencrypted", action_add_queue_tail);
} elseif (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
//....其他
ret= wipe_data_via_recovery();
}
returnret;
}
对do_mount_all来说,它要考虑设备加密的各种情况。仔细想想,应该是有下面三种情况的:
- 系统第一次开机,而且系统设置为设备必须加密,则上述代码走FS_MGR_MNTALL_DEV_NEES_ENCRYPTION。
- 设备加密后的系统开机,它发现设备被加密了,所以走FS_MGR_MNTALL_DEV_MIGHT_BE_ENCYRPTED。
- 设备不用加密,走FS_MGR_MNTALL_DEV_NOT_ENCYRPTED。
来看看fs_mgr_mount_all函数:
[-->fs_mgr.c::fs_mgr_mount_all]
int fs_mgr_mount_all(struct fstab *fstab)
{
int i =0; int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
interror_count = 0; int mret = -1;
intmount_errno = 0; int attempted_idx = -1;
......
for (i =0; i < fstab->num_entries; i++) {
......
intlast_idx_inspected;
//mount fstab里的项,返回值很重要
mret= mount_with_alternatives(fstab, i,&last_idx_inspected,
&attempted_idx);
i =last_idx_inspected;
mount_errno = errno;
//mret=0,表示mount成功。如果设备已经加密,则mount是不会成功的。
//所以,这里需要考虑第一次开机,然后发现设备被强制要求加密的情况
if(!mret) {
//如果该项是强制需要加密的(图2中的encryptable换成forceencrypt)
if ( (fstab->recs[attempted_idx].fs_mgr_flags & MF_FORCECRYPT)
//或者设置了“ro.vold.forceencryption”属性
|| (device_is_force_encrypted()
&& fs_mgr_is_encryptable(&fstab->recs[attempted_idx]))) {
//先卸载这个需要强制加密的设备
if (umount(fstab->recs[attempted_idx].mount_point)== 0) {
if (encryptable == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
encryptable= FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION;
} else {
//系统只支持对一个存储分区加密,不能对多个设备加密
encryptable =FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED;
}
} ......
}
continue;
}
//如果mount失败,则需要判断是不是已经加密过的设备。这种情况下,不能通过mount挂载!
if (mret&& mount_errno != EBUSY && mount_errno != EACCES &&
fs_mgr_is_encryptable(&fstab->recs[attempted_idx])) {
//加密的设备,但是前4K的内容要么是0要么是0xff。这是被wiped分区,需要recovery
if(partition_wiped(fstab->recs[attempted_idx].blk_device)) {
......
encryptable = FS_MGR_MNTALL_DEV_NEEDS_RECOVERY;
continue;
}else {
//在/data目录下挂载tmpfs设备,而不是指定的实际设备
if(fs_mgr_do_tmpfs_mount(fstab->recs[attempted_idx].mount_point)
< 0) {......
}
}
encryptable = FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED;
} ......
}
.......
}
fs_mgr_mount_all的流程可总结如下:
- 先mount fstab中的各个项。如果成功的话,表明对应项的块设备肯定没有被加密(加密的设备没法被mount)。
- 如果这个设备是强制需要加密的,则先卸载这个设备,然后设置返回值为FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION。
- 如果mount失败,则需要判断是不是因为加密设备导致。如果该设备没有被清空(wiped),则先把tmpfs挂载到/data目录下。然后设置返回值为FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED。
注意:为什么要挂载tmpfs到/data/目录下呢?这是因为没有这个/data目录,系统根本没办法起来。因为系统很多运行时保存的信息,app自己的缓存目录等都放在/data下。为了保证系统能启动,所以这里先弄一个临时/data目录。等到后续把加密设备通过device-mapper方式准备好后,我们再把加密设备挂载到/data/目录下。注意,加密设备的mount不是直接挂载加密设备,而是通过挂载一个device-mapper来实现的。
回到do_mount_all,我们看看它退出前都干了些什么:
- 如果是强制设备第一次开机,设置“vold.decrypt”属性值为“trigger_encryption”。
- 如果是已经加密设备的后续开机,设置“ro.crypto.state”属性值为“encrypted”,同时设置"vold.decrypt"属性值为"trigger_default_encryption"。
- 非加密设备,设置"ro.crypto.state"值为"unencrypted"。
下面我们分别来分析前两个流程。
1.2.2 强制加密设备的第一次开机
Android里边,属性可以被设置,也可以获取属性值。除此之外,还可以在属性等于特定值时执行一些特殊的动作,比如在强制加密设备第一次开机的时候,我们需要对这个设备进行加密。但是do_mount_all仅仅是设置vold.decrypt属性为trigger_enryption就可以触发加密了。这是怎么做到的呢?
[-->init.rc]
#当属性vold.decrypt的值变成trigger_enryption后,将执行两个动作:
on property:vold.decrypt=trigger_encryption
startsurfaceflinger #第一个动作是启动surfaceflinger服务
startencrypt #第二个动作是启动encrypt动作
....
#encrypt section定义,它对应为执行/system/bin/vdc函数
service encrypt /system/bin/vdc --wait cryptfsenablecrypto inplace default
disabled
oneshot
# voldwill set vold.decrypt to trigger_restart_framework (default
#encryption)
是不是So easy呢?看来,强制加密设备的首次加密将由vdc来处理。这里先透露一下,vdc其实不过是给vold发送enablecrypto命令罢了,真正的加密工作是由vold来完成的。
anyway,还是来看看vdc的代码吧:
[-->vdc.c::main和do_command]
int main(int argc, char **argv) {
intsock; int wait_for_socket; char *progname;
progname= argv[0];
//关于vold的故事,可阅读我的《深入理解Android》卷1,里边有详细的源码分析。
//vdc将通过“vold”这个socket和vold通信。
while((sock = socket_local_client("vold",
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM))< 0) {
......
}
if(!strcmp(argv[1], "monitor")) {
exit(do_monitor(sock, 0));
} else {
exit(do_cmd(sock, argc, argv));
}
}
static int do_cmd(int sock, int argc, char **argv){
charfinal_cmd[255] = "0 "; int i;size_t ret;
for (i =1; i < argc; i++) {
char*cmp;
.....
//把命令发给vold,vdc工作就算完成了
write(sock, final_cmd,strlen(final_cmd) + 1) < 0;
.....
returndo_monitor(sock, 1);
}
vold怎么处理加密呢?我们到最后再说。到此为止,强制设备首次加密的前半部分流程就算完成了。
下面看看加密设备的加载流程。
1.2.2 已加密设备的加载
init也是通过属性来触发已加密设备加载的工作流程的,如下所示:
[-->init.rc]
on property:vold.decrypt=trigger_default_encryption
start defaultcrypto
...
#也是启动vdc来挂载已加密设备
service defaultcrypto /system/bin/vdc --wait cryptfs mountdefaultencrypted
disabled
oneshot
# voldwill set vold.decrypt to trigger_restart_framework (default
#encryption) or trigger_restart_min_framework (other encryption)
......,最终工作都是由vold来完成的....具体怎么弄,暂且不表。此时,本文和init相关的工作就告一段落。
二 Settings的工作
Android设备中,具体加密工作都是vold来完成的。前面讲的init是为了解决已加密设备的挂载问题和强制加密设备第一次开机进行加密的问题。当然,强制加密设备第一次开机并且被加密后,后续的问题就会变成已加密设备如何挂载了。
我们先把这些东西放到一边去。来看这样一个东西:系统的设置里边,是可以触发设备加密的。相关选项如图3所示:
图3 设置里的加密设备选项
图3右边界面对应的Activity叫CryptKeeperSettings,相关代码如下所示:
2.1 CryptKeeperSettings
[-->CryptKeeperSettings.java::onCreateView]
public View onCreateView(LayoutInflater inflater,ViewGroup container,
Bundle savedState) {
mContentView = inflater.inflate(R.layout.crypt_keeper_settings, null);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_encrypt);
mInitiateButton.setOnClickListener(mInitiateListener);
mInitiateButton.setEnabled(false);
......
returnmContentView;
}
最重要的是代码中的mInitialListener,
[-->CryptKeeperSettings.java::mInitiateListener]
private Button.OnClickListener mInitiateListener =new Button.OnClickListener() {
@Override
publicvoid onClick(View v) {
if (!runKeyguardConfirmation(KEYGUARD_REQUEST)){
......
}
}
};
//直接看代码:
private boolean runKeyguardConfirmation(intrequest) {
Resources res = getActivity().getResources();
ChooseLockSettingsHelper helper =
newChooseLockSettingsHelper(getActivity(), this);
//从DevicePolicyManager那获取设备的密码控制要求,如果没有指定的话,将直接进入
//跳入后面的流程
if(helper.utils().getKeyguardStoredPasswordQuality()
==DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
//一般是没有设置密码控制级别的,所以加密的时候将使用默认加密,密码为””
showFinalConfirmation(StorageManager.CRYPT_TYPE_DEFAULT, "");
return true;
}
returnhelper.launchConfirmationActivity(request, null,
res.getText(R.string.crypt_keeper_confirm_encrypt),
true);
}
注意,在5.1中,系统支持一种默认的加密方式,即加密时默认用户传入的密码是”default_password”。当然,这是在用户没有设置解锁方法的情况下,系统默认的行为。如果有DevicePolicyManager或者用户自己设置了解锁密码,则系统会用它们做为设备加密密码。
为了方便,我们只讨论默认密码的情况。这个时候,showFinalConfirmation函数将被调用。
来看看这个函数,非常简单:
[-->CryptKeeperSettings.java::showFinalConfirmation]
private void showFinalConfirmation(int type,String password) {
Preference preference = new Preference(getActivity());
//启动CrypteKeeperConfirm界面
preference.setFragment(CryptKeeperConfirm.class.getName());
preference.setTitle(R.string.crypt_keeper_confirm_title);
//type为默认的加密方法,password为””
preference.getExtras().putInt("type", type);
preference.getExtras().putString("password", password);
((SettingsActivity) getActivity()).onPreferenceStartFragment(null,
preference);
}
UI小跳转吧,又到了另外一个界面。它和图2右边那个图很类似。
2.2 CryptKeeperConfirm
CryptKeeperConfirm界面里最下方也有一个按钮,我们直接看它的处理吧。
[-->CryptKeeperConfirm.java::mFinalClickListener]
private Button.OnClickListener mFinalClickListener= new
Button.OnClickListener() {
publicvoid onClick(View v) {
.....
//启动一个Blank Activity,代码也在此文件中
Intent intent = new Intent(getActivity(), Blank.class);
intent.putExtras(getArguments());
startActivity(intent);
//2. The system locale.
try{
//调用MountService的setField功能,设置系统语言
IBinder service = ServiceManager.getService("mount");
IMountService mountService = IMountService.Stub.asInterface(service);
mountService.setField("SystemLocale",
Locale.getDefault().toLanguageTag());
}......
}
};
Blank这个Activity比较关键,代码如下所示:
[-->CryptKeeperConfirm.java::Blank]
public static class Blank extends Activity {
privateHandler mHandler = new Handler();
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crypt_keeper_blank);
.....
//禁止使用状态栏的功能,加密过程最好是不要被打搅!
StatusBarManager sbm = (StatusBarManager)
getSystemService(Context.STATUS_BAR_SERVICE);
sbm.disable(StatusBarManager.DISABLE_EXPAND
| StatusBarManager.DISABLE_NOTIFICATION_ICONS
| StatusBarManager.DISABLE_NOTIFICATION_ALERTS
| StatusBarManager.DISABLE_SYSTEM_INFO
| StatusBarManager.DISABLE_HOME
| StatusBarManager.DISABLE_SEARCH
| StatusBarManager.DISABLE_RECENT
| StatusBarManager.DISABLE_BACK);
mHandler.postDelayed(new Runnable() {
public void run() {
IBinder service = ServiceManager.getService("mount");
IMountService mountService =
IMountService.Stub.asInterface(service);
try {
Bundle args = getIntent().getExtras();
//调用MountService的encryptStorage函数
mountService.encryptStorage(args.getInt("type", -1),
args.getString("password"));
}.....
}
},700);
}
}
好吧。Settings的工作还算比较简单,就是界面显示,然后再触发加密流程。在这个过程中,Settings的工作确实没有什么叹为观止的。但是前面代码中我们曾提到说,加密过程中,系统会有一个进度栏显示加密进度和所剩余时间。这个其实也是由Settings来实现的。我们先把这部分介绍了。
2.3 加密进度显示
加密进度显示这个东西说难不难,说简单它也不简单。事情是这样的:
- 当vold在加密设备的过程中,这个时间可能很长很长,根据设备中之前是否有很多数据有关。
- 另外,挂载加密后的设备时,可能用户用得不是默认密码,那这个时候需要启动framework,然后弹出一个密码输入框让用户输入密码。这也就是为什么加密设备挂载时会先mount一个tmpfs到/data分区下。因为这个时候framework里那些service是要启动的,还有输入法等所谓的core app。
所以,在加密过程中或者加密设备挂载前,framework有一些特殊的处理:
[-->SystemServer.java::startBootstrapServices]
private void startBootstrapServices() {
......
StringcryptState = SystemProperties.get("vold.decrypt");
// ENCRYPTING_STATE值为“trigger_restart_min_framework”
if(ENCRYPTING_STATE.equals(cryptState)) {
mOnlyCore= true;
} elseif (ENCRYPTED_STATE.equals(cryptState)) {
// ENCRYPTED_STATE值为“1“
mOnlyCore = true;
}
......
}
当属性vold.decrypt值为“trigger_restart_min_framework“或者为”1“的时候,mOnlyCore为true。mOnlyCore干啥用的?见图4:
图4 coreApp示例
mOnlyCore表示当App的AndroidManifest.xml中有coreApp=”true”的时候,系统启动时,PackageManagerService才会解析这些APK。那么,系统中哪些APK为coreApp呢?如图5所示:
图5 coreApp
图5列出Packages目录和frameworks/base/packages目录中属于coreApp的所有APK。那么,当mOnlyCore为true的时候,系统启动后只会解析这些coreApp。
问题来了,图5中并没有Launcher,那加密过程中进度界面怎么显示?原来,Settings中也有一个能响应Home Intent的Activity,如图6所示:
图6 CryptKeeper
CryptKeeper属于HOME这一类,而Settings又是coreApp。所以,当我们在加密过程中的时候,framework启动完毕后,接着就会启动HOME。这个时候只能由CryptKeeper来响应HOME Intent了。如图7所示:
图7 加密进度显示界面
图6中,状态栏和虚拟按键栏都没法使用了,就是防止在加密过程中用户乱动!!!手欠啊真是....
CryptKeeper比较复杂,除了显示加密进度,还能在加密设备挂载前,让用户输入密码以验证是否允许挂载加密设备。这个时候的UI就变成图8了:
图8 挂载加密设备时需要输入用户设置的密码
关于CryptKeeper,我就不说太多了。这代码没有任何难度的。
注意:对于挂载加密设备来说,系统其实并不是真的去解密存储设备,然后再挂载。而仅是判断用户输入的密码是不是对的。如果是对的话,它会通过device-mapper去挂载一个虚拟设备到/data下,而这个虚拟设备一方面连着实际的block设备。这样,当用户从/data读取、写入数据时,这个虚拟设备都会把数据进行加解密。比如用户读数据时,它会从真实设备里读取加密后的数据,然后解密返回给用户。用户写数据时候,它会加密后再写到真实设备中!
三 vold的工作
我们直接从Settings跳到vold了,因为MountService其实就是和前面提到的vdc一样,发送相关命令给vold。真正干活还是在vold里。
注意,vold的源码,我曾经在《深入理解Android》卷1中讲过了。那时候还是2.3,但是即使到5.1为止,vold的代码和结构都没有太大的变动。
vold中有一个CommandLister,在那里,它注册了自己能处理的各种命令和对应处理模块:
[-->CommandListener.cpp::CommandListener]
CommandListener::CommandListener() :
FrameworkListener("vold", true) {
registerCmd(new DumpCmd());
registerCmd(new VolumeCmd());
registerCmd(new AsecCmd());
registerCmd(new ObbCmd());
registerCmd(new StorageCmd());
registerCmd(new CryptfsCmd());
registerCmd(new FstrimCmd());
}
CryptfsCmd专门处理以“cryptfs“开头的命令,处理的地方在其runCommand函数中。对加密一个设备而言,对应的命令格式如下:
#wipe和inplace:wipe表示先清干净存储设备,然后加密,inplace表示对现有数据加密
#default等,表示密码模式。default是默认密码,password,或者pin,patter都和锁屏使用方式
#有关
cryptfsenablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
enablecrypto对应的处理函数入口是cryptfs_enable或者是cryptfs_enable_default。它们内部都会调用cryptfs_enable_internal,我们重点看这个函数:
3.1 cryptfs_enable_internal
[-->cryptfs.c::cryptfs_enable_internal]
int cryptfs_enable(char *howarg, int type, char *passwd, intallow_reboot)
{
char*adjusted_passwd = adjust_passwd(passwd);
if(adjusted_passwd) {
passwd = adjusted_passwd;
}
int rc =cryptfs_enable_internal(howarg,type, passwd, allow_reboot);
free(adjusted_passwd);
returnrc;
}
int cryptfs_enable_default(char *howarg, int allow_reboot)
{
//如果使用默认密码,就走这个流程:
return cryptfs_enable_internal(howarg,CRYPT_TYPE_DEFAULT,
DEFAULT_PASSWORD, allow_reboot);
}
3.1.1 第一部分工作
[-->cryptfs.c::cryptfs_enable_internal第一部分]
int cryptfs_enable_internal(char *howarg, intcrypt_type, char *passwd,
int allow_reboot)
{
int how= 0;
charcrypto_blkdev[MAXPATHLEN], real_blkdev[MAXPATHLEN];
unsignedlong nr_sec;
unsignedchar decrypted_master_key[KEY_LEN_BYTES];
intrc=-1, fd, i, ret;
structcrypt_mnt_ftr crypt_ftr;
structcrypt_persist_data *pdata;
charencrypted_state[PROPERTY_VALUE_MAX];
charlockid[32] = { 0 };
charkey_loc[PROPERTY_VALUE_MAX];
charfuse_sdcard[PROPERTY_VALUE_MAX];
char*sd_mnt_point;
int num_vols;
structvolume_info *vol_list = 0;
off64_tpreviously_encrypted_upto = 0;
if(!strcmp(howarg, "wipe")) {
how =CRYPTO_ENABLE_WIPE;
} elseif (! strcmp(howarg, "inplace")) {
how = CRYPTO_ENABLE_INPLACE; //我们对应这种情况
} ....
/*
不知道什么原因,上一次加密还没处理完,系统就重启或者vold就重启了。为了处理这种情况,
vold会把加密过程都写到一个地方去,然后要每次加密前都需要检查。因为一旦加密被干扰而又没
正确处理的话,用户数据就会丢失,这是非常严重的事故!
*/
if (how== CRYPTO_ENABLE_INPLACE
&& get_crypt_ftr_and_key(&crypt_ftr)== 0
&& (crypt_ftr.flags & CRYPT_ENCRYPTION_IN_PROGRESS)) {
previously_encrypted_upto = crypt_ftr.encrypted_upto;
crypt_ftr.encrypted_upto = 0;
crypt_ftr.flags &= ~CRYPT_ENCRYPTION_IN_PROGRESS;
crypt_ftr.flags |= CRYPT_INCONSISTENT_STATE;
put_crypt_ftr_and_key(&crypt_ftr);
}
上面这段代码中,比较重要的一个函数就是get_crypt_ftr_and_key,这个函数用于获取之前存储的和设备加密有关的上下文信息。这个信息非常之重要!
(1) get_crypt_ftr_and_key
[-->cryptfs.c::get_crypt_ftr_and_key]
static int get_crypt_ftr_and_key(structcrypt_mnt_ftr *crypt_ftr)
{
int fd;
unsignedint nr_sec, cnt;
off64_tstarting_off;
int rc =-1;
char*fname = NULL;
structstat statbuf;
/*
get_crypt_ftr_info用于获取加密信息的存储位置。按Android的设计,这个信息可以存储在
两个地方。一个是fstab里encryptable=xxxx中xxx这个存储设备里,也可以存储在
需要挂载的设备里最后一段空间里。比如fstab.flo中,
1 /dev/block/platform/msm_sdcc.1/by-name/userdata是要挂载的设备,所以加密信息
存储在这个设备的最后一段空间里。但是由于我们还有下面这句话:
2 encryptable=/dev/block/platform/msm_sdcc.1/by-name/metadata
所以实际的加密信息存储在metadata文件里
*/
get_crypt_ftr_info(&fname, &starting_off);
//上面函数的fname指明加密信息存储在哪个文件中,starting_off表示信息存储在文件里哪个位置
fd =open(fname, O_RDWR));
fstat(fd,&statbuf);
if(S_ISREG(statbuf.st_mode) && (statbuf.st_size != 0x4000))
gotoerrout;
if(lseek64(fd, starting_off, SEEK_SET) == -1)
gotoerrout;
//读取加密信息,保存在一个crypt_mnt_ftr这样的结构体里
if ( (cnt =read(fd, crypt_ftr, sizeof(structcrypt_mnt_ftr)))
!= sizeof(struct crypt_mnt_ftr))
gotoerrout;
......
rc = 0;
......
}
通过上述代码,大家要牢记几个重要知识点:
- 加密所用的上下文信息(包括加密版本号,密钥信息,加密方法,加密进度等)都是存储在某个地方。这个地方可以是加密设备最后一块区域,也可以单独指定一个文件(通过fstab encryptable=xxx来指定)。
3.1.2 第二部分工作
接着看第二部分工作:
[-->cryptfs.c::cryptfs_enable_internal第二部分]
//取出"ro.crypto.state"的属性,本例中其值应该是空
property_get("ro.crypto.state",encrypted_state, "");
if(!strcmp(encrypted_state, "encrypted") &&!previously_encrypted_upto) {
//previously_encrypted_upto表示上一次加密的进度,如果是0的话,而加密状态又是
//”encrypted”,这就是有问题啊
gotoerror_unencrypted;
}
//取出key存储的路径和待加密的路径,本例中:
//key_loc=/dev/block/platform/msm_sdcc.1/by-name/metadata
//real_blkdev=/dev/block/platform/msm_sdcc.1/by-name/userdata
fs_mgr_get_crypt_info(fstab, key_loc, 0, sizeof(key_loc));
fs_mgr_get_crypt_info(fstab, 0, real_blkdev, sizeof(real_blkdev));
//打开真实设备并获取它的夸个数!
fd =open(real_blkdev, O_RDONLY);
nr_sec = get_blkdev_size(fd));
close(fd);
//KEY_IN_FOOTER:key是不是存储在存储空间的最后一部分。我们这不是
if ((how== CRYPTO_ENABLE_INPLACE) && (!strcmp(key_loc, KEY_IN_FOOTER))) {
......
}
//加密耗电,所以要搞一个WakeLock
snprintf(lockid, sizeof(lockid), "enablecrypto%d", (int)getpid());
acquire_wake_lock(PARTIAL_WAKE_LOCK, lockid);
......//其他一些小处理
//设置属性,init肯定会干一些事情!
property_set("vold.decrypt","trigger_shutdown_framework");
SLOGE("Just asked init to shut down class main\n");
//卸载SDCard中ASEC相关的东西
vold_unmountAllAsecs();
property_get("ro.crypto.fuse_sdcard", fuse_sdcard,"");
......//SDCard的处理
//卸载/data分区
if(wait_and_unmount(DATA_MNT_POINT, false)) {
......
}
注意上述代码:
- 通过设置”vold.decrypt”为“trigger_shutdown_framework“来干一些事情。
- 然后卸载/data/分区
按道理,如果卸载/data/分区的话,应用程序肯定会奔溃。怎么避免呢?很简单,把它们都杀了,然后不再启动就好。来看init.rc
[-->init.rc]
on property:vold.decrypt=trigger_shutdown_framework
class_reset late_start #reset late_start和main类别的服务
class_reset main#如果查看init代码的话,reset就是干掉这些服务,并且不会自动重启它们
main类别的服务有谁呢?最重要的就是zygote了。它属于main类别,所以上面那个trigger会干掉zygote,并且不会重启。没有zygote,java世界是没可能启动的!
来看第三部分工作
3.1.3 第三部分工作
[-->cryptfs.c::cryptfs_enable_internal第三部分工作]
if (how ==CRYPTO_ENABLE_INPLACE) {
//挂载tmpfs到/data目录。注意,这里似乎和init的做法有重复。这是因为我们是直接在
//settings里触发的。而Settings则是在加载加密设备的时候触发的。
fs_mgr_do_tmpfs_mount(DATA_MNT_POINT);
//设置加密进度,由“vold.encrypt_progress“属性值表示
property_set("vold.encrypt_progress","0");
//在data目录下创建一些必要的目录,否则framework将启动不了
prep_data_fs();
sleep(2);
}
//首次加密,所以previously_encrypted_upto为0
if(previously_encrypted_upto == 0) {
//初始化一个加密上下文信息,比如版本号之类的
cryptfs_init_crypt_mnt_ftr(&crypt_ftr);
//设置待加密数据的大小,如果存储在加密设备的末尾,就得把这段大小给抠掉
if(!strcmp(key_loc, KEY_IN_FOOTER))
crypt_ftr.fs_size = nr_sec - (CRYPT_FOOTER_OFFSET /
CRYPT_SECTOR_SIZE);
else crypt_ftr.fs_size = nr_sec;
crypt_ftr.flags |= CRYPT_INCONSISTENT_STATE;
crypt_ftr.crypt_type = crypt_type;
#ifndef CONFIG_HW_DISK_ENCRYPTION
//设置加密算法,硬件最好支持,否则这速度上不去啊!
strlcpy((char *)crypt_ftr.crypto_type_name,"aes-cbc-essiv:sha256",
MAX_CRYPTO_TYPE_NAME_LEN);
#else
......
#endif
//光有password作为key还不够,我们还需要加入盐值,最终会搞到keymaster里边得到
//一组密钥。相关基础知识可参考我博客中的关于JavaSecurity方面的文章2篇!
create_encrypted_random_key(passwd, crypt_ftr.master_key,
crypt_ftr.salt, &crypt_ftr))
//把这些信息存到key位置或者存储空间的最末尾
put_crypt_ftr_and_key(&crypt_ftr);
if(!persist_data) {
pdata = malloc(CRYPT_PERSIST_DATA_SIZE);
if (pdata) {
init_empty_persist_data(pdata, CRYPT_PERSIST_DATA_SIZE);
persist_data = pdata;
}
}
//好吧,其实key信息是存储了两份,挨着的。还是怕出事啊。当第一份被破坏的时候,还能
//从第二份那修复回来!
if(persist_data) {
save_persistent_data();
}
}
第三部分工作中,最重要的是prep_data_fs,它其实也是通过设置属性来干活的,直接看init.rc
[-->init.rc]
#prep_data_fs设置属性值为trigger_post_fs_data,
on property:vold.decrypt=trigger_post_fs_data
triggerpost-fs-data #执行post-fs-data这一section所包含的命令
#post-fs-data重建了系统运行时候/data目录下所有必需的资源
on post-fs-data
chown system system /data
chmod0771 /data
restorecon /data
copy /data/system/entropy.dat /dev/urandom
mkdir /data/dontpanic 0750 root log
copy /proc/apanic_console/data/dontpanic/apanic_console
chown root log/data/dontpanic/apanic_console
chmod 0640 /data/dontpanic/apanic_console
......
mkdir /data/misc/zoneinfo 0775 systemsystem
mkdir /data/misc/vpn 0770 system vpn
......
3.1.4 第四部分工作
[-->cryptfs.c::cryptfs_enable_internal第四部分工作]
if (how == CRYPTO_ENABLE_INPLACE) {
//这个动作会触发class main类型的service重启动,当然,zygote也就起来了
//然后framework都起来了。但是由于SystemServer只加载coreApp,所以我们能看见
//图6
property_set("vold.decrypt", "trigger_restart_min_framework");
}
decrypt_master_key(passwd,decrypted_master_key, &crypt_ftr, 0, 0);
/*
利用device-mapper机制创建一个
1 device mapper设备,其实device mapper是一个驱动,叫md(mapped device之意)
2 为这个device mapper设备设置一个crypt虚拟项。这个crypt虚拟项会和
待加密设备(real_blkdev)关联起来.
3 crypto_blkdev是上面device mapper创建的一个虚拟项设备,命名方式为
/dev/block/dm-xxx。xxx为device mapper的minor版本号。
4 设备加解密工作就是在这个crypto_blkdev读写过程中完成的。以后我们挂载这个
crypto_blkdev到/data分区就可以了。当从这个设备读的时候,它会从底层关联的
real_blkdev读取加密数据,然后解密传递给读者。当往这个设备写的时候,它会加密后
再写到real_blkdev中
*/
create_crypto_blk_dev(&crypt_ftr, decrypted_master_key, real_blkdev,
crypto_blkdev,"userdata");
/* If weare continuing, check checksums match */
rc = 0;
......
if (!rc){
/*
加密数据。方法很简单,从real_blkdev读取数据,然后写到crypto_blkdev中。
前面反复讲过了。real_blkdev实际上是和crypto_blkdev绑定好的。
1 我们先从real_blkdev中读取未加密的数据,从存储空间第一块位置开始读取
2 然后写到crypto_bldev中。它会先加密,然后对应写回到real_blkdev第一块位置
*/
rc =cryptfs_enable_all_volumes(&crypt_ftr, how,
crypto_blkdev, real_blkdev,
previously_encrypted_upto);
}
......
//删除crypto_blk_dev设备
delete_crypto_blk_dev("userdata");
free(vol_list);
if (!rc) {
crypt_ftr.flags &= ~CRYPT_INCONSISTENT_STATE;
if(how == CRYPTO_ENABLE_INPLACE
&& crypt_ftr.encrypted_upto != crypt_ftr.fs_size) {
//如果加密进度不等于整体数据大小,则设置一个标志位
crypt_ftr.flags |= CRYPT_ENCRYPTION_IN_PROGRESS;
}
//保存加密信息上下文到指定位置
put_crypt_ftr_and_key(&crypt_ftr);
if (how == CRYPTO_ENABLE_WIPE
|| crypt_ftr.encrypted_upto== crypt_ftr.fs_size) {
//如果加密完毕,encyrpted_upto将等于fs_size。注意下面这个流程:
char value[PROPERTY_VALUE_MAX];
/*
获取ro.crypto.state的属性,这个属性:
1 如果值为空:表明是强制加密设备的第一次加密。那么我们要挂载这个已经加密的设备
2 如果值不为空(此时值应该是“uncrypted”,在init中设置),表明我们是对
以前没有加密的设备进行首次加密
*/
property_get("ro.crypto.state", value, "");
if(!strcmp(value, "")) {
property_set("ro.crypto.state","encrypted");
release_wake_lock(lockid);
//挂载加密设备,先检查默认密码是不是对的
cryptfs_check_passwd(DEFAULT_PASSWORD);
cryptfs_restart_internal(1);
return 0;
} else {
//对于其他情况,则重启机器。这里sleep 2秒的原因是让大家有机会看到
//图6中加密进度能到100的情况!
sleep(2);
cryptfs_reboot(reboot);
}
}else {
sleep(2);
cryptfs_reboot(shutdown);
}
} ......
......
return-1;
}
最后,我们来看一下create_crypto_blk_dev函数。
(1) create_crypto_blk_dev
create_crypto_blk_dev和device-mapper的用法有关系。网上有相关教程,这里也不想说太多。整体流程大概就是通过ioctl把device-mapper的虚拟项设置好。
[-->cryptfs.c::create_crypto_blk_dev]
static int create_crypto_blk_dev(structcrypt_mnt_ftr *crypt_ftr,
unsigned char *master_key,char*real_blk_name, char *crypto_blk_name,
const char *name)
{
charbuffer[DM_CRYPT_BUF_SIZE];
charmaster_key_ascii[129];
char*crypt_params;
struct dm_ioctl *io;//发送给devicemapper的命令,都是通过ioctl来完成的
//为mapped device设置目标,也就是建立虚拟项和实际设备的关系
struct dm_target_spec *tgt;
unsignedint minor;
int fd=0; inti; int retval = -1; int version[3];
char*extra_params; int load_count;
//打开/dev/device-mapper,这个设备对应的驱动在kernel/drivers/md目录下。我们
//后续会简单分析下它
fd =open("/dev/device-mapper",O_RDWR);
io =(struct dm_ioctl *) buffer;
ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
//创建一个mappeddevice
ioctl(fd, DM_DEV_CREATE, io);
//获取这个mappeddevice的状态,其实就是版本号之类的东西
ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
ioctl(fd, DM_DEV_STATUS, io);
minor =(io->dev & 0xff) | ((io->dev >> 12) & 0xfff00);
snprintf(crypto_blk_name,MAXPATHLEN, "/dev/block/dm-%u", minor);
extra_params = "";
//一个mapped device可以注册多个虚拟项,这被称作target项。在驱动中,一个叫"crypt"的
//target项会注册到这个mapped device中。下面这个函数就是找到这个MD设备注册的crypt项
//的版本信息
if (! get_dm_crypt_version(fd, name, version)) {
......
}
//建立target项和真实待加密设备(real_blk_name)的关系,然后把这个映射关系加载到MD里
//在这个函数中,我们会把key相关信息存到target项里去。
//注意,本文没有介绍具体的加解密算法,所以这里key的类型其实从来也没讨论过!
load_count= load_crypto_mapping_table(crypt_ftr,master_key, real_blk_name,
name, fd,extra_params);
......
ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
ioctl(fd,DM_DEV_SUSPEND, io);
retval =0;
...
returnretval;
}
有兴趣的话,也看看load_crypto_mapping_table函数:
(2) load_crypto_mapping_table
[-->cryptfs.c::load_crypto_mapping_table]
static int load_crypto_mapping_table(structcrypt_mnt_ftr *crypt_ftr,
unsigned char *master_key,char*real_blk_name,
const char *name, int fd, char*extra_params)
{
charbuffer[DM_CRYPT_BUF_SIZE];
structdm_ioctl *io; struct dm_target_spec *tgt;
char*crypt_params; char master_key_ascii[129];
int i;
io =(struct dm_ioctl *) buffer;
tgt =(struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)];
ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
//设置映射表所需参数
io->target_count = 1;
tgt->status = 0;
tgt->sector_start = 0;
tgt->length = crypt_ftr->fs_size;
#ifdef CONFIG_HW_DISK_ENCRYPTION
//设置target的名字其实,crypt或者req-crypt,到时候MD会根据这个名字找到对应的target
if(!strcmp((char *)crypt_ftr->crypto_type_name, "aes-xts")) {
strlcpy(tgt->target_type,"req-crypt", DM_MAX_TYPE_NAME);
}
else {
strlcpy(tgt->target_type, "crypt",DM_MAX_TYPE_NAME);
}
#else
strlcpy(tgt->target_type, "crypt", DM_MAX_TYPE_NAME);
#endif
crypt_params = buffer + sizeof(struct dm_ioctl) +
sizeof(struct dm_target_spec);
convert_key_to_hex_ascii(master_key, crypt_ftr->keysize,master_key_ascii);
//把加密算法,key信息,真实设备名按指定格式存储好,
sprintf(crypt_params, "%s%s 0 %s 0 %s", crypt_ftr->crypto_type_name,
master_key_ascii, real_blk_name, extra_params);
crypt_params += strlen(crypt_params) + 1;
crypt_params = (char *) (((unsigned long)crypt_params + 7) & ~8);
tgt->next = crypt_params - buffer;
//加载映射表,多试几次!
for (i =0; i < TABLE_LOAD_RETRIES; i++) {
if (!ioctl(fd, DM_TABLE_LOAD, io)) {
break;
}
usleep(500000);
}
......
}
3.1.5 一点小结
到此为止,vold加密的工作就算介绍完了。其中有一些小地方,我们就先略去不讨论。童鞋们自己对着代码多看两遍也就明白了。
另外:
- 对于强制加密设备第一次加密流程中,vold会直接挂载它。
- 对于其他用默认密码加密的设备,在init里我们看到它会通过vdc发送mountdefaultencrypted给vold来挂载。
下面我们就看看vold挂载默认密码加密时的处理。
3.2 mountdefaultencrypted挂载加密设备
[-->cryptfs.c::cryptfs_mount_default_encrypted]
int cryptfs_mount_default_encrypted(void)
{
chardecrypt_state[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt_state, "0");
if(!strcmp(decrypt_state, "0")) {
SLOGE("Not encrypted - should not call here");
} else {
intcrypt_type = cryptfs_get_password_type();
if(crypt_type < 0 || crypt_type > CRYPT_TYPE_MAX_TYPE) {
SLOGE("Bad crypt type - error");
}else if (crypt_type != CRYPT_TYPE_DEFAULT) {
//如果密码类型不是默认的,则启动framework,弹出图8
property_set("vold.decrypt","trigger_restart_min_framework");
return 0;
}else if (cryptfs_check_passwd(DEFAULT_PASSWORD)== 0) {
//先检查密码是否正确,然后再做后续处理!
cryptfs_restart_internal(0);
return 0;
}else {
SLOGE("Encrypted, default crypt type but can‘t decrypt");
}
}
//其他错误处理
property_set("vold.decrypt","trigger_restart_min_framework");
return0;
}
来看看真正挂载的地方:
[-->cryptfs.c::cryptfs_restart_inernal]
static int cryptfs_restart_internal(intrestart_main)
{
charfs_type[32]; char real_blkdev[MAXPATHLEN];
charcrypto_blkdev[MAXPATHLEN]; char fs_options[256];
unsignedlong mnt_flags; struct stat statbuf;
int rc =-1, i;
staticint restart_successful = 0;
......
if(restart_main) {
......//shutdown framework相关模块
property_set("vold.decrypt", "trigger_reset_main");
SLOGD("Just asked init to shut down class main\n");
sleep(2);
}
//获取虚拟的加密设备,也就是/dev/block/dm-xxx
property_get("ro.crypto.fs_crypto_blkdev", crypto_blkdev,"");
.....
//先卸载/data/目前的挂载
if (!(rc = wait_and_unmount(DATA_MNT_POINT,true)) ) {
......
int retries = RETRY_MOUNT_ATTEMPTS;
intmount_rc;
//把crypto_blkdev挂载到/data上
while ((mount_rc = fs_mgr_do_mount(fstab,DATA_MNT_POINT,
crypto_blkdev, 0))
!= 0) {
......
}
property_set("vold.decrypt", "trigger_load_persist_props");
/*Create necessary paths on /data */
if (prep_data_fs()) {
return -1;
}
//重启framework,这时候设置的属性值不再会导致SystemServer只加载coreApp了
property_set("vold.decrypt", "trigger_restart_framework");
SLOGD("Just triggered restart_framework\n");
......
}
......
returnrc;
}
四 Device Mapper代码简掠
wowo,按我以前的做法,Device Encryption(以后简称DE)的流程其实到这里就算完了。回顾下来,好像DE的流程也没那么复杂:
- 首先是各种处理,比如强制设备的第一次加密,没有加密的设备被用户在settings中触发加密,加密后设备的启动和挂载等。这个流程涉及到init,settings,vold,framework。尤其是设置不同的属性以触发不同的处理过程。需要对Android系统有一些比较深的了解。
- 回归到具体的加密而言,借助Device Mapper这种机制,把一个虚拟的Mapped Device和一个真实的block device关联起来,然后使得往虚拟MD读出或者写人的数据解密/加密,这就是DE关于加解密的核心了。
从上述知识中,我们可以看到Device Mapper的机制在整个DE中扮演了非常重要的角色。这部分代码到没有什么难度,不过涉及kernel方面的知识,我这里不想写那么详细,只是把一些重要的流程相关的东西给大家展示一下。毕竟,在大部分情况下,我们只需要理解原理就行。
先来看看vold是怎么操作DeviceMapper的,我这里列出一些基本命令:
//打开MD驱动
open("/dev/device-mapper",O_RDWR)
//创建一个MD设备
ioctl(fd,DM_DEV_CREATE, io)
//获取MD支持的各种target
ioctl(fd, DM_LIST_VERSIONS, io)
//设置映射表,当然,我们这里只对crypt目标设置映射表
ioctl(fd, DM_TABLE_LOAD, io)
//暂停MD设备
ioctl(fd, DM_DEV_SUSPEND, io)
//挂载crypt block设备,这是一个虚拟出来的设备
mount(crypto_blk_dev)
下面来看看上面这几个函数在MD驱动里是怎么实现的。
4.1 dm_init
DM是一个驱动,好像是RedHat公司的人写的。这个驱动的注册函数是dm_init。
[-->dm.c::dm_init]
static int __initdm_init(void)
{
const int count = ARRAY_SIZE(_inits);//一组需要初始化的函数
int r, i;
for (i = 0; i < count; i++) {
r = _inits[i]();
......
}
return 0;
......
}
static int (*_inits[])(void) __initdata = {
local_init,//在这里将注册一个block device
dm_target_init,
dm_linear_init,
dm_stripe_init,
dm_io_init,
dm_kcopyd_init,
dm_interface_init,//在这里注册一个misc device
};
来看看dm_interface_init:
[dm-ioctl.c::dm_interface_init]
int __initdm_interface_init(void)
{
int r;
r = dm_hash_init();
r = misc_register(&_dm_misc);
.....
DMINFO("%d.%d.%d%s initialised:%s", DM_VERSION_MAJOR,
DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL,DM_VERSION_EXTRA,
DM_DRIVER_EMAIL);
return 0;
}
static structmiscdevice _dm_misc = {
.minor = MAPPER_CTRL_MINOR,
.name = DM_NAME,
.nodename = DM_DIR "/" DM_CONTROL_NODE,
.fops = &_ctl_fops//用户空间open/ioctl等函数在driver中的实现
};
static const structfile_operations _ctl_fops = {
.open = nonseekable_open,
.unlocked_ioctl = dm_ctl_ioctl,
.compat_ioctl = dm_compat_ctl_ioctl,//ioctl函数对应的实现
.owner = THIS_MODULE,
.llseek = noop_llseek,
};
上面对应的ioctl有两个实现,不过最终的处理都在lookup_ioctl函数中。如下所示:
[-->dm-ioctl.c::lookup_ioctl]
static ioctl_fnlookup_ioctl(unsigned int cmd)
{
static struct {
int cmd;
ioctl_fn fn;
} _ioctls[] = {
{DM_VERSION_CMD, NULL}, /* version is dealt with elsewhere */
{DM_REMOVE_ALL_CMD, remove_all},
{DM_LIST_DEVICES_CMD, list_devices},
{DM_DEV_CREATE_CMD, dev_create},
{DM_DEV_REMOVE_CMD, dev_remove},
{DM_DEV_RENAME_CMD, dev_rename},
{DM_DEV_SUSPEND_CMD, dev_suspend},
{DM_DEV_STATUS_CMD, dev_status},
{DM_DEV_WAIT_CMD, dev_wait},
{DM_TABLE_LOAD_CMD, table_load},
{DM_TABLE_CLEAR_CMD, table_clear},
{DM_TABLE_DEPS_CMD, table_deps},
{DM_TABLE_STATUS_CMD, table_status},
{DM_LIST_VERSIONS_CMD,list_versions},
{DM_TARGET_MSG_CMD, target_message},
{DM_DEV_SET_GEOMETRY_CMD,dev_set_geometry}
};
return (cmd >= ARRAY_SIZE(_ioctls)) ?NULL : _ioctls[cmd].fn;
}
下面我们来看看dev_create函数。
4.2 dev_create
[-->dm.c::dm_create]
int dm_create(intminor, struct mapped_device **result)
{
struct mapped_device *md;
md = alloc_dev(minor);
......
dm_sysfs_init(md);
*result = md;
return 0;
}
[-->dm.c::alloc_dev]
static structmapped_device *alloc_dev(int minor)
{
int r;
struct mapped_device *md = kzalloc(sizeof(*md), GFP_KERNEL);
void *old_md;
......
if (minor == DM_ANY_MINOR)
r = next_free_minor(&minor);
else
r = specific_minor(minor);
md->type = DM_TYPE_NONE;
......
md->queue = blk_alloc_queue(GFP_KERNEL);
dm_init_md_queue(md);
md->disk = alloc_disk(1);
.....
md->disk->major = _major;
md->disk->first_minor = minor;
md->disk->fops = &dm_blk_dops; //设置block device的函数实现
md->disk->queue = md->queue;
md->disk->private_data= md;
//block设备的名字,前面见过了
sprintf(md->disk->disk_name, "dm-%d", minor);
add_disk(md->disk);
format_dev_t(md->name, MKDEV(_major,minor));
md->wq =alloc_workqueue("kdmflush",
WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
md->bdev = bdget_disk(md->disk, 0);
......
return md;
......
}
从代码上看,dm_create将构造一个block device,名字为“dm-xxx”(minor号),并注册了针对这个设备的函数实现。
再来看DM_LIST_VERSION的处理。
4.3 list_versions
list_versions本身没有什么神奇,就是把注册的target模块的版本号取出来。前面一直没提到的是,我们在一个MD上可以注册多个Target模块。比如前面反复见过的“crypt”target,它是通过dm_crypt_init注册上去的:
[-->dm-crypt.c::dm_crypt_init]
static int __initdm_crypt_init(void)
{
int r;
_crypt_io_pool = KMEM_CACHE(dm_crypt_io,0);
r = dm_register_target(&crypt_target);
....
return r;
}
static structtarget_type crypt_target = {
.name ="crypt",//这就是前面见过的crypt target
.version = {1, 11, 0},
.module = THIS_MODULE,
.ctr = crypt_ctr,//初始化构造函数,这个要到设置映射表的时候才会被调用
.dtr = crypt_dtr,
.map = crypt_map,
.status = crypt_status,
.postsuspend = crypt_postsuspend,
.preresume = crypt_preresume,
.resume = crypt_resume,
.message = crypt_message,
.merge = crypt_merge,
.iterate_devices = crypt_iterate_devices,
};
4.4 table_load
table_load用于为目标target和一个真实blk设备建立映射关系
[-->dm-ioctl.c::table_load]
static inttable_load(struct dm_ioctl *param, size_t param_size)
{
int r;struct hash_cell *hc;struct dm_table*t;
struct mapped_device *md;
struct target_type *immutable_target_type;
md =find_device(param);
//分配映射表。本例中,target_count值为1
r = dm_table_create(&t, get_mode(param), param->target_count, md);
....
//根据参数来初始化映射表
r = populate_table(t, param, param_size);
....
return r;
}
[-->dm-table.c::dm_table_create]
static intpopulate_table(struct dm_table *table,
struct dm_ioctl *param, size_t param_size)
{
int r;
unsigned int i = 0;
struct dm_target_spec *spec = (structdm_target_spec *) param;
uint32_t next = param->data_start;
void *end = (void *) param + param_size;
char *target_params;
for (i = 0; i < param->target_count;i++) {
r = next_target(spec, next, end,&spec, &target_params);
.....
//target_type就是target的名字,此处为"crypt",里边将把参数传递给
//crypt target的ctr函数
r = dm_table_add_target(table, spec->target_type,
(sector_t)spec->sector_start,
(sector_t) spec->length,
target_params);
.....
}
next = spec->next;
}
return dm_table_complete(table);
}
感兴趣的童鞋可以去看一下crypt target的ctr函数,大概也就是设置一些参数,分配一些资源罢了。在没有介绍Kernel基础知识前,我这里就不多说了。
4.5 I/O操作
当MD挂载到/data分区后,应用程序会进程I/O操作,里边有数据来往。当然,我们这里是底层的block device。
4.5.1 读数据并解密
当cyrpt从底层real block设备读取了数据后,它的crypt_endio被调用,我们看看解密的流程。
[-->dm-crypt.c::crypt_endio]
static voidcrypt_endio(struct bio *clone, int error)
{
struct dm_crypt_io *io =clone->bi_private;
struct crypt_config *cc =io->target->private;
unsigned rw = bio_data_dir(clone);
.....
bio_put(clone);
if (rw== READ && !error) {
//这个io请求被加到一个workqueue里,对应的处理函数是kcryptd_crypt
kcryptd_queue_crypt(io);
return;
}
.....
}
kcryptd_crypt这个函数包括了加密和解密功能。我们这里看到的是读操作是怎么触发进入这个函数的。
[-->dm-crypt.c::kcryptd_crypt]
static voidkcryptd_crypt(struct work_struct *work)
{
struct dm_crypt_io *io = container_of(work,struct dm_crypt_io, work);
if (bio_data_dir(io->base_bio) == READ)
kcryptd_crypt_read_convert(io);
else
kcryptd_crypt_write_convert(io);
}
具体细节就不讨论了.....
4.5.2 写数据并加密
写数据的触发流程如下:
[-->dm-crypt.c::crypt_map]
static intcrypt_map(struct dm_target *ti, struct bio *bio,
union map_info *map_context)
{
struct dm_crypt_io *io;
struct crypt_config *cc;
......
io = crypt_io_alloc(ti, bio,dm_target_offset(ti, bio->bi_sector));
if (bio_data_dir(io->base_bio) == READ){
if (kcryptd_io_read(io, GFP_NOWAIT))
kcryptd_queue_io(io);
} else
//这个io请求被加到一个workqueue里,对应的处理函数是kcryptd_crypt
kcryptd_queue_crypt(io);
....
return 0;
}
五 Device Encryption难吗?
DE难吗?不难也。其基石说白了就是利用device mapper的crypt target来实现的。当然,这些在kernel里都是现成的,只要了解crypt所需要的参数,理论上非常快就能做完。
但是,DE难,在Android平台上比较难。为什么,因为它需要融合到Android自己的架构里,比如vold,比如Framework,比如init以及各种配置文件,比如UI的显示等等等等。
本文主要以Android平台中DE的流程介绍为主,相信大家有了一些理解。随着Google强制要求DE,这块碰到的问题必不会少,所以早点学习下相关内容还是很有必要的。