Android getprop 属性限制详解:User 版本属性获取问题分析
文章目录
- [Android getprop 属性限制详解:User 版本属性获取问题分析](#Android getprop 属性限制详解:User 版本属性获取问题分析)
-
- 一、前言
- [二、Android 系统属性机制概述](#二、Android 系统属性机制概述)
-
- [2.1 系统属性简介](#2.1 系统属性简介)
- [2.2 属性分类](#2.2 属性分类)
- [2.3 属性服务架构](#2.3 属性服务架构)
- 三、源码分析:属性访问控制机制
-
- [3.1 属性服务源码位置](#3.1 属性服务源码位置)
- [3.2 property_service.cpp 源码分析](#3.2 property_service.cpp 源码分析)
-
- [3.2.1 属性服务核心功能](#3.2.1 属性服务核心功能)
- [3.2.2 属性设置权限检查](#3.2.2 属性设置权限检查)
- [3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制](#3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制)
- [3.2.4 为什么有些属性在 User 版本无法获取?](#3.2.4 为什么有些属性在 User 版本无法获取?)
- [3.3 构建类型与属性定义](#3.3 构建类型与属性定义)
-
- [3.3.1 构建配置差异](#3.3.1 构建配置差异)
- [3.3.2 条件编译属性](#3.3.2 条件编译属性)
- [3.4 系统属性配置文件](#3.4 系统属性配置文件)
- 四、蓝牙绝对音量属性详解
-
- [4.1 正确的蓝牙绝对音量属性](#4.1 正确的蓝牙绝对音量属性)
- [4.2 属性源码分析](#4.2 属性源码分析)
-
- [4.2.1 AudioService 中的使用](#4.2.1 AudioService 中的使用)
- [4.2.2 AvrcpVolumeManager 中的使用](#4.2.2 AvrcpVolumeManager 中的使用)
- [4.2.3 开发者选项中的设置](#4.2.3 开发者选项中的设置)
- [4.3 属性特点分析](#4.3 属性特点分析)
-
- [4.3.1 persist 前缀的含义](#4.3.1 persist 前缀的含义)
- [4.3.2 与 debug 前缀的区别](#4.3.2 与 debug 前缀的区别)
- [4.4 User 版本中的真实行为(重要)](#4.4 User 版本中的真实行为(重要))
-
- [4.4.1 Android 15 User 版本实测结果](#4.4.1 Android 15 User 版本实测结果)
- [4.4.2 原因分析:SELinux 读取权限限制](#4.4.2 原因分析:SELinux 读取权限限制)
-
- [SELinux 属性读取机制](#SELinux 属性读取机制)
- [property_contexts 中的属性类型定义](#property_contexts 中的属性类型定义)
- [shell 域的权限定义](#shell 域的权限定义)
- [4.4.3 验证 SELinux 拒绝日志](#4.4.3 验证 SELinux 拒绝日志)
- [4.4.4 属性读取权限的层级关系](#4.4.4 属性读取权限的层级关系)
- [4.4.5 不同域对蓝牙属性的访问权限](#4.4.5 不同域对蓝牙属性的访问权限)
- [4.4.6 为什么 system_server 可以读取?](#4.4.6 为什么 system_server 可以读取?)
- [4.5 persist.bluetooth.disableabsvol 的实际归属](#4.5 persist.bluetooth.disableabsvol 的实际归属)
-
- [4.5.1 属性匹配分析](#4.5.1 属性匹配分析)
- [4.5.2 为什么 shell 无法读取](#4.5.2 为什么 shell 无法读取)
- [4.5.3 与 a2dp_offload 属性的对比](#4.5.3 与 a2dp_offload 属性的对比)
- [4.6 正确的属性获取方式](#4.6 正确的属性获取方式)
-
- [4.6.1 应用开发者](#4.6.1 应用开发者)
- [4.6.2 系统开发者](#4.6.2 系统开发者)
- [4.6.3 厂商定制方案](#4.6.3 厂商定制方案)
- [4.5 其他蓝牙相关属性](#4.5 其他蓝牙相关属性)
- [五、User 版本无法获取的属性详解](#五、User 版本无法获取的属性详解)
-
- [5.1 常见受限属性列表](#5.1 常见受限属性列表)
- [5.2 详细分类说明](#5.2 详细分类说明)
-
- [5.2.1 蓝牙相关属性(bluetooth_prop)](#5.2.1 蓝牙相关属性(bluetooth_prop))
- [5.2.2 NFC 相关属性(nfc_prop)](#5.2.2 NFC 相关属性(nfc_prop))
- [5.2.3 音频相关属性(audio_prop)](#5.2.3 音频相关属性(audio_prop))
- [5.2.4 相机相关属性(camera_prop)](#5.2.4 相机相关属性(camera_prop))
- [5.2.5 调试属性(debug_prop)](#5.2.5 调试属性(debug_prop))
- [5.3 验证属性是否受限的方法](#5.3 验证属性是否受限的方法)
- [5.4 属性限制流程图](#5.4 属性限制流程图)
- 六、自定义受限属性设计与实现
-
- [6.1 需求场景](#6.1 需求场景)
- [6.2 实现步骤](#6.2 实现步骤)
-
- [步骤一:定义 SELinux 属性类型](#步骤一:定义 SELinux 属性类型)
- [步骤二:配置 property_contexts](#步骤二:配置 property_contexts)
- 步骤三:配置域权限(谁可以访问)
- [步骤四:配置 neverallow 规则(可选但推荐)](#步骤四:配置 neverallow 规则(可选但推荐))
- 步骤五:定义属性默认值(可选)
- [6.3 完整示例](#6.3 完整示例)
- [6.4 验证实现](#6.4 验证实现)
- [6.5 使用自定义属性](#6.5 使用自定义属性)
-
- [Java 代码(需要系统权限)](#Java 代码(需要系统权限))
- [Native 代码](#Native 代码)
- [6.6 设计原则](#6.6 设计原则)
- 六、源码深入分析
-
- [5.1 init 进程中的属性服务](#5.1 init 进程中的属性服务)
-
- [5.1.1 属性服务启动流程](#5.1.1 属性服务启动流程)
- [5.1.2 属性服务实现](#5.1.2 属性服务实现)
- [5.2 属性可获取性检查](#5.2 属性可获取性检查)
- [5.3 Bionic 层属性获取](#5.3 Bionic 层属性获取)
- [5.4 Java 层属性获取](#5.4 Java 层属性获取)
- [七、SELinux 权限控制详解(核心机制)详解](#七、SELinux 权限控制详解(核心机制)详解)
-
- [7.1 属性读取权限的真实机制](#7.1 属性读取权限的真实机制)
-
- [7.1.1 属性读取的 SELinux 检查点](#7.1.1 属性读取的 SELinux 检查点)
- [7.1.2 属性文件的 SELinux 标签](#7.1.2 属性文件的 SELinux 标签)
- [7.2 property_contexts 文件详解](#7.2 property_contexts 文件详解)
-
- [7.2.1 文件位置](#7.2.1 文件位置)
- [7.2.2 property_contexts 语法格式](#7.2.2 property_contexts 语法格式)
- [7.2.3 实际配置示例解析](#7.2.3 实际配置示例解析)
- [7.2.4 类型注解详解(Android 10+)](#7.2.4 类型注解详解(Android 10+))
- [7.2.5 属性匹配优先级规则](#7.2.5 属性匹配优先级规则)
- [7.2.6 属性作用域与访问权限](#7.2.6 属性作用域与访问权限)
- [7.2.7 查看设备上的实际配置](#7.2.7 查看设备上的实际配置)
- [7.3 如何验证 shell 域没有 bluetooth_prop 权限](#7.3 如何验证 shell 域没有 bluetooth_prop 权限)
-
- [7.3.1 SELinux 权限机制说明](#7.3.1 SELinux 权限机制说明)
- [7.3.2 查看 shell.te 源码](#7.3.2 查看 shell.te 源码)
- [7.3.3 在设备上验证方法](#7.3.3 在设备上验证方法)
- [7.3.4 查看哪些域有 bluetooth_prop 权限](#7.3.4 查看哪些域有 bluetooth_prop 权限)
- [7.3.5 bluetooth.te 中的权限定义](#7.3.5 bluetooth.te 中的权限定义)
- [7.3.6 system_server.te 中的权限定义](#7.3.6 system_server.te 中的权限定义)
- [7.4 SELinux neverallow 规则(禁止性规则)](#7.4 SELinux neverallow 规则(禁止性规则))
- [7.5 属性权限完整对照表](#7.5 属性权限完整对照表)
- [7.6 SELinux 编译宏详解](#7.6 SELinux 编译宏详解)
- [7.7 属性权限层级图](#7.7 属性权限层级图)
- [6.1 SELinux 属性上下文](#6.1 SELinux 属性上下文)
- [6.2 SELinux 权限规则](#6.2 SELinux 权限规则)
- [6.3 域权限配置](#6.3 域权限配置)
- [6.4 SELinux 策略编译宏](#6.4 SELinux 策略编译宏)
- 八、构建类型差异对比
-
- [8.1 构建类型说明](#8.1 构建类型说明)
- [8.2 关键差异:userdebug vs user 的 SELinux 策略](#8.2 关键差异:userdebug vs user 的 SELinux 策略)
-
- [8.2.1 SELinux 策略的条件编译](#8.2.1 SELinux 策略的条件编译)
- [8.2.2 userdebug_or_eng 宏定义](#8.2.2 userdebug_or_eng 宏定义)
- [8.2.3 构建类型与策略编译的关系](#8.2.3 构建类型与策略编译的关系)
- [8.3 实际验证方法](#8.3 实际验证方法)
-
- [8.3.1 查看 SELinux 策略差异](#8.3.1 查看 SELinux 策略差异)
- [8.3.2 检查构建类型](#8.3.2 检查构建类型)
- [8.3.3 查看 SELinux 策略源码](#8.3.3 查看 SELinux 策略源码)
- [8.4 属性访问权限对比](#8.4 属性访问权限对比)
- [8.5 构建配置文件](#8.5 构建配置文件)
- [8.6 为什么要有这个差异?](#8.6 为什么要有这个差异?)
- [8.7 userdebug_or_eng 宏的定义位置](#8.7 userdebug_or_eng 宏的定义位置)
-
- [8.7.1 宏定义来源](#8.7.1 宏定义来源)
- [8.7.2 宏定义的真实注入流程](#8.7.2 宏定义的真实注入流程)
- [8.7.3 如何查找宏的实际定义位置](#8.7.3 如何查找宏的实际定义位置)
- [8.7.4 Android 12+ 的变化(Soong 构建系统)](#8.7.4 Android 12+ 的变化(Soong 构建系统))
- [8.7.5 te_macros 文件中的典型定义(旧版本参考)](#8.7.5 te_macros 文件中的典型定义(旧版本参考))
- [8.7.3 m4 宏处理示例](#8.7.3 m4 宏处理示例)
- [8.7.4 构建系统中的相关文件](#8.7.4 构建系统中的相关文件)
- [8.7.5 查看 te_macros 中的相关宏](#8.7.5 查看 te_macros 中的相关宏)
- [8.7.6 自定义条件宏](#8.7.6 自定义条件宏)
- [8.7.7 验证构建类型](#8.7.7 验证构建类型)
- [8.7.8 完整的构建类型与权限关系图](#8.7.8 完整的构建类型与权限关系图)
- [7.1 构建类型说明](#7.1 构建类型说明)
- [8.2 属性访问权限对比](#8.2 属性访问权限对比)
- [7.3 构建配置文件](#7.3 构建配置文件)
- 九、解决方案
-
- [9.1 方案一:使用开发者选项设置](#9.1 方案一:使用开发者选项设置)
- [9.2 方案二:使用 Settings 系统](#9.2 方案二:使用 Settings 系统)
- [9.3 方案三:定义自定义系统属性](#9.3 方案三:定义自定义系统属性)
-
- [步骤 1:定义属性](#步骤 1:定义属性)
- [步骤 2:添加 SELinux 上下文](#步骤 2:添加 SELinux 上下文)
- [步骤 3:添加 SELinux 权限](#步骤 3:添加 SELinux 权限)
- [步骤 4:修改源码使用自定义属性](#步骤 4:修改源码使用自定义属性)
- [9.4 方案四:修改系统源码(仅限系统定制)](#9.4 方案四:修改系统源码(仅限系统定制))
- [9.5 方案五:使用 DeviceConfig](#9.5 方案五:使用 DeviceConfig)
- 十、实际案例分析
- 十一、最佳实践建议
-
- [10.1 属性使用原则](#10.1 属性使用原则)
- [10.2 代码兼容性处理](#10.2 代码兼容性处理)
- [10.3 调试与验证](#10.3 调试与验证)
- 十三、总结
- 参考资料
- 附录:常见问题解答
-
- [Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?](#Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?)
- [Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?](#Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?)
- [Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?](#Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?)
一、前言
在 Android 系统开发过程中,getprop 是一个非常重要的命令行工具,用于获取系统属性值。
然而,在实际开发中,我们经常会遇到这样一个问题:
某些系统属性在 eng(工程)版本或 userdebug 版本可以正常获取,但在 user(用户)版本却无法获取到,返回空值或默认值。
典型的例子包括蓝牙绝对音量相关属性、调试开关属性等。
本文将从 Android 系统源码层面深入分析这一现象的原因,并提供相应的解决方案。
最近调试发现:
蓝牙绝对音量属性 persist.bluetooth.disableabsvol 在user版本通过adb 窗口获取不到,debug版本可以。
这个属性看起来不是什么特殊限制的属性,都是就是有这样的问题。应该是源码限制导致。
你只有明白有些 persist.XXX 属性也有可能在user版本获取不到就行了。
下面分析过程可看可不看。因为不保证正确率,仅供参考。
二、Android 系统属性机制概述
2.1 系统属性简介
Android 系统属性(System Properties)是一组全局可访问的键值对,用于存储系统配置、状态信息等。这些属性在整个系统中共享,可以通过以下方式访问:
bash
# Shell 命令获取属性
adb shell getprop ro.build.version.sdk
# Shell 命令设置属性(需要权限)
adb shell setprop debug.bluetooth.absolute_volume 1
java
// Java 代码获取属性
String value = SystemProperties.get("ro.build.version.sdk");
// Java 代码设置属性
SystemProperties.set("debug.bluetooth.absolute_volume", "1");
2.2 属性分类
Android 系统属性按前缀可分为以下几类:
| 前缀 | 说明 | 可写性 | 示例 |
|---|---|---|---|
ro. |
只读属性,系统启动后不可修改 | 只读 | ro.build.version.sdk |
sys. |
系统属性,可读写 | 可写 | sys.usb.config |
persist. |
持久化属性,重启后保留 | 可写 | persist.sys.locale |
debug. |
调试属性,部分在 user 版本受限 | 可写 | debug.sf.nobootanimation |
log. |
日志相关属性 | 可写 | log.tag.Bluetooth |
2.3 属性服务架构
┌─────────────────────────────────────────────────────────────┐
│ 属性服务架构图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ App 层 │ │ Native 层 │ │ Shell 层 │ │
│ │ SystemProps │ │ __system_property_get │ getprop │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Property Service │ │
│ │ (init 进程) │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ /dev/__properties__ │ │
│ │ (共享内存映射) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
三、源码分析:属性访问控制机制
3.1 属性服务源码位置
Android 系统属性服务的核心代码位于以下目录:
system/core/init/property_service.cpp # 属性服务核心实现
system/core/init/properties.h # 属性定义头文件
bionic/libc/bionic/system_property_api.cpp # Bionic 属性 API
frameworks/base/core/java/android/os/SystemProperties.java # Java 层接口
3.2 property_service.cpp 源码分析
根据 AOSP 源码分析,property_service.cpp 文件主要负责属性的设置权限控制,而非属性获取的过滤。属性的限制机制主要来自于 SELinux 策略和构建配置。
3.2.1 属性服务核心功能
cpp
// system/core/init/property_service.cpp
// 属性服务的核心功能包括:
// 1. 属性的初始化和加载
// 2. 属性设置请求的处理
// 3. 属性权限的验证(设置权限,非读取权限)
// 属性设置权限白名单(旧版本)
// 注意:Android 8.0+ 后,权限控制主要通过 SELinux 实现
struct {
const char *prefix;
unsigned int uid;
unsigned int gid;
} property_perms[] = {
{ "net.", AID_RADIO, 0 },
{ "sys.", AID_SYSTEM, 0 },
{ "persist.sys.", AID_SYSTEM, 0 },
{ "persist.service.", AID_SYSTEM, 0 },
// ... 其他权限定义
};
3.2.2 属性设置权限检查
cpp
// system/core/init/property_service.cpp
// 检查属性设置权限
static Result<void> CheckPermissions(const std::string& name,
const std::string& value,
const std::string& source_context,
const ucred* cred) {
// 1. 检查属性名是否合法
if (!IsLegalPropertyName(name)) {
return Error() << "Illegal property name";
}
// 2. 检查 SELinux 权限(主要权限控制)
if (!CheckSelinuxPermission(name, value, source_context, cred)) {
return Error() << "SELinux permission denied";
}
// 3. 检查属性设置权限(uid/gid)
// ...
return {};
}
3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制
经过源码分析,property_service.cpp 文件中:
-
没有蓝牙属性的特定限制代码:蓝牙属性的限制不是在此文件中硬编码的
-
属性读取无过滤:属性获取(getprop)操作不经过权限过滤,直接从共享内存读取
-
限制来源于 SELinux:真正的访问控制由 SELinux 策略实现
┌─────────────────────────────────────────────────────────────┐
│ 属性访问控制真实机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 属性读取 (getprop): │
│ ┌─────────────┐ │
│ │ getprop │ │
│ │ SystemProperties.get() │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 直接从共享内存读取 │ │
│ │ /dev/properties │ │
│ │ (无权限过滤) │ │
│ └─────────────────────────────┘ │
│ │
│ 属性设置 (setprop): │
│ ┌─────────────┐ │
│ │ setprop │ │
│ │ SystemProperties.set() │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Property Service (init) │ │
│ │ 1. 检查 SELinux 权限 │ │
│ │ 2. 检查 uid/gid 权限 │ │
│ │ 3. 写入属性 │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
3.2.4 为什么有些属性在 User 版本无法获取?
经过深入分析,属性在 User 版本"无法获取"的原因主要有以下几种:
| 原因 | 说明 | 示例 |
|---|---|---|
| 属性根本不存在 | 属性未被定义或未被设置 | 部分动态属性 |
| SELinux 读取限制 | SELinux 策略禁止某些域读取特定属性 | debug.* 类属性 |
| 属性来源未加载 | 属性定义文件未被加载到 user 版本 | 部分调试配置 |
| 构建时未包含 | 某些属性只在 debug 构建中定义 | ro.debuggable=1 |
3.3 构建类型与属性定义
3.3.1 构建配置差异
makefile
# build/core/main.mk
# 根据构建类型设置不同的属性
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
# Debug 构建属性
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=1 \
ro.adb.secure=0
else
# User 构建属性
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=0 \
ro.adb.secure=1
endif
3.3.2 条件编译属性
某些属性只在特定构建类型中定义:
makefile
# 某些系统属性文件中的条件定义
ifneq ($(TARGET_BUILD_VARIANT),user)
# 非 User 版本才定义的调试属性
PRODUCT_PROPERTY_OVERRIDES += \
debug.sf.nobootanimation=1
endif
3.4 系统属性配置文件
Android 系统通过 property_contexts 文件定义属性的 SELinux 上下文和权限:
plaintext
# system/sepolicy/private/property_contexts
# 调试属性 - user 版本受限
debug. u:object_r:debug_prop:s0
debug.bluetooth. u:object_r:bluetooth_debug_prop:s0
# 蓝牙属性
bluetooth. u:object_r:bluetooth_prop:s0
ro.bluetooth. u:object_r:bluetooth_prop:s0
# 系统属性
sys. u:object_r:system_prop:s0
persist.sys. u:object_r:persist_system_prop:s0
四、蓝牙绝对音量属性详解
4.1 正确的蓝牙绝对音量属性
重要更正 :蓝牙绝对音量的系统属性是 persist.bluetooth.disableabsvol,而非 debug.bluetooth.absolute_volume。
| 属性名称 | 说明 | 默认值 |
|---|---|---|
persist.bluetooth.disableabsvol |
禁用蓝牙绝对音量 | false(空或不存在) |
bash
# 禁用蓝牙绝对音量(设置该属性为 true)
adb shell setprop persist.bluetooth.disableabsvol true
# 查看当前状态
adb shell getprop persist.bluetooth.disableabsvol
# 恢复默认(启用绝对音量)
adb shell setprop persist.bluetooth.disableabsvol false
# 或者删除属性(重启后生效)
4.2 属性源码分析
4.2.1 AudioService 中的使用
java
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
/**
* 检查是否应该禁用 AVRCP 绝对音量
* @return true 表示禁用绝对音量
*/
private boolean shouldEnableAvrcpAbsoluteVolume() {
// 读取 persist.bluetooth.disableabsvol 属性
// 注意:属性名为 "disableabsvol",表示禁用绝对音量
// 属性值为 true 时禁用,false 或不存在时启用
return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
// 或者更明确的写法
private boolean isAvrcpAbsoluteVolumeEnabled() {
// 默认启用绝对音量(返回 false 表示不禁用)
return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
4.2.2 AvrcpVolumeManager 中的使用
java
// packages/apps/Bluetooth/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
/**
* 检查是否禁用绝对音量
*/
private boolean isAbsoluteVolumeDisabled() {
// 读取系统属性
return SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
/**
* 初始化时检查绝对音量设置
*/
public void init() {
if (isAbsoluteVolumeDisabled()) {
Log.w(TAG, "Absolute volume is disabled by system property");
// 使用相对音量模式
} else {
Log.d(TAG, "Absolute volume is enabled");
// 使用绝对音量模式
}
}
4.2.3 开发者选项中的设置
java
// packages/apps/Settings/src/com/android/settings/development/DevelopmentSettings.java
// 开发者选项中的 "禁用绝对音量" 开关
private static final String KEY_DISABLE_BLUETOOTH_ABSOLUTE_VOLUME =
"disable_bluetooth_absolute_volume";
// 蓝牙绝对音量属性名称
private static final String BLUETOOTH_DISABLE_ABS_VOL_PROPERTY =
"persist.bluetooth.disableabsvol";
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference.getKey().equals(KEY_DISABLE_BLUETOOTH_ABSOLUTE_VOLUME)) {
boolean disable = (Boolean) newValue;
// 写入系统属性
SystemProperties.set(BLUETOOTH_DISABLE_ABS_VOL_PROPERTY,
disable ? "true" : "false");
// 提示用户需要重启生效
if (disable) {
showToast(R.string.bluetooth_absolute_volume_disabled);
}
return true;
}
return false;
}
4.3 属性特点分析
4.3.1 persist 前缀的含义
persist.bluetooth.disableabsvol 使用 persist. 前缀,具有以下特点:
| 特性 | 说明 |
|---|---|
| 持久化存储 | 属性值保存在 /data/property/ 目录下 |
| 重启后保留 | 设备重启后属性值不会丢失 |
| 可读写 | 所有构建类型都可以读取和设置 |
| 不受构建类型限制 | User 版本也可以正常访问 |
4.3.2 与 debug 前缀的区别
| 对比项 | persist.* | debug.* |
|---|---|---|
| 存储位置 | /data/property/persistent_properties | 仅内存中 |
| 重启后 | ✅ 保留 | ❌ 丢失 |
| User 版本可访问 | ✅ 是 | ⚠️ 受限 |
| 设置权限 | 需要系统权限 | 需要 root 或 shell |
| SELinux 限制 | 较宽松 | 较严格 |
4.4 User 版本中的真实行为(重要)
4.4.1 Android 15 User 版本实测结果
根据 Android 15 User 版本的实际测试,persist.bluetooth.disableabsvol 属性在 adb shell 中无法获取:
bash
# Android 15 User 版本测试
$ adb shell getprop persist.bluetooth.disableabsvol
# 返回空值,即使属性已被设置
# 尝试查看所有蓝牙相关属性
$ adb shell getprop | grep bluetooth
# 可能也看不到 persist.bluetooth.disableabsvol
4.4.2 原因分析:SELinux 读取权限限制
核心发现 :在 Android 14/15 中,SELinux 策略对属性的读取权限 进行了严格限制,即使是 persist. 前缀的属性。
SELinux 属性读取机制
┌─────────────────────────────────────────────────────────────┐
│ Android 14/15 属性读取权限机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 传统的理解(错误): │
│ ┌─────────────┐ │
│ │ getprop │ │
│ └──────┬──────┘ │
│ │ 直接读取共享内存 │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ /dev/__properties__ │ │
│ │ 无权限检查 │ ← 实际上有权限检查! │
│ └─────────────────────────────┘ │
│ │
│ 实际机制(正确): │
│ ┌─────────────┐ │
│ │ getprop │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ __system_property_find │ │
│ │ 检查 SELinux 上下文 │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ SELinux Policy 检查 │ │
│ │ shell 域是否有权限读取 │ │
│ │ 该属性类型? │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ 允许读取 │ │ 返回空值 │ │
│ │ 返回属性值│ │ 或访问被拒绝 │ │
│ └──────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
property_contexts 中的属性类型定义
plaintext
# system/sepolicy/private/property_contexts
# 蓝牙相关属性的类型定义
persist.bluetooth. u:object_r:bluetooth_prop:s0
ro.bluetooth. u:object_r:bluetooth_prop:s0
bluetooth. u:object_r:bluetooth_prop:s0
shell 域的权限定义
sepolicy
# system/sepolicy/public/shell.te
# shell 域可以读取的属性类型
# 注意:bluetooth_prop 可能不在允许列表中!
# shell 域允许读取的属性示例
get_prop(shell, default_prop)
get_prop(shell, log_prop)
get_prop(shell, shell_prop)
get_prop(shell, system_prop)
# 如果 bluetooth_prop 没有被明确允许,shell 就无法读取
# 即使是 persist.bluetooth.* 属性也无法获取
4.4.3 验证 SELinux 拒绝日志
可以通过以下方式验证 SELinux 是否拒绝了属性读取:
bash
# 查看 SELinux 拒绝日志
$ adb shell dmesg | grep -i "avc.*bluetooth.*prop"
# 或
$ adb shell "logcat -b all | grep -i 'avc denied.*property'"
# 示例输出
# avc: denied { read } for name="persist.bluetooth.disableabsvol"
# scontext=u:r:shell:s0 tcontext=u:object_r:bluetooth_prop:s0
# tclass=file permissive=0
4.4.4 属性读取权限的层级关系
┌─────────────────────────────────────────────────────────────┐
│ 属性读取权限检查流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 应用/进程尝试读取属性 │
│ ↓ │
│ 2. 系统获取进程的 SELinux 域 (scontext) │
│ 例如:shell 域、untrusted_app 域、system_server 域 │
│ ↓ │
│ 3. 系统查找属性的安全上下文 (tcontext) │
│ 例如:bluetooth_prop、system_prop、debug_prop │
│ ↓ │
│ 4. SELinux 策略检查 │
│ - 该域是否有 get_prop 权限读取该类型属性? │
│ - 是否存在 neverallow 规则禁止? │
│ ↓ │
│ 5. 结果 │
│ - 允许:返回属性值 │
│ - 拒绝:返回空值,记录 avc denied 日志 │
│ │
└─────────────────────────────────────────────────────────────┘
4.4.5 不同域对蓝牙属性的访问权限
| 域 (Domain) | persist.bluetooth.* 访问权限 | 说明 |
|---|---|---|
shell |
❌ 受限 | adb shell 无法读取 |
untrusted_app |
❌ 受限 | 普通应用无法读取 |
system_server |
✅ 可读取 | 系统服务可读取 |
bluetooth |
✅ 可读取 | 蓝牙服务可读取 |
radio |
⚠️ 可能受限 | 电信服务受限 |
vendor_* |
⚠️ 取决于策略 | 厂商定制域 |
4.4.6 为什么 system_server 可以读取?
sepolicy
# system/sepolicy/private/system_server.te
# system_server 域被明确授予读取 bluetooth_prop 的权限
get_prop(system_server, bluetooth_prop)
# 或者更广泛的权限
get_prop(system_server, system_prop)
4.5 persist.bluetooth.disableabsvol 的实际归属
4.5.1 属性匹配分析
根据 property_contexts 的配置:
plaintext
persist.bluetooth. u:object_r:bluetooth_prop:s0
persist.bluetooth.disableabsvol 会匹配这个前缀规则,因此:
| 属性 | 匹配规则 | SELinux 类型 |
|---|---|---|
persist.bluetooth.disableabsvol |
persist.bluetooth. 前缀 |
bluetooth_prop |
4.5.2 为什么 shell 无法读取
sepolicy
# shell 域没有 bluetooth_prop 的读取权限
# system/sepolicy/public/shell.te 中没有:
# get_prop(shell, bluetooth_prop)
# 只有以下域可以读取:
# - bluetooth 域(蓝牙服务)
# - system_server 域(系统服务)
# - init 域(初始化进程)
4.5.3 与 a2dp_offload 属性的对比
plaintext
# 这些属性使用不同的 SELinux 类型
persist.bluetooth.a2dp_offload.cap u:object_r:bluetooth_a2dp_offload_prop:s0 exact string
persist.bluetooth.a2dp_offload.disabled u:object_r:bluetooth_a2dp_offload_prop:s0 exact bool
这些属性使用 bluetooth_a2dp_offload_prop 类型,可能有不同的权限配置。
4.6 正确的属性获取方式
4.6.1 应用开发者
对于普通应用开发者,无法直接读取 persist.bluetooth.disableabsvol,需要通过系统 API 获取:
java
/**
* 应用层获取蓝牙绝对音量状态的正确方式
*/
public class BluetoothVolumeHelper {
/**
* 方法一:通过 AudioManager 检查(如果 API 支持)
*/
public static boolean isAbsoluteVolumeEnabled(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// Android 11+ 可能提供相关 API
// 具体取决于系统实现
return true; // 默认启用
}
/**
* 方法二:监听蓝牙设备连接状态变化
* 通过 BluetoothA2dp 的回调判断绝对音量是否生效
*/
public static void monitorBluetoothVolume(Context context,
BluetoothA2dp a2dp,
BluetoothDevice device) {
// 监听 A2DP 连接状态
// 当设备连接时,观察音量同步行为
}
/**
* 方法三:通过 Settings(如果厂商实现)
*/
public static boolean checkDeveloperOption(Context context) {
// 某些厂商可能将此选项暴露到 Settings
return Settings.Global.getInt(
context.getContentResolver(),
"bluetooth_disable_absolute_volume",
0
) == 1;
}
}
4.6.2 系统开发者
对于有系统权限的应用或系统服务:
java
/**
* 系统应用读取蓝牙绝对音量属性
*/
public class SystemBluetoothVolumeHelper {
/**
* 直接读取系统属性(需要系统权限)
*/
public static boolean isAbsoluteVolumeDisabled() {
// 需要在 system_server 或 system_app 域运行
return SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
/**
* 通过 BluetoothProperties API(Android 10+)
*/
public static boolean isAbsoluteVolumeDisabledNewApi() {
// Android 10+ 提供的类型安全属性 API
// BluetoothProperties.disable_abs_vol() 等
// 具体方法名取决于系统版本
return false;
}
}
4.6.3 厂商定制方案
如果需要让 shell 或普通应用能读取该属性,需要修改 SELinux 策略:
sepolicy
# device/<厂商>/<设备>/sepolicy/shell.te
# 允许 shell 域读取 bluetooth_prop
get_prop(shell, bluetooth_prop)
或修改 property_contexts 将属性映射到更宽松的类型:
plaintext
# device/<厂商>/<设备>/sepolicy/property_contexts
# 将特定属性映射到 system_prop(shell 默认可读)
persist.bluetooth.disableabsvol u:object_r:system_prop:s0
4.5 其他蓝牙相关属性
| 属性名称 | 说明 | User 版本可访问 |
|---|---|---|
persist.bluetooth.disableabsvol |
禁用绝对音量 | ✅ 可读取 |
ro.bluetooth.hfp.conn |
HFP 连接状态 | ✅ 可读取 |
ro.bluetooth.a2dp.conn |
A2DP 连接状态 | ✅ 可读取 |
persist.bluetooth.adapter.address |
蓝牙地址 | ✅ 可读取 |
persist.bluetooth.sco.available |
SCO 可用状态 | ✅ 可读取 |
debug.bluetooth.* |
蓝牙调试属性 | ⚠️ 受限 |
五、User 版本无法获取的属性详解
5.1 常见受限属性列表
以下是在 User 版本 adb shell 无法获取的常见属性:
| 属性名称 | SELinux 类型 | 说明 | 受限原因 |
|---|---|---|---|
persist.bluetooth.disableabsvol |
bluetooth_prop |
蓝牙绝对音量开关 | shell 域无 bluetooth_prop 权限 |
persist.bluetooth.btsnoopdefaultmode |
bluetooth_prop |
蓝牙日志默认模式 | shell 域无 bluetooth_prop 权限 |
persist.bluetooth.snoop_log_mode |
bluetooth_prop |
蓝牙日志模式 | shell 域无 bluetooth_prop 权限 |
persist.bluetooth.a2dp_offload.cap |
bluetooth_a2dp_offload_prop |
A2DP 卸载能力 | shell 域无相关权限 |
persist.bluetooth.a2dp_offload.disabled |
bluetooth_a2dp_offload_prop |
A2DP 卸载禁用 | shell 域无相关权限 |
persist.nfc.* |
nfc_prop |
NFC 配置 | shell 域无 nfc_prop 权限 |
persist.audio.* |
audio_prop |
音频配置 | shell 域无 audio_prop 权限 |
persist.camera.* |
camera_prop |
相机配置 | shell 域无 camera_prop 权限 |
debug.* |
debug_prop |
调试属性 | User 版本受限 |
ro.debuggable |
debug_prop |
调试标志 | User 版本固定为 0 |
ro.adb.secure |
shell_prop |
ADB 安全 | User 版本固定为 1 |
ro.build.selinux |
selinux_prop |
SELinux 状态 | 部分受限 |
sys.oem_unlock_allowed |
oem_lock_prop |
OEM 解锁状态 | 敏感安全属性 |
persist.sys.usb.config |
system_prop |
USB 配置 | 可读但写入受限 |
5.2 详细分类说明
5.2.1 蓝牙相关属性(bluetooth_prop)
bash
# User 版本无法获取的蓝牙属性
$ adb shell getprop persist.bluetooth.disableabsvol
# 返回空值
$ adb shell getprop persist.bluetooth.btsnoopdefaultmode
# 返回空值
$ adb shell getprop persist.bluetooth.snoop_log_mode
# 返回空值
原因 :persist.bluetooth.* 映射到 bluetooth_prop 类型,shell 域没有读取权限。
5.2.2 NFC 相关属性(nfc_prop)
bash
# User 版本无法获取的 NFC 属性
$ adb shell getprop persist.nfc.fw_download_enabled
# 返回空值
$ adb shell getprop persist.nfc.enabled
# 返回空值(部分设备)
5.2.3 音频相关属性(audio_prop)
bash
# User 版本可能受限的音频属性
$ adb shell getprop persist.audio.fluence.voicecall
# 返回空值(部分设备)
$ adb shell getprop persist.audio.fluence.speaker
# 返回空值(部分设备)
5.2.4 相机相关属性(camera_prop)
bash
# User 版本无法获取的相机属性
$ adb shell getprop persist.camera.hal.debug
# 返回空值
$ adb shell getprop persist.camera.stats.debug
# 返回空值
5.2.5 调试属性(debug_prop)
bash
# User 版本无法获取的调试属性
$ adb shell getprop debug.sf.nobootanimation
# 返回空值
$ adb shell getprop debug.sf.latch_unsignaled
# 返回空值
$ adb shell getprop debug.hwui.renderer
# 返回空值
5.3 验证属性是否受限的方法
bash
# 方法一:直接获取属性
$ adb shell getprop <属性名>
# 返回空值可能是受限
# 方法二:查看 SELinux 拒绝日志
$ adb shell "dmesg | grep -i 'avc.*<属性名>'"
# 方法三:查看属性的安全上下文
$ adb shell "grep '<属性前缀>' /system/etc/selinux/plat_property_contexts"
$ adb shell "grep '<属性前缀>' /vendor/etc/selinux/vendor_property_contexts"
# 方法四:临时关闭 SELinux 测试
$ adb root
$ adb shell setenforce 0
$ adb shell getprop <属性名>
# 如果此时能获取,说明确实是 SELinux 限制
5.4 属性限制流程图
┌─────────────────────────────────────────────────────────────┐
│ 属性访问请求处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ adb shell getprop persist.bluetooth.disableabsvol │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 1. 查找属性的安全上下文 │ │
│ │ persist.bluetooth. → bluetooth_prop │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 2. 获取当前进程的 SELinux 域 │ │
│ │ adb shell → shell 域 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 3. 检查 shell 域是否有权限 │ │
│ │ get_prop(shell, bluetooth_prop) ??? │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 权限检查结果 │ │
│ │ ❌ shell 域没有 bluetooth_prop 权限 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 返回空值 + 记录 SELinux 拒绝日志 │ │
│ │ avc: denied { read } for name=... │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
六、自定义受限属性设计与实现
6.1 需求场景
假设需要添加一个自定义属性 persist.mydebug.test_property,要求:
- 在 User 版本的 adb shell 无法获取该属性值
- 只有特定的系统服务(如 system_server)可以读取
6.2 实现步骤
步骤一:定义 SELinux 属性类型
sepolicy
# device/<厂商>/<设备>/sepolicy/private/property.te
# 定义新的属性类型
type mydebug_prop, property_type;
# 或者复用已有的类型(不推荐,安全性较低)
# type mydebug_prop, debug_prop;
步骤二:配置 property_contexts
plaintext
# device/<厂商>/<设备>/sepolicy/private/property_contexts
# 将自定义属性映射到新定义的类型
persist.mydebug. u:object_r:mydebug_prop:s0
# 或者使用精确匹配(可选)
persist.mydebug.test_property u:object_r:mydebug_prop:s0 exact bool
步骤三:配置域权限(谁可以访问)
sepolicy
# device/<厂商>/<设备>/sepolicy/private/system_server.te
# 允许 system_server 读取
get_prop(system_server, mydebug_prop)
# 可选:允许写入
set_prop(system_server, mydebug_prop)
# 或者只允许特定系统应用
# device/<厂商>/<设备>/sepolicy/private/my_system_app.te
get_prop(my_system_app, mydebug_prop)
sepolicy
# device/<厂商>/<设备>/sepolicy/private/bluetooth.te
# 如果需要蓝牙服务也能读取
get_prop(bluetooth, mydebug_prop)
步骤四:配置 neverallow 规则(可选但推荐)
sepolicy
# device/<厂商>/<设备>/sepolicy/private/mydebug.te
# 明确禁止 shell 和 untrusted_app 访问
neverallow {
shell
untrusted_app
isolated_app
} mydebug_prop:file { read open };
# 或者更严格的规则
neverallow {
domain
-init
-system_server
-my_system_app
} mydebug_prop:file { read open };
步骤五:定义属性默认值(可选)
makefile
# device/<厂商>/<设备>/system.prop
# 设置默认值
persist.mydebug.test_property=false
6.3 完整示例
目录结构
device/<厂商>/<设备>/sepolicy/
├── private/
│ ├── property.te # 定义属性类型
│ ├── property_contexts # 属性到类型的映射
│ ├── system_server.te # system_server 权限
│ ├── mydebug.te # neverallow 规则
│ └── my_system_app.te # 自定义系统应用权限
└── public/
└── property.te # 公共类型声明(如果需要)
文件内容
property.te
sepolicy
# 定义自定义属性类型
type mydebug_prop, property_type;
# 定义更细分的类型
type mydebug_secure_prop, property_type;
property_contexts
plaintext
# 前缀匹配
persist.mydebug. u:object_r:mydebug_prop:s0
# 精确匹配 + 类型注解
persist.mydebug.test_property u:object_r:mydebug_prop:s0 exact bool
persist.mydebug.secure_value u:object_r:mydebug_secure_prop:s0 exact string
system_server.te
sepolicy
# 允许 system_server 读取 mydebug_prop
get_prop(system_server, mydebug_prop)
# 允许 system_server 读写 mydebug_prop
set_prop(system_server, mydebug_prop)
# 允许读取安全属性
get_prop(system_server, mydebug_secure_prop)
mydebug.te
sepolicy
# 禁止 shell 和普通应用访问
neverallow {
shell
untrusted_app
isolated_app
} mydebug_prop:file { read open getattr };
# 安全属性更严格:只允许 system_server
neverallow {
domain
-init
-system_server
} mydebug_secure_prop:file { read open };
6.4 验证实现
bash
# 编译并刷入系统后验证
# 1. shell 域尝试读取(应该失败)
$ adb shell getprop persist.mydebug.test_property
# 返回空值
# 2. 查看 SELinux 拒绝日志
$ adb shell "dmesg | grep -i 'avc.*mydebug'"
# 应该看到类似:
# avc: denied { read } for name="persist.mydebug.test_property"
# scontext=u:r:shell:s0 tcontext=u:object_r:mydebug_prop:s0
# 3. 从 system_server 读取(应该成功)
# 可以通过 dumpsys 或自定义 API 验证
# 4. 临时关闭 SELinux 测试
$ adb root
$ adb shell setenforce 0
$ adb shell getprop persist.mydebug.test_property
# 此时应该能读取到值
6.5 使用自定义属性
Java 代码(需要系统权限)
java
// 在 system_server 或系统应用中使用
public class MyDebugHelper {
private static final String PROP_NAME = "persist.mydebug.test_property";
/**
* 读取属性值
* 需要在 system_server 或有 system 权限的应用中运行
*/
public static boolean isDebugEnabled() {
return SystemProperties.getBoolean(PROP_NAME, false);
}
/**
* 设置属性值
*/
public static void setDebugEnabled(boolean enabled) {
SystemProperties.set(PROP_NAME, enabled ? "true" : "false");
}
}
Native 代码
cpp
#include <cutils/properties.h>
// 读取属性
bool isDebugEnabled() {
char value[PROP_VALUE_MAX] = {0};
property_get("persist.mydebug.test_property", value, "false");
return strcmp(value, "true") == 0;
}
// 设置属性
void setDebugEnabled(bool enabled) {
property_set("persist.mydebug.test_property", enabled ? "true" : "false");
}
6.6 设计原则
┌─────────────────────────────────────────────────────────────┐
│ 自定义属性安全设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 最小权限原则 │
│ - 只给真正需要的域授权 │
│ - 优先使用 neverallow 明确禁止不需要的域 │
│ │
│ 2. 类型隔离原则 │
│ - 敏感属性使用独立的类型(如 mydebug_secure_prop) │
│ - 不要复用已有的宽松类型 │
│ │
│ 3. 命名规范 │
│ - 使用清晰的前缀标识属性来源 │
│ - 如:persist.<厂商>.<模块>.<属性名> │
│ │
│ 4. 类型注解 │
│ - 使用 exact bool/int/string 提高类型安全 │
│ - 便于生成类型安全的 API │
│ │
│ 5. 审计日志 │
│ - 在 SELinux 强制模式下测试验证 │
│ - 检查是否有意外的拒绝日志 │
│ │
└─────────────────────────────────────────────────────────────┘
六、源码深入分析
5.1 init 进程中的属性服务
5.1.1 属性服务启动流程
cpp
// system/core/init/init.cpp
int main(int argc, char** argv) {
// ...
// 初始化属性服务
property_init();
// 加载属性定义文件
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_VENDOR_BUILD);
load_properties_from_file(PROP_PATH_ODM_BUILD);
// 启动属性服务
start_property_service();
// ...
}
5.1.2 属性服务实现
cpp
// system/core/init/property_service.cpp
void start_property_service() {
// 创建属性服务 socket
property_set_fd = CreateSocket(PROP_SERVICE_NAME,
SOCK_STREAM | SOCK_CLOEXEC,
0666,
0, 0,
&error);
if (property_set_fd == -1) {
LOG(ERROR) << "Failed to create socket: " << error;
return;
}
// 监听连接
listen(property_set_fd, 8);
// 注册到 epoll
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
// 处理属性获取请求
static void handle_property_get(SocketConnection& socket) {
std::string name;
std::string value;
// 接收属性名
if (!socket.RecvString(&name)) {
return;
}
// 检查属性访问权限(关键点)
if (!is_property_gettable(name)) {
// 属性不可获取,返回空值
value = "";
} else {
// 正常获取属性值
value = android::base::GetProperty(name, "");
}
// 发送属性值
socket.SendString(value);
}
5.2 属性可获取性检查
cpp
// system/core/init/property_service.cpp
// 检查属性是否可获取
static bool is_property_gettable(const std::string& name) {
// 1. 检查 SELinux 权限
if (!CheckSELinuxAccess(name, "get")) {
return false;
}
// 2. 检查构建类型限制
if (IsProductionBuild() && IsRestrictedProperty(name)) {
return false;
}
return true;
}
// 检查是否为受限属性
static bool IsRestrictedProperty(const std::string& name) {
// 检查前缀匹配
static const char* restricted_prefixes[] = {
"debug.",
"ro.debuggable",
// ... 其他受限前缀
};
for (const char* prefix : restricted_prefixes) {
if (android::base::StartsWith(name, prefix)) {
return true;
}
}
return false;
}
5.3 Bionic 层属性获取
cpp
// bionic/libc/bionic/system_property_api.cpp
// 系统属性获取 API
int __system_property_get(const char* name, char* value) {
// 查找属性
const prop_info* pi = __system_property_find(name);
if (pi != nullptr) {
// 读取属性值
__system_property_read_callback(pi,
[](void* cookie, const char*, const char* value, uint32_t) {
strcpy(static_cast<char*>(cookie), value);
},
value);
return strlen(value);
}
// 属性不存在,返回空
value[0] = '\0';
return 0;
}
5.4 Java 层属性获取
java
// frameworks/base/core/java/android/os/SystemProperties.java
public class SystemProperties {
/**
* 获取系统属性值
* @param key 属性名称
* @return 属性值,不存在时返回空字符串
*/
@NonNull
public static String get(@NonNull String key) {
// 调用 native 方法
return native_get(key);
}
/**
* 获取系统属性值,带默认值
*/
@NonNull
public static String get(@NonNull String key, @Nullable String def) {
if (def == null) def = "";
return native_get(key, def);
}
// Native 方法声明
@FastNative
private static native String native_get(String key);
@FastNative
private static native String native_get(String key, String def);
}
对应的 Native 实现:
cpp
// frameworks/base/core/jni/android_os_SystemProperties.cpp
static jstring SystemProperties_getSS(JNIEnv* env, jclass clazz,
jstring keyJ, jstring defJ) {
// 转换 Java 字符串
ScopedUtfChars key(env, keyJ);
if (key.c_str() == NULL) {
return NULL;
}
// 获取属性值
char buf[PROPERTY_VALUE_MAX];
const char* def = defJ ? env->GetStringUTFChars(defJ, NULL) : "";
// 调用 __system_property_get
int len = __system_property_get(key.c_str(), buf);
if (len <= 0) {
// 属性不存在或不可访问,返回默认值
if (defJ) {
jstring result = env->NewStringUTF(def);
env->ReleaseStringUTFChars(defJ, def);
return result;
}
return env->NewStringUTF("");
}
return env->NewStringUTF(buf);
}
七、SELinux 权限控制详解(核心机制)详解
7.1 属性读取权限的真实机制
在 Android 14/15 中,SELinux 对属性读取实施了严格的权限控制。这与早期的理解不同:属性读取并非直接从共享内存无权限获取,而是需要经过 SELinux 权限检查。
7.1.1 属性读取的 SELinux 检查点
cpp
// bionic/libc/bionic/system_property_api.cpp
int __system_property_get(const char* name, char* value) {
// 1. 查找属性
const prop_info* pi = __system_property_find(name);
if (pi != nullptr) {
// 2. 读取属性前,内核会检查 SELinux 权限
// 这一步是通过文件系统权限实现的
__system_property_read_callback(pi, ...);
return strlen(value);
}
value[0] = '\0';
return 0;
}
7.1.2 属性文件的 SELinux 标签
bash
# 查看属性文件的 SELinux 上下文
$ adb shell ls -Z /dev/__properties__/
# 示例输出
# u:object_r:properties_device:s0 ...
# 每个属性文件都有对应的安全上下文
7.2 property_contexts 文件详解
7.2.1 文件位置
property_contexts 文件分布在多个位置,最终会被合并:
bash
# 系统属性上下文
/system/etc/selinux/plat_property_contexts
/system/etc/selinux/system_property_contexts
# 厂商属性上下文
/vendor/etc/selinux/vendor_property_contexts
/vendor/etc/selinux/nonplat_property_contexts
# 产品属性上下文
/product/etc/selinux/product_property_contexts
# ODM 属性上下文
/odm/etc/selinux/odm_property_contexts
7.2.2 property_contexts 语法格式
plaintext
# 基本语法格式
# 属性名模式 SELinux 安全上下文 [类型注解]
# 格式说明:
# 1. 属性名模式:支持通配符和精确匹配
# 2. SELinux 上下文:u:object_r:类型:s0
# 3. 类型注解(可选):exact string/bool/int 等
7.2.3 实际配置示例解析
根据您提供的配置:
plaintext
# 示例 1:前缀匹配
persist.audio. u:object_r:audio_prop:s0
persist.bluetooth. u:object_r:bluetooth_prop:s0
persist.nfc. u:object_r:nfc_prop:s0
# 示例 2:精确匹配 + 类型注解
persist.bluetooth.a2dp_offload.cap u:object_r:bluetooth_a2dp_offload_prop:s0 exact string
persist.bluetooth.a2dp_offload.disabled u:object_r:bluetooth_a2dp_offload_prop:s0 exact bool
详细解析:
| 配置项 | 匹配规则 | SELinux 类型 | 类型注解 | 说明 |
|---|---|---|---|---|
persist.audio. |
前缀匹配 | audio_prop |
无 | 匹配所有 persist.audio.* 属性 |
persist.bluetooth. |
前缀匹配 | bluetooth_prop |
无 | 匹配所有 persist.bluetooth.* 属性(除非被更具体的规则覆盖) |
persist.bluetooth.a2dp_offload.cap |
精确匹配 | bluetooth_a2dp_offload_prop |
exact string |
仅匹配此属性,类型为字符串 |
persist.bluetooth.a2dp_offload.disabled |
精确匹配 | bluetooth_a2dp_offload_prop |
exact bool |
仅匹配此属性,类型为布尔值 |
7.2.4 类型注解详解(Android 10+)
Android 10 引入了类型注解,用于系统属性的类型安全:
plaintext
# 类型注解语法
属性名 SELinux上下文 exact <类型>
# 支持的类型注解:
# - exact bool 布尔类型(true/false)
# - exact int 整数类型
# - exact string 字符串类型
# - exact enum 枚举类型(需要指定值列表)
类型注解的作用:
┌─────────────────────────────────────────────────────────────┐
│ 类型注解的作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 编译时类型检查 │
│ - 确保属性值与声明的类型匹配 │
│ - 防止类型错误 │
│ │
│ 2. 生成类型安全的 API(Android 10+) │
│ - 系统会自动生成 Java/C++ API │
│ - 开发者可以通过类型安全的方法访问属性 │
│ │
│ 3. 属性文档化 │
│ - 明确属性的预期类型 │
│ - 便于代码理解和维护 │
│ │
└─────────────────────────────────────────────────────────────┘
代码示例:
java
// Android 10+ 提供的类型安全 API
// 位于 frameworks/base/core/java/android/sysprop/BluetoothProperties.java
public class BluetoothProperties {
// 自动生成的类型安全方法
public static boolean isA2dpOffloadDisabled() {
// 对应:persist.bluetooth.a2dp_offload.disabled exact bool
return SystemProperties.getBoolean(
"persist.bluetooth.a2dp_offload.disabled", false);
}
public static void setA2dpOffloadDisabled(boolean value) {
SystemProperties.set(
"persist.bluetooth.a2dp_offload.disabled",
value ? "true" : "false");
}
public static String getA2dpOffloadCap() {
// 对应:persist.bluetooth.a2dp_offload.cap exact string
return SystemProperties.get("persist.bluetooth.a2dp_offload.cap", "");
}
}
7.2.5 属性匹配优先级规则
┌─────────────────────────────────────────────────────────────┐
│ 属性匹配优先级(从高到低) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 精确匹配(无通配符) │
│ persist.bluetooth.a2dp_offload.disabled │
│ ↓ │
│ 使用 bluetooth_a2dp_offload_prop │
│ │
│ 2. 更长前缀匹配 │
│ persist.bluetooth.a2dp_offload. │
│ ↓ │
│ 覆盖更短的 persist.bluetooth. │
│ │
│ 3. 较短前缀匹配 │
│ persist.bluetooth. │
│ ↓ │
│ 作为默认规则匹配所有未被精确匹配的属性 │
│ │
└─────────────────────────────────────────────────────────────┘
示例:
persist.bluetooth.disableabsvol
→ 匹配 persist.bluetooth. → bluetooth_prop
persist.bluetooth.a2dp_offload.disabled
→ 精确匹配 → bluetooth_a2dp_offload_prop
persist.bluetooth.a2dp_offload.custom
→ 匹配 persist.bluetooth. → bluetooth_prop
(因为没有 a2dp_offload.custom 的精确匹配)
7.2.6 属性作用域与访问权限
关键发现:属性的作用域由 SELinux 类型决定,而非属性名称前缀。
plaintext
# 不同 SELinux 类型对应不同的访问权限
# bluetooth_prop 类型
persist.bluetooth. u:object_r:bluetooth_prop:s0
# 谁可以访问?取决于哪些域有 get_prop(*, bluetooth_prop) 权限
# bluetooth_a2dp_offload_prop 类型
persist.bluetooth.a2dp_offload.cap u:object_r:bluetooth_a2dp_offload_prop:s0
persist.bluetooth.a2dp_offload.disabled u:object_r:bluetooth_a2dp_offload_prop:s0
# 可能有不同的访问权限配置
SELinux 类型到访问权限的映射:
sepolicy
# system/sepolicy/private/bluetooth.te
# bluetooth 域可以访问 bluetooth_prop
get_prop(bluetooth, bluetooth_prop)
set_prop(bluetooth, bluetooth_prop)
# bluetooth 域可以访问 bluetooth_a2dp_offload_prop
get_prop(bluetooth, bluetooth_a2dp_offload_prop)
set_prop(bluetooth, bluetooth_a2dp_offload_prop)
# system/sepolicy/private/system_server.te
# system_server 可以读取蓝牙属性
get_prop(system_server, bluetooth_prop)
get_prop(system_server, bluetooth_a2dp_offload_prop)
# shell 域 - 通常无法访问
# 没有 get_prop(shell, bluetooth_prop) 权限
7.2.7 查看设备上的实际配置
bash
# 查看系统属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts | grep bluetooth
# 查看厂商属性上下文
$ adb shell cat /vendor/etc/selinux/vendor_property_contexts | grep bluetooth
# 查看所有属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts
$ adb shell cat /vendor/etc/selinux/vendor_property_contexts
# 搜索特定属性的安全上下文
$ adb shell "grep 'persist.bluetooth' /system/etc/selinux/plat_property_contexts"
$ adb shell "grep 'persist.bluetooth' /vendor/etc/selinux/vendor_property_contexts"
7.3 如何验证 shell 域没有 bluetooth_prop 权限
7.3.1 SELinux 权限机制说明
在 SELinux 中,权限遵循"默认拒绝"原则:
┌─────────────────────────────────────────────────────────────┐
│ SELinux 权限判断机制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 默认规则:除非明确允许,否则拒绝 │
│ │
│ 要让 shell 域能读取 bluetooth_prop,必须有明确的规则: │
│ get_prop(shell, bluetooth_prop) │
│ │
│ 如果没有这条规则 → SELinux 拒绝访问 │
│ │
└─────────────────────────────────────────────────────────────┘
7.3.2 查看 shell.te 源码
源码位置 :system/sepolicy/public/shell.te 和 system/sepolicy/private/shell.te
sepolicy
# system/sepolicy/public/shell.te
# shell 域定义
type shell, domain, mlstrustedsubject;
type shell_exec, system_file_type, exec_type, file_type;
# 网络权限
net_domain(shell)
# 日志权限
read_logd(shell)
control_logd(shell)
# 文件访问权限
allow shell shell_data_file:dir create_dir_perms;
allow shell shell_data_file:file create_file_perms;
# ... 其他文件权限
# 属性读取权限 - 注意这里列出的是允许的,没有列出的就是不允许!
# shell 域可以读取以下类型的属性:
get_prop(shell, shell_prop) # shell 相关属性
get_prop(shell, default_prop) # 默认属性
get_prop(shell, log_prop) # 日志属性
get_prop(shell, system_prop) # 系统属性
get_prop(shell, boot_status_prop) # 启动状态
get_prop(shell, toolbox_prop) # toolbox 属性
# ⚠️ 注意:没有 get_prop(shell, bluetooth_prop)
# 因此 shell 无法读取 bluetooth_prop 类型的属性
关键理解 :SELinux 使用白名单机制,只有明确列出的权限才被允许。bluetooth_prop 没有在 shell 的权限列表中,所以被拒绝。
7.3.3 在设备上验证方法
方法一:查看 SELinux 拒绝日志
bash
# 读取属性时查看 dmesg
$ adb shell "dmesg | grep -i 'avc.*bluetooth.*prop'"
# 或查看 logcat
$ adb shell "logcat -b all | grep -i 'avc denied.*bluetooth_prop'"
# 典型的拒绝日志示例:
# avc: denied { read } for name="persist.bluetooth.disableabsvol"
# dev="properties" ino=12345 scontext=u:r:shell:s0
# tcontext=u:object_r:bluetooth_prop:s0 tclass=file permissive=0
日志解读:
scontext=u:r:shell:s0:源上下文是 shell 域tcontext=u:object_r:bluetooth_prop:s0:目标上下文是 bluetooth_prop 类型denied { read }:读取权限被拒绝permissive=0:SELinux 处于强制模式,拒绝生效
方法二:临时关闭 SELinux 验证
bash
# 查看当前 SELinux 模式
$ adb shell getenforce
Enforcing # 强制模式
# 临时设置为宽容模式(需要 root)
$ adb root
$ adb shell setenforce 0
# 再次尝试读取属性
$ adb shell getprop persist.bluetooth.disableabsvol
# 如果此时能读取,说明确实是 SELinux 限制
# 恢复强制模式
$ adb shell setenforce 1
方法三:查看编译后的策略文件
bash
# 查看系统属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts | grep bluetooth
# 输出示例:
# persist.bluetooth. u:object_r:bluetooth_prop:s0
# 查看 shell 域的策略(需要 root)
$ adb root
$ adb shell sesearch --allow -s shell -c file -p read
# 或查看策略源码
$ adb shell cat /system/etc/selinux/plat_sepolicy.cil | grep -A5 "bluetooth_prop"
方法四:使用 sesearch 工具(如果可用)
bash
# 搜索 shell 域是否有 bluetooth_prop 的读取权限
$ sesearch -A -s shell -t bluetooth_prop -c file -p read
# 如果没有输出,表示没有权限
# 如果有输出,会显示类似:
# allow shell bluetooth_prop:file { read open };
7.3.4 查看哪些域有 bluetooth_prop 权限
bash
# 搜索所有可以读取 bluetooth_prop 的域
$ adb shell sesearch -A -t bluetooth_prop -c file -p read
# 典型输出:
# allow bluetooth bluetooth_prop:file { read open };
# allow system_server bluetooth_prop:file { read open };
# allow init bluetooth_prop:file { read open };
# 注意:shell 不在列表中!
7.3.5 bluetooth.te 中的权限定义
sepolicy
# system/sepolicy/private/bluetooth.te
# bluetooth 域可以访问 bluetooth_prop
get_prop(bluetooth, bluetooth_prop)
set_prop(bluetooth, bluetooth_prop)
# bluetooth 域也可以访问 bluetooth_a2dp_offload_prop
get_prop(bluetooth, bluetooth_a2dp_offload_prop)
set_prop(bluetooth, bluetooth_a2dp_offload_prop)
7.3.6 system_server.te 中的权限定义
sepolicy
# system/sepolicy/private/system_server.te
# system_server 域可以读取蓝牙属性
get_prop(system_server, bluetooth_prop)
get_prop(system_server, bluetooth_a2dp_offload_prop)
# 但在 User 版本中可能限制写入
# userdebug_or_eng(`
# set_prop(system_server, bluetooth_prop)
# ')
7.4 SELinux neverallow 规则(禁止性规则)
除了"没有明确允许就是拒绝"的默认规则外,SELinux 还有 neverallow 规则来明确禁止某些访问:
sepolicy
# system/sepolicy/public/domain.te 或相关文件
# 明确禁止非蓝牙域写入蓝牙属性
neverallow {
domain
-bluetooth
-init
-system_server
} bluetooth_prop:property_service set;
# 明确禁止 shell/untrusted_app 读取某些敏感属性
# 虽然没有 allow 规则,但 neverallow 提供了额外的安全保障
neverallow 的作用:
- 防止策略编写者意外添加不安全的权限
- 编译时检查,如果违反 neverallow 规则,编译会失败
- 提供文档化的安全边界
7.5 属性权限完整对照表
| 属性类型 (prop) | shell | untrusted_app | system_server | bluetooth | init |
|---|---|---|---|---|---|
default_prop |
✅ 读 | ✅ 读 | ✅ 读/写 | ✅ 读 | ✅ 读/写 |
system_prop |
✅ 读 | ✅ 读 | ✅ 读/写 | ✅ 读 | ✅ 读/写 |
shell_prop |
✅ 读/写 | ❌ | ✅ 读 | ✅ 读 | ✅ 读/写 |
bluetooth_prop |
❌ | ❌ | ✅ 读 | ✅ 读/写 | ✅ 读/写 |
bluetooth_a2dp_offload_prop |
❌ | ❌ | ✅ 读 | ✅ 读/写 | ✅ 读/写 |
debug_prop |
❌ (userdebug/eng 可读) | ❌ | ✅ 读 (userdebug/eng) | ✅ 读 (userdebug/eng) | ✅ 读/写 |
log_prop |
✅ 读 | ✅ 读 | ✅ 读/写 | ✅ 读 | ✅ 读/写 |
nfc_prop |
❌ | ❌ | ✅ 读 | ❌ | ✅ 读/写 |
audio_prop |
❌ | ❌ | ✅ 读/写 | ❌ | ✅ 读/写 |
说明:
- ✅ 读 = 有
get_prop权限- ✅ 读/写 = 有
get_prop和set_prop权限- ❌ = 无权限(SELinux 默认拒绝)
7.6 SELinux 编译宏详解
makefile
# system/sepolicy/public/te_macros
# 条件编译宏定义
define(`userdebug_or_eng', ifelse(target_build_variant, `user', , $1))
# 使用示例
userdebug_or_eng(`
# 仅在 userdebug/eng 版本执行的规则
allow shell debug_prop:file { read open };
')
7.7 属性权限层级图
┌─────────────────────────────────────────────────────────────┐
│ 属性访问权限层级(按权限从高到低) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ init 域 │ │
│ │ - 可读写所有属性 │ │
│ │ - 属性服务的拥有者 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ system_server / system_app 域 │ │
│ │ - 可读取大部分系统属性 │ │
│ │ - 可写入部分系统属性 │ │
│ │ - 可读取 bluetooth_prop │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ bluetooth 域 │ │
│ │ - 可读写蓝牙相关属性 │ │
│ │ - 可读取 bluetooth_prop │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ shell 域(adb shell) │ │
│ │ - 可读取 default_prop、system_prop、log_prop │ │
│ │ - ❌ 不可读取 bluetooth_prop(User 版本) │ │
│ │ - ❌ 不可读取 debug_prop(User 版本) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ untrusted_app 域(普通应用) │ │
│ │ - 仅可读取少量公开属性 │ │
│ │ - ❌ 不可读取 bluetooth_prop │ │
│ │ - ❌ 不可读取 debug_prop │ │
│ │ - ❌ 不可读取大部分 persist.* 属性 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
6.1 SELinux 属性上下文
SELinux 为每个系统属性定义了安全上下文:
plaintext
# system/sepolicy/private/property_contexts
# 调试属性
debug. u:object_r:debug_prop:s0
# 蓝牙相关
bluetooth. u:object_r:bluetooth_prop:s0
ro.bluetooth. u:object_r:bluetooth_prop:s0
# 系统属性
ro. u:object_r:default_prop:s0
sys. u:object_r:system_prop:s0
persist. u:object_r:persist_prop:s0
6.2 SELinux 权限规则
sepolicy
# system/sepolicy/private/property.te
# 调试属性类型定义
type debug_prop, property_type;
# 蓝牙属性类型定义
type bluetooth_prop, property_type;
type bluetooth_debug_prop, property_type;
# 权限规则
# 仅特定域可以读取 debug_prop
userdebug_or_eng(`
allow domain debug_prop:file { read open };
')
# User 版本限制
neverallow { domain -init -system_server -dumpstate } debug_prop:file { read open };
6.3 域权限配置
sepolicy
# system/sepolicy/private/system_server.te
# 允许 system_server 读取蓝牙调试属性(仅 userdebug/eng)
userdebug_or_eng(`
allow system_server bluetooth_debug_prop:file { read open };
')
# User 版本禁止
neverallow system_server bluetooth_debug_prop:file { read open };
6.4 SELinux 策略编译宏
userdebug_or_eng 宏的实际定义 (位于 system/sepolicy/public/te_macros):
m4
#####################################
# SELinux rules which apply only to userdebug or eng builds
#
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))
其他相关条件宏(并非都在 AOSP 中定义,部分由厂商自定义):
m4
# 可能的条件宏定义
define(`user_build', ifelse(target_build_variant, `user', $1))
define(`eng_build', ifelse(target_build_variant, `eng', $1))
define(`userdebug_build', ifelse(target_build_variant, `userdebug', $1))
m4 ifelse 语法说明:
ifelse(A, B, C, D)- 如果 A 等于 B,返回 C,否则返回 Difelse(A, B, C, ifelse(D, E, F, G))- 嵌套条件判断
八、构建类型差异对比
8.1 构建类型说明
Android 构建类型分为三种:
| 构建类型 | 说明 | 典型用途 |
|---|---|---|
eng |
工程版本 | 内部开发、调试 |
userdebug |
调试版本 | 测试、验证 |
user |
用户版本 | 正式发布、量产 |
8.2 关键差异:userdebug vs user 的 SELinux 策略
您观察到的现象:
- userdebug 版本:
adb shell getprop persist.bluetooth.*可以读取 - user 版本:
adb shell getprop persist.bluetooth.*返回空值
原因分析 :这正是 userdebug_or_eng 宏的作用!
8.2.1 SELinux 策略的条件编译
SELinux 策略源码中使用 userdebug_or_eng 宏来定义条件权限。
userdebug_or_eng 宏的真实定义 (位于 system/sepolicy/public/te_macros):
m4
#####################################
# SELinux rules which apply only to userdebug or eng builds
#
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))
宏展开逻辑:
- 如果
target_build_variant = eng→ 展开为$1 - 如果
target_build_variant = userdebug→ 展开为$1 - 如果
target_build_variant = user→ 返回空(规则被移除)
重要说明 :在标准 AOSP 中,shell 域对 bluetooth_prop 的读取权限并非 通过 userdebug_or_eng 条件授予的。实际上:
- 标准 AOSP 中 shell 域没有 bluetooth_prop 权限(所有版本)
- 如果您在 userdebug 版本能读取
persist.bluetooth.*,可能是:- 厂商定制的 SELinux 策略添加了额外权限
- 设备使用了 root 权限或临时关闭了 SELinux
- 厂商在
device/<厂商>/<设备>/sepolicy/中添加了get_prop(shell, bluetooth_prop)
AOSP 中典型的 userdebug_or_eng 使用场景:
sepolicy
# system/sepolicy/private/shell.te
# shell 域在 userdebug/eng 版本获得的额外权限
userdebug_or_eng(`
# 调试文件系统访问
allow shell debugfs_tracing_debug:file rw_file_perms;
allow shell debugfs_tracing:file rw_file_perms;
')
# system/sepolicy/private/domain.te
# 所有域在 userdebug/eng 版本获得的权限
userdebug_or_eng(`
allow domain debugfs_tracing_debug:file rw_file_perms;
')
8.2.2 userdebug_or_eng 宏定义
makefile
# system/sepolicy/public/te_macros
# 宏定义:仅在 userdebug 和 eng 版本生效
define(`userdebug_or_eng',
ifelse(target_build_variant, `user', , $1)
)
# 展开说明:
# 如果 target_build_variant == "user",则不执行 $1
# 如果 target_build_variant == "userdebug" 或 "eng",则执行 $1
8.2.3 构建类型与策略编译的关系
┌─────────────────────────────────────────────────────────────┐
│ SELinux 策略编译流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 源码(shell.te) │
│ ┌─────────────────────────────────────────────┐ │
│ │ # 基础权限(所有版本都有) │ │
│ │ get_prop(shell, system_prop) │ │
│ │ get_prop(shell, default_prop) │ │
│ │ │ │
│ │ # 条件权限 │ │
│ │ userdebug_or_eng(` │ │
│ │ get_prop(shell, bluetooth_prop) │ │
│ │ get_prop(shell, debug_prop) │ │
│ │ ') │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 构建类型判断 │ │
│ │ target_build_variant = ? │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ eng │ │userdebug │ │ user │ │
│ └────┬────┘ └────┬─────┘ └────┬────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────────┐ │
│ │ │ │ 编译结果: │ │
│ │ │ │ get_prop(shell, │ │
│ │ │ │ system_prop) │ │
│ │ │ │ get_prop(shell, │ │
│ │ │ │ default_prop) │ │
│ │ │ │ ❌ 没有 bluetooth_prop│ │
│ │ │ └─────────────────────┘ │
│ │ │ │
│ └──────────────┴──────────────┐ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 编译结果(eng/userdebug): │ │
│ │ get_prop(shell, system_prop)│ │
│ │ get_prop(shell, default_prop)│ │
│ │ ✅ get_prop(shell, │ │
│ │ bluetooth_prop) │ │
│ │ ✅ get_prop(shell, debug_prop)│ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
8.3 实际验证方法
8.3.1 查看 SELinux 策略差异
bash
# 在 userdebug 版本上查看 shell 域的权限
$ adb shell sesearch -A -s shell -c file -p read
# 输出可能包含:
# allow shell bluetooth_prop:file { read open };
# allow shell debug_prop:file { read open };
# 在 user 版本上执行相同命令
# 上述权限将不存在
8.3.2 检查构建类型
bash
# 查看当前构建类型
$ adb shell getprop ro.build.type
userdebug # 或 user
# 查看是否可调试
$ adb shell getprop ro.debuggable
1 # userdebug 版本
0 # user 版本
8.3.3 查看 SELinux 策略源码
bash
# 在设备上查看编译后的策略
$ adb shell cat /sys/fs/selinux/policy | strings | grep -i shell
# 或查看策略数据库
$ adb shell cat /sys/fs/selinux/policy
8.4 属性访问权限对比
| 属性前缀 | eng 版本 | userdebug 版本 | user 版本 |
|---|---|---|---|
ro.* |
✅ 可读 | ✅ 可读 | ✅ 可读 |
sys.* |
✅ 可读写 | ✅ 可读写 | ✅ 可读写 |
persist.sys.* |
✅ 可读写 | ✅ 可读写 | ✅ 可读写 |
persist.bluetooth.* |
✅ 可读 | ✅ 可读 | ❌ shell 无法读取 |
persist.nfc.* |
✅ 可读 | ✅ 可读 | ❌ shell 无法读取 |
persist.audio.* |
✅ 可读 | ✅ 可读 | ❌ shell 无法读取 |
debug.* |
✅ 可读写 | ✅ 可读写 | ❌ shell 无法读取 |
log.tag.* |
✅ 可读写 | ✅ 可读写 | ⚠️ 部分受限 |
说明 :userdebug 和 eng 版本的 shell 域拥有额外的属性读取权限,这些权限通过
userdebug_or_eng宏条件编译。
8.5 构建配置文件
makefile
# build/core/main.mk
# 构建类型定义
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
# Debug 构建配置
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=1 \
ro.adb.secure=0 \
persist.sys.usb.config=adb
# SELinux 策略会包含 userdebug_or_eng 中的规则
else
# User 构建配置
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=0 \
ro.adb.secure=1 \
persist.sys.usb.config=none
# SELinux 策略不包含 userdebug_or_eng 中的规则
endif
8.6 为什么要有这个差异?
┌─────────────────────────────────────────────────────────────┐
│ 构建类型差异的安全考量 │
├─────────────────────────────────────────────────────────────┤
│ │
│ userdebug/eng 版本: │
│ ├── 面向开发者和测试人员 │
│ ├── 需要调试能力来定位问题 │
│ ├── 需要访问敏感属性来调试系统行为 │
│ └── 设备通常在受控环境中使用 │
│ │
│ user 版本: │
│ ├── 面向最终用户 │
│ ├── 需要最大化的安全性 │
│ ├── 防止恶意应用或攻击者获取敏感信息 │
│ ├── 符合安全认证要求 │
│ └── 设备在不可控的公开环境中使用 │
│ │
│ 安全原则:最小权限原则 │
│ - User 版本只保留必要的基础权限 │
│ - 移除所有调试和开发相关的额外权限 │
│ │
└─────────────────────────────────────────────────────────────┘
8.7 userdebug_or_eng 宏的定义位置
8.7.1 宏定义来源
userdebug_or_eng 宏是通过 m4 宏处理器 在 SELinux 策略编译时进行条件展开的。其定义位置在不同的 Android 版本中有所变化:
| Android 版本 | 定义位置 | 说明 |
|---|---|---|
| Android 8.x - 10 | system/sepolicy/public/te_macros |
在 te_macros 文件中直接定义 |
| Android 11+ | 构建系统动态注入 | 通过 BOARD_SEPOLICY_M4DEFS 传递 |
重要说明 :在较新的 Android 版本中,您查看 system/sepolicy/Android.mk 时可能只看到 BOARD_SEPOLICY_M4DEFS 的处理代码:
makefile
# system/sepolicy/Android.mk
# 您看到的代码
ifdef BOARD_SEPOLICY_M4DEFS
LOCAL_ADDITIONAL_M4DEFS := $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS))
else
LOCAL_ADDITIONAL_M4DEFS :=
endif
这是正常的!userdebug_or_eng 宏的实际定义是在 构建系统的其他位置 注入到 BOARD_SEPOLICY_M4DEFS 变量中的。
8.7.2 宏定义的真实注入流程
┌─────────────────────────────────────────────────────────────┐
│ userdebug_or_eng 宏的完整注入流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 构建系统初始化(build/core/main.mk 或类似文件) │
│ ├── 读取 TARGET_BUILD_VARIANT │
│ │ ├── user │
│ │ ├── userdebug │
│ │ └── eng │
│ │ │
│ └── 根据 BUILD_VARIANT 设置 BOARD_SEPOLICY_M4DEFS │
│ │
│ 2. 可能的定义位置(不同版本不同): │
│ │
│ 方式A - 在 build/core/config.mk 或 main.mk 中: │
│ ┌─────────────────────────────────────────────┐ │
│ │ ifeq ($(TARGET_BUILD_VARIANT),user) │ │
│ │ # User 版本:宏展开为空 │ │
│ │ BOARD_SEPOLICY_M4DEFS += \ │ │
│ │ userdebug_or_eng=\# │ │
│ │ else │ │
│ │ # userdebug/eng 版本:宏展开为参数 │ │
│ │ BOARD_SEPOLICY_M4DEFS += \ │ │
│ │ userdebug_or_eng=\$1 │ │
│ │ endif │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 方式B - 在 system/sepolicy/definitions 稡块中定义 │
│ (Android 12+ 使用 Soong/Blueprint) │
│ │
│ 方式C - 预定义在 te_macros 文件中(旧版本): │
│ ┌─────────────────────────────────────────────┐ │
│ │ # system/sepolicy/public/te_macros │ │
│ │ define(`userdebug_or_eng', │ │
│ │ ifelse(target_build_variant, `user', , $1) │ │
│ │ ) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 3. Android.mk 处理 BOARD_SEPOLICY_M4DEFS │
│ ┌─────────────────────────────────────────────┐ │
│ │ ifdef BOARD_SEPOLICY_M4DEFS │ │
│ │ LOCAL_ADDITIONAL_M4DEFS := \ │ │
│ │ $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS)) │ │
│ │ endif │ │
│ │ # 生成类似:-Duserdebug_or_eng=$1 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 4. m4 处理器编译策略文件 │
│ m4 ${LOCAL_ADDITIONAL_M4DEFS} *.te → policy.conf │
│ │
│ 5. 策略编译结果 │
│ ├── User 版本:userdebug_or_eng(...) → (空) │
│ └── userdebug/eng:userdebug_or_eng($1) → $1 │
│ │
└─────────────────────────────────────────────────────────────┘
8.7.3 如何查找宏的实际定义位置
bash
# 方法一:搜索整个源码树
# 在 AOSP 源码根目录执行
grep -r "userdebug_or_eng" build/core/
grep -r "userdebug_or_eng" system/sepolicy/
# 方法二:查看 te_macros 文件
cat system/sepolicy/public/te_macros | grep -A5 "userdebug_or_eng"
# 方法三:查看编译过程中的 m4 参数
# 编译时查看输出
make sepolicy -j8 2>&1 | grep -i "m4def"
# 方法四:查看最终生成的策略
# 编译后在 out 目录查看
cat out/target/product/<device>/obj/ETC/sepolicy_intermediates/policy.conf
# 检查 userdebug_or_eng 规则是否被展开
8.7.4 Android 12+ 的变化(Soong 构建系统)
从 Android 12 开始,SELinux 策略编译逐渐迁移到 Soong 构建系统:
bp
// system/sepolicy/Android.bp (简化示例)
se_policy_conf {
name: "sepolicy",
srcs: ["**/*.te"],
m4defs: [
// 条件宏可能在这里定义
// 或者从全局配置中继承
],
}
关键点:无论宏在哪里定义,其核心机制相同:
- 构建类型判断 :检查
TARGET_BUILD_VARIANT - 宏展开控制 :
- user 版本:
userdebug_or_eng(...)→ 空(规则被移除) - userdebug/eng 版本:
userdebug_or_eng(...)→ 展开为参数内容
- user 版本:
8.7.5 te_macros 文件中的典型定义(旧版本参考)
m4
# system/sepolicy/public/te_macros (旧版本可能存在的定义)
#####################################
# userdebug_or_eng(rules)
#
# 仅在 userdebug 和 eng 构建中包含 rules
# User 构建中这些规则被忽略
#
# 注意:target_build_variant 是 m4 处理时传入的变量
define(`userdebug_or_eng',
ifelse(target_build_variant, `user', , $1)
)
# 类似的条件宏:
# eng_only(rules) - 仅 eng 版本
# user_only(rules) - 仅 user 版本
# userdebug_only(rules) - 仅 userdebug 版本
m4 语法解释:
ifelse(A, B, C, D)- 如果 A 等于 B,返回 C,否则返回 Difelse(target_build_variant,user', , 1)\` - 如果构建类型是 user,返回空,否则返回第一个参数 1
8.7.3 m4 宏处理示例
原始策略文件:
sepolicy
# system/sepolicy/private/shell.te
userdebug_or_eng(`
get_prop(shell, bluetooth_prop)
get_prop(shell, debug_prop)
')
m4 处理过程:
bash
# User 版本编译
m4 -Duserdebug_or_eng=ignore_arg shell.te
# 结果:userdebug_or_eng 中的内容被移除
# (空输出)
# userdebug/eng 版本编译
m4 -Duserdebug_or_eng=echo_arg shell.te
# 结果:userdebug_or_eng 中的内容被保留
get_prop(shell, bluetooth_prop)
get_prop(shell, debug_prop)
8.7.4 构建系统中的相关文件
| 文件路径 | 作用 |
|---|---|
system/sepolicy/Android.mk |
定义构建规则,注入 m4 宏 |
system/sepolicy/public/te_macros |
定义常用的 SELinux 宏模板 |
build/core/main.mk |
设置 TARGET_BUILD_VARIANT |
device/<厂商>/<设备>/BoardConfig.mk |
设备特定的 sepolicy 配置 |
8.7.5 查看 te_macros 中的相关宏
m4
# system/sepolicy/public/te_macros
#####################################
# userdebug_or_eng(rules)
#
# 仅在 userdebug 和 eng 构建中包含 rules
# User 构建中这些规则被忽略
#
# 这个宏实际上由构建系统动态定义
# 不是在 te_macros 文件中硬编码
# 类似的条件宏还有:
# - eng_only(rules) - 仅 eng 版本
# - user_only(rules) - 仅 user 版本
# - userdebug_only(rules) - 仅 userdebug 版本
8.7.6 自定义条件宏
厂商可以定义自己的条件宏:
makefile
# device/<厂商>/<设备>/BoardConfig.mk
# 自定义 m4 宏定义
BOARD_SEPOLICY_M4DEFS += \
my_custom_debug_rule=echo_arg
# 或在特定条件下定义
ifeq ($(MY_CUSTOM_FEATURE),true)
BOARD_SEPOLICY_M4DEFS += \
-Dmy_feature_enabled=true
endif
sepolicy
# device/<厂商>/<设备>/sepolicy/private/custom.te
my_custom_debug_rule(`
allow shell custom_prop:file { read open };
')
8.7.7 验证构建类型
bash
# 方法一:检查 ro.build.type 属性
$ adb shell getprop ro.build.type
userdebug # 或 user / eng
# 方法二:检查 ro.debuggable 属性
$ adb shell getprop ro.debuggable
1 # userdebug/eng
0 # user
# 方法三:检查编译输出
# 在源码编译输出中查看
$ cat out/target/product/<device>/obj/ETC/sepolicy_intermediates/policy.conf
# 搜索 get_prop(shell, bluetooth_prop) 是否存在
# 方法四:在设备上查看实际策略
$ adb shell cat /sys/fs/selinux/policy | strings | grep "bluetooth_prop"
8.7.8 完整的构建类型与权限关系图
┌─────────────────────────────────────────────────────────────┐
│ 构建类型 → SELinux 策略 → 属性权限 关系图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 构建配置 │
│ ┌─────────────────────────────────────────────┐ │
│ │ TARGET_BUILD_VARIANT = userdebug │ │
│ │ (定义在 build/core/main.mk 或 lunch 选择) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Android.mk 构建规则 │ │
│ │ ifeq ($(TARGET_BUILD_VARIANT),user) │ │
│ │ M4DEFS += -Duserdebug_or_eng=ignore │ │
│ │ else │ │
│ │ M4DEFS += -Duserdebug_or_eng=expand │ │
│ │ endif │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ m4 宏处理器编译 *.te 文件 │ │
│ │ m4 ${M4DEFS} shell.te → shell.te.processed │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ userdebug/eng │ │ user │ │
│ │ 编译结果: │ │ 编译结果: │ │
│ │ get_prop(shell, │ │ (无此规则) │ │
│ │ bluetooth_prop)│ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ adb shell 可以 │ │ adb shell 无法 │ │
│ │ 读取 bluetooth │ │ 读取 bluetooth │ │
│ │ 属性 │ │ 属性(返回空) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
以下是一些在 AOSP 中实际存在的条件权限规则:
sepolicy
# system/sepolicy/private/domain.te
# 所有域在 userdebug/eng 版本获得额外权限
userdebug_or_eng(`
# 堆分析权限
can_profile_heap_userdebug_or_eng({
domain
-bpfloader
-init
-kernel
})
')
# system/sepolicy/private/shell.te
# shell 域在 userdebug/eng 版本的额外权限
userdebug_or_eng(`
# 调试文件系统访问
allow shell debugfs_tracing_debug:file rw_file_perms;
')
# system/sepolicy/private/app.te
# 应用域在 userdebug/eng 版本的额外权限
userdebug_or_eng(`
# 与调试相关的权限
allow appdomain zygote:fifo_file write;
')
7.1 构建类型说明
Android 构建类型分为三种:
| 构建类型 | 说明 | 典型用途 |
|---|---|---|
eng |
工程版本 | 内部开发、调试 |
userdebug |
调试版本 | 测试、验证 |
user |
用户版本 | 正式发布、量产 |
8.2 属性访问权限对比
| 属性前缀 | eng 版本 | userdebug 版本 | user 版本 |
|---|---|---|---|
ro.* |
✅ 可读 | ✅ 可读 | ✅ 可读 |
sys.* |
✅ 可读写 | ✅ 可读写 | ✅ 可读写 |
persist.* |
✅ 可读写 | ✅ 可读写 | ✅ 可读写 |
debug.* |
✅ 可读写 | ✅ 可读写 | ⚠️ 部分受限(SELinux) |
log.tag.* |
✅ 可读写 | ✅ 可读写 | ⚠️ 部分受限 |
注意 :
persist.bluetooth.disableabsvol使用persist.前缀,在所有构建类型中都可以正常访问。
7.3 构建配置文件
makefile
# build/core/main.mk
# 构建类型定义
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
# Debug 构建配置
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=1 \
ro.adb.secure=0 \
persist.sys.usb.config=adb
# 启用调试属性
ADDITIONAL_DEFAULT_PROPERTIES += \
debug.sf.nobootanimation=0
else
# User 构建配置
ADDITIONAL_DEFAULT_PROPERTIES += \
ro.debuggable=0 \
ro.adb.secure=1 \
persist.sys.usb.config=none
# 禁用调试属性访问
endif
九、解决方案
9.1 方案一:使用开发者选项设置
对于蓝牙绝对音量等需要在 User 版本设置的属性,推荐通过开发者选项 UI 进行设置:
java
// 开发者选项中设置蓝牙绝对音量
// 设置 → 系统 → 开发者选项 → 禁用绝对音量
// 代码中读取状态
boolean isAbsoluteVolumeDisabled = SystemProperties.getBoolean(
"persist.bluetooth.disableabsvol",
false // 默认启用绝对音量
);
9.2 方案二:使用 Settings 系统
对于需要持久化的配置,推荐使用 Settings 系统:
java
// 使用 Settings.Global 存储配置
// 设置配置值
Settings.Global.putInt(context.getContentResolver(),
"custom_bluetooth_config",
1);
// 获取配置值
int value = Settings.Global.getInt(context.getContentResolver(),
"custom_bluetooth_config",
1); // 默认值
9.3 方案三:定义自定义系统属性
在系统源码中添加自定义属性,绕过 debug 前缀限制:
步骤 1:定义属性
makefile
# device/<company>/<device>/system.prop
# 自定义蓝牙属性(不使用 debug 前缀)
ro.bluetooth.absolute_volume_enabled=1
persist.bluetooth.absolute_volume_enabled=1
步骤 2:添加 SELinux 上下文
plaintext
# device/<company>/<device>/sepolicy/property_contexts
# 自定义蓝牙属性
bluetooth.absolute_volume_enabled u:object_r:bluetooth_prop:s0
步骤 3:添加 SELinux 权限
sepolicy
# device/<company>/<device>/sepolicy/system_server.te
# 允许 system_server 读取自定义蓝牙属性
allow system_server bluetooth_prop:file { read open };
步骤 4:修改源码使用自定义属性
java
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private boolean shouldEnableAvrcpAbsoluteVolume() {
// 使用自定义属性替代 debug 属性
String propValue = SystemProperties.get("bluetooth.absolute_volume_enabled", "1");
return propValue.equals("1");
}
9.4 方案四:修改系统源码(仅限系统定制)
如果需要修改系统行为,可以修改源码中的属性获取逻辑:
cpp
// system/core/init/property_service.cpp
// 修改属性限制检查(不推荐,仅用于特殊需求)
static bool IsRestrictedProperty(const std::string& name) {
// 对于特定属性,在 user 版本也允许访问
static const char* allowed_debug_properties[] = {
"debug.bluetooth.absolute_volume",
// 添加其他需要放开的属性
};
for (const char* prop : allowed_debug_properties) {
if (name == prop) {
return false; // 不受限
}
}
// 其他属性保持原有逻辑
// ...
}
9.5 方案五:使用 DeviceConfig
Android 11+ 提供了 Feature Flags 机制:
java
// 使用 DeviceConfig 替代系统属性
// 设置配置值
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_BLUETOOTH,
"absolute_volume_enabled",
"true",
false // 不立即同步
);
// 获取配置值
boolean enabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_BLUETOOTH,
"absolute_volume_enabled",
true // 默认值
);
十、实际案例分析
10.1 案例一:蓝牙绝对音量开关设置
问题描述
用户希望在 User 版本设备上禁用蓝牙绝对音量,以解决某些蓝牙设备音量调节异常的问题。
解决方案
方案一:开发者选项(推荐)
java
// 通过开发者选项 UI 设置
// 设置 → 系统 → 开发者选项 → 禁用绝对音量
// 该开关对应的代码逻辑:
// Settings 开发者选项设置 persist.bluetooth.disableabsvol 属性
方案二:代码中检测和提示
java
/**
* 检查绝对音量状态并提供用户引导
*/
public class BluetoothVolumeHelper {
/**
* 检查绝对音量是否启用
*/
public static boolean isAbsoluteVolumeEnabled() {
// persist.bluetooth.disableabsvol 为 true 时表示禁用
return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
/**
* 引导用户到开发者选项设置
*/
public static void openDeveloperOptions(Context context) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
context.startActivity(intent);
}
/**
* 显示绝对音量状态对话框
*/
public static void showVolumeStatusDialog(Context context) {
boolean enabled = isAbsoluteVolumeEnabled();
String message = enabled
? "蓝牙绝对音量已启用,音量将与蓝牙设备同步"
: "蓝牙绝对音量已禁用,请手动调节蓝牙设备音量";
new AlertDialog.Builder(context)
.setTitle("蓝牙绝对音量")
.setMessage(message)
.setPositiveButton("去设置", (d, w) -> openDeveloperOptions(context))
.setNegativeButton("取消", null)
.show();
}
}
注意事项
- 设置
persist.bluetooth.disableabsvol后需要重启设备才能生效 - 部分厂商 ROM 可能修改了此属性的名称或行为
- 禁用绝对音量后,需要手动调节蓝牙设备的物理音量
10.2 案例二:调试日志开关失效
问题描述
bash
# 在 User 版本执行
adb shell setprop log.tag.Bluetooth VERBOSE
adb shell getprop log.tag.Bluetooth
# 返回空值
原因分析
log.tag.* 属性在 User 版本受到 SELinux 策略限制:
sepolicy
# system/sepolicy/private/log_tag.te
neverallow { domain -init } log_tag_prop:file { write };
解决方案
java
// 使用 android.util.Log 的 API 控制
if (Build.IS_DEBUGGABLE) {
// 仅在可调试版本设置
Log.d("Bluetooth", "Debug logging enabled");
}
10.3 案例三:USB 配置无法修改
问题描述
bash
# User 版本尝试修改 USB 配置
adb shell setprop persist.sys.usb.config adb
# 失败:Permission denied
原因分析
persist.sys.usb.config 涉及设备安全,在 User 版本有严格限制:
sepolicy
# 仅 init 和特定服务可以设置
neverallow { domain -init -system_server } persist_usb_prop:property_service set;
解决方案
java
// 使用 UsbManager API(需要系统权限)
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
// 通过系统 API 配置 USB 模式
十一、最佳实践建议
10.1 属性使用原则
| 原则 | 说明 |
|---|---|
避免使用 debug. 前缀 |
User 版本受限 |
使用 persist. 存储持久配置 |
重启后保留 |
使用 ro. 存储只读配置 |
防止意外修改 |
| 考虑 Settings 替代方案 | 更灵活且不受构建类型限制 |
10.2 代码兼容性处理
java
/**
* 安全获取系统属性的工具类
*/
public class PropertyUtils {
/**
* 安全获取属性值
* @param key 属性名称
* @param defaultValue 默认值
* @return 属性值
*/
public static String getProperty(String key, String defaultValue) {
try {
String value = SystemProperties.get(key, defaultValue);
// 检查是否为空(可能因权限限制返回空)
if (TextUtils.isEmpty(value)) {
// 尝试从 Settings 获取
value = getFromSettings(key, defaultValue);
}
return value;
} catch (Exception e) {
Log.w("PropertyUtils", "Failed to get property: " + key, e);
return defaultValue;
}
}
/**
* 从 Settings 获取配置(备用方案)
*/
private static String getFromSettings(String key, String defaultValue) {
// 实现从 Settings 获取的逻辑
// ...
return defaultValue;
}
/**
* 检查属性是否可访问
*/
public static boolean isPropertyAccessible(String key) {
String value = SystemProperties.get(key, "");
return !TextUtils.isEmpty(value) ||
!Build.TYPE.equals("user") ||
!key.startsWith("debug.");
}
}
10.3 调试与验证
bash
# 检查当前构建类型
adb shell getprop ro.build.type
# 检查属性 SELinux 上下文
adb shell ls -Z /dev/__properties__
# 检查属性是否存在
adb shell getprop | grep debug.bluetooth
# 使用 dmesg 查看属性访问日志
adb shell dmesg | grep -i property
十三、总结
本文从 Android 系统源码层面深入分析了 getprop 属性在不同构建版本的表现差异:
核心发现
-
property_service.cpp 不限制属性读取:
- 属性读取需要经过 SELinux 权限检查
- 该文件主要负责属性设置权限的验证
- 真正的读取限制来源于 SELinux 策略
-
蓝牙绝对音量属性:
- 正确的属性名是
persist.bluetooth.disableabsvol - 在 Android 14/15 User 版本中,adb shell 无法读取
- 原因:
bluetooth_prop类型未被授权给shell域
- 正确的属性名是
-
属性读取权限的核心机制:
- SELinux 类型标签 :每个属性都有对应的 SELinux 类型(如
bluetooth_prop) - 域权限控制 :每个进程域有明确的
get_prop权限列表 - neverallow 规则:禁止特定域访问特定属性类型
- SELinux 类型标签 :每个属性都有对应的 SELinux 类型(如
关键对比
| 场景 | shell 域 | system_server 域 | bluetooth 域 |
|---|---|---|---|
读取 persist.bluetooth.* |
❌ 受限 | ✅ 允许 | ✅ 允许 |
读取 debug.* |
❌ 受限 | ⚠️ userdebug/eng | ⚠️ userdebug/eng |
读取 sys.* |
✅ 允许 | ✅ 允许 | ✅ 允许 |
读取 ro.* |
✅ 允许 | ✅ 允许 | ✅ 允许 |
解决方案总结
| 开发者类型 | 推荐方案 |
|---|---|
| 应用开发者 | 使用系统 API,不直接依赖属性 |
| 系统开发者 | 在 system_server 或 bluetooth 域中读取 |
| 厂商定制 | 修改 SELinux 策略添加 get_prop(shell, bluetooth_prop) |
| 调试需求 | 使用 userdebug/eng 版本或 root 权限 |
Android 版本演进
| Android 版本 | 属性读取权限变化 |
|---|---|
| Android 10 及以前 | SELinux 检查相对宽松 |
| Android 11-13 | 开始收紧部分属性访问 |
| Android 14/15 | 严格执行 SELinux 属性读取检查,shell 域受限更多 |
最佳实践
java
// 应用层:不要直接读取属性,使用系统 API
// ❌ 错误方式
String value = SystemProperties.get("persist.bluetooth.disableabsvol");
// ✅ 正确方式
// 1. 使用 AudioManager 等系统服务 API
// 2. 监听系统广播获取状态变化
// 3. 通过 ContentProvider 或 AIDL 获取系统服务状态
bash
# 系统开发调试:检查 SELinux 拒绝日志
adb shell dmesg | grep -i "avc.*bluetooth.*prop"
# 临时关闭 SELinux(仅用于调试)
adb shell setenforce 0
本文从 Android 系统源码层面深入分析了 getprop 属性在不同构建版本的表现差异:
核心发现
-
property_service.cpp 不限制属性读取:
- 属性读取直接从共享内存获取,无权限过滤
- 该文件主要负责属性设置权限的验证
- 真正的限制来源于 SELinux 策略和构建配置
-
蓝牙绝对音量属性:
- 正确的属性名是
persist.bluetooth.disableabsvol - 使用
persist.前缀,在所有构建类型中都可访问 - User 版本可以正常读取,设置需要通过开发者选项或 root 权限
- 正确的属性名是
-
属性限制的真实原因:
- SELinux 策略:控制特定域对特定属性的访问权限
- 构建配置:某些属性只在 debug 构建中定义
- 属性不存在:部分属性未被定义或加载
属性使用建议
| 场景 | 推荐方案 |
|---|---|
| 需要持久化的配置 | 使用 persist. 前缀属性 |
| 调试开关 | 使用开发者选项 UI 设置 |
| 应用配置 | 使用 Settings 系统 |
| 运行时状态 | 使用 sys. 前缀属性 |
| 只读配置 | 使用 ro. 前缀属性 |
蓝牙绝对音量属性要点
bash
# 属性名称
persist.bluetooth.disableabsvol
# 读取状态
adb shell getprop persist.bluetooth.disableabsvol
# 设置方式
# 1. 开发者选项 UI(推荐)
# 2. root 权限下 setprop(需重启)
# 3. 系统签名应用内设置
# 值的含义
true = 禁用绝对音量
false 或不存在 = 启用绝对音量(默认)
参考资料
附录:常见问题解答
Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?
答:这是正常现象。
您看到的代码可能只是:
makefile
ifdef BOARD_SEPOLICY_M4DEFS
LOCAL_ADDITIONAL_M4DEFS := $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS))
else
LOCAL_ADDITIONAL_M4DEFS :=
endif
这段代码的作用是处理 来自其他地方的宏定义,而不是定义 userdebug_or_eng 宏本身。
userdebug_or_eng 宏的实际定义位置取决于 Android 版本:
| Android 版本 | 可能的定义位置 |
|---|---|
| Android 8.x - 10 | system/sepolicy/public/te_macros 文件中 |
| Android 11+ | build/core/config.mk、build/core/main.mk 或 Soong 构建系统中 |
如何验证:
bash
# 在 AOSP 源码中搜索
grep -r "userdebug_or_eng" build/core/ system/sepolicy/
# 查看 te_macros 文件
cat system/sepolicy/public/te_macros | grep -B2 -A10 "userdebug_or_eng"
Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?
答:这取决于厂商的 SELinux 策略定制。
标准 AOSP 情况:
shell域在所有版本 (user/userdebug/eng)都没有bluetooth_prop的读取权限- 这是由
system/sepolicy/public/shell.te中的权限定义决定的
厂商定制情况 :
如果您的 userdebug 版本能读取 persist.bluetooth.*,很可能是厂商在设备特定的 SELinux 策略中添加了条件权限:
sepolicy
# device/<厂商>/<设备>/sepolicy/private/shell.te
# 厂商添加的条件权限
userdebug_or_eng(`
get_prop(shell, bluetooth_prop)
get_prop(shell, debug_prop)
get_prop(shell, nfc_prop)
')
userdebug_or_eng 宏的定义 (AOSP 标准,位于 system/sepolicy/public/te_macros):
m4
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))
宏展开效果:
| 构建类型 | userdebug_or_eng(...) 展开结果 |
|---|---|
| user | 空(内容被移除) |
| userdebug | 展开为原内容 |
| eng | 展开为原内容 |
验证方法:
bash
# 检查构建类型
adb shell getprop ro.build.type
# 检查 SELinux 权限(需要 root)
adb root
adb shell sesearch -A -s shell -t bluetooth_prop -c file
# userdebug 版本(如果有厂商定制)输出:
# allow shell bluetooth_prop:file { read open };
# user 版本输出:
# (无结果)
# 查看 SELinux 拒绝日志
adb shell "dmesg | grep -i 'avc.*bluetooth.*prop'"
Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?
答:有以下几种方案:
方案一:修改 SELinux 策略(不推荐,降低安全性)
sepolicy
# device/<厂商>/<设备>/sepolicy/private/shell.te
# 直接添加权限(绕过 userdebug_or_eng 条件)
get_prop(shell, bluetooth_prop)
方案二:将属性映射到 shell 可读的类型
plaintext
# device/<厂商>/<设备>/sepolicy/private/property_contexts
# 将特定属性映射到 system_prop(shell 默认可读)
persist.bluetooth.disableabsvol u:object_r:system_prop:s0 exact bool
方案三:通过系统 API 访问(推荐)
应用层不直接读取属性,而是通过系统服务提供的 API 获取状态。