Android存储系统 您所在的位置:网站首页 荣耀migic4pro读取外置存储权限开启不了 Android存储系统

Android存储系统

2024-06-09 06:44| 来源: 网络整理| 查看: 265

前言

        在Android存储系统-使用fuse来管理外置存储 一文中我们介绍了Android fuse 来实现灵活的权限管理,我们知道获取不同的外置存储权限使用的外置存储目录是不同的, 获取外置存储读权限的应用程序看到的fuse目录为/mnt/runtime/read/${label}目录,获取了外置存储写权限的应用程序使用的是fuse的/mnt/runtime/write/${label}目录,而没有获取任何外置存储权限的应用看到的fuse目录为/mnt/runtime/default/${label}, 这一点是如何做到的呢? 在权限组管理方面,是如何将应用程序添加到AID_EVERYBODY组的呢? 外置存储权限又是如何动态授权和撤销的呢? 今天我们就来说明这个问题。

Android 设计

        Android在启动应用程序进程的时候,会通过PackageManagerService 来查询应用所属的组以及是否获取了应用外置存储权限。这些信息都会当做参数传递给zygote服务, zygote服务 fork应用进程后,根据参数中的组信息来设置应用进程所属于的组。再根据不同的外置存储权限,利用Linux的mount bind技术,将对应的fuse目录挂载到外置存储目录,这样以后读写外置存储目录,实际上写入到了fuse目录,这样就利用到了fuse权限管理能力。 另外Android还使用的命名空间技术, 每个应用使用主空间的一个副本,当应用程序外置存储权限改变的时候,只需要重新挂载该命名空间即可, 好了不多说,直接看代码。

代码分析

        Andrioid 启动应用的时候会在ActivityManagerService中调用startProcessLocked函数,代码如下:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) { ...... int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; if (!app.isolated) { int[] permGids = null; try { checkTime(startTime, "startProcess: getting gids from package manager"); final IPackageManager pm = AppGlobals.getPackageManager(); permGids = pm.getPackageGids(app.info.packageName, MATCH_DEBUG_TRIAGED_MISSING, app.userId); MountServiceInternal mountServiceInternal = LocalServices.getService( MountServiceInternal.class); mountExternal = mountServiceInternal.getExternalStorageMountMode(uid, app.info.packageName); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } ...... Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, app.info.dataDir, entryPointArgs); ......

        应用进程启动之前要准备一系列参数,这些参数最终会传递给zygote进程,用于fork应用进程。这里面有关外置存储权限管理的两个参数就是permGids和mountExternal。我们先来看permGids如何获取。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@Override public int[] getPackageGids(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; flags = updateFlagsForPackage(flags, userId, packageName); enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission */, false /* checkShell */, "getPackageGids"); // reader synchronized (mPackages) { final PackageParser.Package p = mPackages.get(packageName); if (p != null && p.isMatch(flags)) { PackageSetting ps = (PackageSetting) p.mExtras; return ps.getPermissionsState().computeGids(userId); } if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null && ps.isMatch(flags)) { return ps.getPermissionsState().computeGids(userId); } } } return null; }

        getPackageGids函数通过权限管理计算gid,也就是ps.getPermissionsState().computeGids(userId)这个函数。

frameworks/base/services/core/java/com/android/server/pm/PermissionsState.java

427 /** 428 * Compute the Linux gids for a given device user from the permissions 429 * granted to this user. Note that these are computed to avoid additional 430 * state as they are rarely accessed. 431 * 432 * @param userId The device user id. 433 * @return The gids for the device user. 434 */ 435 public int[] computeGids(int userId) { 436 enforceValidUserId(userId); 437 438 int[] gids = mGlobalGids; 439 440 if (mPermissions != null) { 441 final int permissionCount = mPermissions.size(); 442 for (int i = 0; i 445 continue; 446 } 447 PermissionData permissionData = mPermissions.valueAt(i); 448 final int[] permGids = permissionData.computeGids(userId); 449 if (permGids != NO_GIDS) { 450 gids = appendInts(gids, permGids); 451 } 452 } 453 } 454 455 return gids; 456 }

        447-451行如果应用获取了权限,就看下该权限是否关联了gid,如果关联了gid就放在返回结果的数组里面。最后返回应用获取的所有权限关联的gid,那么哪些权限会关联gid呢,在PackageManagerService构造函数中会进行初始化。注意这里mGroupGids是应用默认的gid,也在PackageManagerService构造函数中设置。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { ...... SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); ...... // Propagate permission configuration in to package manager. ArrayMap permConfig = systemConfig.getPermissions(); for (int i=0; i bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN); mSettings.mPermissions.put(perm.name, bp); } if (perm.gids != null) { bp.setGids(perm.gids, perm.perUser); } } ...... }

        这里可以看出,gid是从systemConfig.getPermissions()取出来的, systemConfig是SystemConfig 类的实例。

