Android之权限架构

1. 权限简介:

所有的权限定义在 Android 系统的源代码中,路径通常位于 frameworks/base/core/res/AndroidManifest.xml
本篇文章以Android 15 原生源码来做的讲解。
LI 、LIF、LPr、LPw 是什么?
首先L代表Lock,I代表mInstall,P代表mPackages,F代表frozen,r代表读,w代表写。

2. 安装时权限:

2.1 普通权限(Normal Permissions):

这类权限不会对用户隐私和系统安全构成重大风险,如查看网络连接状态、访问 Wi-Fi 状态等。应用在 AndroidManifest.xml 文件中声明所需要的权限后,在安装时系统会自动授予,无需用户额外确认。系统会为普通权限分配 normal 保护级别。

2.1.1 普通权限示例:

2.1.2 实现流程

2.1.2.1 在 AndroidManifest.xml 中声明权限

在项目的 AndroidManifest.xml 文件中,使用 标签来声明应用所需的普通权限。例如,如果你需要使用网络连接权限(ACCESS_NETWORK_STATE)和查看 Wi-Fi 状态权限(ACCESS_WIFI_STATE),可以这样声明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.app">
     <!-- 声明查看网络连接状态的权限 -->
     <uses - permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <!-- 声明查看 Wi-Fi 状态的权限 -->
     <uses - permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <application...>
     ...
     </application>
</manifest>
2.1.2.2 构建应用

完成权限声明后,像平常一样构建应用(可以通过 Android Studio 的构建工具进行编译和打包)。在代码中使用权限相关功能:

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkInfo

public class NetworkUtils {
    fun isNetworkConnected(context: Context): Boolean {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManagerval 
        networkInfo: NetworkInfo? = connectivityManager.activeNetworkInforeturn 
        networkInfo!= null && networkInfo.isConnected
    }
}

在上述代码中,getSystemService 方法获取 ConnectivityManager 实例,进而可以调用 getActiveNetworkInfo 方法来获取网络连接信息。由于在 AndroidManifest.xml 中已经声明了 ACCESS_NETWORK_STATE 权限,系统会自动授予该权限,使得代码能够正常执行。

2.1.2.3 发起安装,系统记录APK的权限

当用户在设备上安装应用时,系统会检查应用声明的普通权限列表。由于这些权限被标记为普通权限,系统会自动将它们授予应用,无需用户进行任何手动操作。
在PackageInstallerRecive收到安装通知,调用frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java的postAppInstalledNotification()来发送一个安装通知。并打开installer的lauch intent;

解析APK流程

2.1.2.3.1 读取文件

系统首先需要通过文件路径或 URI 来读取 APK 文件。在 frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java 中初始化mPm、mInstaller,并通过packageUri来读去APK文件。

public class PackageInstallerActivity extends AlertActivity {    
    ...
    private int mSessionId = -1;
    PackageManager mPm;
    IPackageManager mIpm;...
    PackageInstaller mInstaller;
    PackageInfo mPkgInfo; //解析apk得到的PackageInfo    
     ...

    protected void onCreate(Bundle icicle) {    
        ...
        mPm = getPackageManager(); //ApplicationPackageManager            
        ...
        mInstaller = mPm.getPackageInstaller(); // mInstaller,安装器        
        ...
        final Intent intent = getIntent();
        final Object packageSource;
        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(action)) {
           ...
           packageSource = Uri.fromFile(new File(resolvedPath));
           ...
        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
           ...
           final SessionInfo info = mInstaller.getSessionInfo(sessionId);
           ...
           packageSource = info;
           ...
        } else {
           ...
           packageSource = intent.getData();
           ...
        }
        ...
        final boolean wasSetUp = processAppSnippet(packageSource);
        ...
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (mLocalLOGV) Log.i(TAG, "onResume(): mAppSnippet=" + mAppSnippet);
        if (mAppSnippet != null) {
            // 开启安装确认
            bindUi();
           checkIfAllowedAndInitiateInstall();
    }

    if (mOk != null) {
        mOk.setEnabled(mEnableOk);
    }
}
     ...

    private boolean processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;
        final String scheme = packageUri.getScheme();
        switch (scheme) {    
            ...
            case ContentResolver.SCHEME_FILE: {
                File sourceFile = new File(packageUri.getPath());
                //解析APK,注意参数是 GET_PERMISSIONS            
                mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile, PackageManager.GET_PERMISSIONS);
                ..
                //获取apk摘要:图标、名字            
                mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);            
            ...
            }
            break;    
            ...
        }
        return true;
    }

    ...
}
2.1.2.3.2 解析APK

frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java中调用PackageManager 类解析了参数flags 是 PackageManager.GET_PERMISSIONS的APK权限信息。

@Nullable
public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
    String filePath = sourceFile.getAbsolutePath();
    if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) { //SPLIT_BASE_APK_END_WITH:base.apk
        File dir = sourceFile.getParentFile();
        if (dir.listFiles().length > 1) {
            // split apks, use file directory to get archive info
            filePath = dir.getPath();
        }
    }
    try {
        return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
    } catch (Exception ignored) {
        return null;
    }
}
//PackageManager.java 解析apk后归档处理
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
        @NonNull PackageInfoFlags flags) {
    final File apkFile = new File(archiveFilePath);
    ...
    try {
        ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);

        return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
    } catch (PackageParserException e) {
        Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
        return null;
    }
}
// frameworks/base/core/java/com/android/internal/pm/parsing/PackageParser2.java中继续调用ParsingPackageUtils.parsePackage()去解析APK包并返回一个解析结果,然后把解析结果放到缓存中去。
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
        throws PackageParserException {
    var files = packageFile.listFiles();
    ...
    ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
    ...
    ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();

    long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    if (mCacher != null) {
        mCacher.cacheResult(packageFile, flags, parsed);
    }
    ...

    return parsed;
}
  • 无论解析多个APK还是单个APK都会去通过frameworks/base/core/java/android/content/pm/parsing/ApkLiteParseUtils.java获得一个APK的轻量级详细信息,包括包名、查分名称和安装位置,并调用ParsingPackageUtils.parseBaseApk()解析AndroidManifest.xml,获得APK的四大组件的功能点。
//ApkLiteParseUtils.java
private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
        File apkFile, FileDescriptor fd, String debugPathName, int flags) {
    final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();

    XmlResourceParser parser = null;
    ApkAssets apkAssets = null;
    try {
        ...
        parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);

        ...
        //解析APK的签名、使用的SDK版本、APK版本号等等
        return parseApkLite(input, apkPath, parser, signingDetails, flags);
    } 
    ...
    // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
    }
}

