並不是每個partition都可以做encryption,基本上,OEM必須在fstab裡對該partition標註forceencrypt或encryptable,該partition才能做encryption。如果標註forceencrypt的話,在Android第一次開機時就會將該partition做encrypt,如果是encryptable的話,則交由user決定。
針對被encrypt的partition,系統必須記錄一些資料(像是key相關資料)才能正常的管理它,這些資料稱meta data。Meta data可以存放在該Partition裡,也可以儲存在不同的partition。前者Android稱為Footer,因為這些資料是存放在該partition的最後16K,也就說user可使用的空間必須扣掉16K。
下面是fstab裡的UDA partition的描述,Android在第一次開機時,會強制對UDA這個partition做encrypt, meta data會存放在MD1這個partition。這個UDA partition的file system是f2fs
/dev/block/platform/sdhci-tegra.3/by-name/UDA /data f2fs noatime,nosuid,nodev,errors=recover wait,check,forceencrypt=/dev/block/platforom/sdhci-tegra.3/by-name/MD1
forceencrpyt可以使用encryptable取代,encryptable代表這個partition可以被encrypt,但是不會在第一次開機就強制執行,而是交由使用者去選擇。如果OEM不想用另一個partition儲存meta data,可以用"footer"這個字(forceencrypt=footer),Android會將UDA partition的最後16K保留,用來存放meta data.
Service Class
在開始了解Encryption process之前,我們要先知道Init process,因為不管是Encryption Process或Encryption Booting,都跟Init Process有很大的關係。大部份用來Customize Init Process的設定都是寫入rc file。rc file可以定義Service,而每一個Service可以屬於某個class。在Android裡,定義了三種class,分別是core, main與late_start。一般來講, 屬於core class的service在啟動之後就不會被stop或restart,屬於main或late_start這兩個class的service則是有可能視情況需要而stop或restart。像是healthd, vold或surfaceflinger是屬於core class,所以除非這些service自己crash,Android並不會故意將它stop或restart,但是main與late_start這兩個class就不一定,像是encryption process或encryption booting就會將這兩個class停止或重啟。至於一個service應該定義在哪個class,則是應該視該service的用途來做判斷。以目前Android的設定,當main class啟動完成之後,重要的service都己經啟動了,user也可以看到launcher的畫面。稍後會看到encryption或encryption booting時為了要顯示UI,會先將main class啟動,而late_start則是在/data己經mount好之後才啟動。Triggering Encryption & Encryption Booting
Encryption Process的觸發可分別兩種,一種是OEM在fstab裡指定forceencrypt參數,這是由Init Process在第一次開機(或factory reset後的第一次開機)後自動觸發。另一種則是開機後由App或User從Settings裡去觸發。不管是哪一種,最後Encryption都是由vold, init process與app/framework來共同完成。init process與vold及framework之間的溝通是透過property。當init process執行mount_all時,它會fork一個process根據fstab內容去做mount,這是為了避免mount出現任何問題以致於init本身crash。child process做完mount之後就會結束,並將mount結果傳回給init process, init本身在fork之後則是會等child process結束才繼續。child process的結果有以下幾種
FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION
表示某個partition (其實就是指/data)被標註forceencryption,但是還沒被encrypt,init process會將vold.decrypt這個property內容設為trigger_encryption,表示要啟動Encryption process。
FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED
表示Encryption process己經做過了,init process會設定下面幾個property的內容進行Encryption booting的程序
ro.crypto.state = encrypted
vold.decrypt = trigger_default_encryption
FS_MGR_MNTALL_DEV_NOT_ENCRYPTED
表示沒有任何一個partition被encrypt,而且也沒有任何一個partition被標註forceencrypt (有可能有標註encryptable)。這就是一般沒做partition encryption的開機流程。init process會將ro.crypto.state設為unencrypted,然後觸發nonencrypted的action。nonencrypted的action就是啟動main與late_start這兩個class的service。
< from rc file >
on nonencrypted
class_start main
class_start late_start
FS_MGR_MNTALL_DEV_NEEDS_RECOVERY
這表示child process沒辦法將partition正常mount上,有可能是encryption發生不正常的現象,init process必須wipe partition (將partition內容清除,也就是factory reset),系統會進入recovery mode。
除了由init process觸發的encryption (或factory reset)之外,也可以由Setting裡的CryptKeeperConfirm來觸發。User可以從Setting選擇要做disk encryption,最後會由CryptKeeperConfirm呼叫MountService.encryptStorage(),MountService再要求vold執行"cryptfs enablecrypto inplace"命令開始做disk encryption。
如果我們比較forceencrypt與user從Setting觸發的encryption command,可以發現一點差異,稍後看到encryption的過程再來討論。
vold在做encryption時主要是有下面幾個步驟
1. shutdown framework
2. unmount all asec
3. unmount sdcard
4. unmount /data
5. mount tmpfs on /data
6. initialize /data through init post_fs section
7. set encryption progress property to 0
8. Show progress UI
9. encrypt each sector while updating progress property
10. Reboot system when all sectors encrypted
Encryption的做法在概念上很簡單,以ext4來講,只要將/data這個partition的每個sector加密過就完成了,簡單講就是像下面這個樣子,也就是上面step 9。
之所以要有step1 ~ step8主要就是要避免有process正在使用/data,因為/data這個partition就是我們要做encryption的對像。另一個原因就是希望將Encryption的進度以UI的方式顯示,讓user知道系統還活著,Encryption會花點時間,Step 5~9主要就是為了UI的關係。
Step 1 - Shutdown framework
Shutdown framework是將vold.decrypt設為trigger_shutdown_framework,init process收到後會將main與late_start這兩個class services停止,這是為了避免framework或app正在使用/data partition。
Step 2 - Unmount all asec
Asec是android secure container,這是加密過的資料,當user將app移到sdcard時,系統會在sdcard上產生一個asec檔案,framework會將它mount在/data裡,如果不將asec unmount的話,最後/data也無法unmount,encryption就無法進行。
Step 3 - Unmount sdcard
Sdcard的access是透過FUSE的架構,有一個sdcard service用來處理sdcard files的access,基本上user process是access /storage/emulated/,透過bind mount的方式,這些access會被導引到/mnt/shell/emulated/,而這是/dev/fuse的一個mount point,因此這些access會由/dev/fuse的driver處理,而/dev/fuse會將這些access再導引至sdcard service。sdcard service會再將這些access導引到真正sdcard的mount point,也就是/data/media。因此,如果sdcard沒有unmount的話,/data也沒辦法unmount。
Step 4 - Unmount /data
做encryption前,/data必須被unmount,避免一邊使用一邊Encryption的事情發生。
Step 5 - Mount tmpfs on /data
tmpfs是用在RAM的file system,也就是割出一塊RAM,initialize成tmpfs format,並mount在/data,這主要是為了顯示UI做準備。目前顯示UI的方式是啟動framework,執行一個特殊的Launcher,它的用途就是顯示目前Encryption的進度,當然它還有其它用途,但以Encryption而言,這是它其中一個用途。可是framework的運作是需要/data存在,而且還必須有一些資料設定好,因此,vold才會產生一個暫時的/data供framework使用。
Step 6 - Initialize /data through post_fs
上面提到vold將一塊RAM mount在/data供framework使用,但是不是只要提供/data就好了,/data裡面還是要有一些設定,這些設定就由rc file的post_fs section來處理,因此,vold會將vold.decrypt設為trigger_post_fs_data,init process就會開始執行rc file的post_fs section,進而觸發其它section的命令。
Step 7 - Set encryption progress to 0
由於Encryption還沒開始,所以它的progress是0,這個progress要能讓framework app/launcher可以看到,目前是使用一個property (vold.encrypt_progress)當作彼此溝通的媒介。
Step 8 - Show progress UI
vold會將vold.decrypt設為trigger_restart_min_framework,init process會將main class services啟動,launcher也會被啟動,但被啟動的launcher是一個特殊的launcher,在Encryption Booting這個section,會有比較詳細的討論。簡單的講,這個Launcher會根據vold.encrypt_progress內容顯示encryption進度。
Step 9 - Encryption for each sector
這個步驅主要就是產生一個crypto device (dm-crypt driver),並將它map到原本的/data partition,把原本的/data partition的每個sector讀出來,寫到crypto device,dm-crypt driver會將寫入的sector做encryption,再寫入原來的/data partition相同的sector,這樣就完成了一個sector的encryption,每個sector都按照一樣的方式處理過就完成了encryption,稍後我們會再進一步討論這部份。在Encrypt每個sector的時候,vold也會持續更新vold.encrypt_progress內容,因此Launcher也可以讓user知道最新的進度。
Step 10 - Reboot system
當encryption完成後,vold會直接reboot system,它會留個幾秒讓Launcher顯示到100%才reboot system。
有一個容易造成誤解的是encryption password並不是用來做為disk encryption的key。當disk encryption開始時,framework會產生一個key,這個key才是disk encryption時所要用的,我們稱為master key,user所輸入的password是用來加密master key而己。而加密後的master key會儲存在Meta data裡,因此,每次開機,framework都需要向user詢問encryption key,這樣它才能從meta data裡讀出加密過的key,再將它解密,然後交給dm-crypt,系統才能正常booting。
下面這段code是vold cryptfs的function,vold將dm-crypt的device產生出來後,會將它的path儲存在ro.crypto.fs_crypto_blkdev這個property裡,這個function再將它讀出,mount到/data下。
< from rc file >
on nonencrypted
class_start main
class_start late_start
FS_MGR_MNTALL_DEV_NEEDS_RECOVERY
這表示child process沒辦法將partition正常mount上,有可能是encryption發生不正常的現象,init process必須wipe partition (將partition內容清除,也就是factory reset),系統會進入recovery mode。
除了由init process觸發的encryption (或factory reset)之外,也可以由Setting裡的CryptKeeperConfirm來觸發。User可以從Setting選擇要做disk encryption,最後會由CryptKeeperConfirm呼叫MountService.encryptStorage(),MountService再要求vold執行"cryptfs enablecrypto inplace
如果我們比較forceencrypt與user從Setting觸發的encryption command,可以發現一點差異,稍後看到encryption的過程再來討論。
- forceencrypt - "cryptfs enablecrypto inplace default"
- Settings - "cryptfs enablecrypto inplace
"
Encryption Process
前面提到Encryption process可以由init process觸發,也可以由Settings觸發,兩者其都是要求vold去執行"cryptfs enablecrypto inplace" command,差別在於參數略有不同而己。
下圖是一張粗略的Encryption流程,過程大致了解再來深入幾個比較有意思的地方。星星標註的地方代表可以觸發Encryption,也就是可以從Settting與開機時由Init process觸發 (forceencrypt)。不管哪一個,都是下command到vold。Vold command分成幾個類別,cryptfs這一類用來處理跟encryption相關的。
下圖是一張粗略的Encryption流程,過程大致了解再來深入幾個比較有意思的地方。星星標註的地方代表可以觸發Encryption,也就是可以從Settting與開機時由Init process觸發 (forceencrypt)。不管哪一個,都是下command到vold。Vold command分成幾個類別,cryptfs這一類用來處理跟encryption相關的。
1. shutdown framework
2. unmount all asec
3. unmount sdcard
4. unmount /data
5. mount tmpfs on /data
6. initialize /data through init post_fs section
7. set encryption progress property to 0
8. Show progress UI
9. encrypt each sector while updating progress property
10. Reboot system when all sectors encrypted
Encryption的做法在概念上很簡單,以ext4來講,只要將/data這個partition的每個sector加密過就完成了,簡單講就是像下面這個樣子,也就是上面step 9。
for (int i = 0; i < sector_count; ++i) {
encrypt(sector[i]);
}
encrypt(sector[i]);
}
之所以要有step1 ~ step8主要就是要避免有process正在使用/data,因為/data這個partition就是我們要做encryption的對像。另一個原因就是希望將Encryption的進度以UI的方式顯示,讓user知道系統還活著,Encryption會花點時間,Step 5~9主要就是為了UI的關係。
Step 1 - Shutdown framework
Shutdown framework是將vold.decrypt設為trigger_shutdown_framework,init process收到後會將main與late_start這兩個class services停止,這是為了避免framework或app正在使用/data partition。
Step 2 - Unmount all asec
Asec是android secure container,這是加密過的資料,當user將app移到sdcard時,系統會在sdcard上產生一個asec檔案,framework會將它mount在/data裡,如果不將asec unmount的話,最後/data也無法unmount,encryption就無法進行。
Step 3 - Unmount sdcard
Sdcard的access是透過FUSE的架構,有一個sdcard service用來處理sdcard files的access,基本上user process是access /storage/emulated/
Step 4 - Unmount /data
做encryption前,/data必須被unmount,避免一邊使用一邊Encryption的事情發生。
Step 5 - Mount tmpfs on /data
tmpfs是用在RAM的file system,也就是割出一塊RAM,initialize成tmpfs format,並mount在/data,這主要是為了顯示UI做準備。目前顯示UI的方式是啟動framework,執行一個特殊的Launcher,它的用途就是顯示目前Encryption的進度,當然它還有其它用途,但以Encryption而言,這是它其中一個用途。可是framework的運作是需要/data存在,而且還必須有一些資料設定好,因此,vold才會產生一個暫時的/data供framework使用。
Step 6 - Initialize /data through post_fs
上面提到vold將一塊RAM mount在/data供framework使用,但是不是只要提供/data就好了,/data裡面還是要有一些設定,這些設定就由rc file的post_fs section來處理,因此,vold會將vold.decrypt設為trigger_post_fs_data,init process就會開始執行rc file的post_fs section,進而觸發其它section的命令。
Step 7 - Set encryption progress to 0
由於Encryption還沒開始,所以它的progress是0,這個progress要能讓framework app/launcher可以看到,目前是使用一個property (vold.encrypt_progress)當作彼此溝通的媒介。
Step 8 - Show progress UI
vold會將vold.decrypt設為trigger_restart_min_framework,init process會將main class services啟動,launcher也會被啟動,但被啟動的launcher是一個特殊的launcher,在Encryption Booting這個section,會有比較詳細的討論。簡單的講,這個Launcher會根據vold.encrypt_progress內容顯示encryption進度。
Step 9 - Encryption for each sector
這個步驅主要就是產生一個crypto device (dm-crypt driver),並將它map到原本的/data partition,把原本的/data partition的每個sector讀出來,寫到crypto device,dm-crypt driver會將寫入的sector做encryption,再寫入原來的/data partition相同的sector,這樣就完成了一個sector的encryption,每個sector都按照一樣的方式處理過就完成了encryption,稍後我們會再進一步討論這部份。在Encrypt每個sector的時候,vold也會持續更新vold.encrypt_progress內容,因此Launcher也可以讓user知道最新的進度。
Step 10 - Reboot system
當encryption完成後,vold會直接reboot system,它會留個幾秒讓Launcher顯示到100%才reboot system。
Encryption Password
在KK,password有兩種,一個是screen password,一個是encryption password,前者用來解除screen lock,後者用在開機時驗證encryption password。但是到的L的時候,這兩個password被合而為一。在KK,當user啟動disk encryption時,framework會要求user提供一組encryption password,但是在L的時候,就不再詢問user了,它會直接使用usr設定的screen password,如果user也沒設定screen password,就會直接用預設的password,可是當user更改screen password時,encryption password也會跟著更改。有一個容易造成誤解的是encryption password並不是用來做為disk encryption的key。當disk encryption開始時,framework會產生一個key,這個key才是disk encryption時所要用的,我們稱為master key,user所輸入的password是用來加密master key而己。而加密後的master key會儲存在Meta data裡,因此,每次開機,framework都需要向user詢問encryption key,這樣它才能從meta data裡讀出加密過的key,再將它解密,然後交給dm-crypt,系統才能正常booting。
dm-crypt
dm-crypt是Linux device mapper的一種target,它可以做到transparent encryption/decryption,device mapping是一種mapping的架構,它會產生一個virtual device,並設定一個mapping table,所以當user在access這個virtual device時,它會將這個access轉介到另一個device,並根據mapping table改變要access的區域。以dm-crypt來講,假設真正儲存/data的device是B,我們會產生一個dm-crypt device,假設名字是dm-0,而mapping table會設定為dm-0的第N個sector對應到B的第N的sector。Disk encryption就會變成,將B的每個sector讀出,並寫入dm-0的每個sector。B的內容讀出後是還沒做過encryption的,但是寫到dm-0之後,dm-crypt會對寫入的資料做encryption,並寫入B的同樣sector,這樣一來,B的內容就是加密過的,如果這個時候我們直接從B再讀取一次,就會得到加密過的資料,但是如果我們透過dm-0去讀,dm-crypt會先讀B的內容,再將它解密,然後再回傳。如果我們寫入資料到dm-0,dm-crypt就會將寫入的資料加密,再寫到B的相同sector。所以,在encryption過的系統,/data是mount在dm-crypt的device,而不是原始的device,我們可以透過adb shell mount來觀察。下面這段code是vold cryptfs的function,vold將dm-crypt的device產生出來後,會將它的path儲存在ro.crypto.fs_crypto_blkdev這個property裡,這個function再將它讀出,mount到/data下。
static int cryptfs_restart_internal(int restart_main) {
...
property_get("ro.crypto.fs_crypto_blkdev", crypto_blkdev, "");
if (strlen(crypto_blkdev) == 0) {
SLOGE("fs_crypto_blkdev not set\n");
return -1;
}
if (! (rc = wait_and_unmount(DATA_MNT_POINT, true)) ) {
/* If that succeeded, then mount the decrypted filesystem */
int retries = RETRY_MOUNT_ATTEMPTS;
int mount_rc;
while ((mount_rc = fs_mgr_do_mount(fstab, DATA_MNT_POINT,
crypto_blkdev, 0))
!= 0) {
....
}
....
}
}
Encryption Booting
下面這張圖描述Encryption Booting的過程。Init process根據fstab在mount所有的partition時,如果disk己經被encrypt過,它會設定以下兩個property開始Encryption Booting。- ro.crypto.state = encrypted
- vold.decrypt = trigger_default_encryption
trigger_default_encryption會啟動defaultcrypto這個service (one-shot service),而defaultcrypto則是純粹的使用vdc要求vold去執行"cryptfs mountdefaultencrypted"這個command。如果當初在做Encryption時,沒有指定password的話,vold這邊的動作比較單純,vold都會產生一個crypto device(dm-0),使用同樣的mapping table對應到userdata partition,並將dm-0 mount在/data目錄。因此,在Encryption之後,所有對/data partition的access都會在dm-0 device上,而dm-0則會將access導引之原本的data partition,並做encryption/decryption的動作。對user mode的process之言,並不會感覺有所變化,當然,performance或許有變,但使用上跟disk encryption之前沒有差別。
當crypto device被mount到/data後,vold會通知init process將persistent properties讀取進來(將vold.decrypt設為trigger_load_persistent_. Persistent properties (persist.*)是儲存在/data裡,因此,在Encryption Booting時,一直要等到/data被mount後才能將它讀出來。有一點要注意,這個時core class service都己經被啟動,如果core class service有用到persistent properties,它就讀不到真正的property內容。
接下來,vold會通知init process開始執行rc file裡post_fs這個section裡的command(將vold.decrypt設為trigger_post_fs_data),最後才要求init process啟動framework(將vold.decrypt設為trigger_restart_framework),而所謂啟動framework就是將main與late_start這兩個class裡的service啟動,main啟動完後,user就可以看到launcher的畫面了。
但是如果當初在做Encryption時,如果user有設定password的話,那狀況會比較複雜一點,因為必須將framework啟動,顯示UI詢問password。所以在vold收到cryptfs mountdefaultencrypted這個command時,它會要求init process先將main class service啟動(將vold.decrypt設為trigger_restart_min_framework)。前面有提到當main class啟動完成後,user就會看到Launcher畫面,也就是說user會看到UI詢問password,得到password後,UI會直接使用MountService.decryptStorage()來與vold確認password是否正確。decryptStorage()這裡會做兩件事,它會利用cryptfs checkpw這個command要求vold確認password是否正確,如果正確的的話,它會利用cryptfs restart這個command要求vold繼續Encryption Booting的程序。
這裡有一個比較tricky的地方,當main class啟動時,理論上user看到的第一個畫面是launcher,那為何在trigger_restart_min_framework時,會出現問user password的畫面? 原因在於這個系統裡其實至少有二個Launcher,一個是一般user使用的launcher,像是Launcher3或GoogleLauncher,另一個Launcher就是用來顯示詢問Password畫面,這個Launcher就是Settings app裡其中一個activity (CryptKeeper)
final String state = SystemProperties.get("vold.decrypt");
if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
// Disable the crypt keeper.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, CryptKeeper.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
// Typically CryptKeeper is launched as the home app. We didn't
// want to be running, so need to finish this activity. We can count
// on the activity manager re-launching the new home app upon finishing
// this one, since this will leave the activity stack empty.
// NOTE: This is really grungy. I think it would be better for the
// activity manager to explicitly launch the crypt keeper instead of
// home in the situation where we need to decrypt the device
finish();
return;
}
cryptfs restart這個command會要求vold將main class裡的service停止(reset main),因為這時候/data還沒mount起來,所以,可能有些service讀取到的設定並不正確(像Launcher就不是user所期待的),因此這個時候將main class reset,稍後等/data mount起來,再重新啟動main class才會讓main class的service讀取到正確的設定。reset完main class後,就跟前面提到的順序一樣,mount /data,讀取persistent properties,執行rc檔的post_fs命令,然後啟動framework。
Uncrypt (OTA Update)
在KK的時候,OTA package是download到/cache裡,因此在recovery mode時,只要從/cache讀出OTA package內容,就可以直接做OTA update。/cache並不會被encrypt,所以即使/data被encrypt,OTA update也不受影響。但是這會有一個問題,/cache的size必須夠大,要不然可能無法容納OTA package。OTA package內容主要就是/system的內容,因此,在最差的情況下,OTA package的大小可能會跟/system partiton的大小類似,其實這是有點浪費空間的,畢竟除了OTA package之外,/cache也用不了那麼多的空間。
在L之後,OTA package可以被放在/data裡,在recovery mode時再將它讀出做OTA update。這似乎沒什麼大問題,但其實有兩件事值得思索一下
1. 由於OTA package放在/data,因此,recovery mode必須將/data mount起來。
2. 如果/data被encrypt,recovery mode就得想辦法將/data decrypt才能讀出OTA package。
第一件事似乎沒什麼大不了,反正就mount /data就好,我想也是,但似乎Google不怎麼想在recovery mode將/data mount起來,或許有什麼原因我們不知道。第二件事就比較麻煩,因為,recovery mode必須知道encrypt key的內容並將dm-crypt的device mount起來,並設定好mapping table,但這些事都是在vold做的,Google大概不想再搬一份相同的code到recovery mode裡。因此,在L做OTA update時,它會產生一個map file放在/cache裡,把OTA package (zip file)所使用到的block記錄下來,內容類似下面這個樣子
因此,在做OTA update時,只要根據這個map file,直接讀取/data所屬的block device內容就可以了。但是/data如果被encrypt過怎麼辦?當系統要reboot到recovery mode之前,它會執行一個做uncrypt的service,這個service就是用來產生map file,如果/data是被encrypt的,它會先從/data將OTA package所使用的block讀出(這個時候/data是mount在dm-crypt device上,所以讀出來的內容是decrypt過的),再將讀出的block直接寫入原本/data所屬的block device的相同block。所以在recovery mode時,系統只要按照這個map file就可以讀出OTA package的內容,即使/data己經被encrypt過。
這樣還是有一個問題,它會造成OTA update在reboot到recovery mode前有一些delay,目前framework預設是5分鐘,但是,如果這個platform decrypt的速度不快的話,可能會造成uncrypt還沒處理完,系統就會被shutdown (因為ShutdownThread.rebootOrShutdown會呼叫PowerManagerService.lowLevelReboot來reboot系統,但是如果timeout的話,它會認為reboot失敗,它就會將系統shutdown),然後OTA update就會失敗。下面這段code是PowerManagerService.lowLelvelReboot,當它被要求要reboot到recovery mode,它會要求init process啟動uncrypt,並開始計時,它最多會等個5分鐘讓uncrypt把事情做完,如果做不完,系統會被shutdown。
在L之後,OTA package可以被放在/data裡,在recovery mode時再將它讀出做OTA update。這似乎沒什麼大問題,但其實有兩件事值得思索一下
1. 由於OTA package放在/data,因此,recovery mode必須將/data mount起來。
2. 如果/data被encrypt,recovery mode就得想辦法將/data decrypt才能讀出OTA package。
第一件事似乎沒什麼大不了,反正就mount /data就好,我想也是,但似乎Google不怎麼想在recovery mode將/data mount起來,或許有什麼原因我們不知道。第二件事就比較麻煩,因為,recovery mode必須知道encrypt key的內容並將dm-crypt的device mount起來,並設定好mapping table,但這些事都是在vold做的,Google大概不想再搬一份相同的code到recovery mode裡。因此,在L做OTA update時,它會產生一個map file放在/cache裡,把OTA package (zip file)所使用到的block記錄下來,內容類似下面這個樣子
/dev/block/platform/msm_sdcc.1/by-name/userdata # block device
49652 4096 # file size in bytes, block size
3 # count of block ranges
1000 1008 # block range 0
2100 2102 # ... block range 1
30 33 # ... block range 2
這樣還是有一個問題,它會造成OTA update在reboot到recovery mode前有一些delay,目前framework預設是5分鐘,但是,如果這個platform decrypt的速度不快的話,可能會造成uncrypt還沒處理完,系統就會被shutdown (因為ShutdownThread.rebootOrShutdown會呼叫PowerManagerService.lowLevelReboot來reboot系統,但是如果timeout的話,它會認為reboot失敗,它就會將系統shutdown),然後OTA update就會失敗。下面這段code是PowerManagerService.lowLelvelReboot,當它被要求要reboot到recovery mode,它會要求init process啟動uncrypt,並開始計時,它最多會等個5分鐘讓uncrypt把事情做完,如果做不完,系統會被shutdown。
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();
}
}
啟動uncrypt的方法是將sys.powerctl設為pre-recovery,在rc file裡,pre-recovery是一個oneshot service,它會直接執行uncrypt這個命令。uncrypt產生完map file後,它會直接reboot系統。
service pre-recovery /system/bin/uncrypt
class main
disabled
oneshot
沒有留言:
張貼留言