浅入浅出 Android 安全 (四) Android 框架层安全

本文详细介绍了Android系统中Binder框架的工作原理及其在进程间通信中的作用,并深入探讨了基于Binder的权限管理和实施机制。

转:http://www.kanxue.com/?article-read-575.htm


如我们在第1.2节中所描述的那样,应用程序框架级别上的安全性由 IPC 引用监视器实现。 在4.1 节中,我们以 Android 中使用的进程间通信系统的描述开始,讲解这个级别上的安全机 制。 之后,我们在 4.2 节中引入权限,而在 4.3 节中,我们描述了在此级别上实现的权限实施系统。


4.1 Android Binder 框架


如 2.1 节所述,所有 Android 应用程序都在应用程序沙箱中运行。粗略地说,应用程序的沙箱通过在带有不同 Linux 身份的不同进程中运行所有应用程序来保证。此外,系统服务也在具有更多特权身份的单独进程中运行,允许它们使用 Linux Kernel DAC 功能,访问受保护的系统不同部分( 参见第 2.1, 2.2 和 1.2 节) 。因此,需要进程间通信( IPC) 框架来管理不同进程之间的数据和信号交换。在 Android 中,一个称为 Binder 的特殊框架用于进程间通信。标准的 Posix System V IPC 框架不支持由 Android 实现的 Bionic libc 库( 参见这里) 。此外,除了用于一些特殊情况的 Binder 框架,也会使用 Unix 域套接字( 例如,用于与 Zygote 守护进程的通信) ,但是这些机制不在本文的考虑范围之内。


Binder 框架被特地重新开发来在 Android 中使用。 它提供了管理此操作系统中的进程之间的所有类型的通信所需的功能。 基本上,甚至应用程序开发人员熟知的机制,例如 Intents 和 ContentProvider ,都建立在 Binder 框架之上。 这个框架提供了多种功能,例如可以调用远程对象上的方法,就像本地对象那样,以及同步和异步方法调用,Link to Death( 某个进程的 Binder 终止时的自动通知) ,跨进程发送文件描述符的能力等等。 


根据由客户端 - 服务器同步模型组织的进程之间的通信。客户端发起连接并等待来自服务端的回复。 因此,客户端和服务器之间的通信可以被想象为在相同的进程线程中执行。 这为开发人员提供了调用远程对象上的方法的可能性,就像它们是本地的一样。 通过 Binder 的通信模型如图 4.1 所示。 在这个图中,客户端进程 A 中的应用程序想要使用进程 B [12]中运行的服务的公开行为。 




使用 Binder 框架的客户端和服务之间的所有通信,都通过 Linux 内核驱动程序 /dev/binder 进行。此设备驱动程序的权限设置为全局可读和可写( 见 3.1 节中的清单 3.3中的第 3 行) 。因此,任何应用程序可以写入和读取此设备。为了隐藏 Binder 通信协议的特性, libbinder 库在 Android 中使用。它提供了一种功能,使内核驱动程序的交互过程对应用程序开发人员透明。尤其是,客户端和服务器之间的所有通信通过客户端侧的代理和服务器侧的桩进行。代理和桩负责编码和解码数据和通过 Binder 驱动程序发送的命令。为了使用代理和桩,开发人员只需定义一个 AIDL 接口,在编译应用程序期间将其转换为代理和桩。在服务端,调用单独的 Binder 线程来处理客户端请求。


从技术上讲,使用Binder机制的每个公开服务( 有时称为 Binder 服务) 都分配有标识。内核驱动程序确保此 32 位值在系统中的所有进程中是唯一的。因此,此标识用作 Binder 服务的句柄。拥有此句柄可以与服务交互。然而,为了开始使用服务,客户端首先必须找到这个值。服务句柄的发现通过 Binder 的上下文管理器(servicemanager 是 Android Binder 的上下文管理器的实现,在这里我们互换使用这些概念) 来完成。上下文管理器是一个特殊的Binder 服务,其预定义的句柄值等于 0( 指代清单 4.1 的第 8 行中获得的东西) 。因为它有一个固定的句柄值,任何一方都可以找到它并调用其方法。基本上,上下文管理器充当名称服务,通过服务的名称提供服务句柄。为了实现这个目的,每个服务必须注册上下文管理器( 例如,使用第 26 行中的 ServiceManager 类的 addService 方法) 。因此,客户端可以仅知道与其通信的服务名称。使用上下文管理器来解析此名称( 请参阅 getService 第 12 行) ,客户端将收到稍后用于与服务交互的标识。 Binder 驱动程序只允许注册单个上下文管理器。因此, servicemanager 是由 Android 启动的第一个服务之一( 见第 3.1节) 。 servicemanager 组件确保了只允许特权系统标识注册服务。