//ParsingPackageUtils.java
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
        String codePath, SplitAssetLoader assetLoader, int flags,
        boolean shouldSkipComponents) {
    final String apkPath = apkFile.getAbsolutePath();
    final String volumeUuid = getVolumeUuid(apkPath);
    if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
    final AssetManager assets;
    ...
    try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
            ANDROID_MANIFEST_FILENAME)) {
        final Resources res = new Resources(assets, mDisplayMetrics, null);
        //解析Application下的四大组件
        ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
                parser, flags, shouldSkipComponents);
        ...

        final ParsingPackage pkg = result.getResult();
        ...
        
        return input.success(pkg);
    } catch (Exception e) {
        return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    }
}
2.1.2.3.3 解析权限
  • frameworks/base/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java调用generate() 解析 APK 的权限授权状态 和包名等信息放到PackageInfo中去
@Nullable
public static PackageInfo generate(@Nullable AndroidPackage pkg,
        @PackageManager.PackageInfoFlagsBits long flags, int userId) {
    if (pkg == null) {
        return null;
    }
    ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, userId);

    PackageInfo info = new PackageInfo();
    info.packageName = pkg.getPackageName();
    ...
    if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
        int size = ArrayUtils.size(pkg.getPermissions());
        if (size > 0) {
            info.permissions = new PermissionInfo[size];
            for (int i = 0; i < size; i++) {
                final var permission = pkg.getPermissions().get(i);
                //把解析后的权限(比如组、权限类型)放到PermissionInfo中去
                final var permissionInfo = generatePermissionInfo(permission, flags);
                info.permissions[i] = permissionInfo;
            }
        }
        //获取APP解析后的权限列表
        final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
        size = usesPermissions.size();
        if (size > 0) {
            info.requestedPermissions = new String[size];
            info.requestedPermissionsFlags = new int[size];
            for (int i = 0; i < size; i++) {
                final ParsedUsesPermission usesPermission = usesPermissions.get(i);
                info.requestedPermissions[i] = usesPermission.getName();
                // The notion of required permissions is deprecated but for compatibility.
                ...
                if (pkg.getImplicitPermissions().contains(info.requestedPermissions[i])) {
                    info.requestedPermissionsFlags[i] |=
                            PackageInfo.REQUESTED_PERMISSION_IMPLICIT;
                }
            }
        }
    }
    ...

    return info;
}
2.1.2.4 安装过程
2.1.2.4.1 Session:发送包到PMS
  • 在frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java通过 PackageInstaller 创建Session(会话) 并返回 mSessionId,把包信息写入mSessionId对应的session,通过Session把包发送到PMS、处理从PMS得到的安装结果。
//InstallInstalling.java
@Override
protected void onResume() {
    super.onResume();
    // This is the first onResume in a single life of the activity
    if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        //把包信息写入mSessionId对应的session
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);

        if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
            ...
        }
    }
}


//发送包到 installer
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
        PackageInstaller.Session> {
    ...
    @Override
    protected PackageInstaller.Session doInBackground(Void... params) {
        try {
            //创建mSessionId对应的session
            return getPackageManager().getPackageInstaller().openSession(mSessionId);
        } catch (IOException e) {
            return null;
        } 
        ...
    }
    @Override
    protected void onPostExecute(PackageInstaller.Session session) {
        if (session != null) {
            ...
            
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallInstalling.this,
                    mInstallId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
            // Delay committing the session by 100ms to fix a UI glitch while displaying the
            // Update-Owner change dialog on top of the Installing dialog
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                try {
                    //包写入session后,进行提交
                    session.commit(pendingIntent.getIntentSender());
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Cannot install package: ", e);
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                        PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                    return;
                }
            }, 100);
            ...
        } 
        ...
    }
}
//PackageInstallerSession.java
//根据session.commit()继续往下看具体做了哪些操作
private List<CompletableFuture<InstallResult>> installNonStaged() {
    try {
        ...
        final InstallingSession installingSession = createInstallingSession(future);
        if (isMultiPackage()) {
            ...
            if (!installingChildSessions.isEmpty()) {
                Objects.requireNonNull(installingSession).installStage(installingChildSessions);
            }
        } else if (installingSession != null) {
            installingSession.installStage();
        }
        return futures;
    } 
    ...
}
//frameworks/base/services/core/java/com/android/server/pm/InstallingSession.java
public void installStage() {
    setTraceMethod("installStage").setTraceCookie(System.identityHashCode(this));
    ...
    //安装走到了InstallingSession的start()中,并通过PackageManagerService的mHandler排队执行一个异步操作
    mPm.mHandler.post(this::start);
}
public void installStage(List<InstallingSession> children)
        throws PackageManagerException {
    final MultiPackageInstallingSession installingSession =
            new MultiPackageInstallingSession(getUser(), children, mPm);    ...
    //安装走到了InstallingSession的start()中,并通过PackageManagerService的mHandler排队执行一个异步操作
    mPm.mHandler.post(installingSession::start);
}
  • 在InstallingSession的start()中handleStartCopy()主要是获取安装目录位置信息。
private void handleStartCopy(InstallRequest request) {
    if ((mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
        mRet = INSTALL_SUCCEEDED;
        return;
    }
    PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
            mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride);

    // For staged session, there is a delay between its verification and install. Device
    // state can change within this delay and hence we need to re-verify certain conditions.
    boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
    if (isStaged) {
        Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
                pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
        mRet = ret.first;
        if (mRet != INSTALL_SUCCEEDED) {
            request.setError(mRet, "Failed to verify version code");
            return;
        }
    }

    final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
    if (DEBUG_INSTANT && ephemeral) {
        Slog.v(TAG, "pkgLite for install: " + pkgLite);
    }

    if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
            == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
        // If we are not staged and have too little free space, try to free cache
        // before giving up.
        pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
                pkgLite.recommendedInstallLocation, mPackageLite,
                mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
    }
    //Override install location based on default policy if needed.
    mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
            pkgLite.installLocation);
    if (mRet != INSTALL_SUCCEEDED) {
        request.setError(mRet, "Failed to override installation location");
    }
}
  • 在InstallingSession的start()中handleReturnCode()主要是为了拷贝apk、lib库到指定的安装目录位置;
