Android外部存储空间及动态权限授予原理

外部存储空间


    在Android的世界中,应用程序可以使用的文件存储区域包括两个:内部存储空间、外部存储空间。这两个名称是在Android早期确定的,那时候大部分设备都提供内置的非易失性存储(内部存储空间)以及可移动的存储媒介,例如Micro SD卡(外部存储空间)。现在,很多设备将永久性存储空间划分为单独的“内部”和“外部”分区。因此,即使没有可移动存储媒介,这两种存储空间也始终存在,并且无论外部存储空间是否可移动,这两种存储空间的 API 行为都是相同的。

        在目前的Adnroid系统中,外部存储空间实际上是通过Linux Mount 和Bind Mount对内部数据目录"/data/media"的重新挂在,在真实的设备上我们通过mount命令可以看到系统对“/data/media"目录进行了多次挂载:

1|xxxx:/ # mount |grep "/data/media"
/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,reserved=200MB)
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,reserved=200MB)
/data/media on /mnt/runtime/full/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,reserved=200MB)

    /mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated的挂载是在系统启动时完成,其中/mnt/runtime/default/emulated是一个新的挂载点,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated是bind mount出来的,这样做的好处是减少文件系统内核开销(inode、dentry),但是带来的问题是inode共享之后要做不同的权限控制需要动态生成inode的权限位,uid,gid,这些是sdcard_fs通过挂载参数来完成的。具体的挂载参考”system/core/sdcard/sdcard.cpp“中的源码,本文参考android10.0的代码:

static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid,
                         gid_t gid, userid_t userid, bool multi_user, bool full_write,
                         bool derive_gid, bool default_normal, bool unshared_obb, bool use_esdfs) {
    std::string dest_path_default = "/mnt/runtime/default/" + label;
    std::string dest_path_read = "/mnt/runtime/read/" + label;
    std::string dest_path_write = "/mnt/runtime/write/" + label;
    std::string dest_path_full = "/mnt/runtime/full/" + label;
 
    umask(0);
     if (multi_user) {
        // Multi-user storage is fully isolated per user, so "other"
        // permissions are completely masked off.
        if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
                            AID_SDCARD_RW, 0006, derive_gid, default_normal, unshared_obb,
                            use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, 0027, derive_gid,
                                      default_normal, unshared_obb, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0027,
                                      derive_gid, default_normal, unshared_obb, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_full, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, 0007, derive_gid,
                                      default_normal, unshared_obb, use_esdfs)) {
            LOG(FATAL) << "failed to sdcardfs_setup";
        }
    } else {
        ....
    }
    ...
}
 
