Android 6.0 存儲(chǔ)權(quán)限管理

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    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末襟企,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狮含,更是在濱河造成了極大的恐慌顽悼,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几迄,死亡現(xiàn)場離奇詭異蔚龙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)映胁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門木羹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人解孙,你說我怎么就攤上這事坑填。” “怎么了弛姜?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵脐瑰,是天一觀的道長。 經(jīng)常有香客問我娱据,道長蚪黑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任中剩,我火速辦了婚禮,結(jié)果婚禮上抒寂,老公的妹妹穿的比我還像新娘结啼。我一直安慰自己,他們只是感情好屈芜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布郊愧。 她就那樣靜靜地躺著,像睡著了一般井佑。 火紅的嫁衣襯著肌膚如雪属铁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天躬翁,我揣著相機(jī)與錄音焦蘑,去河邊找鬼。 笑死盒发,一個(gè)胖子當(dāng)著我的面吹牛例嘱,可吹牛的內(nèi)容都是我干的狡逢。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼拼卵,長吁一口氣:“原來是場噩夢啊……” “哼奢浑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腋腮,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤雀彼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后即寡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體详羡,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年嘿悬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了实柠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡善涨,死狀恐怖窒盐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钢拧,我是刑警寧澤蟹漓,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站源内,受9級(jí)特大地震影響葡粒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膜钓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一嗽交、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颂斜,春花似錦夫壁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至司蔬,卻和暖如春邑茄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俊啼。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工肺缕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓搓谆,卻偏偏與公主長得像炒辉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泉手,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,872評(píng)論 25 707
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程黔寇,因...
    小菜c閱讀 6,377評(píng)論 0 17
  • 那些要?jiǎng)討B(tài)權(quán)限(這么記比較危險(xiǎn)的權(quán)限,獲取硬件和用戶信息權(quán)限) 不過現(xiàn)在as 中會(huì)主動(dòng)提示加動(dòng)態(tài)權(quán)限 calend...
    菩提大師閱讀 2,704評(píng)論 1 8
  • 最開始參加訓(xùn)練營斩萌,是在一個(gè)公眾號(hào)的推文看到的缝裤,價(jià)格不貴,抱著試一試的想法就參加了颊郎。2017年是付費(fèi)知識(shí)大爆...
    楊彤彤M閱讀 441評(píng)論 1 3