Android 6.0 存儲(chǔ)權(quán)限管理
官方說明
先翻譯一段Android的官方文檔枫绅,原文在:https://source.android.com/devices/storage/
Android 6.0開始支持運(yùn)行時(shí)權(quán)限管理的功能镰矿。運(yùn)行時(shí)權(quán)限管量中當(dāng)然也包括對(duì)READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE這兩個(gè)權(quán)限的動(dòng)態(tài)管理吴菠。系統(tǒng)需要提供在不殺掉或重啟已經(jīng)運(yùn)行的應(yīng)用的情況下去動(dòng)態(tài)授權(quán)的機(jī)制仔戈。目前系統(tǒng)是通過維護(hù)三個(gè)View來實(shí)現(xiàn)的:
- /mnt/runtime/default: 針對(duì)對(duì)于存儲(chǔ)權(quán)限沒有特殊需求的情況。這也是adbd的其它系統(tǒng)組件使用的方式乔宿。
- /mnt/runtime/read:對(duì)于申請READ_EXTERNAL_STORAGE權(quán)限的應(yīng)用可見祈秕。
- /mnt/runtime/write:對(duì)于申請WRITE_EXTERNAL_STORAGE權(quán)限的應(yīng)用可見。
在Zygote fork的時(shí)刻操软,我們?yōu)槊總€(gè)運(yùn)行的應(yīng)用創(chuàng)建一個(gè)命名空間嘁锯,然后將其綁定到上面所進(jìn)的三個(gè)View中作為初始的View。在運(yùn)行時(shí)獲得新的授權(quán)后聂薪,vold會(huì)跳轉(zhuǎn)到這個(gè)裝載的命名空間并重新綁定新的View. 需要注意的一點(diǎn)是家乘,如果權(quán)限降級(jí),則一定會(huì)導(dǎo)致應(yīng)用被殺藏澳。
setns()方法從Linux 3.8移植到了3.4就專為干這事兒仁锯。有個(gè)PermissionsHostTest的CTS測試用例用來保證這個(gè)功能的有效性。
在Android 6.0翔悠,第三方應(yīng)用沒有訪問sdcard_r和sdcard_rw GID的權(quán)限业崖。作為替代野芒,通過上面所講的View的方式來控制。使用everybody GID跨用戶的交互會(huì)被阻止双炕。
代碼實(shí)現(xiàn)
看了上面的介紹,我們來看代碼中是如何實(shí)現(xiàn)的雄家。實(shí)現(xiàn)這個(gè)功能的函數(shù)在framework/base/core/jni/com_android_internal_os_Zygote.cpp中的MountEmulatedStorage函數(shù)效诅。
294// Create a private mount namespace and bind mount appropriate emulated
295// storage for the given user.
296static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
297 bool force_mount_namespace) {
298 // See storage config details at http://source.android.com/tech/storage/
第一步,先調(diào)用unshare系統(tǒng)調(diào)用去禁止同享命名空間趟济。
unshare函數(shù)定義于sched.h中乱投,用于將部分進(jìn)程上下文信息不共享父進(jìn)程的,CLONE_NEWNS是指定不共享命名空間顷编。
下面是ARM v8a AArch64下時(shí)調(diào)用unshare系統(tǒng)調(diào)用的代碼:
5ENTRY(unshare)
6 mov x8, __NR_unshare
7 svc #0
8
9 cmn x0, #(MAX_ERRNO + 1)
10 cneg x0, x0, hi
11 b.hi __set_errno_internal
12
13 ret
14END(unshare)
15
下面是第一步的代碼:
300 // Create a second private mount namespace for our process
301 if (unshare(CLONE_NEWNS) == -1) {
302 ALOGW("Failed to unshare(): %s", strerror(errno));
303 return false;
304 }
第二步戚炫,調(diào)用UnmountTree函數(shù):
首先解釋一下為什么要做unmount,在init.rc里面媳纬,root namespace已經(jīng)默認(rèn)地mount /mnt/runtime/default到/storage了双肤,
請看init.rc的片段:
244on post-fs
245 start logd
246 # once everything is setup, no need to modify /
247 mount rootfs rootfs / ro remount
248 # Mount shared so changes propagate into child namespaces
249 mount rootfs rootfs / shared rec
250 # Mount default storage into root namespace
251 mount none /mnt/runtime/default /storage slave bind rec
下面看代碼:
306 // Unmount storage provided by root namespace and mount requested view
307 UnmountTree("/storage");
我們轉(zhuǎn)到UnmountTree函數(shù):
static int UnmountTree(const char* path) {
size_t path_len = strlen(path);
FILE* fp = setmntent("/proc/mounts", "r");
setmntent函數(shù)定義如下:
#include <stdio.h>
#include <mntent.h>
FILE *setmntent(const char *filename, const char *type);
用于獲取系統(tǒng)mount的信息。
具體去讀每一行的時(shí)候使用getmntent()函數(shù)钮惠。
結(jié)束時(shí)調(diào)用endmntent()函數(shù)茅糜,相當(dāng)于fclose()。
getmntent讀取的是一個(gè)mntent結(jié)構(gòu)體的結(jié)構(gòu)素挽,該結(jié)構(gòu)定義于<mntent.h>中:
struct mntent {
char *mnt_fsname; /* name of mounted file system */
char *mnt_dir; /* file system path prefix */
char *mnt_type; /* mount type (see mntent.h) */
char *mnt_opts; /* mount options (see mntent.h) */
int mnt_freq; /* dump frequency in days */
int mnt_passno; /* pass number on parallel fsck */
};
下面代碼中蔑赘,調(diào)用getmntent函數(shù)將目錄信息讀出來放在一個(gè)列表中:
if (fp == NULL) {
ALOGE("Error opening /proc/mounts: %s", strerror(errno));
return -errno;
}
// Some volumes can be stacked on each other, so force unmount in
// reverse order to give us the best chance of success.
std::list<std::string> toUnmount;
mntent* mentry;
while ((mentry = getmntent(fp)) != NULL) {
if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
toUnmount.push_front(std::string(mentry->mnt_dir));
}
}
endmntent(fp);
接著,通過調(diào)用umount2函數(shù)將這些目錄都unmount掉预明。
for (auto path : toUnmount) {
if (umount2(path.c_str(), MNT_DETACH)) {
ALOGW("Failed to unmount %s: %s", path.c_str(), strerror(errno));
}
}
return 0;
}
umount2函數(shù)原型如下缩赛,用于unmount文件系統(tǒng)。
#include <sys/mount.h>
int umount2(const char *target, int flags);
第三步撰糠,我們從UnmountTree中回來酥馍,按照上面所講的幾種模式,分別設(shè)置不同路徑名:
309 String8 storageSource;
310 if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
311 storageSource = "/mnt/runtime/default";
312 } else if (mount_mode == MOUNT_EXTERNAL_READ) {
313 storageSource = "/mnt/runtime/read";
314 } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
315 storageSource = "/mnt/runtime/write";
316 } else {
317 // Sane default of no storage visible
318 return true;
319 }
第四步阅酪,根據(jù)第三步的模式值旨袒,重新mount。
320 if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
321 NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
322 ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno));
323 return false;
324 }
第五步遮斥,針對(duì)多用戶的情況峦失,額外需要做符號(hào)鏈接。
326 // Mount user-specific symlink helper into place
327 userid_t user_id = multiuser_get_user_id(uid);
328 const String8 userSource(String8::format("/mnt/user/%d", user_id));
329 if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
330 return false;
331 }
332 if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
333 NULL, MS_BIND, NULL)) == -1) {
334 ALOGW("Failed to mount %s to /storage/self: %s", userSource.string(), strerror(errno));
335 return false;
336 }
337
338 return true;
339}
mount_mode參數(shù)
我們開始看這兩個(gè)參數(shù)术吗,首先看mount_mode是從哪里獲取的.
這往上一找尉辑,就是ActivityManagerService的startProcessLocked,我們摘錄個(gè)片斷看下:
...
3280 int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
3281 if (!app.isolated) {
3282 int[] permGids = null;
3283 try {
...
3285 final IPackageManager pm = AppGlobals.getPackageManager();
3286 permGids = pm.getPackageGids(app.info.packageName, app.userId);
3287 MountServiceInternal mountServiceInternal = LocalServices.getService(
3288 MountServiceInternal.class);
3289 mountExternal = mountServiceInternal.getExternalStorageMountMode(uid,
3290 app.info.packageName);
3291 } catch (RemoteException e) {
3292 throw e.rethrowAsRuntimeException();
3293 }
...
然后我們看getExternalStorageMountMode较屿,定義在frameworks/base/services/core/java/com/android/server/MountService.java里隧魄,遍歷所有的Policy卓练,取其中最小的為最終結(jié)果。
3505 public int getExternalStorageMountMode(int uid, String packageName) {
3506 // No locking - CopyOnWriteArrayList
3507 int mountMode = Integer.MAX_VALUE;
3508 for (ExternalStorageMountPolicy policy : mPolicies) {
3509 final int policyMode = policy.getMountMode(uid, packageName);
3510 if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
3511 return Zygote.MOUNT_EXTERNAL_NONE;
3512 }
3513 mountMode = Math.min(mountMode, policyMode);
3514 }
3515 if (mountMode == Integer.MAX_VALUE) {
3516 return Zygote.MOUNT_EXTERNAL_NONE;
3517 }
3518 return mountMode;
3519 }
下面我們再看PM中是如何為policy賦值的购啄,實(shí)現(xiàn)在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java中,通過checkUidPermission的結(jié)果來決定policy的結(jié)果:
14731 MountServiceInternal mountServiceInternal = LocalServices.getService(
14732 MountServiceInternal.class);
14733 mountServiceInternal.addExternalStoragePolicy(
14734 new MountServiceInternal.ExternalStorageMountPolicy() {
14735 @Override
14736 public int getMountMode(int uid, String packageName) {
14737 if (Process.isIsolated(uid)) {
14738 return Zygote.MOUNT_EXTERNAL_NONE;
14739 }
14740 if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
14741 return Zygote.MOUNT_EXTERNAL_DEFAULT;
14742 }
14743 if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
14744 return Zygote.MOUNT_EXTERNAL_DEFAULT;
14745 }
14746 if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
14747 return Zygote.MOUNT_EXTERNAL_READ;
14748 }
14749 return Zygote.MOUNT_EXTERNAL_WRITE;
14750 }
14751
14752 @Override
14753 public boolean hasExternalStorage(int uid, String packageName) {
14754 return true;
14755 }
14756 });
14757 }