static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path,
                           uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid,
                           mode_t mask, bool derive_gid, bool default_normal, bool unshared_obb,
                           bool use_esdfs) {
    // Add new options at the end of the vector.
    std::vector<std::string> new_opts_list;
 
    if (multi_user) new_opts_list.push_back("multiuser,");
    if (derive_gid) new_opts_list.push_back("derive_gid,");
    if (default_normal) new_opts_list.push_back("default_normal,");
    if (unshared_obb) new_opts_list.push_back("unshared_obb,");
    // Try several attempts, each time with one less option, to gracefully
    // handle older kernels that aren't updated yet.
    for (int i = 0; i <= new_opts_list.size(); ++i) {
        std::string new_opts;
        for (int j = 0; j < new_opts_list.size() - i; ++j) {
            new_opts += new_opts_list[j];
        }
 
        auto opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d",
                                                fsuid, fsgid, new_opts.c_str(), mask, userid, gid);
        if (mount(source_path.c_str(), dest_path.c_str(), use_esdfs ? "esdfs" : "sdcardfs",
                  MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {
            PLOG(WARNING) << "Failed to mount sdcardfs with options " << opts;
        } else {
            return true;
        }
    }
 
    return false;
}
 
static bool sdcardfs_setup_secondary(const std::string& default_path,
                                     const std::string& source_path, const std::string& dest_path,
                                     uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid,
                                     gid_t gid, mode_t mask, bool derive_gid, bool default_normal,
                                     bool unshared_obb, bool use_esdfs) {
    if (use_esdfs) {
        return sdcardfs_setup(source_path, dest_path, fsuid, fsgid, multi_user, userid, gid, mask,
                              derive_gid, default_normal, unshared_obb, use_esdfs);
    } else {
        return sdcardfs_setup_bind_remount(default_path, dest_path, gid, mask);
    }
}
 
static bool sdcardfs_setup_bind_remount(const std::string& source_path, const std::string& dest_path,
                                        gid_t gid, mode_t mask) {
    std::string opts = android::base::StringPrintf("mask=%d,gid=%d", mask, gid);
 
    if (mount(source_path.c_str(), dest_path.c_str(), nullptr,
            MS_BIND, nullptr) != 0) {                                  // ***bind mount***
        PLOG(ERROR) << "failed to bind mount sdcardfs filesystem";
        return false;
    }
 
    if (mount(source_path.c_str(), dest_path.c_str(), "none",
            MS_REMOUNT | MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) != 0) {   // *** remount 修改挂载参数,sdcard_fs权限控制需要使用***
        PLOG(ERROR) << "failed to mount sdcardfs filesystem";
        if (umount2(dest_path.c_str(), MNT_DETACH))
            PLOG(WARNING) << "Failed to unmount bind";
        return false;
    }
 
    return true;
}

      /storage/emulated是在init.rc中挂载/storage间接挂载的,bind,slave:

mount none /mnt/runtime/default /storage bind rec
mount none none /storage slave rec

    上面分析的5处挂载都发生在root namespace空间,android为了能够做到动态权限管理的目的,Android使用了mount namespace,在Zygote fork应用进程的时候会通过unshare系统调用为应用进程创建一个mount namespace,在应用自己的mount namespace中根据AMS传递给Zygote的参数决定将/storage bind mount到 /mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated中的一个。在应用权限发生变化时,权限管理模块会调用到vold对应用进程空间的/storage根据权限变动重新挂载,这块我们在后面动态权限授予章节结合代码具体分析。

        Zygote起来之后首先会在自己的自己的进程空间中执行unshare(CLONE_NEWNS)跳出root namespace进入到新的mount namespace中,执行UnmountTree("/storage"),unmount掉在root namespace中对/storage挂载点的挂载,在fork应用程序子进程的时候在子进程的命名空间中调用:

        mount(storage_source.string(), "/storage", nullptr,MS_BIND | MS_REC | MS_SLAVE, nullptr)),就能完成应用进程空间的真实挂载点绑定,根据权限的不同,应用通过外部存储路径/storage/emulated/0 访问到的实际上是/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated中的一个。运行时关系如下图:

sdcard_fs


    sdcard_fs是google用来替代fuse接管文件系统权限而忽略真实磁盘文件系统权限(ext4,...)的一种堆叠文件系统,它是由三星的wrapfs改写而来。sdcard_fs并不真实访问磁盘,sdcard_fs会调用底层真实文件系统的方法进行真实的读写。由于还没有对sdcard_fs文件系统的源码进行研读,我们只分析一下和本文相关的对外部权限进行管控的逻辑。

        1. sdcard_fs进行文件的各种操作之前都会调用override_fsids方法(inode.c),在override_fsids方法中sdcard_fs会替换掉进程的struct cred,将cred->fsuid 和cred→sgid替换成真实文件系统的uid和gid,通过替换真实文件系统的uid和gid 通过sdcard_fs文件系统权限检查的方法调用总是能够反应到真实的文件系统上的调用执行:

const struct cred *override_fsids(struct sdcardfs_sb_info *sbi,
        struct sdcardfs_inode_data *data)
{
    struct cred *cred;
    const struct cred *old_cred;
    uid_t uid;
 
    cred = prepare_creds();
    if (!cred)
        return NULL;
 
    if (sbi->options.gid_derivation) {
        if (data->under_obb)
            uid = AID_MEDIA_OBB;
        else
            uid = multiuser_get_uid(data->userid, sbi->options.fs_low_uid);
    } else {
        uid = sbi->options.fs_low_uid;    // *** fs_low_uid 是sdcard_fs挂载时传入的参数 fsuid ***
    }
    cred->fsuid = make_kuid(&init_user_ns, uid);
    cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid); // *** fs_low_gid 是sdcard_fs挂载时传入的参数fsgid***
 
    old_cred = override_creds(cred);
 
    return old_cred;
}

      比如我们挂载点/mnt/runtime/default/emulated,Android在挂载的时候传入的fsuid, fsgid都是1023,而1023就是/data/media目录的所属用户和组:

OP46B1:/ # ls -ll /data |grep media
drwxrwx---   3 media_rw media_rw  4096 2020-09-01 10:56:04.489999979 +0800 media
drwxrwx---   2 mediadrm mediadrm  4096 1970-02-12 02:26:35.651999975 +0800 mediadrm
OP46B1:/ # id media_rw
uid=1023(media_rw) gid=1023(media_rw) groups=1023(media_rw), context=u:r:su:s0

     2. sdcard_fs权限校验发生在访问真实文件系统之前,sdcardfs重写了inode_operation的permission方法:

static int sdcardfs_permission(struct vfsmount *mnt, struct inode *inode, int mask)
{
    int err;
    struct inode tmp;
    struct sdcardfs_inode_data *top = top_data_get(SDCARDFS_I(inode));
 
    if (IS_ERR(mnt))
        return PTR_ERR(mnt);
    if (!top)
        return -EINVAL;
 
    /*
     * Permission check on sdcardfs inode.
     * Calling process should have AID_SDCARD_RW permission
     * Since generic_permission only needs i_mode, i_uid,
     * i_gid, and i_sb, we can create a fake inode to pass
     * this information down in.
     *
     * The underlying code may attempt to take locks in some
     * cases for features we're not using, but if that changes,
     * locks must be dealt with to avoid undefined behavior.
     */
    copy_attrs(&tmp, inode);
    tmp.i_uid = make_kuid(&init_user_ns, top->d_uid);  
    tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, inode->i_sb, top));
    tmp.i_mode = (inode->i_mode & S_IFMT)
            | get_mode(mnt, SDCARDFS_I(inode), top);
 
    data_put(top);
    tmp.i_sb = inode->i_sb;
    if (IS_POSIXACL(inode))
        pr_warn("%s: This may be undefined behavior...\n", __func__);
    err = generic_permission(&tmp, mask);
    return err;
}

     sdcardfs_permission最重要的是对inode的权限位,gid,uid的替换,完成替换之后调用vfs的generic_permission进行文件系统权限校验。 

     tmp.i_uid = make_kuid(&init_user_ns, top→d_uid)最终取值是AID_ROOT也即是root用户,他是sdcard_fs在挂载时得到的,具体逻辑参考sdcardfs_read_super 和setup_derived_state方法:

static int sdcardfs_read_super(struct vfsmount *mnt, struct super_block *sb,
        const char *dev_name, void *raw_data, int silent)
{
    ...
    /* setup permission policy */
    sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL);
    mutex_lock(&sdcardfs_super_list_lock);
    if (sb_info->options.multiuser) {
        setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT,
                sb_info->options.fs_user_id, AID_ROOT);
        snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name);
    } else {
        setup_derived_state(d_inode(sb->s_root), PERM_ROOT,
                sb_info->options.fs_user_id, AID_ROOT);
        snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name);
    }
    ...
}
 
void setup_derived_state(struct inode *inode, perm_t perm, userid_t userid,
                    uid_t uid)
{
    ...
    info->data->d_uid = uid;
    ...
}

    tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, inode->i_sb, top))调用get_gid方法来构造gid,在get_gid方法中通过vfsopt→gid来构造gid的值,而vfsopt→gid的值来源于挂载参数gid,/mnt/runtime/default/emulated的挂载参数gid是1015(sdcard_rw),而/mnt/runtime/full(read|write)/emulated的挂载参数gid是9997(everybody)

OP46B1:/ # id sdcard_rw
uid=1015(sdcard_rw) gid=1015(sdcard_rw) groups=1015(sdcard_rw), context=u:r:su:s0
OP46B1:/ # id everybody
uid=9997(everybody) gid=9997(everybody) groups=9997(everybody), context=u:r:su:s0
static inline int get_gid(struct vfsmount *mnt,
        struct super_block *sb,
        struct sdcardfs_inode_data *data)
{
    struct sdcardfs_vfsmount_options *vfsopts = mnt->data;
    struct sdcardfs_sb_info *sbi = SDCARDFS_SB(sb);
 