handleReturnCode()中只调用了processPendingInstall()
private void processPendingInstall(InstallRequest installRequest) {
    if (mRet == PackageManager.INSTALL_SUCCEEDED) {
        //如果前面校验ok,这里执行apk、NativeLibrary的拷贝
        mRet = copyApk(installRequest);
    }
    if (mRet == PackageManager.INSTALL_SUCCEEDED) {
        F2fsUtils.releaseCompressedBlocks(
                mPm.mContext.getContentResolver(), new File(installRequest.getCodePath()));
    }
    installRequest.setReturnCode(mRet);
    if (mParentInstallingSession != null) {
        //MultiPackageInstallingSession: 多个APK session发起安装请求
        mParentInstallingSession.tryProcessInstallRequest(installRequest);
    } else {
        // Queue up an async operation since the package installation may take a little while.
        mPm.mHandler.post(() -> processInstallRequests(
                mRet == PackageManager.INSTALL_SUCCEEDED /* success */,
                Collections.singletonList(installRequest)));
    }
}
// processInstallRequests()主要做了两件事情:APEX安装、APK安装。这里主要讲解APK安装,他调用了processApkInstallRequests()方法来继续安装APK
private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) {
    if (!success) {
        //走到PackageManagerService中清除APK的相关缓存以及安装路径
        for (InstallRequest request : installRequests) {
            if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
                cleanUpForFailedInstall(request);
            }
        }
    } else {
        //到PackageManagerService中调用InstallPackageHelper的installPackagesTraced()方法去开启安装;
        //主要调用了installPackagesLI(requests);
        mPm.installPackagesTraced(installRequests);

        for (InstallRequest request : installRequests) {
            //如果上面安装成功,就会移除包名和CodePath
            doPostInstall(request);
        }
    }
    for (InstallRequest request : installRequests) {
        //安装后续:备份、可能的回滚、发送安装完成先关广播
        mPm.restoreAndPostInstall(request);
    }
}
2.1.2.4.2 PackageManagerService中的安装
  • frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java的installPackagesLI()主要干了以下几件事情:
@GuardedBy("mPm.mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
    ...
    try {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
        for (InstallRequest request : requests) {
            try {
                ...
                //1.准备:分析当前安装状态,解析包并初始验证
                preparePackageLI(request);
            }             
            ...
            
            try {
                //2.扫描:根据准备阶段解析的包信息上下文 进一步解析
                final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
                        request.getParseFlags(), request.getScanFlags(),
                        System.currentTimeMillis(), request.getUser(),
                        request.getAbiOverride());
                ...
                //注册appId
                createdAppId.put(packageName, optimisticallyRegisterAppId(request));
                //保存version信息
                versionInfos.put(packageName,
                        mPm.getSettingsVersionForPackage(packageToScan));
            } 
            ...
        }

        List<ReconciledPackage> reconciledPackages;
        synchronized (mPm.mLock) {
            try {
                //3.核对:验证扫描后的包信息和系统状态,确保安装成功
                reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                        requests, Collections.unmodifiableMap(mPm.mPackages),
                        versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
                        mPm.mSettings, mPm.mInjector.getSystemConfig());
            } 
            ...

            try {
                //4.提交:提交扫描的包、更新系统状态。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
                commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        }
        //安装成功的后续才做:准备app数据、编译布局资源、执行dex优化
        //执行dex优化:dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。
        executePostCommitStepsLIF(reconciledPackages);
    } finally {
        if (success) {
            for (InstallRequest request : requests) {
                ...
                //对于增量安装,我们在安装前绕过验证程序。既然我们已经知道软件包是有效的,那么向验证程序发送一条通知,其中包含 base.apk 的根哈希值。
                //增量安装是一种安装方式,它仅更新软件包中发生变化的部分,而不是重新安装整个软件包。
                final String rootHashString = PackageManagerServiceUtils
                        .buildVerificationRootHashString(baseCodePath, splitCodePaths);
                VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                        PackageManager.VERIFICATION_ALLOW, rootHashString,
                        request.getDataLoaderType(), request.getUser(), mContext);
            }
        } ...
    }
}
2.1.2.4.2.1. 准备:
  • 分析当前安装状态,解析包 并初始校验: 在 preparePackageLI() 内使用 PackageParser2.parsePackage() 解析AndroidManifest.xml,获取四大组件等信息;使用ParsingPackageUtils.getSigningDetails() 解析签名信息;重命名包最终路径 等。
  • 解析软件包:这涉及到读取软件包的元数据,包括软件包的版本、依赖关系、所包含的文件和配置信息等。对于不同的软件包格式(如 .deb、.rpm、.apk 等),可能使用不同的解析工具和方法。
  • 初步验证:验证可能包括检查软件包的完整性,例如检查软件包的签名是否有效,软件包的结构是否符合预期,以及软件包是否针对当前系统的架构和操作系统版本进行了适配。
//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mInstallLock")
private void preparePackageLI(InstallRequest request) throws PrepareFailure { 
    ...
    // either use what we've been given or parse directly from the APK
    if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
        parsedPackage.setSigningDetails(request.getSigningDetails());
    } else {
        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
        final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
                input, parsedPackage, false /*skipVerify*/);
        if (result.isError()) {
            throw new PrepareFailure("Failed collect during installPackageLI",
                    result.getException());
        }
        parsedPackage.setSigningDetails(result.getResult());
    }
    ...
            // Quick validity check that we're signed correctly if updating;
            // we'll check this again later when scanning, but we want to
            // bail early here before tripping over redefined permissions.
            final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
            final SharedUserSetting signatureCheckSus = mPm.mSettings.getSharedUserSettingLPr(
                    signatureCheckPs);
            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, signatureCheckSus,
                    scanFlags)) {
                    ...
            }
            
        //检查APK中的权限组;
        final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups());
        for (int groupNum = 0; groupNum < numGroups; groupNum++) {
            final ParsedPermissionGroup group =
                    parsedPackage.getPermissionGroups().get(groupNum);
            final PermissionGroupInfo sourceGroup = mPm.getPermissionGroupInfo(group.getName(),
                    0);
            ...
        }

        // TODO: Move logic for checking permission compatibility into PermissionManagerService
        final int n = ArrayUtils.size(parsedPackage.getPermissions());
        for (int i = n - 1; i >= 0; i--) {
            final ParsedPermission perm = parsedPackage.getPermissions().get(i);
            final Permission bp = mPm.mPermissionManager.getPermissionTEMP(perm.getName());
            // Don't allow anyone but the system to define ephemeral permissions.
            if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
                    && !systemApp) {
                ...
            }
            // Check whether the newly-scanned package wants to define an already-defined perm
            if (bp != null) {
                final String sourcePackageName = bp.getPackageName();
                if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
                        scanFlags)) {
                    // If the owning package is the system itself, we log but allow
                    // install to proceed; we fail the install on all other permission
                    // redefinitions.
                    ...
                } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) {
                    // Prevent apps to change protection level to dangerous from any other
                    // type as this would allow a privilege escalation where an app adds a
                    // normal/signature permission in other app's group and later redefines
                    // it as dangerous leading to the group auto-grant.
                    ...
                }
            }

           ...
        }
    }

    ...

    if (!isApex) {
        /**
         * Rename package into final resting place. All paths on the given
         * scanned package should be updated to reflect the rename.
         */
        doRenameLI(request, parsedPackage);
        ...
    }
    ...
}
2.1.2.4.2.2 扫描:
  • 根据准备阶段解析的包信息上下文 进一步解析: 确认包名真实;根据解析出的信息校验包有效性(是否有签名信息等);搜集apk信息——PackageSetting、apk的静态库/动态库信息等。