frameworks/base/core/java/com/android/server/SystemConfig.java

public ArrayMap getPermissions() { return mPermissions; } SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); // Allow ODM to customize system configs around libs, features and apps int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS; readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag); readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); // Only allow OEM to customize features readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES); }

        SystemConfig初始化过程会读取一些列文件,来获取权限和gid的关系,举个例子,在我的手机上 /system/etc/permissions/platform.xml文件内容如下。

        android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw。 具体读文件的代码就不分析了,感兴趣的读者可以自行分析下。 这里我们可以看到读写外置存储不关联任何组。android.permission.WRITE_MEDIA_STORAGE 权限关联的gid是media_rw,是为了兼容老版本。老版本Android 声明WRITE_MEDIA_STORAGE权限就可以读写外置存储是因为它属于media_rw组。SystemConfig.mGlobalGids里面包含AID_EVERYBODY。读过Android存储系统-使用fuse来管理外置存储都是知道android的fuse管理外置存储使用AID_EVERYBODY作为属主提供读写权限。

         到现在我们知道了应用进程启动的gids参数如何获取,下面来看下mountExternal参数如何获取。

frameworks/base/services/core/java/com/android/server/MountService.java

@Override public int getExternalStorageMountMode(int uid, String packageName) { // No locking - CopyOnWriteArrayList int mountMode = Integer.MAX_VALUE; for (ExternalStorageMountPolicy policy : mPolicies) { final int policyMode = policy.getMountMode(uid, packageName); if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) { return Zygote.MOUNT_EXTERNAL_NONE; } mountMode = Math.min(mountMode, policyMode); } if (mountMode == Integer.MAX_VALUE) { return Zygote.MOUNT_EXTERNAL_NONE; } return mountMode; }

         mountMode主要通过ExternalStorageMountPolicy中获取, 其中一个policy就是PackageManagerService。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

@Override public void systemReady() { mountServiceInternal.addExternalStoragePolicy( new MountServiceInternal.ExternalStorageMountPolicy() { @Override public int getMountMode(int uid, String packageName) { if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) { return Zygote.MOUNT_EXTERNAL_DEFAULT; } if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) { return Zygote.MOUNT_EXTERNAL_DEFAULT; } if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) { return Zygote.MOUNT_EXTERNAL_READ; } return Zygote.MOUNT_EXTERNAL_WRITE; } @Override public boolean hasExternalStorage(int uid, String packageName) { return true; } }); ...... }

         如果应用程序获取了WRITE_MEDIA_STORAGE权限则mountMode 为 MOUNT_EXTERNAL_DEFAULT,没有获取读权限则mountMode 为MOUNT_EXTERNAL_DEFAULT, 获得了读外置存储权限但是没有获得写外置存户权限则mountMode 为MOUNT_EXTERNAL_READ, 获得了读和写外置存储权限则mountMode 为 MOUNT_EXTERNAL_READ。 到此位置启动应用程序的mountExternal权限也有了。 下面来看下进程启动过程怎么设置参数。

frameworks/base/core/java/android/os/Process.java