    if (vfsopts->gid == AID_SDCARD_RW && !sbi->options.default_normal)
        /* As an optimization, certain trusted system components only run
         * as owner but operate across all users. Since we're now handing
         * out the sdcard_rw GID only to trusted apps, we're okay relaxing
         * the user boundary enforcement for the default view. The UIDs
         * assigned to app directories are still multiuser aware.
         */
        return AID_SDCARD_RW;
    else
        return multiuser_get_uid(data->userid, vfsopts->gid); // *** /mnt/runtime/read/emulated 文件访问权限校验gid使用9997 ***
}

    tmp.i_mode = (inode->i_mode & S_IFMT) | get_mode(mnt, SDCARDFS_I(inode), top)调用get_mode方法来获取权限位,get_mode方法中最终要的一句代码是int visible_mode = 0775 & ~opts->mask, 而opts→mask来自于sdcard_fs挂载点挂载时传入的挂载参数mask,比如挂载点/mnt/runtime/read/emulated挂载时传入的参数mode=23,所以在everybody组中的用户对挂载点/mnt/runtime/read/emulated只有读权限没有写权限。源码参考:

static inline int get_mode(struct vfsmount *mnt,
        struct sdcardfs_inode_info *info,
        struct sdcardfs_inode_data *data)
{
    int owner_mode;
    int filtered_mode;
    struct sdcardfs_vfsmount_options *opts = mnt->data;
    int visible_mode = 0775 & ~opts->mask;    // *** opts->mask来自挂载参数mask ***
 
 
    if (data->perm == PERM_PRE_ROOT) {
        /* Top of multi-user view should always be visible to ensure
        * secondary users can traverse inside.
        */
        visible_mode = 0711;
    } else if (data->under_android) {
        /* Block "other" access to Android directories, since only apps
        * belonging to a specific user should be in there; we still
        * leave +x open for the default view.
        */
        if (opts->gid == AID_SDCARD_RW)
            visible_mode = visible_mode & ~0006;
        else
            visible_mode = visible_mode & ~0007;
    }
    owner_mode = info->lower_inode->i_mode & 0700;
    filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));
    return filtered_mode;
}

    讲到这里我们可以看一看上面提到的各个挂载点上的权限:

OP46B1:/ # ls /mnt/runtime/default/emulated/ -ll
total 4
drwxrwx--x 36 root sdcard_rw 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/read/emulated/ -ll
total 4
drwxr-x--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/write/emulated/ -ll
total 4
drwxrwx--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /mnt/runtime/full/emulated/ -ll
total 4
drwxrwx--- 36 root everybody 4096 2020-09-01 01:43:00.486299234 +0800 0
OP46B1:/ # ls /storage/emulated/ -ll
total 4
drwxrwx--x 36 root sdcard_rw 4096 2020-09-01 01:43:00.486299234 +0800 0

动态权限授予


    前面讲了外部存储空间和sdcard_fs的一些权限相关的核心逻辑,那么Android中如何做到动态权限控制的呢?大致情况是这样:

        1. 在App启动的时候,Zygote会根据应用权限的授予情况,在进程fork的时候为应用进程创建mount namespace,在应用程序所在的mount namespace中bind mount "/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated"中的一个;

        2.  App在启动的时候,Zygote会调用setgroups函数将9997(everybody)设置为应用程序进程所在的组;

        3. 在应用程序权限发生变化时(获得权限),权限管理模块会通知vold 重新mount "/mnt/runtime/defalut/emulated,/mnt/runtime/read/emulated,/mnt/runtime/write/emulated,/mnt/runtime/full/emulated"中的一个;

        因为所有的app进程都俗语9997组,所以当/storage bind mount 到/mnt/runtime/write 之后,app 进程对/storage/emulated/0下的文件就拥有了可读写权限。