//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mInstallLock")
private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
        final @ParsingPackageUtils.ParseFlags int parseFlags,
        @PackageManagerService.ScanFlags int scanFlags, long currentTime,
        @Nullable UserHandle user, String cpuAbiOverride)
        throws PackageManagerException {
    final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
        scanFlags, user, cpuAbiOverride);    
    ...

    synchronized (mPm.mLock) {
        ...
        return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
                currentTime);
    }
}

//frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
        PackageManagerServiceInjector injector,
        boolean isUnderFactoryTest, long currentTime)
        throws PackageManagerException {
    ...
    if (createNewPackage) {
        ...
        // REMOVE SharedUserSetting from method; update in a separate call
        pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
                originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
                destCodeFile, parsedPackage.getNativeLibraryRootDir(),
                AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
                AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
                parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
                true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
                UserManagerService.getInstance(), usesSdkLibraries,
                parsedPackage.getUsesSdkLibrariesVersionsMajor(),
                parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries,
                parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
                newDomainSetId,
                parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
        ...
    } 
    ...
}
2.1.2.4.2.3 核对:
  • 验证扫描后的包信息,确保安装成功:主要就是覆盖安装的签名匹配验证。
//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
public static List<ReconciledPackage> reconcilePackages(
        List<InstallRequest> installRequests,
        Map<String, AndroidPackage> allPackages,
        Map<String, Settings.VersionInfo> versionInfos,
        SharedLibrariesImpl sharedLibraries,
        KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
        throws ReconcileFailure {
    final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());

    // make a copy of the existing set of packages so we can combine them with incoming packages
    final ArrayMap<String, AndroidPackage> combinedPackages =
            new ArrayMap<>(allPackages.size() + installRequests.size());
    combinedPackages.putAll(allPackages);
    
    //incomingSharedLibraries对象是安装包对象中声明的即将安装的库。
    final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
            new ArrayMap<>();
    ...
    for (InstallRequest installRequest : installRequests) {
        final String installPackageName = installRequest.getParsedPackage().getPackageName();
        final List<SharedLibraryInfo> allowedSharedLibInfos =
                sharedLibraries.getAllowedSharedLibInfos(installRequest);
        ...
        //封装到ReconciledPackage对象的包括ReconcileRequest对象request、安装参数installArgs、新生成的PackageSettingscanResult.pkgSetting、安装信息res、准备结果对象、浏览结果对象、待删除包行动兑对象deletePackageAction、允许添加的包中的共享库信息、解析包签名信息对象、共享用户签名是否改变、是否删除签名信息removeAppKeySetData。

        final ReconciledPackage reconciledPackage =
                new ReconciledPackage(installRequests, allPackages, installRequest,
                        deletePackageAction, allowedSharedLibInfos, signingDetails,
                        sharedUserSignaturesChanged, removeAppKeySetData);

        // Check all shared libraries and map to their actual file path.
        // We only do this here for apps not on a system dir, because those
        // are the only ones that can fail an install due to this.  We
        // will take care of the system apps by updating all of their
        // library paths after the scan is done. Also during the initial
        // scan don't update any libs as we do this wholesale after all
        // apps are scanned to avoid dependency based scanning.
        ...
            try {
                reconciledPackage.mCollectedSharedLibraryInfos =
                        sharedLibraries.collectSharedLibraryInfos(
                                installRequest.getParsedPackage(), combinedPackages,
                                incomingSharedLibraries);
            } 
            ...
    }

    return result;
}
2.1.2.4.2.3 提交:
  • 提交扫描的包、更新系统状态:应用的权限添加到 mPermissionManager
    还有其他的操作:添加 PackageSetting 到 PMS 的 mSettings、添加 AndroidPackage 到 PMS 的 mPackages 、添加 秘钥集 到系统、四大组件信息添加到 mComponentResolver 。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。本篇只做权限部分讲解,所以不做过多赘述
//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mLock")
private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
        @NonNull int[] allUsers) {
    // TODO: remove any expected failures from this method; this should only be able to fail due
    //       to unavoidable errors (I/O, etc.)
    for (ReconciledPackage reconciledPkg : reconciledPackages) {
        ...
        AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, allUsers);
        updateSettingsLI(pkg, allUsers, installRequest);
        ...
        if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
            // If this is an archival installation then we'll initialize the archive status,
            // while also marking package as not installed.
            // Doing this at the very end of the install as we are using ps.getInstalled
            // to figure out which users were changed.
            ...
        }
    }
    ...
}
private void updateSettingsLI(AndroidPackage newPackage,
        int[] allUsers, InstallRequest installRequest) {
    updateSettingsInternalLI(newPackage, allUsers, installRequest);
}

