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.2 签名权限(Signature Permissions):
用于系统定制开发中,不同的系统组件或特定应用之间需要进行深度的交互和协作,并且这些交互涉及到对系统资源的敏感访问。系统会为签名权限分配 signature
保护级别。与系统签名一样的签名Apk和系统Apk (比如launcher、Apk安装器Apk等)可以使用signature权限。
在aosp 系统平台的签名证书和密钥所存在的目录:build/make/target/product/security/
。
2.2.1 签名权限检查
确认应用声明的权限是否符合系统要求,源码路径:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
在安装过程中,InstallPackageHelper在准备安装的时候调用preparePackageLI(),主要做了以下几件事情:
- 根据request.getSigningDetails()是否等于SigningDetails.UNKNOWN来重新setSigningDetails,要么使用我们已获得的签名内容,要么直接从 APK 中解析签名内容。
- 快速有效性检查,再次PackageManagerServiceUtils.verifySignatures()通过签名认证,以确认在更新时我们是否正确签名;我们将在扫描时再次检查此项,但在此处,我们希望在遇到重新定义的权限问题之前尽早退出。
- 将检查权限兼容性的逻辑移至 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.2 签名权限分配
在应用安装时,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() 方法提交相关的设置并记录在/data/system/packages.xml
、/system/etc/default-permissions/default-permission.xml
和/data/misc_de/0/apexdata/com.android.permission/runtime-permission.xml
中;根据 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.3 授予指定APP默认签名权限
从 Android 15 开始,可以将授予应用的签名权限许可名单列在 frameworks/base/etc/permissions 目录下的单个 XML 文件或多个 XML 文件中:
- /etc/permissions/signature-permissions-OEM_NAME.xml
- /etc/permissions/signature-permissions-DEVICE_NAME.xml
<!--
This XML file declares which platform signature permissions to grant to
platform signed nonsystem apps.
-->
// /etc/permissions 解析为 partition/etc/permissions
<permissions>
<signature-permissions package="com.android.example">
<permission name="android.permission.READ_DEVICE_CONFIG"/>
...
</signature-permissions>
...
</permissions>
2.2.4 系统签名、测试签名、APK调试签名、三方签名的区别
- 系统签名:
主体是软件开发者,对象是具体的软件安装包(如 Android 平台的 APK 文件),通过签名保证软件包的完整性和来源可靠性。存储在系统分区的 /system/etc/security 目录下。例如,某些设备可能将证书文件存储为 .pk8(私钥)和 .x509.pem(公钥证书) 格式的文件。 - 系统验收测试时签名:
主体是参与软件项目验收的相关人员(开发团队、测试团队、客户等),对象是软件系统的验收结果,通过签名确认软件是否符合验收标准。软件系统验收测试时的签名并非像数字证书那样有一个固定的、统一的具体存储路径,其存储方式和位置取决于签名的形式、使用的工具和企业的管理流程。 - apk调试签名:
App以Debug调试模式编译时,使用一个默认的debug.keystore来进行签名。这个默认签名(keystore)是不需要密码的,它的默认位置在C:\Users<用户名>.Android\debug.keystore,如果不存在Android studio会自动创建它。
//在原生Android15中,调试时签名校验
// frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
private static ParseResult verifySignatures(ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull) {
…
SigningDetails signingDetails = result.getResult().signingDetails;
if (Build.isDebuggable()) {
SigningDetails overrideSigningDetails;
synchronized (sOverrideSigningDetails) {
overrideSigningDetails = sOverrideSigningDetails.get(signingDetails);
}
if (overrideSigningDetails != null) {
Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath);
signingDetails = overrideSigningDetails;
}
}
return input.success(signingDetails);
}
4. 三方签名:
以小米系统为例,开发好的APK提交到网页版的小米开放平台,通过审核之后就可以上架到小米应用商店了。在提交的过程中,需要提交包名、APP Key、APP Secret、APP所需权限等应用信息。当安装时应用商店通过获取到的APK网络数据来调用内部的特定接口做签名的检测和比对。
3.运行时权限–危险权限
3.1.危险权限示例
3.2.APP动态申请危险权限
(1) 动态申请权限源码流程
- AndroidMainfest.xml中声明应用权限
<manifest ...>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application ...>
...
</application>
</manifest>
- 在需要申请权限的Activity中做动态权限检查
// 请求单个权限
private fun checkPermission() {
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.CAMERA
)
!= PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "onResume(): requestPermission()没有权限")
// 没有权限,需要请求权限
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.CAMERA), 1001
)
} else {
// 已经有权限,可以执行相关操作
Log.d(TAG, "onResume(): requestPermission()已有权限")
}
}
// 请求多个个权限
private val PERMISSIONS_STORAGE: Array<String> = arrayOf<String>(
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.CAMERA
)
private fun ckeckPermissions() {
for ( permission in PERMISSIONS_STORAGE){
if (ActivityCompat.checkSelfPermission(this,
permission)!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onResume(): requestPermission()没有权限")
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, 200);
}else {
Log.d(TAG, "onResume(): requestPermission()已有权限")
}
}
}
- 在onRequestPermissionsResult() 方法中查看回调结果
// 请求权限回调方法
@Deprecated("Deprecated in Java")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1001 -> {
// 1001的请求码对应的是申请的权限
//单个授权
if (grantResults.isNotEmpty()) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onRequestPermissionsResult(): 同意授权")
} else {
Log.d(TAG, "onRequestPermissionsResult(): 未同意授权")
}
}else{
Log.d(TAG, "onRequestPermissionsResult(): 授权结果为空")
}
// 判断是否同意授权,PERMISSION_GRANTED 这个值代表的是已经获取了权限
//多个授权
// var allGranted = true
// for (result in grantResults) {
// if (result != PackageManager.PERMISSION_GRANTED) {
// allGranted = false
// break
// }
// }
// if (allGranted) {
// // 所有权限都已授予,可以执行相关操作
// Log.d(TAG, "onRequestPermissionsResult(): 同意授权")
// } else {
// // 有部分权限未授予,可能需要提示用户或调整应用功能
// Log.d(TAG, "onRequestPermissionsResult(): 未同意授权")
// }
}
}
}
- 小知识点
有个点需要注意一下就是同一个APP同时或者间隔一分钟去申请同一个权限两次结果是怎么样的?
结果是第一次正常走检查申请流程,第二次申请的时候,在Activity.java
中会提示“Can request only one set of permissions at a time”只能申请完一组,等用户同意或者拒绝才能申请下一组,所以不会重复弹窗,并且在onRequestPermissionsResult()中会立马收到一个空结果;
override fun requestPermissions(permissions: Array<String?>, requestCode: Int) {
require(requestCode >= 0) { "requestCode should be >= 0" }
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can request only one set of permissions at a time")
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, arrayOfNulls(0), IntArray(0))
return
}
val intent: Intent = packageManager.buildRequestPermissionsIntent(permissions)
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null)
mHasCurrentPermissionsRequest = true
}
另外,不同的应用申请同一个权限不会有问题,因为申请权限的包名不同,弹窗是根据包名以及权限名去显示的
(3) 流程图
- 请求运行权限流程图
- 请求特殊权限流程图
3.2.定位相关权限
- 等到用户与您应用中某项需要获取位置信息的功能互动后再请求 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 权限,你也可以获取到不同精度的位置信息。
- 后台定位权限(ACCESS_BACKGROUND_LOCATION)十分敏感,与其他新增的权限不太一样。在Android 10系统上可以在同一个权限弹窗中同时申请ACCESS_FINE_LOCATION和 ACCESS_BACKGROUND_LOCATION权限。但在 Android 11(API 级别 30)及以上系统开发的应用,必须先授予 ACCESS_COARSE_LOCATION 权限或 ACCESS_FINE_LOCATION 权限后再请求 ACCESS_BACKGROUND_LOCATION 权限。但授权弹窗只有一个
如果在Android11系统中同时请求在前台访问位置信息的权限和在后台访问位置信息的权限,系统会忽略该请求,且不会向您的应用授予其中的任一权限,最终导致授权失败。
3.3 特许APP设置危险权限为默认权限
- 方式一,在应用的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 文件夹主要关注设备硬件的特定实现和驱动程序,两者共同协作。
/ default_permission_xxx.xml
<?xml version="1.0" encoding="utf-8"?>
<exceptions>
<!-- exception package 为要授予权限的app包名 -->
<!-- permission name 为要授予权限的app默认授予的权限 -->
<!-- permission的fixed表示授权后是否可以被非系统组件修改权限 -->
<exception package="com.android.xxxtest">
<permission name="android.permission.calendar" fixed="false"/>
</exception>
</exceptions>
//Android.bp
android_app {
name : "balabala",
...
required:["permission_balabala"],
}
prebuilt_etc {
name: "permission_balabala",
product_specific: true,
sub_dir: "default-permissions",
src: "default-permissions-xxxx.xml",
}
//DefaultPermissionGrantPolicy.java负责默认权限授予工作
public void grantDefaultPermissions(int userId) {
DelayingPackageManagerCache pm = new DelayingPackageManagerCache();
grantPermissionsToSysComponentsAndPrivApps(pm, userId);
grantDefaultSystemHandlerPermissions(pm, userId);
grantDefaultPermissionExceptions(pm, userId);
// Apply delayed state
pm.apply();
}
4.5 组合危险权限的动态申请
以定位权限为例,APP侧动态申请可以参考如下代码:
//AndroidManifest.xml
<!-- 声明精确位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 声明粗略位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
//MainActivity.kt
private val locationPermissions = arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)
private fun requestLocationPermissions() {
if (hasLocationPermissions()) {
Log.d(TAG, "onResume(): requestPermission()已有权限")
} else {
ActivityCompat.requestPermissions(
this,
locationPermissions,
LOCATION_PERMISSION_REQUEST_CODE
)
}
}
private fun hasLocationPermissions(): Boolean {
return locationPermissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
}
4. APPOP Permissions:
4.1.简介
Appops是Application Operations的简称,拥有该权限的应用一般位于系用户映像某个分区上的priv-app 目录(Android 9 及更高版本:/system, /product, /vendor;Android 8.1 及更低版本:/system)下。
应用的权限许可名单可列在位于 `frameworks/base/etc/permissions` 目录下的单个或多个 XML 文件中:
- /etc/permissions/privapp-permissions-OEM_NAME.xml
- /etc/permissions/privapp-permissions-DEVICE_NAME.xml
- 但对于已包含在 Android 开源项目 (AOSP) 树中的应用的权限列在 /etc/permissions/privapp-permissions-platform.xml 中。其他应用要求使用上面的第二个配置文件。
Appops的两个重要组成部分是AppOpsManager
和AppOpsService
,它们是典型的客户端和服务端设计,通过Binder跨进程调用。其中AppOpsService
是做最终检查的系统服务,在AMS构造函数中启动的,它的注册名字是appops
,应用可以类似于mAppOps=(AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);
的方式来获取这个服务;AppOpsManager
提供了接口,访问AppOpsService的核心方法。
4.2. 默认授予所有应用的某个AppOps权限
4.2.1. 背景知识
系统为每个操作定义了一个默认的模式(Mode),表示该操作是否被允许或拒绝。例如,WRITE_SETTINGS这个操作的默认模式是MODE_DEFAULT,表示默认由系统决定是否允许修改设置。
但是,有些情况下,我们可能需要修改某些操作的默认模式,比如为了提高安全性或方便调试。这时候,我们就可以修改系统AppOps权限列表来指定哪些操作可以被默认允许,而不需要用户手动授予。
模式 | 含义 | 效果 |
---|---|---|
MODE_ALLOWED | 访问者可以访问该敏感操作 | 系统会执行记录,不会引发crash |
MODE_ERRORED | 访问者不可以访问该敏感操作,会引发crash | 系统会执行记录,会引发crash |
MODE_DEFAULT | 访问者来决定访问该敏感操作的准入规则 | 系统会执行记录,可能会引发crash |
MODE_IGNORED | 访问者不可以访问该敏感操作,但是不会引发crash | 系统会执行记录,不会引发crash |
4.2.2. 修改方法
需要在frameworks/base/core/java/android/app/AppOpsManager.java文件中修改每个操作对应的默认模式数组sAppOpInfos[]。具体地说,我们需要将WRITE_SETTINGS这个操作对应的数组元素从AppOpsManager.MODE_DEFAULT改为AppOpsManager.MODE_ALLOWED。同理,如果我们想要修改其他操作的默认模式,也可以在这个数组中进行相应的修改。具体的修改代码如下:
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
new AppOpInfo.Builder(OP_WRITE_SETTINGS, OPSTR_WRITE_SETTINGS, "WRITE_SETTINGS")
.setPermission(android.Manifest.permission.WRITE_SETTINGS)
++ .setDefaultMode(AppOpsManager.MODE_ALLOWED)
.build(),
new AppOpInfo.Builder(OP_SYSTEM_ALERT_WINDOW, OPSTR_SYSTEM_ALERT_WINDOW,
"SYSTEM_ALERT_WINDOW")
.setPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW)
.setRestriction(UserManager.DISALLOW_CREATE_WINDOWS)
.setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
.setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
…………
};
4.2.3. 检查修改后效果的命令
执行adb shell dumpsys package com.xxx.xxx ,查看应用是否默认就拥有了WRITE_SETTINGS这个权限。
4.3.默认授予或拒绝某个特指应用AppOps权限
在Android15的原生源码中可根据需要自定义的许可名单实现。文件位于:/etc/permissions/privapp-permissions-DEVICE_NAME.xml
如果必须拒绝权限,请将 XML 修改为使用 deny-permission
标记,而不是 permission 标记。例如:
<!-- This XML file declares which signature|privileged permissions to grant toprivileged apps that come with the platform -->
<permissions>
<privapp-permissions package="com.android.backupconfirm">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.CRYPT_KEEPER"/>
</privapp-permissions>
<privapp-permissions package="com.android.cellbroadcastreceiver">
<!-- Don't allow the application to interact across users -->
<deny-permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.RECEIVE_EMERGENCY_BROADCAST"/>
</privapp-permissions>
...
其中privapp-permissions.xml 文件只有在与特权应用位于同一分区时才能授予或拒绝授予该应用权限。例如,如果 /product 分区上的应用请求特许权限,则只能由 /product 上的 privapp-permissions.xml 文件来同意或拒绝该请求。
5. 权限组:
- 权限组由一组逻辑相关的权限组成。可以通过permission.getGroup()来获取APK的权限组。例如,发送和接收短信的权限可能属于同一组,因为它们都涉及应用与短信的互动。
- 权限组的作用是在应用请求密切相关的多个权限时,帮助系统尽可能减少向用户显示的系统对话框数量。当系统提示用户授予应用权限时,属于同一组的权限会在同一个界面中显示。 但是,权限可能会在不另行通知的情况下更改组,因此不要假定特定权限与任何其他权限组合在一起。
5.1. 组合危险权限的动态申请
以定位权限为例,APP侧动态申请可以参考如下代码:
//AndroidManifest.xml
<!-- 声明精确位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 声明粗略位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
//MainActivity.kt
private val locationPermissions = arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)
private fun requestLocationPermissions() {
if (hasLocationPermissions()) {
Log.d(TAG, "onResume(): requestPermission()已有权限")
} else {
ActivityCompat.requestPermissions(
this,
locationPermissions,
LOCATION_PERMISSION_REQUEST_CODE
)
}
}
private fun hasLocationPermissions(): Boolean {
return locationPermissions.all {
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
}
6. 自定义权限与其他级别权限
6.1 自定义权限:
6.1.1 自定义权限简介
应用可以定义其他应用可请求的权限,从而将自己的功能提供给后者。它们还可以定义能够自动提供给已使用同一证书进行签名的任何其他应用的权限。
6.1.2 自定义权限示例
1. 首先必须使用一个或多个 <permission> 元素在您的 AndroidManifest.xml 中声明它们。
2. 如果某个应用需要控制有哪些其他应用可以启动它的 activity,可以遵循以下方法为此操作声明权限:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp" >
<!-1.protectionLevel 属性是必需属性,用于告知系统如何让用户知
道哪些应用正在请求权限或者哪些应用可以获得该权限;
2.android:permissionGroup 属性是可选属性,仅用于帮助系统
向用户显示权限。->
<permission
android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>
6.2 其他级别权限
除了基础权限级别的其他权限级别都属于附加权限级别。它们必须附加在基础权限级别上使用。从目前系统定义的权限来看,附加权限级别基本都是与signature基础权限级别搭配使用。
- privileged级权限
- 该类型权限只能与signature同时使用。
- 系统预置应用 system/priv-app/目录下的应用。定义权限的文件的路径通常位于 frameworks/base/data/etc/privapp-permissions-platform.xml
- recents级权限
该类型的权限只能授予给拥有最近任务功能的Apk (其实就是launcher) - installer级权限
此类权限自动被授权给那些负责安装apk的系统app。 - development级权限
development applications可以被自动授予此权限。 - internal级权限
该类型权限是signature权限的子集,只要它的子权限符合一定条件,即可在Apk安装后就会授予该权限。一个Apk使用该类型的权限,该类型权限的权限状态可是与Android设备的用户没有关系的。可以使用该权限的Apk主要是系统Apk,它与signature权限拥有相同的子权限。 - system级权限
与privileged相同,是privileged的老版本。需要在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。 - verifier级权限
此类权限自动被授权给那些负责验证apk的系统app。 - setup级权限
此类权限自动被授予安装向导app。 - pre23级权限
应用请求此类权限后,系统将在应用安装时自动授权给那些targetSdkVersion在23(Android 6.0)以下的应用。 - preinstalled级权限
此类权限可以自动被授权给任何预安装在system image中的app,不只是privileged app。配置文件在frameworks/base/data/etc/preinstalled-packages-platform.xml - instant级权限
主要应用于 Instant App 中。Instant App 是一种可以让用户无需安装完整的 APK 即可快速体验应用程序部分功能的方式,用户可以在不占用设备空间的情况下使用某些功能,而 instant 权限则是为了保障 Instant App 在运行时的权限管理。比如:签名权限:android.permission.INSTANT_APP_FOREGROUND_SERVICE(允许APP前台服务); - runtime级权限
是在应用程序运行时授予或拒绝的权限,比如:正常权限:android.permission.INTERNET(允许应用程序打开网络套接字);危险权限:android.permission.CAMERA(使用相机)。
配置文件是runtime-permissions.xml。
6.3 添加用户组
6.3.1 增加自定义用户组 gid
- 在Android原生中每一个用户组都有一个唯一的ID号,定义在文件:system\core\include\private\android_filesystem_config.h
//仿照其添加自己的ID号(不允许重复)、并赋予它一个字符串的名字(后文用到)
/** 第1步 在system\core\include\private\android_filesystem_config.h文件中定义用户组ID和名称 */
#define AID_SELF_GROUP 8011
/** 第2步 在bionic/libc/bionic/empty_android_ids.h文件中的android_ids数组里添加新的用户组 */
static const struct android_id_info android_ids[] = {
{ "root", AID_ROOT, },
{省略 .......................},
/** 自定义组名 */
{{ "selfgroup", AID_SELF_GROUP, },},
}
/** 第3步 编译并烧录boot.img,重启设备后,查看设备节点的组别,可以看见自定义的“selfgroup”。
6.3.2 关联自定义用户组 gid 与上层 permission 定义:
- 在原生Android中,需要修改两个文件:/frameworks/base/data/etc/platform.xml、/frameworks/base/core/res/AndroidManifest.xml
/*第一步、组控制权限的实现:Android源码/frameworks/base/data/etc/platform.xml*/
<permission name="android.permission.SELFGROUP" >
<group gid="selfgroup" />
</permission>
/*第二步:组控制权限的声明:Android源码/frameworks/base/core/res/AndroidManifest.xml*/
<permission android:name="android.permission.SELFGROUP"
android:permissionGroup="android.permission-group.SELFGROUP"
android:protectionLevel="dangerous"
android:label="@string/permlab_sys_selfgroup"
android:description="@string/permdesc_sys_selfgroup" />
/*第三步:如果设备中App要访问我们的设备,需要加入“selfgroup”组中,添加以下权限即可。*/
<uses-permission android:name="android.permission.SELFGROUP" />
7.Android中具体权限需求
7.1 请求USB权限白名单如何添加
- 在App的AndroidManifest.xml文件中声明USB权限;
- 使用USBManager请求权限;
- 系统侧可以USbManager来将应用添加到USB权限的白名单中(实现方式可以查看特许权限白名单),而在APP侧无法直接通过代码修改系统设置,需要引导用户到设置界面,进行手动添加(实现方式可以看Android请求权限白名单如何添加);
7.2 多进程之间数据共享方式
- Bundle:Activity、Service、Receiver都是支持在Intent中传递Bundle数据的
- 文件共享:适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。SharedPreferences是个特例,SharedPreferences是Android中提供的轻量级存储方案,它通过键值对的方式来存储数据的,但是当面对高并发的读/写访问, Sharedpreferences有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences。
- Messenger:Messenger是一种轻量级的IPC方案,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。而它的底层实现是AIDL,内部分为服务端和客户端。Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了
- AIDL:服务端服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。客户端客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
- ContentProvider:是Android中提供的专门用于不同应用间进行数据共享的方式。和Messenger一样,ContentProvider的底层实现同样也是Binder。(APP侧实现过程)
- Socket:是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成。而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,
- Binder连接池:主要作用就是将每个业务模块的Binder请求统一转发到同一个远程Service中去执行,从而避免了重复创建Service的过程
- 系统自定义 UID:用于管理相同类型应用间的数据共享。原生Android中参考
7.3 多账号下的权限处理
7.3.1. 理解多账号对权限的影响
- 独立的权限状态存储:每个用户账号下应用的权限状态是相互独立的。每个用户账号都有独立的权限数据库,用于记录该账号下各个应用的权限授予状态。系统会根据不同的用户 ID 来区分不同账号的权限数据,确保不同账号之间的权限信息不会相互干扰。例如,用户 A 账号下应用 X 被授予了相机权限,而在用户 B 账号下应用 X 的相机权限可能处于未授予状态,这两种状态会分别存储在各自账号对应的权限数据库中。
- 运行时隔离:不同用户账号的数据是隔离的,当应用在不同用户账号下运行时,系统会根据该账号的权限设置来限制应用的行为。即使同一个应用在不同账号下运行,其所能访问的资源和执行的操作也会因权限不同而有所差异。例如,在一个受限的用户账号下,应用可能无法访问某些敏感的系统设置或个人数据。
7.3.2.处理权限变更的实施方案
- 在应用侧可以监听权限变更事件并做出相应处理。可以通过 PackageManager 的
addOnPermissionsChangeListener()
方法来监听权限变更,示例代码如下:
private PackageManager.OnPermissionsChangeListener permissionsChangeListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
permissionsChangeListener = new PackageManager.OnPermissionsChangeListener() {
@Overridepublic
void onPermissionsChanged(String[] permissions) {// 权限发生变更,检查相关权限状态
for (String permission : permissions) {
if (permission.equals(android.Manifest.permission.CAMERA)) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
// 相机权限被撤销,做出相应处理
}
}
}
}
};
getPackageManager().addOnPermissionsChangeListener(permissionsChangeListener);
}
@Override
protected void onDestroy() {
super.onDestroy();// 移除权限变更监听器
getPackageManager().removeOnPermissionsChangeListener(permissionsChangeListener);
}
- 在系统侧对账号的变化需要实时监听,无论是新增还是删除。可以通过
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
的onNewUserCreated()方法去做处理,以修改runtime-permission-xx.xml文件来缓存每个用户的权限,当账号切换时,读取不同的运行权限文件。具体问题具体分析。
7.4 多账号下权限可否使用持久化存储?
可以,以USB权限为案例:
1. 持久化 usb 授权不区分下电与插拔,都会记录:即一次授权即可,而后永久不再弹窗
2. 授权不区分账户,A 账户授予后,持久化记录所有账户
3. 以下是相关代码修改,将记忆接口 setDevicePersistentPermission 添加到 grantDevicePermission 同意授权的方法里,setDevicePersistentPermission 这个授权接口会把 USB 设备的持久权限授予状态记录在系统目录:/data/system/users/0/usb_permissions.xml
7.5 预安装系统软件包
使用以 frameworks/base/data/etc/preinstalled-packages-platform.xml 为模型的系统配置 XML 文件,根据新用户的用户类型声明应为新用户初始安装哪些系统软件包。
示例:
- 仅在用户 0 中预安装系统软件包:
<install-in-user-type package="com.android.example">
<install-in user-type="SYSTEM" />
</install-in-user-type>
- 要为所有人类用户预安装的系统软件包(例如网络浏览器),即要为面向所有人类用户的 FULL 或 PROFILE 类型的任何用户安装的系统软件包:
<install-in-user-type package="com.android.example">
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
</install-in-user-type>
- 要为除个人资料用户以外的所有人类用户预安装的系统软件包。例如,这可以应用于壁纸应用:
<install-in-user-type package="com.android.example">
<install-in user-type="FULL" />
</install-in-user-type>
- 无论类型如何,确实需要为所有用户预安装某些系统软件包。在这种情况下,请使用:
<install-in-user-type package="com.android.example">
<install-in user-type="SYSTEM">
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
</install-in-user-type>
- 还可以通过指定具体的用户类型获得更精细的选项。例如,以下示例代码为用户类型为受管理个人资料、访客或 SYSTEM 基本类型之一的任何用户上安装此软件包。
<install-in-user-type package="com.android.example">
<install-in user-type="android.os.usertype.profile.MANAGED" />
<install-in user-type="android.os.usertype.full.GUEST" />
<install-in user-type="SYSTEM">
</install-in-user-type>
- 另外,把install-in 替换成do-not-install-in 标记阻止为特定用户类型的用户预安装软件包。
7.6 定位方式
7.6.1 GPS定位
通过 GPS 来获取地理位置的经纬度信息。
1、定义权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
2、通过LocationManager.requestLocationUpdates()请求权限更新
var locManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
var loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if(loc != null){
Log.e("gpslocation", loc.toString())
toast(loc.toString())
}
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0F, object : LocationListener{
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String?) {
}
override fun onProviderDisabled(provider: String?) {
}
override fun onLocationChanged(location: Location?) {
Log.e("gpslocation", location.toString())
toast(location.toString())
}
})
7.6.2 NETWORK定位
与gps定位代码基本一致,只不过将provider改成LocationManager.NETWORK_PROVIDER
7.6.3 基站定位
通过TelephonyManager我们可以拿到基站信息,再通过相关的api接口就能得到经纬度,但是基站定位精度很差。
1、定义权限。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_CARSE_LOCATION"/>
2、实现。
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if(telManager.cellLocation is GsmCellLocation) {
val cellLoc = telManager.cellLocation as GsmCellLocation
if (cellLoc != null) {
...
}
}
7.6.4 WIFI定位
1、定义权限:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
2、实现
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
if(wifiManager.isWifiEnabled) {
var mac = wifiManager.connectionInfo.bssid
...
}
7.6.5 蓝牙定位
1、权限:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- 声明精确位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2、实现是通过蓝牙广播接收者的形式接收位置信息
7.7 系统目录权限管控
- 为实现目录资源权限管控,添加自定义 gid(参考
6.3.1 增加自定义用户组 gid
) 配置权限组,例如 my_speak,并将自定义的 gid 与
permission 关联,可以通过声明 permission 加入 group,根据自主访问控制权限策略,来获取 group 组的
读写权限 - 上层应用可通过 xml 声明 permission 加入 group
- 以上各目录 DAC 权限配置目前还没有在 init.rc 里强制更改,还都是 777 权限即任何进程都可在
以上根目录下读写文件,但子目录下各资源文件的权限配置不是开放的 777,每个进程自己创建
的文件跟随自身的 uid 可读可写,还有一些预置资源的权限。
8. SELinux权限部分
8.1 概念
安全增强型 Linux (SELinux) 是适用于 Linux 操作系统的强制访问控制 (MAC) 系统。通过结合使用其他 Android 安全措施,Android 的访问权限控制政策能够大大降低遭到入侵的计算机和账号可能蒙受的损失。Android 的自主访问控制和强制访问控制等工具可为您提供一种结构,确保您的软件仅以最低权限级别运行。SELinux 可以在各种模式下实现:
- 宽容模式 - 仅记录但不强制执行 SELinux 安全政策,从Android 4.3开始。
- 强制模式 - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。 从Android 5.x 及更高版本中开始。
8.2 实现方式
具体可以看实现SELinux
8.3 常见的SELinux权限问题
可以查看相关链接
8.4 挂载分区设置权限
- 定义user和属性
system/sepolicy/private/seapp_contexts 添加以下内容:
user=selfgroup seinfo=platform domain=selfgroup type=app_data_file
- 在system/sepolicy/public/中新建selfgroup.te添加以下内容:
# phone subsystem
type selfgroup, domain, mlstrustedsubject;
# system/sepolicy/public is for vendor-facing type and attribute definitions.
# DO NOT ADD allow, neverallow, or dontaudit statements here.
# Instead, add such policy rules to system/sepolicy/private/*.te.
- 在system/sepolicy/private/中新建 selfgroup.te 添加以下内容:
typeattribute selfgroup coredomain, mlstrustedsubject;
app_domain(selfgroup)
read_runtime_log_tags(selfgroup)
# Property service
set_prop(selfgroup, selfgroup_control_prop)
set_prop(selfgroup, selfgroup_prop)
set_prop(selfgroup, net_selfgroup_prop)
set_prop(selfgroup, telephony_status_prop)
set_prop(selfgroup, selfgroup_cdma_ecm_prop)
# ctl interface
set_prop(selfgroup, ctl_rildaemon_prop)
# Telephony code contains time / time zone detection logic so it reads the associated properties.
get_prop(selfgroup, time_prop)
# allow telephony to access platform compat to log permission denials
allow selfgroup platform_compat_service:service_manager find;
allow selfgroup uce_service:service_manager find;
# Manage /data/misc/emergencynumberdb
allow selfgroup emergency_data_file:dir r_dir_perms;
allow selfgroup emergency_data_file:file r_file_perms;
# allow telephony to access related cache properties
set_prop(selfgroup, binder_cache_telephony_server_prop);
# allow sending pulled atoms to statsd
binder_call(selfgroup, statsd)
net_domain(selfgroup)
bluetooth_domain(selfgroup)
binder_service(selfgroup)
# Talks to hal_telephony_server via the rild socket only for devices without full treble
not_full_treble(`unix_socket_connect(selfgroup, rild, hal_telephony_server)')
# Data file accesses.
allow selfgroup selfgroup_data_file:dir create_dir_perms;
allow selfgroup selfgroup_data_file:notdevfile_class_set create_file_perms;
allow selfgroup selfgroup_core_data_file:dir r_dir_perms;
allow selfgroup selfgroup_core_data_file:file r_file_perms;
allow selfgroup net_data_file:dir search;
allow selfgroup net_data_file:file r_file_perms;
add_service(selfgroup, selfgroup_service)
allow selfgroup audioserver_service:service_manager find;
allow selfgroup cameraserver_service:service_manager find;
allow selfgroup drmserver_service:service_manager find;
allow selfgroup mediaserver_service:service_manager find;
allow selfgroup nfc_service:service_manager find;
allow selfgroup app_api_service:service_manager find;
allow selfgroup system_api_service:service_manager find;
allow selfgroup timedetector_service:service_manager find;
allow selfgroup timezonedetector_service:service_manager find;
# Perform HwBinder IPC.
hwbinder_use(selfgroup)
hal_client_domain(selfgroup, hal_telephony)
# Used by TelephonyManager
allow selfgroup proc_cmdline:file r_file_perms;
###
### Neverallow rules
###
neverallow { domain -selfgroup -init }
binder_cache_telephony_server_prop:property_service set;
- 在system/sepolicy/vendor/目录下创建selfgroup.te文件,添加以下内容:
type hal_radio_config_default, domain;
hal_server_domain(selfgroup, hal_telephony)
type hal_radio_config_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(selfgroup)
- 如果需要在环境中添加某个文件夹到编译镜像中,那就在xx-sepolicy.mk添加以下内容:
BOARD_PLAT_PUBLIC_SEPOLICY_DIR += $(SEPOLICY_PATH)/xx_sepolicy/public
BOARD_PLAT_PRIVATE_SEPOLICY_DIR += $(SEPOLICY_PATH)/xx_sepolicy/private
参考文献
Android 中的权限 | Android Developers
Android权限汇总(分类详细版)_android 权限-CSDN博客
Android权限管理及动态申请权限_android 申请多组权限-CSDN博客
Android:Android 应用权限详解-阿里云开发者社区
Android中的安全
添加Selinux 权限/常见的Selinux 权限问题