举个例子:以操作今日头条为例(今日头条的包名时com.ss.android.article.news),在获得外部空间写入权限前后gid从1015 变为了9997。通过nsenter -t pid -m sh可以进入到今日头条进程的mount namespace空间中查看到操作前后如下内容:

OP46B1:/ # ps -elf |grep com.ss.android.article.news
u0_a208      19826  1914 85 11:24:18 ?    00:04:11 com.ss.android.article.news
u0_a208      20200  1914 55 11:24:25 ?    00:02:41 com.ss.android.article.news:sandboxed_process1
u0_a208      20291  1914 5 11:24:26 ?     00:00:13 com.ss.android.article.news:pushservice
u0_a208      20322  1914 4 11:24:26 ?     00:00:12 com.ss.android.article.news:push
u0_a208      21051  1914 7 11:24:42 ?     00:00:18 com.ss.android.article.news:miniapp0
root         22251 21988 5 11:29:15 pts/0 00:00:00 grep com.ss.android.article.news
OP46B1:/ # nsenter -t 19826 -m sh
OP46B1:/ # mount |grep "/storage/emulated"   <-------【获得外部存储权限前】
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,
mask=6,derive_gid,default_normal,reserved=200MB)
OP46B1:/ # exit
OP46B1:/ # nsenter -t 19826 -m sh
OP46B1:/ # mount |grep "/storage/emulated"   <-------【获得外部存储权限后】
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,
mask=7,derive_gid,default_normal,reserved=200MB)

     下面我们结合代码具体分析Android中外部存储动态权限的授予过程,首先在App启动的时候,AMS会调用到方法ProcessList.java#startProcessLocked,在startProcessLocked方法中会将9997作为gid参数传给Zygote:

// ProcessList.java
public static final int SHARED_USER_GID = 9997;
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            boolean disableHiddenApiChecks, boolean mountExtStorageFull,
            String abiOverride) {
       ...
                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
       ...
    }
// UserHandle.java
 public static int getUserGid(@UserIdInt int userId) {
        return getUid(userId, Process.SHARED_USER_GID);
    }

    接着流程进入jni程序 com_android_internal_os_Zygote.cpp#SpecializeCommon方法中,在这个方法中调用了MountEmulatedStorage完成app进程mount namespace创建和/storage bind挂载,并调用SetGids将9997设置为app进程所属组:

static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
                             jint runtime_flags, jobjectArray rlimits,
                             jlong permitted_capabilities, jlong effective_capabilities,
                             jint mount_external, jstring managed_se_info,
                             jstring managed_nice_name, bool is_system_server,
                             bool is_child_zygote, jstring managed_instruction_set,
                             jstring managed_app_data_dir) {
  ...
  
  MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
 
  ...
 
  SetGids(env, gids, fail_fn);
   
  ...
}
 
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
        bool force_mount_namespace,
        fail_fn_t fail_fn) {
  // See storage config details at http://source.android.com/tech/storage/
  ATRACE_CALL();
 
  String8 storage_source;
  if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
    storage_source = "/mnt/runtime/default";
  } else if (mount_mode == MOUNT_EXTERNAL_READ) {
    storage_source = "/mnt/runtime/read";
  } else if (mount_mode == MOUNT_EXTERNAL_WRITE
      || mount_mode == MOUNT_EXTERNAL_LEGACY
      || mount_mode == MOUNT_EXTERNAL_INSTALLER) {
    storage_source = "/mnt/runtime/write";
  } else if (mount_mode == MOUNT_EXTERNAL_FULL) {
    storage_source = "/mnt/runtime/full";
  } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
    // Sane default of no storage visible
    return;
  }
 
  // Create a second private mount namespace for our process
  if (unshare(CLONE_NEWNS) == -1) {
    fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
  }
 
  // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
  if (mount_mode == MOUNT_EXTERNAL_NONE) {
    return;
  }
 
  if (TEMP_FAILURE_RETRY(mount(storage_source.string(), "/storage", nullptr,
                               MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
    fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
                         storage_source.string(),
                         strerror(errno)));
  }
 
  ...
}
 