private void updateSettingsInternalLI(AndroidPackage pkg,
        int[] allUsers, InstallRequest installRequest) {
    ...
    synchronized (mPm.mLock) {
        // For system-bundled packages, we assume that installing an upgraded version
        // of the package implies that the user actually wants to run that new code,
        // so we enable the package.
        final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
        if (ps != null) {
            ...
            final PermissionManagerServiceInternal.PackageInstalledParams.Builder
                    permissionParamsBuilder =
                    new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
            
            //权限授权
            final boolean grantRequestedPermissions = (installRequest.getInstallFlags()
                    & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0;
            if (grantRequestedPermissions) {
                var permissionStates = new ArrayMap<String, Integer>();
                for (var permissionName : pkg.getRequestedPermissions()) {
                    permissionStates.put(permissionName,
                            PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED);
                }
                permissionParamsBuilder.setPermissionStates(permissionStates);
            } else {
                var permissionStates = installRequest.getPermissionStates();
                if (permissionStates != null) {
                    permissionParamsBuilder
                            .setPermissionStates(permissionStates);
                }
            }
            //记录受限的权限
            final boolean allowlistAllRestrictedPermissions =
                    (installRequest.getInstallFlags()
                            & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
            final List<String> allowlistedRestrictedPermissions =
                    allowlistAllRestrictedPermissions
                            ? new ArrayList<>(pkg.getRequestedPermissions())
                            : installRequest.getAllowlistedRestrictedPermissions();
            if (allowlistedRestrictedPermissions != null) {
                permissionParamsBuilder.setAllowlistedRestrictedPermissions(
                        allowlistedRestrictedPermissions);
            }
            //记录自动撤销权限模型
            final int autoRevokePermissionsMode = installRequest.getAutoRevokePermissionsMode();
            permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
            //下面的方法将负责移除过时的权限并授予安装权限
            mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(),
                    permissionParamsBuilder.build(), userId);
        }
        
        ...
        
        mPm.writeSettingsLPrTEMP();
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }

    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
2.1.2.4.2.4 APK的安装过程梳理:
  • APK用写入Session且包信息和APK安装操作 都提交到了PMS;
  • PMS中先把APK拷贝到 /data/app,然后使用PackageParser2解析APK 获取 四大组件、搜集签名、PackageSetting等信息,并进行校验确保安装成功;
  • 接着提交信息包更新系统状态及PMS的内存数据;
  • 然后使用 Installer 准备用户目录/data/user、进行 dexOpt;
  • 最后发送安装结果通知UI层。
2.1.2.4.3 安装后续:
  • 备份、可能的回滚、发送安装完成先关广播
  • 其中,当一个新用户在设备上首次安装应用程序时,应用程序所需的权限应该被授予该用户。
//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
public void restoreAndPostInstall(InstallRequest request) {
    ...

    if (succeeded && doRestore && !request.hasPostInstallRunnable()) {
        boolean hasNeverBeenRestored =
                packageSetting != null && packageSetting.isPendingRestore();
        request.setPostInstallRunnable(() -> {
            // Permissions should be restored on each user that has the app installed for the
            // first time, unless it's an unarchive install for an archived app, in which case
            // the permissions should be restored on each user that has the app updated.
            int[] userIdsToRestorePermissions = hasNeverBeenRestored
                    ? request.getUpdateBroadcastUserIds()
                    : request.getFirstTimeBroadcastUserIds();
            for (int restorePermissionUserId : userIdsToRestorePermissions) {
                //将应用程序在 AndroidManifest.xml 中声明的权限赋予用户,使得应用程序能够正常访问设备的各种资源
                mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
                        restorePermissionUserId);
            }
        });
    }
    ...
}

//frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void restorePermissionsAndUpdateRolesForNewUserInstall(String packageName,
        @UserIdInt int userId) {
    // We may also need to apply pending (restored) runtime permission grants
    // within these users.
    //未被使用的权限去做备份
    mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);

    // Restore default browser setting if it is now installed.
    String defaultBrowser;
    synchronized (mLock) {
        defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId);
    }
    if (Objects.equals(packageName, defaultBrowser)) {
        mDefaultAppProvider.setDefaultBrowser(packageName, userId);
        synchronized (mLock) {
            mSettings.removePendingDefaultBrowserLPw(userId);
        }
    }

    // Persistent preferred activity might have came into effect due to this
    // install.
    mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId);
}

2.2 签名权限(Signature Permissions):

用于系统定制开发中,不同的系统组件或特定应用之间需要进行深度的交互和协作,并且这些交互涉及到对系统资源的敏感访问。系统会为签名权限分配 signature 保护级别。

2.2.1 生成JKS签名文件

  • 使用Keytool工具生成JKS文件
  • 从 JKS 密钥库文件导出公共证书
    1.2.2 应用中添加系统签名
  • Studio 在对应需要签名的module(默认是app)的build.gradle中添加如下代码,源码中生成 jks 签名文件:
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    ……
    //证书信息在这里配置
    signingConfigs {
        main {
            storeFile file("./platform.jks") //签名文件路径,根目录
            storePassword "skg202302"
            keyAlias "skg"
            keyPassword "skg202302"
        }
    }
    ……
}
  • 如果想实现具有系统权限的应用,只添加了系统签名是不行的,还需要在AndroidManifest.xml中的manifest标签下声明系统权限,配置uid权限:android:sharedUserId=“android.uid.system”
  • 如果想实现具有可以访问核心的应用,还需要添加coreApp=“true”

2.2.3 预备安装APK的签名校验

在 Android 中,每个 APK 都必须进行签名校验,确保 APK 文件没有被篡改。签名校验是通过 PackageManager调用 PackageParser2来进行解析获取的。在Android原生的源码路径:frameworks/base/core/java/com/android/internal/pm/parsing/PackageParser2.java。如果解析异常会报异常。
解析到最后会调用ParsingPackageUtils.parseBaseApk()去解析包,调用setSigningDetails()去设置签名;

ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
    throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
            result.getException());
}
//frameworks/base/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
    final ParseResult<SigningDetails> ret =
            getSigningDetails(input, pkg, false /*skipVerify*/);
    if (ret.isError()) {
        return input.error(ret);
    }
    pkg.setSigningDetails(ret.getResult());
} else {
    pkg.setSigningDetails(SigningDetails.UNKNOWN);
}

当(flags & PARSE_COLLECT_CERTIFICATES) != 0为true时,会调用ApkSignatureVerifier(frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java).verifySignaturesInternal()去校验签名;

/**
 * Verifies the provided APK using all allowed signing schemas.
 * @return the certificates associated with each signer and content digests.
 * @param verifyFull whether to verify all contents of this APK or just collect certificates.
 * @hide
 */
public static ParseResult<SigningDetailsWithDigests> verifySignaturesInternal(ParseInput input,
        String apkPath, @SignatureSchemeVersion int minSignatureSchemeVersion,
        boolean verifyFull) {

    if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
        // V4 and before are older than the requested minimum signing version
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }
    // first try v4
    try {
        //V4是V3或者V2的一个升级版验证方式
        //虽然调用的是V4,内部实现还是会先用到V3去校验 
        //如果V3不行的话会使用V2去实现校验
        return verifyV4Signature(input, apkPath, minSignatureSchemeVersion, verifyFull);
    } catch (SignatureNotFoundException e) {
        // not signed with v4, try older if allowed
        if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
            return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No APK Signature Scheme v4 signature in package " + apkPath, e);
        }
    }

    if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
        // V3 and before are older than the requested minimum signing version
        return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                "No signature found in package of version " + minSignatureSchemeVersion
                        + " or newer for package " + apkPath);
    }
    //如果V4验证失败会采用V3认证的方式;
    //无论V4还是V3都会使用ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
    return verifyV3AndBelowSignatures(input, apkPath, minSignatureSchemeVersion, verifyFull);
}

以V4校验方式为例,frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java中,先得到最好算法对应的JCA算法,再得到最好算法对应的JCA签名算法和参数。之后得到公钥,然后通过公钥验证signed data和签名bestSigAlgorithmSignatureBytes。如果验证没通过,会报SecurityException异常。

