Android Runtime安全上下文管理
一、安全上下文概述
在Android Runtime(ART)中,安全上下文(Security Context)是用于表示和管理代码执行权限与身份的核心机制。它在进程间通信(IPC)、系统服务调用、文件访问等场景中扮演关键角色,确保系统资源只能被授权的代码访问。安全上下文包含了进程的UID、GID、SELinux标签等信息,是Android安全模型的重要组成部分。
从源码角度来看,安全上下文管理涉及art/runtime
目录下的多个核心模块。security
目录定义了安全上下文的核心接口和实现,jni
目录处理JNI调用中的安全上下文传递,service
目录实现了系统服务调用时的安全检查。接下来,我们将深入分析每个关键步骤的原理与实现细节。
二、安全上下文的基本结构
2.1 核心数据结构
安全上下文的核心数据结构在art/runtime/security/security_context.h
中定义:
cpp
class SecurityContext {
public:
// 构造函数
SecurityContext() : uid_(0), gid_(0), pid_(0) {}
// 获取用户ID
uid_t GetUid() const { return uid_; }
// 获取组ID
gid_t GetGid() const { return gid_; }
// 获取进程ID
pid_t GetPid() const { return pid_; }
// 获取SELinux标签
const std::string& GetSELinuxContext() const { return selinux_context_; }
// 获取Bionic用户能力
const std::vector<cap_t>& GetCapabilities() const { return capabilities_; }
// 设置安全上下文
void Set(uid_t uid, gid_t gid, pid_t pid,
const std::string& selinux_context,
const std::vector<cap_t>& capabilities);
// 检查是否具有特定权限
bool HasPermission(const char* permission) const;
// 检查是否具有特定SELinux权限
bool CheckSELinuxPermission(const char* operation) const;
//...其他方法
private:
uid_t uid_; // 用户ID
gid_t gid_; // 组ID
pid_t pid_; // 进程ID
std::string selinux_context_; // SELinux安全上下文
std::vector<cap_t> capabilities_; // Bionic用户能力
};
这个数据结构存储了安全上下文的基本信息,包括用户ID、组ID、进程ID、SELinux标签和用户能力。
2.2 安全上下文的初始化
安全上下文的初始化在进程启动时完成。在art/runtime/runtime.cc
中,进程启动时会设置初始安全上下文:
cpp
// 进程启动时设置安全上下文
void Runtime::Init() {
//...其他初始化代码
// 获取当前进程的UID、GID和PID
uid_t uid = getuid();
gid_t gid = getgid();
pid_t pid = getpid();
// 获取SELinux上下文
std::string selinux_context;
if (selinux_is_enabled() > 0) {
char* context = nullptr;
if (getcon(&context) == 0) {
selinux_context = context;
free(context);
}
}
// 获取进程的能力
std::vector<cap_t> capabilities = GetProcessCapabilities();
// 设置初始安全上下文
security::SecurityContext initial_context;
initial_context.Set(uid, gid, pid, selinux_context, capabilities);
Thread::Current()->SetSecurityContext(initial_context);
//...其他初始化代码
}
这段代码展示了安全上下文的初始化过程:获取当前进程的UID、GID、PID和SELinux上下文,然后设置到当前线程的安全上下文中。
三、安全上下文的传递
3.1 线程间传递
在同一进程内,安全上下文在线程间传递。在art/runtime/thread.h
中,定义了线程的安全上下文:
cpp
class Thread {
public:
//...其他成员
// 获取当前线程的安全上下文
const security::SecurityContext& GetSecurityContext() const {
return security_context_;
}
// 设置当前线程的安全上下文
void SetSecurityContext(const security::SecurityContext& context) {
security_context_ = context;
}
// 保存当前安全上下文并设置新的上下文
void PushSecurityContext(const security::SecurityContext& context) {
security_context_stack_.push_back(security_context_);
security_context_ = context;
}
// 恢复之前保存的安全上下文
void PopSecurityContext() {
if (!security_context_stack_.empty()) {
security_context_ = security_context_stack_.back();
security_context_stack_.pop_back();
}
}
private:
//...其他成员
security::SecurityContext security_context_; // 当前安全上下文
std::vector<security::SecurityContext> security_context_stack_; // 安全上下文栈
};
线程可以通过PushSecurityContext
和PopSecurityContext
方法临时改变安全上下文,执行完特定操作后再恢复。
3.2 进程间传递
在进程间通信(IPC)中,安全上下文通过Binder机制传递。在frameworks/native/libs/binder/IPCThreadState.cpp
中,处理Binder调用时会传递安全上下文:
cpp
// 处理Binder事务
status_t IPCThreadState::executeCommand(int32_t cmd) {
//...其他处理
case BR_TRANSACTION: {
// 解析Binder事务
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
// 获取调用者的PID和UID
pid_t pid = getpid();
uid_t uid = getuid();
// 获取调用者的SELinux上下文
std::string selinux_context;
if (selinux_is_enabled() > 0) {
char* context = nullptr;
if (getcon(&context) == 0) {
selinux_context = context;
free(context);
}
}
// 创建调用者的安全上下文
security::SecurityContext caller_context;
caller_context.Set(uid, 0, pid, selinux_context, std::vector<cap_t>());
// 将安全上下文传递给目标线程
Thread* target_thread = Thread::Current();
target_thread->PushSecurityContext(caller_context);
// 处理事务
status_t result = mProcess->handleTransaction(tr, target_thread);
// 恢复安全上下文
target_thread->PopSecurityContext();
return result;
}
//...其他处理
}
这段代码展示了Binder通信中安全上下文的传递过程:获取调用者的PID、UID和SELinux上下文,创建安全上下文对象,然后将其压入目标线程的安全上下文栈中,处理完事务后再弹出恢复。
四、安全上下文的验证
4.1 权限检查机制
安全上下文的验证主要通过权限检查实现。在frameworks/base/services/core/java/com/android/server/SystemService.java
中,定义了权限检查的核心接口:
java
/**
* 检查调用者是否具有指定权限
* @param permission 权限名称
* @throws SecurityException 如果调用者没有该权限
*/
protected final void enforcePermission(String permission) {
if (permission != null) {
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
// 获取当前安全上下文
SecurityManager securityManager = getContext().getSystemService(SecurityManager.class);
securityManager.checkPermission(permission, callingPid, callingUid);
}
}
/**
* 检查调用者是否具有指定权限,如果没有则返回false
* @param permission 权限名称
* @return 如果调用者具有该权限返回true,否则返回false
*/
protected final boolean checkPermission(String permission) {
if (permission == null) {
return true;
}
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
// 获取当前安全上下文
SecurityManager securityManager = getContext().getSystemService(SecurityManager.class);
return securityManager.checkPermission(permission, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
这些方法通过SecurityManager
检查调用者是否具有指定权限。
4.2 SELinux验证
SELinux验证在art/runtime/security/selinux.cc
中实现:
cpp
// 检查SELinux权限
bool SecurityContext::CheckSELinuxPermission(const char* operation) const {
if (selinux_context_.empty()) {
// 如果没有SELinux上下文,默认允许
return true;
}
// 获取目标上下文(通常是服务的上下文)
const char* target_context = GetTargetContext(operation);
if (target_context == nullptr) {
// 无法获取目标上下文,拒绝访问
return false;
}
// 执行SELinux权限检查
int result = selinux_check_access(
selinux_context_.c_str(), // 源上下文(调用者)
target_context, // 目标上下文(被调用者)
"binder", // 类别(binder表示Binder通信)
operation, // 操作名称
nullptr); // 额外的权限信息
return (result == 0);
}
// 获取目标上下文
const char* SecurityContext::GetTargetContext(const char* operation) const {
// 根据操作名称查找对应的目标上下文
// 这通常通过一个映射表或配置文件实现
//...
return target_context;
}
这段代码展示了SELinux权限检查的过程:获取调用者和目标的SELinux上下文,然后调用selinux_check_access
函数检查是否允许该操作。
五、安全上下文与进程隔离
5.1 沙箱机制
Android通过沙箱机制实现进程间的隔离。每个应用进程都运行在自己的沙箱中,具有独立的安全上下文。在frameworks/base/core/java/android/app/ActivityManagerService.java
中,创建应用进程时会设置其安全上下文:
java
/**
* 创建应用进程
* @param info 应用信息
* @param token 进程令牌
* @param startFlags 启动标志
* @param hostingType 宿主类型
* @param hostingNameStr 宿主名称
* @return 进程ID
*/
private int startProcessLocked(ApplicationInfo info, String processName,
ProcessRecord app, int uid, int[] gids,
int debugFlags, int mountExternal,
String seInfo, String requiredAbi,
String instructionSet, String invokeWith,
long startTime) {
//...其他处理
// 设置进程的UID和GID
int pid = Process.start("android.app.ActivityThread",
processName, uid, uid, gids, debugFlags, mountExternal,
seInfo, requiredAbi, instructionSet,
app.startDir, invokeWith);
//...其他处理
return pid;
}
这段代码展示了创建应用进程时设置其UID和GID的过程,这些信息构成了进程安全上下文的一部分。
5.2 多用户支持
Android的多用户支持也依赖于安全上下文。在frameworks/base/services/core/java/com/android/server/am/UserController.java
中,处理多用户场景下的安全上下文:
java
/**
* 获取指定用户的安全上下文
* @param userId 用户ID
* @return 安全上下文
*/
public SecurityContext getSecurityContextForUser(int userId) {
// 检查用户是否存在
if (!userManager.exists(userId)) {
throw new IllegalArgumentException("User " + userId + " does not exist");
}
// 获取用户的UID范围
int uid = UserHandle.getUid(userId, 0); // 基本UID
// 获取用户的SELinux上下文
String selinuxContext = getUserSELinuxContext(userId);
// 创建并返回安全上下文
SecurityContext context = new SecurityContext();
context.setUid(uid);
context.setSELinuxContext(selinuxContext);
// 设置其他安全上下文属性
//...
return context;
}
这段代码展示了如何为不同用户创建独立的安全上下文,确保用户间的数据隔离。
六、安全上下文的特殊场景处理
6.1 系统服务调用
系统服务调用时会进行严格的安全上下文验证。在frameworks/base/services/core/java/com/android/server/ServiceManager.java
中,获取系统服务时会检查权限:
java
/**
* 获取系统服务
* @param name 服务名称
* @return 服务代理对象
*/
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(getIServiceManager().getService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
/**
* 注册系统服务
* @param name 服务名称
* @param service 服务实现
* @param allowIsolated 是否允许隔离进程
* @param dumpPriority 转储优先级
*/
public static void addService(String name, IBinder service, boolean allowIsolated,
int dumpPriority) {
try {
getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
} catch (RemoteException e) {
Log.e(TAG, "error in addService", e);
}
}
在服务实现中,会进一步检查调用者的权限:
java
/**
* 系统服务的基类实现
*/
public abstract class SystemService {
private final Context mContext;
public SystemService(Context context) {
mContext = context;
}
/**
* 检查调用者是否具有指定权限
* @param permission 权限名称
*/
protected final void enforceCallingPermission(String permission) {
mContext.enforceCallingPermission(permission, TAG);
}
/**
* 检查调用者是否是系统进程
*/
protected final void enforceCallingIsSystemProcess() {
if (!isCallingSystemProcess()) {
throw new SecurityException("Caller must be system process");
}
}
/**
* 判断调用者是否是系统进程
* @return 如果调用者是系统进程返回true,否则返回false
*/
protected final boolean isCallingSystemProcess() {
return UserHandle.isSystemUser(Binder.getCallingUserId());
}
//...其他方法
}
6.2 内容提供者访问
内容提供者(Content Provider)访问时也会进行安全上下文验证。在frameworks/base/core/java/android/content/ContentProvider.java
中,定义了内容提供者的安全检查机制:
java
/**
* 查询内容提供者
* @param uri 内容URI
* @param projection 投影列
* @param selection 选择条件
* @param selectionArgs 选择参数
* @param sortOrder 排序顺序
* @return 查询结果游标
*/
@Override
public final Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 检查调用者权限
int callingUid = Binder.getCallingUid();
if (!checkAccessPermission(callingUid, uri, "query")) {
throw new SecurityException("Permission Denial: reading " +
getClass().getName() + " uri " + uri +
" from pid=" + Binder.getCallingPid() +
", uid=" + callingUid);
}
// 调用具体实现
return onQuery(uri, projection, selection, selectionArgs, sortOrder);
}
/**
* 插入数据到内容提供者
* @param uri 内容URI
* @param values 要插入的值
* @return 插入后的URI
*/
@Override
public final Uri insert(Uri uri, ContentValues values) {
// 检查调用者权限
int callingUid = Binder.getCallingUid();
if (!checkAccessPermission(callingUid, uri, "insert")) {
throw new SecurityException("Permission Denial: writing " +
getClass().getName() + " uri " + uri +
" from pid=" + Binder.getCallingPid() +
", uid=" + callingUid);
}
// 调用具体实现
return onInsert(uri, values);
}
/**
* 检查访问权限
* @param callingUid 调用者UID
* @param uri 内容URI
* @param operation 操作类型
* @return 如果允许访问返回true,否则返回false
*/
protected boolean checkAccessPermission(int callingUid, Uri uri, String operation) {
// 获取所需权限
String readPermission = getReadPermission();
String writePermission = getWritePermission();
// 根据操作类型检查权限
if ("query".equals(operation) && readPermission != null) {
return getContext().checkCallingOrSelfPermission(readPermission) ==
PackageManager.PERMISSION_GRANTED;
} else if (("insert".equals(operation) || "update".equals(operation) ||
"delete".equals(operation)) && writePermission != null) {
return getContext().checkCallingOrSelfPermission(writePermission) ==
PackageManager.PERMISSION_GRANTED;
}
// 默认允许
return true;
}
这段代码展示了内容提供者如何验证调用者的安全上下文,确保只有具有适当权限的应用才能访问其数据。
七、安全上下文的性能优化
7.1 缓存机制
为了提高安全上下文验证的性能,Android使用了缓存机制。在frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
中,权限检查结果被缓存:
java
/**
* 权限检查结果缓存
*/
private final ArrayMap<String, ArrayMap<Integer, Integer>> mPermissionCache =
new ArrayMap<>();
/**
* 检查应用是否具有指定权限
* @param permName 权限名称
* @param pkgName 包名
* @param userId 用户ID
* @return 如果具有权限返回PERMISSION_GRANTED,否则返回PERMISSION_DENIED
*/
@Override
public int checkPermission(String permName, String pkgName, int userId) {
// 检查缓存
ArrayMap<Integer, Integer> userPerms = mPermissionCache.get(permName);
if (userPerms != null) {
Integer res = userPerms.get(userId);
if (res != null) {
return res;
}
}
// 未命中缓存,执行实际权限检查
int result = performPermissionCheck(permName, pkgName, userId);
// 更新缓存
synchronized (mPermissionCache) {
if (userPerms == null) {
userPerms = new ArrayMap<>();
mPermissionCache.put(permName, userPerms);
}
userPerms.put(userId, result);
}
return result;
}
这段代码展示了权限检查结果的缓存机制,避免了重复的权限检查操作。
7.2 预授权机制
在某些场景下,Android使用预授权机制减少安全上下文验证的开销。例如,在Binder通信中,频繁调用的方法可以预先授权:
java
/**
* 预授权Binder方法调用
* @param interfaceName 接口名称
* @param methodCode 方法代码
* @param permissions 所需权限
*/
public void preAuthorizeBinderMethod(String interfaceName, int methodCode,
String[] permissions) {
// 创建预授权条目
BinderPreAuthorizedMethod method = new BinderPreAuthorizedMethod(
interfaceName, methodCode, permissions);
// 添加到预授权列表
synchronized (mPreAuthorizedMethods) {
mPreAuthorizedMethods.add(method);
}
}
/**
* 检查Binder方法调用是否已预授权
* @param interfaceName 接口名称
* @param methodCode 方法代码
* @param callingPid 调用者PID
* @param callingUid 调用者UID
* @return 如果已预授权返回true,否则返回false
*/
public boolean checkBinderMethodPreAuthorized(String interfaceName, int methodCode,
int callingPid, int callingUid) {
synchronized (mPreAuthorizedMethods) {
for (BinderPreAuthorizedMethod method : mPreAuthorizedMethods) {
if (method.matches(interfaceName, methodCode)) {
// 检查调用者是否具有所需权限
for (String permission : method.getPermissions()) {
if (checkPermission(permission, callingPid, callingUid) !=
PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
}
return false;
}
这种预授权机制在需要频繁调用且权限检查开销较大的场景下特别有用。
八、安全上下文的调试与监控
8.1 调试工具
Android提供了多种工具用于调试安全上下文相关问题。例如,adb shell dumpsys activity
命令可以显示当前活动的安全上下文信息:
yaml
ACTIVITY MANAGER ACTIVITIES (dumpsys activity)
...
Running activities (most recent first):
TaskRecord{12345678 #1 A=com.example.app U=0 StackId=1 sz=1}
Run #0: ActivityRecord{87654321 u0 com.example.app/.MainActivity t1}
Process: ProcessRecord{45678912 1234:com.example.app/u0a123}
...
Permissions:
android.permission.READ_EXTERNAL_STORAGE: granted
android.permission.WRITE_EXTERNAL_STORAGE: granted
...
SELinux Context: u:r:untrusted_app:s0:c123,c456
其中,SELinux Context
显示了应用的SELinux安全上下文,Permissions
显示了应用已获得的权限。
8.2 监控API
Android提供了API用于监控安全上下文相关信息。例如,ActivityManager.getRunningAppProcesses()
方法可以获取当前运行的应用进程及其安全上下文信息:
java
// 获取当前运行的应用进程信息
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processes = activityManager.getRunningAppProcesses();
// 遍历进程信息
for (ActivityManager.RunningAppProcessInfo process : processes) {
Log.d(TAG, "Process: " + process.processName + ", PID: " + process.pid + ", UID: " + process.uid);
// 获取进程的SELinux上下文
try {
FileInputStream fis = new FileInputStream("/proc/" + process.pid + "/attr/current");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
String selinuxContext = reader.readLine();
reader.close();
fis.close();
Log.d(TAG, " SELinux Context: " + selinuxContext);
} catch (IOException e) {
e.printStackTrace();
}
// 获取进程的权限信息
PackageManager packageManager = getPackageManager();
try {
PackageInfo packageInfo = packageManager.getPackageInfo(
process.processName, PackageManager.GET_PERMISSIONS);
String[] permissions = packageInfo.requestedPermissions;
if (permissions != null) {
Log.d(TAG, " Permissions:");
for (String permission : permissions) {
int status = packageManager.checkPermission(permission, process.processName);
Log.d(TAG, " " + permission + ": " +
(status == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
这些API允许开发者在运行时监控应用的安全上下文信息,帮助诊断安全相关问题。
九、安全上下文的风险与挑战
9.1 权限提升漏洞
安全上下文管理不当可能导致权限提升漏洞。例如,如果一个具有高权限的进程没有正确验证调用者的安全上下文,低权限的应用可能通过该进程执行高权限操作。在frameworks/base/services/core/java/com/android/server/SystemService.java
中,错误的权限检查可能导致此类漏洞:
java
/**
* 错误的权限检查示例(请勿模仿)
*/
protected void incorrectPermissionCheck() {
// 错误:没有检查调用者权限
performSensitiveOperation();
}
/**
* 正确的权限检查示例
*/
protected void correctPermissionCheck() {
// 正确:检查调用者权限
enforceCallingPermission(Manifest.permission.SENSITIVE_OPERATION);
performSensitiveOperation();
}
开发者必须始终确保在执行敏感操作前验证调用者的安全上下文。
9.2 SELinux配置错误
SELinux配置错误可能导致安全漏洞。例如,如果SELinux策略配置过于宽松,可能允许未授权的访问。在system/sepolicy
目录下的SELinux策略文件中,错误的规则可能导致安全问题:
ini
# 错误的SELinux规则示例(请勿模仿)
allow untrusted_app system_service:binder *;
# 正确的SELinux规则示例
allow untrusted_app system_service:binder call;
SELinux策略必须经过严格审核,确保只授予必要的权限。
十、安全上下文的应用场景
10.1 系统服务安全
系统服务是Android系统的核心组件,它们的安全上下文管理至关重要。例如,ActivityManagerService
负责管理应用的生命周期,它在处理各种请求时会严格验证调用者的安全上下文:
java
/**
* 启动Activity的请求
* @param caller 调用者
* @param intent 意图
* @param resolvedType 解析的类型
* @param resultTo 结果返回对象
* @param resultWho 结果标识
* @param requestCode 请求码
* @param startFlags 启动标志
* @param profilerInfo 性能分析信息
* @param options 启动选项
* @return 启动结果
*/
@Override
public final int startActivity(IApplicationThread caller, Intent intent,
String resolvedType, IBinder resultTo,
String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo,
Bundle options) {
// 检查调用者权限
enforceCallingPermission(android.Manifest.permission.START_ACTIVITIES,
"startActivity()");
// 验证调用者身份
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
if (!isCallerAllowedToStartActivity(callingUid, callingPid, intent)) {
throw new SecurityException("Caller " + callingUid + " is not allowed to start " + intent);
}
// 执行启动Activity的操作
return startActivityAsUser(caller, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags,
profilerInfo, options, UserHandle.getCallingUserId());
}
这段代码展示了ActivityManagerService
如何验证调用者的安全上下文,确保只有具有适当权限的应用才能启动Activity。
10.2 数据访问控制
安全上下文在数据访问控制中也扮演重要角色。例如,ContentProvider
通过验证调用者的安全上下文来控制对数据的访问:
java
/**
* 内容提供者的查询方法
* @param uri 内容URI
* @param projection 投影列
* @param selection 选择条件
* @param selectionArgs 选择参数
* @param sortOrder 排序顺序
* @return 查询结果游标
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 检查调用者权限
int callingUid = Binder.getCallingUid();
if (!checkAccessPermission(callingUid, uri, "query")) {
throw new SecurityException("Permission denied for query on " + uri);
}
// 执行查询
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor cursor = db.query(getTableName(uri), projection, selection,
selectionArgs, null, null, sortOrder);
// 设置通知URI,以便数据变化时通知观察者
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
这段代码展示了内容提供者如何验证调用者的安全上下文,确保只有具有适当权限的应用才能查询其数据。