static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
  ...
 
  if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
    fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
  }
}

       当应用程序发生权限改变时(获得权限),逻辑流程大致如下:

        PermissionManangerService.java#grantRuntimePermission ->

        StorageManagerService.java#onExternalStoragePolicyChanged ->

        StorageManagerServic.java#remountUidExternalStorage ->

        VolumeManager.cpp#remountUid

         java 层只是一些过程调用,最后关键动作是Vold进程中的VolumeManager.cpp#remountUid方法,在remountUid方法中会通过遍历/porc的方法找到app uid对应的进程,并打开对应进程的“ns/mnt”,fork出一个零时的子进程,在子进程中通过setns切换app进程的mount namespace,在新的namespace中先unmount 掉/storage/挂载点,然后像在进程启动时Zygote做的一样在/storage/挂载点上重新bind mount 合适的目录:

int VolumeManager::remountUid(uid_t uid, int32_t mountMode) {
    std::string mode;
    switch (mountMode) {
        case VoldNativeService::REMOUNT_MODE_NONE:
            mode = "none";
            break;
        case VoldNativeService::REMOUNT_MODE_DEFAULT:
            mode = "default";
            break;
        case VoldNativeService::REMOUNT_MODE_READ:
            mode = "read";
            break;
        case VoldNativeService::REMOUNT_MODE_WRITE:
        case VoldNativeService::REMOUNT_MODE_LEGACY:
        case VoldNativeService::REMOUNT_MODE_INSTALLER:
            mode = "write";
            break;
        case VoldNativeService::REMOUNT_MODE_FULL:
            mode = "full";
            break;
        default:
            PLOG(ERROR) << "Unknown mode " << std::to_string(mountMode);
            return -1;
    }
    LOG(DEBUG) << "Remounting " << uid << " as mode " << mode;
 
    DIR* dir;
    struct dirent* de;
    std::string rootName;
    std::string pidName;
    int pidFd;
    int nsFd;
    struct stat sb;
    pid_t child;
 
    static bool apexUpdatable = android::sysprop::ApexProperties::updatable().value_or(false);
 
    if (!(dir = opendir("/proc"))) {
        PLOG(ERROR) << "Failed to opendir";
        return -1;
    }
 
    // Figure out root namespace to compare against below
    if (!android::vold::Readlinkat(dirfd(dir), "1/ns/mnt", &rootName)) {
        PLOG(ERROR) << "Failed to read root namespace";
        closedir(dir);
        return -1;
    }
 
    // Poke through all running PIDs look for apps running as UID
    while ((de = readdir(dir))) {
        pid_t pid;
        if (de->d_type != DT_DIR) continue;
        if (!android::base::ParseInt(de->d_name, &pid)) continue;
 
        pidFd = -1;
        nsFd = -1;
 
,        if (pidFd < 0) {
            goto next;
        }
        if (fstat(pidFd, &sb) != 0) {
            PLOG(WARNING) << "Failed to stat " << de->d_name;
            goto next;
        }
        if (sb.st_uid != uid) {
            goto next;
        }
 
        // Matches so far, but refuse to touch if in root namespace
        LOG(DEBUG) << "Found matching PID " << de->d_name;
        if (!android::vold::Readlinkat(pidFd, "ns/mnt", &pidName)) {
            PLOG(WARNING) << "Failed to read namespace for " << de->d_name;
            goto next;
        }
        if (rootName == pidName) {
            LOG(WARNING) << "Skipping due to root namespace";
            goto next;
        }
 
        if (apexUpdatable) {
            std::string exeName;
            // When ro.apex.bionic_updatable is set to true,
            // some early native processes have mount namespaces that are different
            // from that of the init. Therefore, above check can't filter them out.
            // Since the propagation type of / is 'shared', unmounting /storage
            // for the early native processes affects other processes including
            // init. Filter out such processes by skipping if a process is a
            // non-Java process whose UID is < AID_APP_START. (The UID condition
            // is required to not filter out child processes spawned by apps.)
            if (!android::vold::Readlinkat(pidFd, "exe", &exeName)) {
                PLOG(WARNING) << "Failed to read exe name for " << de->d_name;
                goto next;
            }
            if (!StartsWith(exeName, "/system/bin/app_process") && sb.st_uid < AID_APP_START) {
                LOG(WARNING) << "Skipping due to native system process";
                goto next;
            }
        }
 
        // We purposefully leave the namespace open across the fork
        // NOLINTNEXTLINE(android-cloexec-open): Deliberately not O_CLOEXEC
        nsFd = openat(pidFd, "ns/mnt", O_RDONLY);
        if (nsFd < 0) {
            PLOG(WARNING) << "Failed to open namespace for " << de->d_name;
            goto next;
        }
 
        if (!(child = fork())) {
            if (setns(nsFd, CLONE_NEWNS) != 0) {   // 切换 mount namespace
                PLOG(ERROR) << "Failed to setns for " << de->d_name;
                _exit(1);
            }
 
            android::vold::UnmountTree("/storage/");
 
            std::string storageSource;
            if (mode == "default") {
                storageSource = "/mnt/runtime/default";
            } else if (mode == "read") {
                storageSource = "/mnt/runtime/read";
            } else if (mode == "write") {
                storageSource = "/mnt/runtime/write";
            } else if (mode == "full") {
                storageSource = "/mnt/runtime/full";
            } else {
                // Sane default of no storage visible
                _exit(0);
            }
            if (TEMP_FAILURE_RETRY(
                    mount(storageSource.c_str(), "/storage", NULL, MS_BIND | MS_REC, NULL)) == -1) {  // 重新挂载
                PLOG(ERROR) << "Failed to mount " << storageSource << " for " << de->d_name;
                _exit(1);
            }
            if (TEMP_FAILURE_RETRY(mount(NULL, "/storage", NULL, MS_REC | MS_SLAVE, NULL)) == -1) {
                PLOG(ERROR) << "Failed to set MS_SLAVE to /storage for " << de->d_name;
                _exit(1);
            }
 
            // Mount user-specific symlink helper into place
            userid_t user_id = multiuser_get_user_id(uid);
            std::string userSource(StringPrintf("/mnt/user/%d", user_id));
            if (TEMP_FAILURE_RETRY(
                    mount(userSource.c_str(), "/storage/self", NULL, MS_BIND, NULL)) == -1) {
                PLOG(ERROR) << "Failed to mount " << userSource << " for " << de->d_name;
                _exit(1);
            }
 
            _exit(0);
        }
 
        if (child == -1) {
            PLOG(ERROR) << "Failed to fork";
            goto next;
        } else {
            TEMP_FAILURE_RETRY(waitpid(child, nullptr, 0));
        }
 
    next:
        close(nsFd);
        close(pidFd);
    }
    closedir(dir);
    return 0;
}