private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
        final byte[] signedData) throws SecurityException {
    if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
        throw new SecurityException("No supported signatures found");
    }

    final int signatureAlgorithmId = signingInfo.signatureAlgorithmId;//获取签名算法的ID
    final byte[] signatureBytes = signingInfo.signature;//获取签名
    final byte[] publicKeyBytes = signingInfo.publicKey;//获取公钥
    final byte[] encodedCert = signingInfo.certificate;//获取证书

    String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId);
    Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
            getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId);//JCA 签名算法。
    String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 
    AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
    boolean sigVerified;
    try {
        PublicKey publicKey =
                KeyFactory.getInstance(keyAlgorithm)
                        .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
        Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
        sig.initVerify(publicKey);
        if (jcaSignatureAlgorithmParams != null) {
            sig.setParameter(jcaSignatureAlgorithmParams);
        }
        sig.update(signedData);
        sigVerified = sig.verify(signatureBytes);
    } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
            | InvalidAlgorithmParameterException | SignatureException e) {
        throw new SecurityException(
                "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
    }
    if (!sigVerified) {
        throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
    }

    // Signature over signedData has verified.
    CertificateFactory certFactory;
    try {
        certFactory = CertificateFactory.getInstance("X.509");
    } catch (CertificateException e) {
        throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
    }

    X509Certificate certificate;
    try {
        certificate = (X509Certificate)
                certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
    } catch (CertificateException e) {
        throw new SecurityException("Failed to decode certificate", e);
    }
    certificate = new VerbatimX509Certificate(certificate, encodedCert);

    byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded();
    if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
        throw new SecurityException(
                "Public key mismatch between certificate and signature record");
    }

    return Pair.create(certificate, signingInfo.apkDigest);
}

四种应用签名校验方案:

  1. v1 方案:基于 JAR 签名。它会对 APK 中的所有文件进行签名,除了 META-INF 目录。使用 jarsigner 工具来验证 V1 签名。签名信息存储在 META-INF 目录下的一些文件中,如 CERT.RSA 和 CERT.SF。
  2. v2 方案:APK 签名方案 v2(在 Android 7.0 中引入)。它会对 APK 的整个文件内容进行签名,包括文件的顺序和结构。可以使用 apksigner 工具来验证。
  3. v3 方案:APK 签名方案 v3(在 Android 9.0 中引入)。它在 V2 签名的基础上增加了对密钥轮换的支持。验证方式与 V2 签名类似。
  4. V4方案:APK 签名方案 v4(在 Android 12.0 中引入)。它进一步优化了签名过程,提高了验证效率,并且更好地支持超大 APK 文件。V4 签名采用了新的签名格式,将签名信息存储在 APK 的特定位置,而不是像之前的方案那样存储在 META-INF 目录中。依然可以使用 apksigner 工具进行验证。
  5. 首先尝试V4进行签名校验,以哈希值的形式来配对,通过SHA-256生成文件系统认证摘要;其次verifyFull=true,尝试V3进行签名校验,通过findSignature()的来做APK_SIGNATURE_SCHEME_V31_BLOCK_ID的签名查找;通过verifySigner()的getLengthPrefixedSlice()生成签名的ByteBuffer,X509Certificate
    然后尝试V2进项签名校验

2.2.4 权限检查

确认应用声明的权限是否符合系统要求,源码路径:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
在安装过程中,InstallPackageHelper在准备安装的时候调用preparePackageLI(),主要做了以下几件事情:

  1. 根据request.getSigningDetails()是否等于SigningDetails.UNKNOWN来重新setSigningDetails,要么使用我们已获得的签名内容,要么直接从 APK 中解析签名内容。
  2. 快速有效性检查,再次PackageManagerServiceUtils.verifySignatures()通过签名认证,以确认在更新时我们是否正确签名;我们将在扫描时再次检查此项,但在此处,我们希望在遇到重新定义的权限问题之前尽早退出。
  3. 将检查权限兼容性的逻辑移至 PermissionManagerService 中。通过for循环得到parsedPackage.getPermissions(),并获得ProtectionLevel()