Binder 框架本身不实施任何安全性。 同时,它提供了在 Android 中实施安全性的设施。


Binder 驱动程序将发送者进程的 UID 和 PID 添加到每个事务。 因此,由于系统中的每个应用具有其自己的 UID,所以该值可以用于识别调用方。 调用的接收者可以检查所获得的值并且决定是否应该完成事务。 接收者可以调用 android.os.Binder.getCallingUid() 和 android.os.Binder.getCallingPid()来获得发送
者的 UID 和 PID。 另外,由于 Binder 句柄在所有进程中的唯一性和其值的模糊性,它也可以用作安全标识。



代码 4.1: ServiceManager 的源码


4.2 Android 权限


如我们在 2.1 节中所设计的那样,在 Android 中,每个应用程序默认获得其自己的 UID 和 GID 系统标识。 此外,在操作系统中还有一些硬编码的标识( 参见清单 3.5) 。 这些身份用于使用在 Linux 内核级别上实施的 DAC,分离 Android 操作系统的组件,从而提高操作系统的整体安全性。 在这些身份中, AID SYSTEM 最为显著。 此 UID 用于运行系统服务器(system server ) ,这个组件统一了由 Android 操作系统提供的服务。 系统服务器具有访问操作系统资源,以及在系统服务器内运行的每个服务的特权,这些服务提供对其他 OS 组件和应用的特定功能的受控访问。 此受控访问基于权限系统。


正如我们在 4.1 节中所提及的,Binder 框架向接收方提供了获得发送方 UID 和 PID 的能力。


在一般情况下,该功能可以由服务利用来限制想要连接到服务的消费者。这可以通过将消费者的 UID 和 PID 与服务所允许的 UID 列表进行比较来实现。然而,在 Android 中,这种功能以略微不同的方式来实现。服务的每个关键功能(或简单来说是服务的方法) 被称为权限的特殊标签保护。粗略地说,在执行这样的方法之前,会检查调用进程是否被分配了权限。如果调用进程具有所需权限,则允许调用服务。否则,将抛出安全检查异常(通常, SecurityException ) 。例如,如果开发者想要向其应用程序提供发送短信的功能,则必须将以下行添加到应用程序的 AndroidManifest.xml 文件中:。Android 还提供了一组特殊调用,允许在运行时检查服务使用者是否已分配权限。


到目前为止所描述的权限模型提供了一种强化安全性的有效方法。 同时,这个模型是无效的,因为它认为所有的权限是相等的。 在移动操作系统的情况下,所提供的功能在安全意义上并不总是相等。 例如,安装应用程序的功能比发送 SMS 的功能更重要,相反,发送 SMS的功能比设置警告或振动更危险。


这个问题在 Android 中通过引入权限的安全级别来解决。有四个可能的权限级别: normal , dangerous , signature 和 signatureOrSystem 。权限级别要么硬编码到Android 操作系统(对于系统权限) ,要么由自定义权限声明中的第三方应用程序的开发者分配。此级别影响是否决定向请求的应用程序授予权限。为了被授予权限,正常的权限可以只在应用程序的AndroidManifest.xml 文件中请求。危险权限除了在清单文件中请求之外,还必须由用户批准。在这种情况下,安装应用程序期间,安装包所请求的权限集会显示给用户。