final String niceName, int uid, int gid, int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, String instructionSet, String appDataDir, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); throw new RuntimeException( "Starting VM process through Zygote failed", ex); } } private static ProcessStartResult startViaZygote(final String processClass, final String niceName, final int uid, final int gid, final int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, String instructionSet, String appDataDir, String[] extraArgs) throws ZygoteStartFailedEx { synchronized(Process.class) { ArrayList argsForZygote = new ArrayList(); // --runtime-args, --setuid=, --setgid=, // and --setgroups= must go first argsForZygote.add("--runtime-args"); argsForZygote.add("--setuid=" + uid); argsForZygote.add("--setgid=" + gid); ...... if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { argsForZygote.add("--mount-external-default"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) { argsForZygote.add("--mount-external-read"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { argsForZygote.add("--mount-external-write"); } ...... if (gids != null && gids.length > 0) { StringBuilder sb = new StringBuilder(); sb.append("--setgroups="); int sz = gids.length; for (int i = 0; i sb.append(','); } sb.append(gids[i]); } argsForZygote.add(sb.toString()); } ...... return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); } }

        --setgroups= 参数指定gids, 多个gid使用逗号分割。-mount-external-read参数表示应用获取到了读外置存储权限。–mount-external-write参数表示应用获得了写外置存储权限,–mount-external-default表示应用没有获取读写外置存储权限。

         调用zygote进程的过程我们就不分析了,下面来看下zygote如何使用上述参数。

// Utility routine to fork zygote and specialize the child process. static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, jint debug_flags, jobjectArray javaRlimits, jlong permittedCapabilities, jlong effectiveCapabilities, jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, jintArray fdsToClose, jstring instructionSet, jstring dataDir) { ...... pid_t pid = fork(); if (pid == 0) { if (!MountEmulatedStorage(uid, mount_external, use_native_bridge)) { ALOGW("Failed to mount emulated storage: %s", strerror(errno)); if (errno == ENOTCONN || errno == EROFS) { // When device is actively encrypting, we get ENOTCONN here // since FUSE was mounted before the framework restarted. // When encrypted device is booting, we get EROFS since // FUSE hasn't been created yet by init. // In either case, continue without external storage. } else { RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage"); } } } ...... SetGids(env, javaGids); ...... }

         zygote 在fork子进程后,子进程还有特权,先调用MountEmulatedStorage函数来挂载外置存储,然后调用SetGids来设置进程属于的组。我们先看MountEmulatedStorage函数。

296 // Create a private mount namespace and bind mount appropriate emulated 297 // storage for the given user. 298 static bool MountEmulatedStorage(uid_t uid, jint mount_mode, 299 bool force_mount_namespace) { 300 // See storage config details at http://source.android.com/tech/storage/ 301 302 // Create a second private mount namespace for our process 303 if (unshare(CLONE_NEWNS) == -1) { // 1 创建一个私有的mount命令空间,脱离原来共享的命名空间。 304 ALOGW("Failed to unshare(): %s", strerror(errno)); 305 return false; 306 } 307 308 String8 storageSource; 309 if (mount_mode == MOUNT_EXTERNAL_DEFAULT) { //2 根据应用获取的权限选择使用的fuse目录。 310 storageSource = "/mnt/runtime/default"; 311 } else if (mount_mode == MOUNT_EXTERNAL_READ) { 312 storageSource = "/mnt/runtime/read"; 313 } else if (mount_mode == MOUNT_EXTERNAL_WRITE) { 314 storageSource = "/mnt/runtime/write"; 315 } else { 316 // Sane default of no storage visible 317 return true; 318 } 319 if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", // 3 bind /storage 到 storageSource 320 NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { 321 ALOGW("Failed to mount %s to /storage: %s", storageSource.string(), strerror(errno)); 322 return false; 323 } 324 325 // Mount user-specific symlink helper into place 326 userid_t user_id = multiuser_get_user_id(uid); 327 const String8 userSource(String8::format("/mnt/user/%d", user_id)); 328 if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { 329 return false; 330 } 331 if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", // 4 /storage/self 指向 /mnt/user/\${userid} 332 NULL, MS_BIND, NULL)) == -1) { 333 ALOGW("Failed to mount %s to /storage/self: %s", userSource.string(), strerror(errno)); 334 return false; 335 } 336 337 return true; 338 }

         上述代码分为四个步骤: 1、创建一个私有的mount命令空间,脱离原来共享的命名空间。 2、根据应用获取的权限选择使用的fuse目录。 3、 /storage 目录bind到 storageSource, 也就是之后操作/storage目录实际上都是操作storageSource。 4、/storage/self 目录bind 到 /mnt/user/${userid}

         结合我们前面的Android存储系统-MountService 和vold 对外置存储的管理(3) 和 Android存储系统-使用fuse来管理外置存储 我们可以得到一个完整的应用进程视角的目录结构。

/sdcard -> /storage/self/primary (软链接) system/core/rootdir/init.rc 中设置

/mnt/user/${userid}/primary -> /storage/emulated/${userid}/ ( 软链接) vold中用户创建时设置

/storage/self/ -> /mnt/user/${userid}/(mount bind) zygote MountEmulatedStorage 函数中设置

/storage/ -> /mnt/runtime/[default|read|write]/ ( mount bind) zygote MountEmulatedStorage 函数中设置

/mnt/runtime/[default|read|write]/emulated -> /data/media (fuse) (可能label不是emulated,这里只是拿emulated举例)。

         最终看到的主存储结构如下

/sdcard - > /storage/self/primary -> /storage/emulated/${userid}/ -> /mnt/runtime/[default|read|write]/emulated/${userid}/ -> /data/media/${userid}

        所以应用读写/sdcard目录会转到读写主存储,读写主存储会转到读写/storage/${primary_label}/${userid}/ , 读写 /storage/${primary_label}/${userid}/会被转到读写/mnt/runtime/[default|read|write]/${primary_label}/${userid}/ 这个fuse文件目录,然后fuse又把实际操作转到 主存储路径下的 media/${userid} 目录下。

        再来看下SetGids函数

// Calls POSIX setgroups() using the int[] object as an argument. // A NULL argument is tolerated. static void SetGids(JNIEnv* env, jintArray javaGids) { if (javaGids == NULL) { return; } ScopedIntArrayRO gids(env, javaGids); if (gids.get() == NULL) { RuntimeAbort(env, __LINE__, "Getting gids int array failed"); } int rc = setgroups(gids.size(), reinterpret_cast(&gids[0])); if (rc == -1) { std::ostringstream oss; oss final long token = Binder.clearCallingIdentity(); try { if (sUserManager.isInitialized(userId)) { MountServiceInternal mountServiceInternal = LocalServices.getService( MountServiceInternal.class); mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName); } } finally { Binder.restoreCallingIdentity(token); } } }

        应用授权后如果该权限是读外置存储权限或者写外置存储权限,就会调用mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName) 通过MountService。

frameworks/base/services/core/java/com/android/server/MountService.java

private void remountUidExternalStorage(int uid, int mode) { waitForReady(); String modeName = "none"; switch (mode) { case Zygote.MOUNT_EXTERNAL_DEFAULT: { modeName = "default"; } break; case Zygote.MOUNT_EXTERNAL_READ: { modeName = "read"; } break; case Zygote.MOUNT_EXTERNAL_WRITE: { modeName = "write"; } break; } try { mConnector.execute("volume", "remount_uid", uid, modeName); } catch (NativeDaemonConnectorException e) { Slog.w(TAG, "Failed to remount UID " + uid + " as " + modeName + ": " + e); } }

        MountService 给vold进程发送一个remount_uid命令。关于vold进程的消息处理细节在Android存储系统-MountService 和vold 对外置存储的管理(1) 这篇文章,我们直接看下vold怎样处理的remount_uid命令。

508 int VolumeManager::remountUid(uid_t uid, const std::string& mode) { 509 LOG(DEBUG) // 1. 读/proc/1/ns/mnt, 获取init进程的mount空间名称保存到rootName 527 PLOG(ERROR) 539 goto next; 540 } 541 if (fstat(pidFd, &sb) != 0) { 542 PLOG(WARNING) //3 读取/proc/${x}/ns/mnt,获取进程的mount空间 552 PLOG(WARNING) 563 PLOG(WARNING) 568 if (setns(nsFd, CLONE_NEWNS) != 0) { // 5 设置下面后续操作的mount空间 569 PLOG(ERROR) 579 storageSource = "/mnt/runtime/read"; 580 } else if (mode == "write") { 581 storageSource = "/mnt/runtime/write"; 582 } else { 583 // Sane default of no storage visible 584 _exit(0); 585 } 586 if (TEMP_FAILURE_RETRY(mount(storageSource.c_str(), "/storage", 587 NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { 588 PLOG(ERROR) 607 PLOG(ERROR) ...... killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); }

        撤销权限的过程简单粗暴,直接杀死应用程序,因为应用程序可能已经打开了一些文件,如果不杀死,可以继续写入数据。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有