@GuardedBy("mPm.mInstallLock")
private void preparePackageLI(InstallRequest request) throws PrepareFailure {
    final int[] allUsers =  mPm.mUserManager.getUserIds();
    // 要么使用我们已获得的签名内容,要么直接从 APK 中解析签名内容。
    if (request.getSigningDetails() != SigningDetails.UNKNOWN) {
         parsedPackage.setSigningDetails(request.getSigningDetails());
    } else {
         final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
         final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(input, parsedPackage, false /*skipVerify*/);
         if (result.isError()) {
               throw new PrepareFailure("Failed collect during installPackageLI",
                    result.getException());
         }
         parsedPackage.setSigningDetails(result.getResult());
    }
    ...
    PackageSetting signatureCheckPs = ps;
    if (signatureCheckPs != null) {
         // 快速有效性检查,以确认在更新时我们是否正确签名;我们将在扫描时再次检查此项,但在此处,我们希望在遇到重新定义的权限问题之前尽早退出。
        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
        final SharedUserSetting signatureCheckSus = mPm.mSettings.getSharedUserSettingLPr(signatureCheckPs);
        if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, signatureCheckSus,
                scanFlags)) {
            if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                    + parsedPackage.getPackageName() + " upgrade keys do not match the "
                    + "previously installed version");
            }
        } else {
            try {
                final boolean compareCompat =
                    ReconcilePackageUtils.isCompatSignatureUpdateNeeded(
                            mPm.getSettingsVersionForPackage(parsedPackage));
                final boolean compareRecover =
                    ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
                            mPm.getSettingsVersionForPackage(parsedPackage));
                // We don't care about disabledPkgSetting on install for now.
                final boolean compatMatch =
                    PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
                            signatureCheckSus, null,
                    parsedPackage.getSigningDetails(), compareCompat, compareRecover,
                    isRollback);
                // The new KeySets will be re-added later in the scanning process.
                if (compatMatch) {
                    synchronized (mPm.mLock) {
                        ksms.removeAppKeySetDataLPw(parsedPackage.getPackageName());
                    }
                }
            } catch (PackageManagerException e) {
                throw new PrepareFailure(e.error, e.getMessage());
            }
        }
    }
    // TODO: Move logic for checking permission compatibility into PermissionManagerService
    final int n = ArrayUtils.size(parsedPackage.getPermissions());
    for (int i = n - 1; i >= 0; i--) {
        final ParsedPermission perm = parsedPackage.getPermissions().get(i);
        final Permission bp = mPm.mPermissionManager.getPermissionTEMP(perm.getName());

        // 除了系统之外,不允许任何一方定义临时权限。
        if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
                && !systemApp) {
            Slog.w(TAG, "Non-System package " + parsedPackage.getPackageName()
                    + " attempting to delcare ephemeral permission "
                    + perm.getName() + "; Removing ephemeral.");
            ComponentMutateUtils.setProtectionLevel(perm,
                    perm.getProtectionLevel() & ~PermissionInfo.PROTECTION_FLAG_INSTANT);
        }

        // 检查新扫描的包是否想要定义一个已经被定义过的权限。
        if (bp != null) {
            final String sourcePackageName = bp.getPackageName();
            if (!doesSignatureMatchForPermissions(sourcePackageName, parsedPackage,
                scanFlags)) {
            // 如果拥有该包的是系统本身,我们会记录日志,但允许继续安装;对于所有其他权限的重新定义情况,我们会使安装失败。
            if (!sourcePackageName.equals("android")) {
                throw new PrepareFailure(INSTALL_FAILED_DUPLICATE_PERMISSION,
                        "Package "
                                + parsedPackage.getPackageName()
                                + " attempting to redeclare permission "
                                + perm.getName() + " already owned by "
                                + sourcePackageName)
                        .conflictsWithExistingPermission(perm.getName(),
                                sourcePackageName);
            } else {
                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
                        + " attempting to redeclare system permission "
                        + perm.getName() + "; ignoring new declaration");
                parsedPackage.removePermission(i);
            }
        } else if (!PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())) {
            // 防止应用程序将保护级别从任何其他类型更改为 “危险”,因为这会允许权限提升,即一个应用程序在另一个应用程序的组中添加一个普通 / 签名权限,然后将其重新定义为 “危险”,从而导致该组自动授予权限。
            if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE)
                    == PermissionInfo.PROTECTION_DANGEROUS) {
                if (!bp.isRuntime()) {
                    Slog.w(TAG, "Package " + parsedPackage.getPackageName()
                            + " trying to change a non-runtime permission "
                            + perm.getName()
                            + " to runtime; keeping old protection level");
                    ComponentMutateUtils.setProtectionLevel(perm,
                            bp.getProtectionLevel());
                }
            }
        }
    }

    ...
}
private boolean doesSignatureMatchForPermissions(@NonNull String sourcePackageName,
        @NonNull ParsedPackage parsedPackage, int scanFlags) {
    // If the defining package is signed with our cert, it's okay.  This
    // also includes the "updating the same package" case, of course.
    // "updating same package" could also involve key-rotation.

    final PackageSetting sourcePackageSetting;
    final KeySetManagerService ksms;
    final SharedUserSetting sharedUserSetting;
    synchronized (mPm.mLock) {
        sourcePackageSetting = mPm.mSettings.getPackageLPr(sourcePackageName);
        ksms = mPm.mSettings.getKeySetManagerService();
        sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(sourcePackageSetting);
    }

    final SigningDetails sourceSigningDetails = (sourcePackageSetting == null
            ? SigningDetails.UNKNOWN : sourcePackageSetting.getSigningDetails());
    if (sourcePackageName.equals(parsedPackage.getPackageName())
            && (ksms.shouldCheckUpgradeKeySetLocked(
                    sourcePackageSetting, sharedUserSetting, scanFlags))) {
        return ksms.checkUpgradeKeySetLocked(sourcePackageSetting, parsedPackage);
    } else {

        // in the event of signing certificate rotation, we need to see if the
        // package's certificate has rotated from the current one, or if it is an
        // older certificate with which the current is ok with sharing permissions
        if (sourceSigningDetails.checkCapability(
                parsedPackage.getSigningDetails(),
                SigningDetails.CertCapabilities.PERMISSION)) {
            return true;
        } else if (parsedPackage.getSigningDetails().checkCapability(
                sourceSigningDetails,
                SigningDetails.CertCapabilities.PERMISSION)) {
            // the scanned package checks out, has signing certificate rotation
            // history, and is newer; bring it over
            synchronized (mPm.mLock) {
                sourcePackageSetting.setSigningDetails(parsedPackage.getSigningDetails());
            }
            return true;
        } else {
            return false;
        }
    }
}

2.2.5 权限分配