如果用户批准它们,则安装应用程序。否则,安装将被取消。如果请求权限的应用与声明它的应用拥有相同签名,( 6.1 中提到了 Android 中的应用程序签名的用法) ,系统将授予 signature 权限。如果请求的权限应用和声明权限的使用相同证书签名,或请求应用位于系统映像上,则授予 signatureOrSystem 权限。因此,对于我们的示例,振动功能被正常级别的权限保护,发送 SMS 的功能被危险级别的权限保护,以及软件包安装功能被 signatureOrSystem 权限级别保护。


 

4.2.1 系统权限定义


用于保护 Android 操作系统功能的系统权限在框架的 AndroidManifest.xml 文件中定义,位于Android 源的 frameworks/base/core/res 文件夹中。 这个文件的一个摘录包含一些权限定义的例子,如代码清单 4.2 所示。 在这些示例中,展示了用于保护发送 SMS,振动器和包安装功能的权限声明。



代码 4.2:系统权限的定义


默认情况下,第三方应用程序的开发人员无法访问受 signature 和 signatureOrSystem 级别的系统权限保护的功能。 这种行为以以下方式来保证:应用程序框架包使用平台证书签名。 因此,需要使用这些级别的权限保护的功能的应用程序必须使用相同的平台证书进行签名。 然而,仅有操作系统的构建者才可以访问该证书的私钥,通常是硬件生产者( 他们自己定制Android) 或电信运营商( 使用其修改的操作系统映像来分发设备) 。


 

4.2.2 权限管理


系统服务 PackageManagerService 负责 Android 中的应用程序管理。 此服务有助于在操作系统中安装,卸载和更新应用程序。 此服务的另一个重要作用是权限管理。 基本上,它可以被认为是一个策略管理的要素。 它存储了用于检查 Android 包是否分配了特定权限的信息。 此外,在应用程序安装和升级期间,它执行一堆检查,来确保在这些过程中不违反权限模型的完整性。 此外,它还作为一个策略判定的要素。 此服务的方法( 我们将在后面展示) 是权限检查链中的最后一个元素。 我们不会在这里考虑 PackageManagerService 的操作。 然而,感兴趣的读者可以参考来获得如何执行应用安装的更多细节。


PackageManagerService 将所有第三方应用程序的权限的相关信息存储在 /data/system/packages.xml中。 该文件用作系统重新启动之间的永久存储器。 但是,在运行时,所有有关权限的信息都保存在 RAM 中,从而提高系统的响应速度。 在启动期间,此信息使用存储在用于第三方应用程序的 packages.xml 文件中的数据,以及通过解析系统应用程序来收集。


 

4.2.3 Android 框架层的权限实施


为了了解 Android 如何在应用程序框架层强制实施权限,我们考虑 Vibrator 服务用法。 在清单 4.3 的第 6 行中,展示了振动器服务如何保护其方法 vibrate 的示例。 这一行检查了调用组件是否分配有由常量android.Manifest.permission.VIBRATE 定义的标签 android.permission.VIBRATE 。 Android 提供了几种方法来检查发送者( 或服务使用者) 是否已被分配了权限。 在我们这个库,这些设施由方法 checkCallingOrSelfPermission 表示。


除了这种方法,还有许多其他方法可以用于检查服务调用者的权限。



代码 4.3:权限的检查


方法 checkCallingOrSelfPermission 的实现如清单 4.4 所示。 在第 24 行中,方法 checkPermission 被调用。 它接收 uid 和 pid 作为 Binder 框架提供的参数。



代码 4.4: ContextImpl 类的摘录


在第 11 行中,检查被重定向到ActivityManagerService 类,继而在 ActivityManager 组件的方法 checkComponentPermission 中执行实际检查。


此方法的代码如清单 4.5 所示。 在第 4 行中它检查调用者 UID 是否拥有特权。 具有 root 和系统 UID 的组件由具有所有权限的系统授予。



代码 4.5: ActivityManager 的checkComponentPermission 方法。


在清单 4.5 的第 26 行中,权限检查被重定向到包管理器,将其转发到 PackageManagerService 。 正如我们前面解释的,这个服务知道分配给 Android 包的权限。


执行权限检查的 PackageManagerService 方法如清单 4.6 所示。 在第 7 行中,如果将权限授予由其 UID 定义的 Android 应用程序,则会执行精确检查。