总结


    Android外部存储空间的实现比较复杂,主要是借助了Linux文件系统的机制,运用到的关机技术有sdscard_fs、mnt namespace、bind mount。sdcard_fs接管了底层文件系统的权限控制,通过bind mount避免了为每个进程mount带来的内核文件系统节点开销,mnt namespace保证每个应用进程有自己的挂载点,并在运行时通过在vold中为进程切换新的mount namespace来实现了动态授予权限的母的。

主要参考资料:

魅族内核团队:http://kernel.meizu.com/android-m-external-storage.html

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android 9.0中,动态申请存储权限是必须的,以提高应用程序的安全性和隐私保护。以下是如何在Android 9.0中动态申请存储权限的步骤: 1. 在AndroidManifest.xml文件中添加存储权限的声明。 ```xml <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 2. 检查应用程序是否已经被授予存储权限。可以使用`checkSelfPermission`方法进行检查,并在需要的情况下请求授予权限。 ```java if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 未获得存储权限,进行权限请求 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); } else { // 已获得存储权限,执行相应操作 // do something } ``` 3. 实现`onRequestPermissionsResult`方法,处理用户对权限请求的响应。如果用户授予权限,可以继续执行相关操作。 ```java @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == requestCode) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 用户授予了存储权限,执行相应操作 // do something } else { // 用户拒绝了存储权限,可以向用户解释为什么需要该权限 } } } ``` 需要注意的是,在Android 9.0之后,需要在`AndroidManifest.xml`文件中进行权限声明,同时还需要在运行时动态请求权限。这样做可以提高用户对应用程序的信任,同时保护用户的隐私和数据安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值