在应用安装时,frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java 会调用 grantPermissions() 方法来根据 AndroidManifest.xml 中声明的权限,为应用授予相应的权限。
`在应用安装提交时,frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java 会调用 commitPackageSettings() 方法提交相关的设置;来根据 AndroidManifest.xml 中声明的权限,为应用授予相应的权限。

/**
 * Adds a scanned package to the system. When this method is finished, the package will
 * be available for query, resolution, etc...
 */
private void commitPackageSettings(@NonNull AndroidPackage pkg,
        @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
        ReconciledPackage reconciledPkg) {
    final String pkgName = pkg.getPackageName();
    ...
    // If the app metadata file path is not null then this is a system app with a preloaded app
    // metadata file on the system image. Do not reset the path and source if this is the
    // case.
    if (pkgSetting.getAppMetadataFilePath() == null) {
        String dir = pkg.getPath();
        if (pkgSetting.isSystem()) {
            dir = Environment.getDataDirectoryPath() + "/app-metadata/" + pkg.getPackageName();
        }
        String appMetadataFilePath = dir + "/" + APP_METADATA_FILE_NAME;
        if (request.hasAppMetadataFile()) {
            pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
            if (Flags.aslInApkAppMetadataSource()) {
                pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
            }
        } else if (Flags.aslInApkAppMetadataSource()) {
            Map<String, PackageManager.Property> properties = pkg.getProperties();
            if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
                // ASL file extraction is done in post-install
                pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
                pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
            }
        }
    }

    if (pkg.getPackageName().equals("android")) {
        mPm.setPlatformPackage(pkg, pkgSetting);
    }

    if ((scanFlags & SCAN_BOOTING) != 0) {
        // No apps can run during boot scan, so they don't need to be frozen
    } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {
        // Caller asked to not kill app, so it's probably not frozen
    } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {
        // Caller asked us to ignore frozen check for some reason; they
        // probably didn't know the package name
    } else {
        // 显示APP
        mPm.snapshotComputer().checkPackageFrozen(pkgName);
    }

    final boolean isReplace = request.isInstallReplace();
    // Also need to kill any apps that are dependent on the library, except the case of
    // installation of new version static shared library.
    if (clientLibPkgs != null) {
        if (pkg.getStaticSharedLibraryName() == null || isReplace) {
            for (int i = 0; i < clientLibPkgs.size(); i++) {
                AndroidPackage clientPkg = clientLibPkgs.get(i);
                String packageName = clientPkg.getPackageName();
                mPm.killApplication(packageName,
                        clientPkg.getUid(), "update lib",
                        ApplicationExitInfo.REASON_DEPENDENCY_DIED);
            }
        }
    }

    // writer
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");

    synchronized (mPm.mLock) {
        // We don't expect installation to fail beyond this point
        // Add the new setting to mSettings
        mPm.mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        // Add the new setting to mPackages
        mPm.mPackages.put(pkg.getPackageName(), pkg);
        if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
            mApexManager.registerApkInApex(pkg);
        }

        if ((mPm.isDeviceUpgrading() && pkgSetting.isSystem()) || isReplace) {
            for (int userId : mPm.mUserManager.getUserIds()) {
                pkgSetting.restoreComponentSettings(userId);
            }
        }

        // Don't add keysets for APEX as their package settings are not persisted and will
        // result in orphaned keysets.
        if ((scanFlags & SCAN_AS_APEX) == 0) {
            // Add the package's KeySets to the global KeySetManagerService
            KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
            ksms.addScannedPackageLPw(pkg);
        }

        final Computer snapshot = mPm.snapshotComputer();
        mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot);
        mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace,
                (scanFlags & SCAN_DONT_KILL_APP) != 0 /* retainImplicitGrantOnReplace */);
        mPm.addAllPackageProperties(pkg);

        // Only verify app links for non-archival installations, otherwise there won't be any
        // declared app links.
        if (!request.isArchived()) {
            if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
                mPm.mDomainVerificationManager.addPackage(pkgSetting,
                        request.getPreVerifiedDomains());
            } else {
                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting,
                        request.getPreVerifiedDomains());
            }
        }

        int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
        StringBuilder r = null;
        int i;
        for (i = 0; i < collectionSize; i++) {
            ParsedInstrumentation a = pkg.getInstrumentations().get(i);
            ComponentMutateUtils.setPackageName(a, pkg.getPackageName());
            mPm.addInstrumentation(a.getComponentName(), a);
            if (chatty) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.getName());
            }
        }
        if (r != null) {
            if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Instrumentation: " + r);
        }

        final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
        if (!protectedBroadcasts.isEmpty()) {
            synchronized (mPm.mProtectedBroadcasts) {
                mPm.mProtectedBroadcasts.addAll(protectedBroadcasts);
            }
        }

        mPm.mPermissionManager.onPackageAdded(pkgSetting,
                (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
     }
     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 }

2.2.6 问题答疑:

  1. V1方案中jar 签名是如何校验文件的合法性呢?
    通过解压软件打开签名后的apk文件,就会发现里面包含了 dex 字节码文件,清单文件,资源文件等。其中
  • META-INF/MANIFEST.MF 文件:包含每个文件的摘要信息。
  • META-INF/.SF(签名文件):包含MANIFEST中每个摘要项的摘要,使用私钥加密。
  • META-INF/.(RSA|DSA|EC)(签名块文件 ):包含 .SF 文件的签名和来自密钥仓库的证书或证书链。其中证书包含了签名者的有关信息和 public key
    校验签名的时候,首先对 jar 中每个文件进行摘要计算,然后与MANIFEST中已记录的摘要进行比较,来判断文件是否更改过。同时还要计算MANIFEST文件中每项的摘要,并与签名文件中的每一项(用公钥解密后)比较,以验证MANIFEST文件是否被修改过。

2.3 安装流程总结

  1. 将APK文件拷贝到指定目录下,系统应用是在/system/app,第三方应用在/data/app。
  2. 解压apk,拷贝文件,创建UID,创建/data/data/${package_name}目录,设置权限,这个就是应用的数据目录。
  3. 从apk中提取dex,放到/data/dalvik-cache目录;
  4. 解析AndroidManifest.xml文件,提取信息添加到PMS中,更新PMS中相应的数据结构,具体是,将提取到的包信息更新到/data/system/packages.list和/data/system/packages.xml
  5. 发送广播Intent.ACTION_PACKAGE_ADDED和Intent.ACTION_PACAKGE_REPLACE。从名字可以判断分别对应全新安装和覆盖安装。

3.运行时权限–危险权限

2.1.应用执行相关操作时注意事件

  1. 等到用户向您的应用授予 CAMERA 权限后再使用设备的相机。
  2. 等到用户向您的应用授予 RECORD_AUDIO 权限后再使用设备的麦克风。
  3. 等到用户与您应用中某项需要获取位置信息的功能互动后再请求 ACCESS_COARSE_LOCATION 权限或 ACCESS_FINE_LOCATION 权限,如介绍如何请求位置信息权限的指南中所述。
  • ACCESS_COARSE_LOCATION:这个权限只允许应用访问大致的位置,通常通过Wi-Fi或移动网络来获得。例如根据大致位置推送本地广告或者消息。
  • ACCESS_FINE_LOCATION:这个权限允许应用访问精确的位置,通常通过GPS、Wi-Fi或者移动网络来获得。如导航、定位、地图应用等。
  • 在 Android 应用中,申请权限时可以只申请 ACCESS_FINE_LOCATION(精确位置权限)而不申请 ACCESS_COARSE_LOCATION(大致位置权限)。因为ACCESS_FINE_LOCATION 权限实际上涵盖了 ACCESS_COARSE_LOCATION 的部分功能的。例如,你可以使用 LocationManager 的 getLastKnownLocation 方法,并根据 LocationManager.GPS_PROVIDER 或 LocationManager.NETWORK_PROVIDER 等提供的位置信息,即使只申请了 ACCESS_FINE_LOCATION 权限,你也可以获取到不同精度的位置信息。
  1. 等到用户向您的应用授予 ACCESS_COARSE_LOCATION 权限或 ACCESS_FINE_LOCATION 权限后再请求 ACCESS_BACKGROUND_LOCATION 权限。

为危险权限设置成默认权限有两种方式:

  • 方式一,在应用的AndroidManifest.xml中添加:android:sharedUserId=“android.uid.system”,一般的apk是运行在user用户下,加上此标签之后apk将在system用户下运行;
  • 方式二,通过系统预制到system/etc/default-permission.xml或vendor/etc/default-permission.xml中来达到默认授权的结果。其中fix=true表示用户不能手动关闭权限,fix=false表示用户可以正常开关。而system 文件夹主要关注 Android 系统的通用核心功能和系统应用程序,而 vendor 文件夹主要关注设备硬件的特定实现和驱动程序,两者共同协作

4. 特殊权限:

4.1 特殊权限(Special Permissions):

只有经过系统签名的系统应用才可能被授予某些特殊权限。例如,某些系统级应用才具备的访问底层硬件驱动、修改系统设置、管理其他应用进程等权限。并且只有平台和原始设备制造商 (OEM) 可以定义特殊权限。一些特殊权限示例:设定精确的闹钟、在其他应用前方显示和绘图、访问所有存储数据。
系统会为特殊权限分配 appop 保护级别。

5. 权限组:

  • 权限可以属于权限组。 权限组由一组逻辑相关的权限组成。例如,发送和接收短信的权限可能属于同一组,因为它们都涉及应用与短信的互动。
  • 权限组的作用是在应用请求密切相关的多个权限时,帮助系统尽可能减少向用户显示的系统对话框数量。当系统提示用户授予应用权限时,属于同一组的权限会在同一个界面中显示。 但是,权限可能会在不另行通知的情况下更改组,因此不要假定特定权限与任何其他权限组合在一起。

你可能感兴趣的:(android,架构)