代码 4.6: PackageManagerService 的 checkUidPermission 方法



本文由飞龙使者编译
来源:Yury Zhauniarovich | Publications



<think>我们正在讨论 `Binder.getCallingUid()`,这是一个 Android 系统中的重要方法。根据用户提供的引用,我们可以总结如下: ### 1. 方法定义 `Binder.getCallingUid()` 是一个本地方法(native method),它返回当前正在处理的 Binder 事务的调用方的 Linux 用户 ID(UID)。如果没有正在处理的事务,则返回当前进程的 UID。 ### 2. 底层实现 根据引用[1][2][3]: - 在 Java 层,它声明为 `public static final native int getCallingUid();`。 - 在 JNI 层(C++),它实现在 `android_util_Binder.cpp` 中,调用 `IPCThreadState::self()->getCallingUid()`。 - `IPCThreadState` 是每个线程特有的,它存储了当前线程正在处理的 Binder 事务的调用方信息,包括 UID 和 PID。 ### 3. 工作原理 当客户端通过 Binder 调用服务端的方法时,服务端线程会处理这个调用。在调用服务端方法之前,Binder 驱动会将调用方的 UID 和 PID 等信息写服务端线程的 `IPCThreadState` 中(具体是在 `IPCThreadState::executeCommand()` 中解析事务时设置)。因此,在服务端方法中调用 `Binder.getCallingUid()` 即可获取客户端的 UID。 ### 4. 作用 - **权限检查**:服务端可以使用此 UID 来验证客户端是否有权执行操作。例如,系统服务经常使用它来检查调用者是否具有特定权限。 - **审计与日志**:记录操作是由哪个 UID 发起的。 - **多用户支持**:在 Android 的多用户环境中,UID 可以区分不同用户。 ### 5. 使用方法示例 假设我们有一个 AIDL 接口的服务端实现,我们可以在服务方法中使用 `Binder.getCallingUid()` 来获取调用方的 UID: ```java import android.os.Binder; public class MyService extends IMyService.Stub { @Override public void someMethod() { int callingUid = Binder.getCallingUid(); // 根据 UID 进行权限检查 if (callingUid != Process.SYSTEM_UID) { throw new SecurityException("Caller is not system"); } // 执行方法逻辑 } } ``` ### 6. 可能现的问题及解决方法 - **问题1:在非Binder线程调用返回当前进程UID** 如果在服务端没有通过Binder调用(例如,服务端自己调用该方法),则返回的是当前进程的UID,而不是调用方的UID。因此,在非Binder线程中使用此方法可能得不到预期的调用方UID。 - **问题2:跨进程传递UID的安全问题** 如果服务端将 `getCallingUid()` 返回的UID传递给其他组件,需要确保这个传递过程是安全的,避免被恶意篡改。 - **问题3:缓存问题** 注意,`getCallingUid()` 返回的是当前正在处理的事务的调用方UID。如果在同一个线程中,服务端在处理完一个事务后,又进行了其他操作,此时再调用 `getCallingUid()` 可能得不到之前事务的UID(因为事务已经结束)。因此,通常应该在服务方法的一开始就获取并保存它。 - **问题4:多线程环境** 每个线程有自己的 `IPCThreadState`,因此多线程环境下,每个线程调用 `getCallingUid()` 获取的是各自当前事务的调用方UID。 ### 7. 性能与最佳实践 - **性能**:由于 `getCallingUid()` 只是从线程本地存储中读取一个整数,所以非常高效。 - **最佳实践**:在服务端进行权限检查时,应使用 `checkCallingPermission(String permission)` 方法(内部使用 `getCallingUid()`)而不是直接使用 `getCallingUid()`。因为权限检查更抽象,且能处理权限组等复杂情况。 ### 8. 底层机制(补充) 根据引用[5],Binder 的权限控制依赖于调用方 UID 和 PID。Binder 驱动在传递事务时会将调用方的凭证(包括 UID)写事务数据,接收方线程在处理事务时,会将这些信息设置到自己的 `IPCThreadState` 中。 §§相关问题§§ 1. `Binder.getCallingUid()` 和 `Binder.getCallingPid()` 有什么区别? 2. 如何通过 `Binder.getCallingUid()` 实现自定义权限检查? 3. 在非 Binder 线程中如何获取原始调用方 UID? 4. Android 系统服务是如何利用 UID 进行安全控制的? [^1]: [Binder.getCallingUid() 源码](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Binder.java) [^2]: [android_util_Binder.cpp 实现](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_util_Binder.cpp) [^3]: [IPCThreadState 解析](https://www.cnblogs.com/roger-yu/p/5829589.html) [^5]: [Android Binder 权限控制](https://blog.csdn.net/yangwen123/article/details/9317567) </think>### `Binder.getCallingUid()` 详解 `Binder.getCallingUid()` 是 Android 系统级 API,用于**获取当前 Binder 事务调用方的 Linux 用户 ID(UID)**,核心作用是实现 IPC 通信中的权限控制[^1][^5]。 --- #### 一、核心作用与原理 1. **权限验证** 服务端通过此方法验证调用方身份: ```java public void sensitiveOperation() { int callerUid = Binder.getCallingUid(); if (callerUid != Process.SYSTEM_UID) { // 系统UID=1000 throw new SecurityException("Permission denied"); } // 执行敏感操作 } ``` 2. **底层实现流程** ```mermaid sequenceDiagram 客户端进程->>Binder驱动: 发送事务(含PID/UID) Binder驱动->>服务端线程: 写mCallingUid/mCallingPid 服务端线程->>IPCThreadState: IPCThreadState::self()->getCallingUid() 服务端线程->>Binder.getCallingUid(): 返回mCallingUid值 ``` - 数据来源:Binder 驱动在事务传递时写线程本地存储 `IPCThreadState`[^2][^3] - 关键代码:`frameworks/base/core/jni/android_util_Binder.cpp` 中的本地方法[^2] 3. **数学表达** 设事务集合 $T = \{ t_1, t_2, \dots, t_n \}$,对于事务 $t_i$: $$ \text{getCallingUid}(t_i) = \text{UID}_\text{client} \quad \text{where} \quad t_i \in T $$ --- #### 二、典型使用场景 | 场景 | 用途 | 代码示例 | |------|------|----------| | 系统服务 | 验证调用者身份 | `if (getCallingUid() != 1000) throw SecurityException` | | 多用户管理 | 区分用户空间 | `UserHandle.getUserId(callerUid)` | | 自定义权限 | 实现权限检查 | `enforcePermission(callerUid, PERMISSION_X)` | | SELinux 审计 | 记录操作来源 | `auditLog(callerUid, "access_sensor")` | --- #### 三、常见问题与解决方案 1. **返回-1或无效UID** **原因**:非 Binder 线程调用(如主线程直接调用) **解决**:仅限 Binder 线程使用,可通过 `Binder.isProxy()` 验证调用环境: ```java if (!Binder.isProxy(this)) { // 当前不是Binder调用环境 } ``` 2. **权限绕过风险** **原理**:恶意应用伪造 UID (需 root) **防御**:结合 `checkCallingPermission()` 二次验证: ```java if (getCallingUid() == expectedUid && checkCallingPermission("android.permission.REAL")) { // 安全执行 } ``` 3. **线程安全问题** **注意**:`IPCThreadState` 为线程局部变量,跨线程传递 UID 时需同步: ```java final int callerUid = Binder.getCallingUid(); new Handler(Looper.getMainLooper()).post(() -> { // 必须使用final变量传递,而非再次调用getCallingUid() handleRequest(callerUid); }); ``` 4. **性能优化** **高频调用场景**:缓存 UID 值(事务内不变): ```java private int mCachedUid = -1; public void performOperation() { if (mCachedUid == -1) { mCachedUid = Binder.getCallingUid(); } // 使用mCachedUid } ``` --- #### 、调试技巧 1. **日志追踪** ```bash adb logcat | grep -E 'BinderCall|getCallingUid' ``` 2. **堆栈分析** 异常时打印调用链: ```java Log.d("BinderDebug", "Caller UID=" + Binder.getCallingUid() + "\nStack: " + Log.getStackTraceString(new Throwable())); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值