Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b5043a1bb | ||
|
|
8001a8678a | ||
|
|
d21822eb9d | ||
|
|
095a658996 | ||
|
|
70e8968e44 | ||
|
|
c122ded7bf | ||
|
|
7b510a9915 | ||
|
|
8f63dda31b | ||
|
|
9896df93de | ||
|
|
0280bcf189 | ||
|
|
438a462bdf | ||
|
|
40b08cd648 | ||
|
|
c5ed627f68 | ||
|
|
bee73eb39b | ||
|
|
a0ee77202c | ||
|
|
09d9896228 | ||
|
|
5a599025ad | ||
|
|
f5c2bcc024 | ||
|
|
85e34ed42f | ||
|
|
52b04675e7 | ||
|
|
ba0628c687 | ||
|
|
c11465d660 | ||
|
|
0d5fd44992 | ||
|
|
776a7b2343 | ||
|
|
14786ecce0 | ||
|
|
41b9cc8f10 | ||
|
|
6df266b688 | ||
|
|
32cfcb3ece | ||
|
|
727b32f6b0 | ||
|
|
ac9641ed2a | ||
|
|
c87759c6c3 | ||
|
|
76b18706f1 | ||
|
|
e76f5115e0 | ||
|
|
0725aed094 | ||
|
|
9d61af6ce8 | ||
|
|
7863e8dd17 | ||
|
|
f840eed42b | ||
|
|
4c7f0a09ea | ||
|
|
4971f7b4a5 | ||
|
|
9ea39f0545 | ||
|
|
c332f8ad0d | ||
|
|
d76e6abeed | ||
|
|
142fe7bf13 | ||
|
|
e41a679928 | ||
|
|
209d5c8902 | ||
|
|
3817b37e18 | ||
|
|
53ae250fc7 | ||
|
|
34ad97366e | ||
|
|
593bcfef83 | ||
|
|
5e68cb5f4b | ||
|
|
a1bb3bbfa3 | ||
|
|
e13adb925d | ||
|
|
c3f8f087a6 | ||
|
|
51f32b9db2 | ||
|
|
d60ad8fe47 | ||
|
|
9a1fbe8c79 | ||
|
|
68b660dfe1 | ||
|
|
068188503c | ||
|
|
1bbc50d138 | ||
|
|
d2492df02e | ||
|
|
e7d7b21daa | ||
|
|
c29bc35a36 | ||
|
|
549b5cecc2 | ||
|
|
04d003ff4d | ||
|
|
0a842c6e07 | ||
|
|
9f77771e7b | ||
|
|
ab4fe643a3 | ||
|
|
ce740542f7 | ||
|
|
c27523fd97 | ||
|
|
5a8454af7b | ||
|
|
83b65f09c9 | ||
|
|
2a76b18308 | ||
|
|
d9e47712f3 | ||
|
|
d846de4332 | ||
|
|
13d89c4314 | ||
|
|
00c91adfaa | ||
|
|
119350f24b | ||
|
|
7d4c753d66 | ||
|
|
b988d04971 | ||
|
|
d0cc5e3b56 | ||
|
|
e66e558ce5 | ||
|
|
8d431cc946 | ||
|
|
30746892b0 | ||
|
|
28cfe70a85 | ||
|
|
65a613ae0e | ||
|
|
9146b86648 | ||
|
|
457a58da04 | ||
|
|
b2838ac04b | ||
|
|
a7534feac7 | ||
|
|
4e67371193 | ||
|
|
2ef89f15c6 | ||
|
|
4f608247fe | ||
|
|
22cbe5a9a7 | ||
|
|
a6fa137e32 | ||
|
|
5afefba7bd | ||
|
|
ba9578c59b | ||
|
|
733e64c3cb | ||
|
|
7f94ba4b5b | ||
|
|
fa1d9ecc56 | ||
|
|
eec9e77631 | ||
|
|
d18692fbef | ||
|
|
13b4786cd9 | ||
|
|
612de6cdf2 | ||
|
|
020a930a31 | ||
|
|
0c1937bd3e | ||
|
|
95262d4b58 |
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -0,0 +1,159 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths-ignore: [ '**.md' ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
paths-ignore: [ '**.md' ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
|
||||
|
||||
- name: Cache Rust artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
~/.cargo/bin/cargo-ndk
|
||||
native-certgen/target
|
||||
key: rust-${{ runner.os }}-${{ hashFiles('native-certgen/Cargo.lock') }}
|
||||
restore-keys: rust-${{ runner.os }}-
|
||||
|
||||
- name: Install cargo-ndk
|
||||
run: command -v cargo-ndk || cargo install cargo-ndk
|
||||
|
||||
- name: Set up ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ccache-${{ runner.os }}-${{ github.ref_name }}
|
||||
restore-keys: |
|
||||
ccache-${{ runner.os }}-${{ github.ref_name }}
|
||||
ccache-${{ runner.os }}-
|
||||
ccache-
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
chmod +x ./gradlew
|
||||
./gradlew zipRelease zipDebug -Porg.gradle.parallel=true -Porg.gradle.vfs.watch=true -Dorg.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
- name: Read version
|
||||
id: ver
|
||||
run: |
|
||||
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
|
||||
echo "version=${ver}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Rename ZIPs for release
|
||||
run: |
|
||||
RELEASE_FILE=$(find out -name "*Release*.zip" | head -1)
|
||||
DEBUG_FILE=$(find out -name "*Debug*.zip" | head -1)
|
||||
|
||||
if [[ -z "$RELEASE_FILE" || -z "$DEBUG_FILE" ]]; then
|
||||
echo "::error::Could not find release or debug ZIPs in out/"
|
||||
ls -la out/ || echo "out/ does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$RELEASE_FILE" "out/TEESimulator-${VER}-Release.zip"
|
||||
mv "$DEBUG_FILE" "out/TEESimulator-${VER}-Debug.zip"
|
||||
|
||||
echo "Release: TEESimulator-${VER}-Release.zip ($(du -h "out/TEESimulator-${VER}-Release.zip" | cut -f1))"
|
||||
echo "Debug: TEESimulator-${VER}-Debug.zip ($(du -h "out/TEESimulator-${VER}-Debug.zip" | cut -f1))"
|
||||
env:
|
||||
VER: ${{ steps.ver.outputs.version }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TEESimulator-release-zip
|
||||
path: out/TEESimulator-*-Release.zip
|
||||
retention-days: 30
|
||||
compression-level: 0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TEESimulator-debug-zip
|
||||
path: out/TEESimulator-*-Debug.zip
|
||||
retention-days: 7
|
||||
compression-level: 0
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-mappings
|
||||
path: app/build/outputs/mapping/release
|
||||
retention-days: 30
|
||||
compression-level: 9
|
||||
|
||||
release:
|
||||
needs: build
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Read version
|
||||
id: ver
|
||||
run: |
|
||||
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
|
||||
echo "version=${ver}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TEESimulator-release-zip
|
||||
path: zips
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: TEESimulator-debug-zip
|
||||
path: zips
|
||||
|
||||
- name: Extract changelog
|
||||
run: |
|
||||
ver="${VER#v}"
|
||||
awk "/^## TEESimulator v${ver}/{flag=1; next} /^## TEESimulator v/{if(flag) exit} flag" module/changelog.md > /tmp/notes.md
|
||||
cat /tmp/notes.md
|
||||
env:
|
||||
VER: ${{ steps.ver.outputs.version }}
|
||||
|
||||
- name: Create release
|
||||
run: |
|
||||
gh release delete "$VER" --yes 2>/dev/null || true
|
||||
gh release create "$VER" \
|
||||
--title "$VER" \
|
||||
--latest \
|
||||
--notes-file /tmp/notes.md \
|
||||
"zips/TEESimulator-${VER}-Release.zip" \
|
||||
"zips/TEESimulator-${VER}-Debug.zip"
|
||||
env:
|
||||
VER: ${{ steps.ver.outputs.version }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1 @@
|
||||
out
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "app/src/main/cpp/external/LSPlt"]
|
||||
path = app/src/main/cpp/external/LSPlt
|
||||
url = https://github.com/JingMatrix/LSPlt
|
||||
@@ -1,32 +1,143 @@
|
||||
# TEESimulator – A Full TEE Emulation Framework
|
||||
<p align="center">
|
||||
<h1 align="center">🔐 TEESimulator</h1>
|
||||
<p align="center"><b>Full TEE Emulation for Rooted Android</b></p>
|
||||
<p align="center">Hardware attestation. Software keys. Zero detection.</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/Enginex0/TEESimulator/actions/workflows/build.yml"><img src="https://github.com/Enginex0/TEESimulator/actions/workflows/build.yml/badge.svg" alt="Build"></a>
|
||||
<img src="https://img.shields.io/badge/version-v4.2-blue?style=for-the-badge" alt="v4.2">
|
||||
<img src="https://img.shields.io/badge/Android-10%2B-green?style=for-the-badge&logo=android" alt="Android 10+">
|
||||
<img src="https://img.shields.io/badge/Telegram-community-blue?style=for-the-badge&logo=telegram" alt="Telegram">
|
||||
</p>
|
||||
</p>
|
||||
|
||||
**TEESimulator** is a system module designed to create a complete, software-based simulation of a hardware-backed Trusted Execution Environment ([TEE](https://source.android.com/docs/security/features/trusty)) for [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation).
|
||||
---
|
||||
|
||||
The project's goal is to move beyond simple certificate patching and build a robust framework that can create and manage virtual, self-consistent cryptographic keys.
|
||||
> [!NOTE]
|
||||
> **This is a personal fork of [JingMatrix/TEESimulator](https://github.com/JingMatrix/TEESimulator)** with additional hardening, native Rust certificate generation, key persistence, and anti-detection features. For the upstream project, see the original repo.
|
||||
|
||||
## ✨ Core Principles
|
||||
---
|
||||
|
||||
* **Bypass Hardware-Backed Attestation:** The primary goal of this project is to defeat Key Attestation, a security mechanism that allows apps to verify that they are running on a secure, unmodified device. This module provides the tools to bypass these checks on rooted or modified devices.
|
||||
* **Stateful Emulation:** Instead of patching responses from the real TEE, the ultimate goal is to create and manage virtual keys entirely in a simulated software environment. Any request concerning a virtual key will be handled by the simulator, ensuring perfect consistency without ever touching the real hardware.
|
||||
* **Architectural Interception:** By hooking low-level Binder IPC calls to the Keystore, the framework can transparently redirect requests for virtual keys to the software-based simulator, while allowing requests for real keys to pass through to the hardware TEE.
|
||||
* **100% FOSS:** Licensed under GPLv3, ensuring it stays free, auditable, and compliant with open-source laws.
|
||||
## 🧬 What is TEESimulator?
|
||||
|
||||
## 📱 Requirements
|
||||
- Android 10 or above
|
||||
TEESimulator is a **complete software simulation** of Android's hardware-backed [Trusted Execution Environment](https://source.android.com/docs/security/features/trusty) for [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation). Instead of patching certificates from the real TEE after the fact, TEESimulator intercepts Binder IPC at the `ioctl` level and generates entire certificate chains from scratch — signed by your keybox, with correct attestation extensions, indistinguishable from hardware-generated keys.
|
||||
|
||||
## 📦 Installation & Configuration
|
||||
The result: **apps that verify hardware attestation see a legitimate, unmodified device** — even on rooted hardware with an unlocked bootloader.
|
||||
|
||||
1. Flash this module via (Magisk / KernelSU / APatch) and reboot. It will replace [TrickyStore](https://github.com/5ec1cff/TrickyStore), [TrickyStoreOSS](https://github.com/beakthoven/TrickyStoreOSS) and their forks.
|
||||
2. (Optional) Place a hardware-backed `keybox.xml` at `/data/adb/tricky_store/keybox.xml`. This provides the cryptographic "root of trust" for the simulator.
|
||||
3. (Optional) Customize target packages in `/data/adb/tricky_store/target.txt`.
|
||||
4. (Optional) Customize the simulated security patch level in `/data/adb/tricky_store/security_patch.txt`.
|
||||
5. Enjoy!
|
||||
> **This is not TrickyStore.** TEESimulator replaces TrickyStore and its forks entirely. It shares the same config paths for drop-in compatibility, but the architecture is fundamentally different: native Rust certificate generation, binder-level interception via `lsplt`, per-UID rate limiting, key persistence, and a multi-layer defense against detector apps.
|
||||
|
||||
**All configuration files are monitored and will take effect immediately upon saving.**
|
||||
---
|
||||
|
||||
## 🔥 Why TEESimulator?
|
||||
|
||||
🔐 **Native Cert Generation** — v4.0 generates X.509 certificate chains in Rust with `ring` and manual DER encoding. No BouncyCastle overhead, no Java crypto quirks, byte-perfect issuer chain linkage.
|
||||
|
||||
🎯 **Binder-Level Interception** — Hooks `ioctl()` on `libc.so` via `lsplt` inside the `keystore2` process. Intercepts `generateKey`, `importKey`, and `getKeyEntry` transactions before the HAL ever sees them.
|
||||
|
||||
🛡️ **Detector Resistant** — Per-UID rate limiting blocks DuckDetector-style keygen flooding. Oversized challenges rejected with real KeyMint error codes. Chain consistency verified byte-for-byte.
|
||||
|
||||
💾 **Key Persistence** — Generated keys survive reboots. Apps that store attestation keys (banking, biometrics) don't break after a restart.
|
||||
|
||||
🔧 **Drop-In Replacement** — Same config paths as TrickyStore (`/data/adb/tricky_store/`). Swap the module ZIP, keep your keybox and target list.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
**Core Attestation Engine**
|
||||
- [x] **Full certificate chain generation** — leaf + intermediates + root, signed by your keybox
|
||||
- [x] **Native Rust certgen** — `libcertgen.so` built with `ring`, `rsa`, and manual DER assembly
|
||||
- [x] **BouncyCastle fallback** — unsupported curves (P-224, P-521, Curve25519) fall back to Java
|
||||
- [x] **ASN.1 attestation extensions** — OID 1.3.6.1.4.1.11129.2.1.17 with all AOSP-specified tags
|
||||
- [x] **Multi-keybox support** — different keybox files per app group via `target.txt`
|
||||
|
||||
**Interception Layer**
|
||||
- [x] **Binder ioctl hook** — `lsplt` PLT hook on `libc.so` inside `keystore2` process
|
||||
- [x] **generateKey / importKey / getKeyEntry** — all three transaction types intercepted
|
||||
- [x] **256KB native payload cap** — oversized binder payloads bypass interception cleanly
|
||||
- [x] **Challenge validation** — rejects >128-byte attestation challenges with `INVALID_INPUT_LENGTH`
|
||||
|
||||
**Hardening**
|
||||
- [x] **Per-UID rate limiter** — 2 hardware keygens per 30s burst window, software fallback on overflow
|
||||
- [x] **importKey eviction guard** — retained patch chains prevent generate-then-import cache attacks
|
||||
- [x] **Key persistence** — file-backed storage with file-level locking, survives reboots and keybox rotations
|
||||
- [x] **Global exception handler** — uncaught exceptions logged, daemon stays alive
|
||||
|
||||
**Configuration**
|
||||
- [x] **Live config reload** — `FileObserver` watches all config files, changes apply immediately
|
||||
- [x] **Security patch spoofing** — per-package `system`, `vendor`, `boot` patch levels with dynamic templates
|
||||
- [x] **Lifecycle scripts** — KSU Action button clears key cache, uninstall removes all traces
|
||||
|
||||
---
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
> [!IMPORTANT]
|
||||
> TEESimulator requires root access and a valid `keybox.xml` for hardware-level attestation results. Without a keybox, the module generates software-level certificates that won't pass strict hardware attestation checks.
|
||||
|
||||
**You need:**
|
||||
1. Android 10 or above
|
||||
2. A supported root manager (KernelSU, Magisk, or APatch)
|
||||
3. A hardware-backed `keybox.xml` placed at `/data/adb/tricky_store/keybox.xml`
|
||||
|
||||
---
|
||||
|
||||
## 📱 Compatibility
|
||||
|
||||
### Root Managers
|
||||
|
||||
| Manager | Status | Notes |
|
||||
|---|---|---|
|
||||
| KernelSU | ✅ Tested | Full support including Action button and lifecycle scripts |
|
||||
| Magisk | ✅ Supported | Standard module install |
|
||||
| APatch | ✅ Supported | Standard module install |
|
||||
|
||||
### Tested Devices
|
||||
|
||||
| Device | Android | TEE | Status |
|
||||
|---|---|---|---|
|
||||
| Redmi 14C (2409BRN2CA) | 14 (SDK 34) | Beanpod KeyMaster | ✅ Daily driver |
|
||||
|
||||
> Tested against DuckDetector, Luna, Play Integrity, and Key Attestation Demo. If you test on a different device, [open an issue](https://github.com/Enginex0/TEESimulator/issues) with your results.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. **Download** the latest release ZIP from [Releases](https://github.com/Enginex0/TEESimulator/releases)
|
||||
2. **Install** via your root manager (KSU / Magisk / APatch) and reboot
|
||||
3. **Place your keybox** at `/data/adb/tricky_store/keybox.xml`
|
||||
4. **Configure targets** in `/data/adb/tricky_store/target.txt`
|
||||
5. **Verify** — check Play Integrity or run Key Attestation Demo
|
||||
|
||||
TEESimulator replaces TrickyStore, TrickyStoreOSS, and their forks. Existing config files are compatible.
|
||||
|
||||
---
|
||||
|
||||
## 🔨 Building from Source
|
||||
|
||||
The CI workflow builds on every push to `main`. You can also build locally or trigger a build from your own fork.
|
||||
|
||||
**Prerequisites:** JDK 21, Android SDK/NDK 27, Rust stable with `aarch64-linux-android` target, `cargo-ndk`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Enginex0/TEESimulator.git
|
||||
cd TEESimulator
|
||||
./gradlew zipRelease zipDebug
|
||||
```
|
||||
|
||||
Output ZIPs land in `out/`. The Gradle build automatically invokes `cargo ndk` to cross-compile `libcertgen.so` before packaging.
|
||||
|
||||
To rebuild from a fork, push to `main` or use **Actions → Build → Run workflow**. The workflow installs all toolchains (Java, Rust, cargo-ndk, ccache) and uploads Release + Debug ZIPs as artifacts.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
All configuration files live at `/data/adb/tricky_store/` and are monitored by `FileObserver` — changes take effect immediately without rebooting.
|
||||
|
||||
### The `keybox.xml` Root of Trust
|
||||
|
||||
This file provides the master cryptographic identity for the simulator. It contains a private key and a valid, hardware-backed certificate chain from a real device. The simulator uses this to sign the virtual certificates it generates, making them appear legitimate to verifiers.
|
||||
This file provides the master cryptographic identity. It contains a private key and a hardware-backed certificate chain from a real device. TEESimulator signs all generated certificates with this key, making them appear legitimate to verifiers.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
@@ -40,48 +151,106 @@ This file provides the master cryptographic identity for the simulator. It conta
|
||||
</AndroidAttestation>
|
||||
```
|
||||
|
||||
### Mode and Keybox Configuration (`target.txt`)
|
||||
### Target Packages (`target.txt`)
|
||||
|
||||
TEESimulator currently operates in two primary modes as it transitions towards full emulation.
|
||||
You can control the simulation mode and the specific keybox.xml file used on a per-package basis.
|
||||
Controls which apps get intercepted and what simulation mode to use.
|
||||
|
||||
#### Mode Suffixes
|
||||
|
||||
* **`!` → Force Generation Mode:** Creates a complete, software-based virtual key. This is the foundation of the full TEE simulation.
|
||||
* **`?` → Force Leaf Hacking Mode:** A legacy mode where a real TEE key is generated, but its attestation certificate is intercepted and modified.
|
||||
* **No symbol → Automatic Mode:** The module selects the most appropriate mode for the device.
|
||||
* **`!` → Force Generation** — Creates a complete software-based virtual key. Full TEE simulation.
|
||||
* **`?` → Force Leaf Hacking** — Real TEE key generated, but its attestation certificate is intercepted and patched.
|
||||
* **No symbol → Automatic** — Module selects the best mode for your device.
|
||||
|
||||
#### Multi-Keybox Configuration
|
||||
#### Multi-Keybox
|
||||
|
||||
You can specify different keybox files for different groups of applications. This is done by adding a line with the filename in square brackets (e.g., [demo_keybox.xml]).
|
||||
Specify different keybox files for different app groups. Apps listed after a `[filename.xml]` line use that keybox. Apps before any declaration use the default `keybox.xml`.
|
||||
|
||||
All applications listed after this line will use the specified keybox file, until a new keybox is declared. Applications listed before any custom keybox declaration will use the default `keybox.xml`.
|
||||
|
||||
For example:
|
||||
```
|
||||
# These two apps will use the default /data/adb/tricky_store/keybox.xml
|
||||
# Default keybox
|
||||
com.google.android.gms!
|
||||
io.github.vvb2060.keyattestation?
|
||||
|
||||
# Switch to a different keybox for the following apps.
|
||||
# The file must be located at /data/adb/tricky_store/aosp_keybox.xml
|
||||
# Switch to a different keybox for the following apps
|
||||
[aosp_keybox.xml]
|
||||
com.google.android.gsf
|
||||
|
||||
# Switch again to another keybox.
|
||||
# The file must be located at /data/adb/tricky_store/demo_keybox.xml
|
||||
# Another keybox
|
||||
[demo_keybox.xml]
|
||||
org.matrix.demo
|
||||
```
|
||||
|
||||
### Security Patch Level (`security_patch.txt`)
|
||||
|
||||
This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
|
||||
Configure the `osPatchLevel`, `vendorPatchLevel`, and `bootPatchLevel` reported in attestation certificates. This only affects attestation data — it does not change actual system properties.
|
||||
|
||||
#### Global and Per-Package
|
||||
|
||||
Settings at the top of the file are global defaults. Add `[package.name]` to override for specific apps.
|
||||
|
||||
#### Keys
|
||||
|
||||
| Key | Scope |
|
||||
|---|---|
|
||||
| `system` | OS patch level |
|
||||
| `vendor` | Vendor patch level |
|
||||
| `boot` | Boot/kernel patch level |
|
||||
| `all` | Shorthand — sets all three at once |
|
||||
|
||||
#### Special Keywords
|
||||
|
||||
| Keyword | Effect |
|
||||
|---|---|
|
||||
| `today` | Current date, dynamically resolved on each attestation |
|
||||
| `YYYY-MM-DD` templates | Semi-dynamic — `YYYY-MM-05` resolves to the 5th of the current month |
|
||||
| `no` | Omit this patch level tag entirely from the attestation |
|
||||
| `device_default` | Use the device's real hardware value |
|
||||
| `prop` | Read from `ro.build.version.security_patch` (matches what detectors see via getprop) |
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
# Advanced Configuration
|
||||
system=2025-11
|
||||
boot=no # Do not report a boot patch level
|
||||
vendor=20251101 # Report a specific vendor patch level
|
||||
# Global — default for all apps
|
||||
system=YYYY-MM-05
|
||||
vendor=device_default
|
||||
boot=no
|
||||
|
||||
# Override for GMS
|
||||
[com.google.android.gms]
|
||||
system=2024-10-01
|
||||
|
||||
# Custom config for a demo app
|
||||
[org.matrix.demo]
|
||||
all=2025-09-15
|
||||
boot=device_default
|
||||
```
|
||||
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
|
||||
|
||||
---
|
||||
|
||||
## 💬 Community
|
||||
|
||||
<p align="center">
|
||||
<a href="https://t.me/superpowers9">
|
||||
<img src="https://img.shields.io/badge/⚡_JOIN_THE_GRID-SuperPowers_Telegram-black?style=for-the-badge&logo=telegram&logoColor=cyan&labelColor=0d1117&color=00d4ff" alt="Telegram">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
- **[JingMatrix](https://github.com/JingMatrix/TEESimulator)** — original author of TEESimulator and the interception architecture
|
||||
- **[5ec1cff](https://github.com/5ec1cff/TrickyStore)** — TrickyStore, the project that pioneered keystore interception on Android
|
||||
- **[LSPlt](https://github.com/LSPosed/LSPlt)** — PLT hook library used for binder interception
|
||||
- **[ring](https://github.com/briansmith/ring)** — Rust cryptography library powering native cert generation
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the [GNU General Public License v3.0](LICENSE).
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<b>🔐 Because the best attestation is the one the TEE never generated.</b>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import com.android.build.api.artifact.SingleArtifact
|
||||
import java.io.ByteArrayOutputStream
|
||||
import javax.inject.Inject
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.ktfmt)
|
||||
}
|
||||
|
||||
ktfmt { kotlinLangStyle() }
|
||||
|
||||
// Helper class to get access to the ExecOperations service
|
||||
abstract class GitExecutor @Inject constructor(private val execOperations: ExecOperations) {
|
||||
fun execute(command: String, currentWorkingDir: File): String {
|
||||
val byteOut = ByteArrayOutputStream()
|
||||
execOperations.exec {
|
||||
workingDir = currentWorkingDir
|
||||
commandLine = command.split("\\s".toRegex())
|
||||
standardOutput = byteOut
|
||||
}
|
||||
return String(byteOut.toByteArray()).trim()
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the helper class using Gradle's object factory
|
||||
val gitExecutor = objects.newInstance(GitExecutor::class.java)
|
||||
|
||||
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
||||
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
||||
val verName = "v4.4"
|
||||
|
||||
android {
|
||||
namespace = "org.matrix.TEESimulator"
|
||||
compileSdk = 36
|
||||
ndkVersion = "27.3.13750724"
|
||||
buildToolsVersion = "36.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "org.matrix.TEESimulator"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = gitCommitCount
|
||||
versionName = verName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
buildFeatures { buildConfig = true }
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
buildStagingDirectory = layout.buildDirectory.get().asFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":stub"))
|
||||
compileOnly(libs.annotation)
|
||||
implementation(libs.bcpkix)
|
||||
}
|
||||
|
||||
// --- Rust native cert gen build task ---
|
||||
val buildRustCertgen by tasks.registering(Exec::class) {
|
||||
group = "TEESimulator Native Build"
|
||||
description = "Builds libcertgen.so via cargo-ndk for arm64-v8a."
|
||||
|
||||
workingDir = rootProject.projectDir.resolve("native-certgen")
|
||||
|
||||
commandLine(
|
||||
"cargo", "ndk",
|
||||
"-t", "arm64-v8a",
|
||||
"-o", rootProject.projectDir.resolve("app/src/main/jniLibs").absolutePath,
|
||||
"build", "--release"
|
||||
)
|
||||
|
||||
inputs.dir(rootProject.projectDir.resolve("native-certgen/src"))
|
||||
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.toml"))
|
||||
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.lock"))
|
||||
outputs.dir(rootProject.projectDir.resolve("app/src/main/jniLibs"))
|
||||
|
||||
environment("ANDROID_NDK_HOME", android.ndkDirectory.absolutePath)
|
||||
}
|
||||
|
||||
// AGP auto-detects jniLibs/ as an input to mergeJniLibFolders — wire the dependency
|
||||
tasks.configureEach {
|
||||
if (name.endsWith("JniLibFolders") && name.startsWith("merge")) {
|
||||
dependsOn(buildRustCertgen)
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants(selector().all()) { variant ->
|
||||
val capitalized = variant.name.replaceFirstChar { it.uppercase() }
|
||||
val isDebug = variant.buildType == "debug"
|
||||
|
||||
// --- Define output locations and file names ---
|
||||
// Stage all files in a temporary directory inside 'build' before zipping
|
||||
val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}")
|
||||
val zipFileName = "TEESimulator-$verName-$gitCommitCount-$gitCommitHash-$capitalized.zip"
|
||||
|
||||
// Task 1: Prepare all module files in the temporary build directory.
|
||||
// Using Sync ensures that stale files from previous runs are removed.
|
||||
val prepareModuleFilesTask =
|
||||
tasks.register<Sync>("prepareModuleFiles${capitalized}") {
|
||||
group = "TEESimulator Module Packaging"
|
||||
description = "Prepares all files for the ${variant.name} module zip."
|
||||
|
||||
if (isDebug) {
|
||||
dependsOn("package${capitalized}")
|
||||
} else {
|
||||
dependsOn("minify${capitalized}WithR8")
|
||||
dependsOn("strip${capitalized}DebugSymbols")
|
||||
}
|
||||
dependsOn(buildRustCertgen)
|
||||
|
||||
if (isDebug) {
|
||||
from(variant.artifacts.get(SingleArtifact.APK)) {
|
||||
include("*.apk")
|
||||
rename { "service.apk" }
|
||||
}
|
||||
} else {
|
||||
from(
|
||||
project.layout.buildDirectory.dir(
|
||||
"intermediates/dex/${variant.name}/minify${capitalized}WithR8"
|
||||
)
|
||||
) {
|
||||
include("classes.dex")
|
||||
}
|
||||
}
|
||||
|
||||
val nativeLibsDir = if (isDebug) {
|
||||
"intermediates/merged_native_libs/${variant.name}/merge${capitalized}NativeLibs/out/lib"
|
||||
} else {
|
||||
"intermediates/stripped_native_libs/${variant.name}/strip${capitalized}DebugSymbols/out/lib"
|
||||
}
|
||||
from(project.layout.buildDirectory.dir(nativeLibsDir)) {
|
||||
into("lib")
|
||||
include("**/libinject.so", "**/libTEESimulator.so", "**/libsupervisor.so", "**/libcertgen.so")
|
||||
}
|
||||
|
||||
// Now, copy and process the files from 'module' directory.
|
||||
val sourceModuleDir = rootProject.projectDir.resolve("module")
|
||||
from(sourceModuleDir) {
|
||||
exclude("module.prop") // Exclude the template file.
|
||||
}
|
||||
|
||||
// Copy and filter the module.prop template separately.
|
||||
from(sourceModuleDir) {
|
||||
include("module.prop")
|
||||
// Use expand() for simple key-value replacement.
|
||||
expand(
|
||||
"REPLACEMEVERCODE" to gitCommitCount.toString(),
|
||||
"REPLACEMEVER" to
|
||||
"$verName ($gitCommitCount-$gitCommitHash-${variant.name})",
|
||||
)
|
||||
}
|
||||
|
||||
// The destination for all the above 'from' operations.
|
||||
into(tempModuleDir)
|
||||
}
|
||||
|
||||
// Task 2: Zip the prepared files from the temporary directory.
|
||||
val zipTask =
|
||||
tasks.register<Zip>("zip${capitalized}") {
|
||||
group = "TEESimulator Module Packaging"
|
||||
description = "Creates the flashable zip for the ${variant.name} module."
|
||||
dependsOn(prepareModuleFilesTask)
|
||||
|
||||
archiveFileName.set(zipFileName)
|
||||
destinationDirectory.set(project.rootDir.resolve("out"))
|
||||
from(tempModuleDir) // Zip the entire contents of the staging directory.
|
||||
}
|
||||
|
||||
// Task 3: A helper function to create installation tasks for different root providers.
|
||||
fun createInstallTasks(rootProvider: String, installCli: String) {
|
||||
val pushTask =
|
||||
tasks.register<Exec>("push${rootProvider}Module${capitalized}") {
|
||||
group = "TEESimulator Module Installation"
|
||||
description =
|
||||
"Pushes the ${variant.name} module to the device for $rootProvider."
|
||||
dependsOn(zipTask)
|
||||
commandLine(
|
||||
"adb",
|
||||
"push",
|
||||
zipTask.get().archiveFile.get().asFile,
|
||||
"/data/local/tmp",
|
||||
)
|
||||
}
|
||||
|
||||
val installTask =
|
||||
tasks.register<Exec>("install${rootProvider}${capitalized}") {
|
||||
group = "TEESimulator Module Installation"
|
||||
description = "Installs the ${variant.name} module via $rootProvider."
|
||||
dependsOn(pushTask)
|
||||
commandLine(
|
||||
"adb",
|
||||
"shell",
|
||||
"su",
|
||||
"-c",
|
||||
"$installCli /data/local/tmp/$zipFileName",
|
||||
)
|
||||
}
|
||||
|
||||
tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") {
|
||||
group = "TEESimulator Module Installation"
|
||||
description = "Installs the ${variant.name} module via $rootProvider and reboots."
|
||||
dependsOn(installTask)
|
||||
commandLine("adb", "reboot")
|
||||
}
|
||||
}
|
||||
|
||||
createInstallTasks("Magisk", "magisk --install-module")
|
||||
createInstallTasks("Ksu", "ksud module install")
|
||||
createInstallTasks("Apatch", "/data/adb/apd module install")
|
||||
}
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
-keep class org.matrix.TEESimulator.interception.keystore.** { *; }
|
||||
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-keep class org.bouncycastle.jce.provider.** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
-keepclasseswithmembers class org.matrix.TEESimulator.App {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class org.matrix.TEESimulator.pki.NativeCertGen {
|
||||
native <methods>;
|
||||
*;
|
||||
}
|
||||
-keep class org.matrix.TEESimulator.pki.CertGenConfig { *; }
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(TEESimulator)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
|
||||
|
||||
# LSPlt configuration
|
||||
OPTION(LSPLT_BUILD_SHARED OFF)
|
||||
add_subdirectory(external/LSPlt/lsplt/src/main/jni)
|
||||
|
||||
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
|
||||
add_library(utils SHARED stub/stub_utils.cpp)
|
||||
target_include_directories(utils PUBLIC external/AOSP/include)
|
||||
|
||||
add_library(binder SHARED stub/stub_binder.cpp)
|
||||
target_include_directories(binder PUBLIC external/AOSP/include)
|
||||
target_link_libraries(binder PRIVATE utils)
|
||||
|
||||
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
|
||||
target_include_directories(libinject.so PUBLIC include)
|
||||
target_link_libraries(libinject.so PRIVATE lsplt_static)
|
||||
|
||||
add_executable(libsupervisor.so supervisor.cpp)
|
||||
target_link_libraries(libsupervisor.so PRIVATE log)
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
|
||||
|
||||
@@ -0,0 +1,725 @@
|
||||
#include <android/binder.h>
|
||||
#include <binder/Binder.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <shared_mutex>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "lsplt.hpp"
|
||||
|
||||
/**
|
||||
* =========================================================================================
|
||||
* BINDER INTERCEPTION LOGIC
|
||||
* =========================================================================================
|
||||
*
|
||||
* [ Application / libbinder.so ] [ Android System / Service ]
|
||||
* | ^
|
||||
* | (1. Calls ioctl(BINDER_WRITE_READ) to wait for work) |
|
||||
* v |
|
||||
* [ Kernel Driver ] <------------------------------------------------------+
|
||||
* |
|
||||
* | (2. Kernel has an incoming transaction for this process,
|
||||
* | prepares a BR_TRANSACTION command in the read_buffer)
|
||||
* |
|
||||
* v
|
||||
* [ return from ioctl() is HOOKED ]
|
||||
* |
|
||||
* +---(3. Hook inspects the read_buffer from the Kernel)
|
||||
* |
|
||||
* +--- If a BR_TRANSACTION targets a monitored Binder:
|
||||
* | (4) Rewrites the transaction's target to our BinderStub
|
||||
* |
|
||||
* v
|
||||
* [ libbinder.so ]
|
||||
* |
|
||||
* | (5. libbinder processes the (modified) buffer and
|
||||
* | dispatches the command to the BinderStub)
|
||||
* |
|
||||
* v
|
||||
* [ BinderStub::onTransact ]
|
||||
* |
|
||||
* v
|
||||
* [ BinderInterceptor ]
|
||||
* |
|
||||
* +---(6. Pre-Process / Modify / Log)
|
||||
* |
|
||||
* +---(7. Forward to Real Target) ----> [ Real Target BBinder ]
|
||||
* |
|
||||
* +---(8. Post-Process Reply)
|
||||
* |
|
||||
* v
|
||||
* [ (9) Return Result to libbinder ]
|
||||
*
|
||||
|
||||
* --- Explanation of the Flow ---
|
||||
*
|
||||
* This diagram illustrates a "man-in-the-middle" attack on the Binder framework, achieved
|
||||
* by hooking the ioctl system call within the application's process.
|
||||
*
|
||||
* 1. Waiting for Work:
|
||||
* An application's binder thread calls `ioctl()` with the `BINDER_WRITE_READ` command.
|
||||
* This call typically blocks in the kernel, waiting for incoming transactions or other commands.
|
||||
*
|
||||
* 2. Kernel Prepares Command:
|
||||
* When an external process sends a transaction to a service hosted in this application,
|
||||
* the kernel driver prepares a `BR_TRANSACTION` command and places it in the `read_buffer`
|
||||
* associated with the waiting `ioctl` call.
|
||||
*
|
||||
* 3. Interception on Return:
|
||||
* The `ioctl()` call returns to userspace.
|
||||
* Our hook intercepts this return. It now has access to the `read_buffer`
|
||||
* populated by the kernel *before* `libbinder` gets to see it.
|
||||
*
|
||||
* 4. Hijacking:
|
||||
* The hook parses the `read_buffer`. If it finds a `BR_TRANSACTION` command destined
|
||||
* for a service that is registered with our `BinderInterceptor`, it rewrites the transaction data in-place.
|
||||
* Specifically, it changes the target binder handle to that of our `BinderStub`
|
||||
* and saves the original transaction details in a thread-local map.
|
||||
*
|
||||
* 5. Dispatch to Stub:
|
||||
* The hook then returns control to the original caller, `libbinder`.
|
||||
* `libbinder` proceeds to parse the now-modified buffer.
|
||||
* Seeing a transaction for `BinderStub`, it invokes its `onTransact` method.
|
||||
*
|
||||
* 6. Pre-Processing:
|
||||
* The `BinderStub` retrieves the original, unmodified transaction details from the thread-local map.
|
||||
* It then passes control to the `BinderInterceptor`, which can log, modify,
|
||||
* or block the transaction before it reaches its real destination.
|
||||
*
|
||||
* 7. Forwarding:
|
||||
* The `BinderInterceptor` forwards the (potentially modified) transaction to the original,
|
||||
* intended `BBinder` service.
|
||||
*
|
||||
* 8. Post-Processing:
|
||||
* After the real service processes the transaction and generates a reply,
|
||||
* the reply is returned to the `BinderInterceptor`,
|
||||
* which gets a final chance to inspect or modify the result.
|
||||
*
|
||||
* 9. Return Result:
|
||||
* The final result is returned up the call stack to `libbinder`,
|
||||
* which sends the reply back to the kernel driver to be delivered to the original caller.
|
||||
*
|
||||
*
|
||||
* =========================================================================================
|
||||
**/
|
||||
|
||||
using namespace android;
|
||||
|
||||
// =============================================================================================
|
||||
// Constants and Protocols
|
||||
// =============================================================================================
|
||||
|
||||
namespace {
|
||||
namespace intercept {
|
||||
|
||||
// Interceptor protocol codes (User space agreement between App and Interceptor Service)
|
||||
constexpr uint32_t kRegisterInterceptor = 1;
|
||||
constexpr uint32_t kUnregisterInterceptor = 2;
|
||||
|
||||
constexpr uint32_t kPreTransact = 1;
|
||||
constexpr uint32_t kPostTransact = 2;
|
||||
|
||||
constexpr uint32_t kActionSkipTransaction = 1;
|
||||
constexpr uint32_t kActionContinue = 2;
|
||||
constexpr uint32_t kActionOverrideReply = 3;
|
||||
constexpr uint32_t kActionOverrideData = 4;
|
||||
constexpr uint32_t kActionContinueAndSkipPost = 5;
|
||||
|
||||
constexpr uint32_t kBackdoorCode = 0xdeadbeef;
|
||||
|
||||
// Strings for LibBinder hooks
|
||||
constexpr std::string_view kBinderLibName = "/libbinder.so";
|
||||
constexpr std::string_view kIoctlSymbol = "ioctl";
|
||||
|
||||
} // namespace intercept
|
||||
|
||||
// =============================================================================================
|
||||
// Binder Driver Protocol Definitions (Ref: Android Kernel Header)
|
||||
// =============================================================================================
|
||||
|
||||
// Use an X-Macro to define a list of all binder return protocols. This allows us
|
||||
// to generate a string conversion function without a massive, hard-to-maintain switch statement.
|
||||
#define BINDER_RETURN_COMMAND_LIST(X) \
|
||||
X(BR_ERROR) \
|
||||
X(BR_OK) \
|
||||
X(BR_TRANSACTION_SEC_CTX) \
|
||||
X(BR_TRANSACTION) \
|
||||
X(BR_REPLY) \
|
||||
X(BR_ACQUIRE_RESULT) \
|
||||
X(BR_DEAD_REPLY) \
|
||||
X(BR_TRANSACTION_COMPLETE) \
|
||||
X(BR_INCREFS) \
|
||||
X(BR_ACQUIRE) \
|
||||
X(BR_RELEASE) \
|
||||
X(BR_DECREFS) \
|
||||
X(BR_ATTEMPT_ACQUIRE) \
|
||||
X(BR_NOOP) \
|
||||
X(BR_SPAWN_LOOPER) \
|
||||
X(BR_FINISHED) \
|
||||
X(BR_DEAD_BINDER) \
|
||||
X(BR_CLEAR_DEATH_NOTIFICATION_DONE) \
|
||||
X(BR_FAILED_REPLY) \
|
||||
X(BR_FROZEN_REPLY) \
|
||||
X(BR_ONEWAY_SPAM_SUSPECT) \
|
||||
X(BR_TRANSACTION_PENDING_FROZEN) \
|
||||
X(BR_FROZEN_BINDER) \
|
||||
X(BR_CLEAR_FREEZE_NOTIFICATION_DONE)
|
||||
|
||||
// Helper macro to generate a 'case CMD: return "CMD";' line.
|
||||
#define GENERATE_CASE_STRING(CMD) \
|
||||
case CMD: \
|
||||
return #CMD;
|
||||
|
||||
/**
|
||||
* @brief Converts a binder driver return command code into its string representation.
|
||||
* @param cmd The command code (e.g., BR_TRANSACTION).
|
||||
* @return A string literal of the command name or "UNKNOWN_BR_COMMAND".
|
||||
*/
|
||||
const char *getBinderReturnCommandName(uint32_t cmd) {
|
||||
switch (cmd) {
|
||||
BINDER_RETURN_COMMAND_LIST(GENERATE_CASE_STRING)
|
||||
default:
|
||||
return "UNKNOWN_BR_COMMAND";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// =============================================================================================
|
||||
// Global State & Forward Declarations
|
||||
// =============================================================================================
|
||||
|
||||
// Original ioctl function pointer
|
||||
int (*g_original_ioctl)(int fd, int request, ...) = nullptr;
|
||||
|
||||
// Unique ID generator for transactions
|
||||
static std::atomic<uint64_t> g_transaction_id_counter = 0;
|
||||
|
||||
// Context info to pass from the ioctl hook (processBinderWriteRead) to the BinderStub.
|
||||
struct ThreadTransactionInfo {
|
||||
uint64_t transaction_id;
|
||||
uint32_t transaction_code;
|
||||
wp<BBinder> target_binder;
|
||||
|
||||
// Default constructor
|
||||
ThreadTransactionInfo() : transaction_id(0), transaction_code(0) {}
|
||||
|
||||
ThreadTransactionInfo(uint64_t id, uint32_t code, wp<BBinder> target)
|
||||
: transaction_id(id), transaction_code(code), target_binder(std::move(target)) {}
|
||||
};
|
||||
|
||||
// A map keyed by thread ID. When ioctl intercepts a transaction intended for us,
|
||||
// it pushes the info here. When the runtime calls our Stub, it pops the info.
|
||||
static std::mutex g_thread_context_mutex;
|
||||
static std::map<std::thread::id, std::queue<ThreadTransactionInfo>> g_thread_context_map;
|
||||
|
||||
// =============================================================================================
|
||||
// Class: BinderInterceptor
|
||||
// Logic: Manages the registry of intercepted Binders and handles the protocol (Pre/Post calls).
|
||||
// =============================================================================================
|
||||
|
||||
class BinderInterceptor : public BBinder {
|
||||
struct RegistrationEntry {
|
||||
wp<IBinder> target;
|
||||
sp<IBinder> callback_interface;
|
||||
};
|
||||
|
||||
// Reader-Writer lock for the registry to allow concurrent reads (lookups)
|
||||
mutable std::shared_mutex registry_mutex_;
|
||||
std::map<wp<IBinder>, RegistrationEntry> registry_;
|
||||
|
||||
public:
|
||||
BinderInterceptor() = default;
|
||||
|
||||
// Checks if a specific Binder instance is currently registered for interception
|
||||
bool isBinderIntercepted(const wp<BBinder> &target) const {
|
||||
std::shared_lock lock(registry_mutex_);
|
||||
return registry_.find(target) != registry_.end();
|
||||
}
|
||||
|
||||
// Main entry point for processing the "Man-in-the-Middle" logic
|
||||
bool processInterceptedTransaction(uint64_t tx_id, sp<BBinder> target, uint32_t code, const Parcel &data,
|
||||
Parcel *reply, uint32_t flags, status_t &result);
|
||||
|
||||
protected:
|
||||
// Handle configuration commands sent to the Interceptor itself
|
||||
status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override;
|
||||
|
||||
private:
|
||||
status_t handleRegister(const Parcel &data);
|
||||
status_t handleUnregister(const Parcel &data);
|
||||
|
||||
// Helpers to serialize data for the remote callback interface
|
||||
status_t writeTransactionData(Parcel &out, uint64_t tx_id, sp<BBinder> target, uint32_t code, uint32_t flags,
|
||||
const Parcel &in_data) const;
|
||||
};
|
||||
|
||||
static sp<BinderInterceptor> g_interceptor_instance = nullptr;
|
||||
|
||||
// =============================================================================================
|
||||
// Class: BinderStub
|
||||
// Logic: The "Dummy" binder that acts as the destination for intercepted calls.
|
||||
// It retrieves context from the global map and delegates to BinderInterceptor.
|
||||
// =============================================================================================
|
||||
|
||||
class BinderStub : public BBinder {
|
||||
public:
|
||||
const String16& getInterfaceDescriptor() const override {
|
||||
static const String16 kDescriptor("org.matrix.TEESimulator.BinderStub");
|
||||
return kDescriptor;
|
||||
}
|
||||
|
||||
protected:
|
||||
status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override {
|
||||
if (code != intercept::kBackdoorCode) {
|
||||
LOGE("BinderStub received an unexpected direct call with code %u! This is a bug or misuse.", code);
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
|
||||
ThreadTransactionInfo info;
|
||||
bool found_context = false;
|
||||
|
||||
// 1. Retrieve the context for this thread (set previously by inspectAndRewriteTransaction)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_thread_context_mutex);
|
||||
auto it = g_thread_context_map.find(std::this_thread::get_id());
|
||||
if (it != g_thread_context_map.end() && !it->second.empty()) {
|
||||
info = std::move(it->second.front());
|
||||
it->second.pop();
|
||||
if (it->second.empty()) {
|
||||
g_thread_context_map.erase(it); // Cleanup to prevent memory leak
|
||||
}
|
||||
found_context = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_context) {
|
||||
LOGW("BinderStub received transaction but no context found for thread");
|
||||
return UNKNOWN_TRANSACTION;
|
||||
}
|
||||
|
||||
// 2. Handle special "Backdoor" to get the Interceptor reference
|
||||
if (info.transaction_code == intercept::kBackdoorCode && info.target_binder == nullptr && reply) {
|
||||
LOGD("Backdoor handshake received.");
|
||||
reply->writeStrongBinder(g_interceptor_instance);
|
||||
return OK;
|
||||
}
|
||||
|
||||
// 3. Promote the weak reference to the real target
|
||||
sp<BBinder> real_target = info.target_binder.promote();
|
||||
if (!real_target) {
|
||||
LOGE("[TX_ID: %" PRIu64 "] Target binder is dead.", info.transaction_id);
|
||||
return DEAD_OBJECT;
|
||||
}
|
||||
|
||||
// 4. Delegate to the Interceptor logic
|
||||
status_t status = OK;
|
||||
bool interceptorManagedFlow = g_interceptor_instance->processInterceptedTransaction(
|
||||
info.transaction_id, real_target, info.transaction_code, data, reply, flags, status);
|
||||
|
||||
// 5. If Interceptor logic says "Forward it", we call the original binder
|
||||
if (!interceptorManagedFlow) {
|
||||
LOGV("[TX_ID: %" PRIu64 "] Forwarding to original implementation.", info.transaction_id);
|
||||
status = real_target->transact(info.transaction_code, data, reply, flags);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
static sp<BinderStub> g_stub_instance = nullptr;
|
||||
|
||||
// =============================================================================================
|
||||
// Hook Logic: IOCTL & Buffer Parsing
|
||||
// =============================================================================================
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr binder_size_t kMaxInterceptableDataSize = 256 * 1024;
|
||||
|
||||
void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
|
||||
if (!txn_data || txn_data->target.ptr == 0)
|
||||
return;
|
||||
|
||||
// Bypass interception for oversized payloads to prevent thread starvation from flood attacks
|
||||
if (txn_data->data_size > kMaxInterceptableDataSize)
|
||||
return;
|
||||
|
||||
// AIDL methods use codes in [FIRST_CALL_TRANSACTION, LAST_CALL_TRANSACTION] (1..0x00ffffff).
|
||||
// System transactions (PING, INTERFACE, DUMP, SHELL_COMMAND) use codes above that range.
|
||||
// Skip those — intercepting a ping adds measurable latency that timing detectors flag.
|
||||
if (txn_data->code > 0x00ffffffu && txn_data->code != intercept::kBackdoorCode)
|
||||
return;
|
||||
|
||||
bool hijack = false;
|
||||
ThreadTransactionInfo info;
|
||||
|
||||
// Check 1: Root user backdoor for retrieving the interceptor service binder
|
||||
if (txn_data->code == intercept::kBackdoorCode && txn_data->sender_euid == 0) {
|
||||
info.transaction_code = intercept::kBackdoorCode;
|
||||
info.target_binder = nullptr;
|
||||
hijack = true;
|
||||
// Check 2: Spoof uid of KeyStore requests from the daemon to bypass permission check
|
||||
} else if (txn_data->sender_euid == 0) {
|
||||
// The kernel driver fills sender_euid.
|
||||
// libbinder.so trusts this value to populate IPCThreadState.
|
||||
txn_data->sender_euid = 1000;
|
||||
LOGV("[Hook] Spoofing UID for transaction: 0 -> %d", txn_data->sender_euid);
|
||||
hijack = false; // Never hijack to avoid recursion
|
||||
// Check 3: Normal interception based on registry of monitored binders
|
||||
} else {
|
||||
// Safe casting based on Binder driver ABI
|
||||
RefBase::weakref_type *weak_ref = reinterpret_cast<RefBase::weakref_type *>(txn_data->target.ptr);
|
||||
|
||||
// Try to acquire a temporary strong reference to check the object safely
|
||||
if (weak_ref && weak_ref->attemptIncStrong(nullptr)) {
|
||||
// The raw pointer to the binder object itself is stored in the cookie
|
||||
BBinder *target_binder_ptr = reinterpret_cast<BBinder *>(txn_data->cookie);
|
||||
|
||||
// Create a weak pointer for the lookup and to store in our context map.
|
||||
// This is safe because we are holding a strong reference.
|
||||
wp<BBinder> wp_target = target_binder_ptr;
|
||||
|
||||
if (g_interceptor_instance->isBinderIntercepted(wp_target)) {
|
||||
info.transaction_code = txn_data->code;
|
||||
info.target_binder = wp_target; // Assign the valid weak pointer
|
||||
hijack = true;
|
||||
}
|
||||
// Manually release the temporary strong reference we acquired at the start.
|
||||
target_binder_ptr->decStrong(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (hijack) {
|
||||
uint64_t tx_id = ++g_transaction_id_counter;
|
||||
info.transaction_id = tx_id;
|
||||
|
||||
LOGV("[Hook] Hijacking Transaction %" PRIu64 " (Code: %u)", tx_id, txn_data->code);
|
||||
|
||||
// Rewrite the destination to our Stub
|
||||
txn_data->target.ptr = reinterpret_cast<uintptr_t>(g_stub_instance->getWeakRefs());
|
||||
txn_data->cookie = reinterpret_cast<uintptr_t>(g_stub_instance.get());
|
||||
txn_data->code = intercept::kBackdoorCode;
|
||||
|
||||
// Store context for the stub to retrieve later in its onTransact
|
||||
std::lock_guard<std::mutex> lock(g_thread_context_mutex);
|
||||
g_thread_context_map[std::this_thread::get_id()].push(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses the read buffer from a BINDER_WRITE_READ ioctl call, which contains
|
||||
* commands sent from the kernel driver to userspace.
|
||||
* @param bwr The binder_write_read struct containing buffer pointers and sizes.
|
||||
*/
|
||||
void processBinderReadBuffer(const binder_write_read &bwr) {
|
||||
if (bwr.read_size == 0 || bwr.read_consumed == 0 || bwr.read_buffer == 0)
|
||||
return;
|
||||
|
||||
uintptr_t ptr = bwr.read_buffer;
|
||||
uintptr_t end = ptr + bwr.read_consumed;
|
||||
|
||||
LOGV("[Hook] Processing Read Buffer: Size=%llu, Consumed=%llu", bwr.read_size, bwr.read_consumed);
|
||||
|
||||
while (ptr < end) {
|
||||
// Ensure we can read at least the command header
|
||||
if (end - ptr < sizeof(uint32_t))
|
||||
break;
|
||||
|
||||
uint32_t cmd = *reinterpret_cast<const uint32_t *>(ptr);
|
||||
ptr += sizeof(uint32_t);
|
||||
|
||||
// Calculate payload size from the ioctl command code
|
||||
size_t cmd_size = _IOC_SIZE(cmd);
|
||||
|
||||
// Log the command using our generated to-string function
|
||||
LOGV("[Driver -> User] Command: %s (0x%x), DataSize: %zu", getBinderReturnCommandName(cmd), cmd, cmd_size);
|
||||
|
||||
// Safety check: ensure the command's data does not exceed the buffer
|
||||
if (ptr + cmd_size > end) {
|
||||
LOGE("[Hook] Buffer overflow detected while parsing command %s", getBinderReturnCommandName(cmd));
|
||||
break;
|
||||
}
|
||||
|
||||
// We are primarily interested in BR_TRANSACTION commands to intercept
|
||||
if (cmd == BR_TRANSACTION || cmd == BR_TRANSACTION_SEC_CTX) {
|
||||
binder_transaction_data *txn = nullptr;
|
||||
|
||||
if (cmd == BR_TRANSACTION_SEC_CTX) {
|
||||
// The data is wrapped in a secctx struct
|
||||
auto *wrapper = reinterpret_cast<binder_transaction_data_secctx *>(ptr);
|
||||
txn = &wrapper->transaction_data;
|
||||
} else {
|
||||
txn = reinterpret_cast<binder_transaction_data *>(ptr);
|
||||
}
|
||||
|
||||
inspectAndRewriteTransaction(txn);
|
||||
}
|
||||
|
||||
// Advance pointer to the next command
|
||||
ptr += cmd_size;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// =============================================================================================
|
||||
// The Actual Hook Function
|
||||
// =============================================================================================
|
||||
|
||||
int intercepted_ioctl(int fd, int request, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, request);
|
||||
void *arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
// 1. Call original kernel ioctl to let the driver do its work
|
||||
int result = g_original_ioctl(fd, request, arg);
|
||||
|
||||
// 2. After the call returns, check if it was a BINDER_WRITE_READ and if it succeeded
|
||||
if (result >= 0 && request == BINDER_WRITE_READ && arg != nullptr) {
|
||||
const auto *bwr = static_cast<const binder_write_read *>(arg);
|
||||
|
||||
// We only care about data read FROM the driver (i.e., incoming commands)
|
||||
if (bwr->read_consumed > 0) {
|
||||
processBinderReadBuffer(*bwr);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================================
|
||||
// BinderInterceptor Implementation
|
||||
// =============================================================================================
|
||||
|
||||
// Placed at the top of the .cpp file, inside the BinderInterceptor implementation section.
|
||||
|
||||
#define VALIDATE_STATUS(tx_id, expr) \
|
||||
do { \
|
||||
status_t __result = (expr); \
|
||||
if (__result != OK) { \
|
||||
LOGE("[TX_ID: %" PRIu64 "] Parcel operation failed in %s: '%s' returned %d", (tx_id), __func__, #expr, \
|
||||
__result); \
|
||||
return __result; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
status_t BinderInterceptor::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
|
||||
switch (code) {
|
||||
case intercept::kRegisterInterceptor:
|
||||
return handleRegister(data);
|
||||
case intercept::kUnregisterInterceptor:
|
||||
return handleUnregister(data);
|
||||
default:
|
||||
return BBinder::onTransact(code, data, reply, flags);
|
||||
}
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::handleRegister(const Parcel &data) {
|
||||
sp<IBinder> target;
|
||||
sp<IBinder> callback;
|
||||
|
||||
if (data.readStrongBinder(&target) != OK || !target)
|
||||
return BAD_VALUE;
|
||||
if (data.readStrongBinder(&callback) != OK || !callback)
|
||||
return BAD_VALUE;
|
||||
|
||||
// We can only intercept local Binders (BBinder), not remote proxies (BpBinder)
|
||||
if (target->localBinder() == nullptr) {
|
||||
LOGE("Cannot intercept remote binder proxies.");
|
||||
return BAD_TYPE;
|
||||
}
|
||||
|
||||
wp<IBinder> weak_target = target;
|
||||
|
||||
std::unique_lock lock(registry_mutex_);
|
||||
registry_[weak_target] = {weak_target, callback};
|
||||
|
||||
LOGI("Interceptor registered for binder %p", target.get());
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::handleUnregister(const Parcel &data) {
|
||||
sp<IBinder> target;
|
||||
if (data.readStrongBinder(&target) != OK || !target)
|
||||
return BAD_VALUE;
|
||||
|
||||
wp<IBinder> weak_target = target;
|
||||
|
||||
std::unique_lock lock(registry_mutex_);
|
||||
if (registry_.erase(weak_target) > 0) {
|
||||
LOGI("Interceptor unregistered for binder %p", target.get());
|
||||
return OK;
|
||||
}
|
||||
LOGW("Attempted to unregister a non-existent interceptor for binder %p", target.get());
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
status_t BinderInterceptor::writeTransactionData(Parcel &out, uint64_t tx_id, sp<BBinder> target, uint32_t code,
|
||||
uint32_t flags, const Parcel &in_data) const {
|
||||
// This is the data contract for communicating with the remote analysis/control tool
|
||||
VALIDATE_STATUS(tx_id, out.writeInt64(tx_id));
|
||||
VALIDATE_STATUS(tx_id, out.writeStrongBinder(target));
|
||||
VALIDATE_STATUS(tx_id, out.writeUint32(code));
|
||||
VALIDATE_STATUS(tx_id, out.writeUint32(flags));
|
||||
VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingUid()));
|
||||
VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingPid()));
|
||||
VALIDATE_STATUS(tx_id, out.writeUint64(in_data.dataSize()));
|
||||
VALIDATE_STATUS(tx_id, out.appendFrom(&in_data, 0, in_data.dataSize()));
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder> target, uint32_t code,
|
||||
const Parcel &request, Parcel *reply, uint32_t flags,
|
||||
status_t &result) {
|
||||
sp<IBinder> callback;
|
||||
{
|
||||
std::shared_lock lock(registry_mutex_);
|
||||
auto it = registry_.find(target);
|
||||
if (it == registry_.end())
|
||||
return false; // Should not happen given logic in hook, but safe
|
||||
callback = it->second.callback_interface;
|
||||
}
|
||||
|
||||
// --- Phase 1: Pre-Transaction Callback ---
|
||||
Parcel pre_req, pre_resp;
|
||||
writeTransactionData(pre_req, tx_id, target, code, flags, request);
|
||||
|
||||
status_t pre_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp);
|
||||
if (pre_status != OK) {
|
||||
// Block when interceptor is dead to prevent privacy leak to third-party apps
|
||||
if (callback->pingBinder() != OK) {
|
||||
LOGE("[TX_ID: %" PRIu64 "] Interceptor DEAD. Blocking to prevent attestation leak.", tx_id);
|
||||
result = DEAD_OBJECT;
|
||||
return true;
|
||||
}
|
||||
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed (not dead). Forwarding.", tx_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t action = pre_resp.readInt32();
|
||||
|
||||
// ACTION: Override Reply immediately and skip the real transaction
|
||||
if (action == intercept::kActionOverrideReply) {
|
||||
if (reply) {
|
||||
result = pre_resp.readInt32(); // Read status code from response
|
||||
size_t size = pre_resp.readUint64();
|
||||
reply->setDataSize(0);
|
||||
reply->appendFrom(&pre_resp, pre_resp.dataPosition(), size);
|
||||
}
|
||||
return true; // Handled
|
||||
}
|
||||
|
||||
// ACTION: Silently skip/drop the transaction
|
||||
if (action == intercept::kActionSkipTransaction) {
|
||||
result = OK; // Return OK to caller, but do nothing
|
||||
return true; // Handled
|
||||
}
|
||||
|
||||
// ACTION: Skip the post-transaction hook
|
||||
if (action == intercept::kActionContinueAndSkipPost) {
|
||||
result = OK; // Return OK to caller, but do nothing
|
||||
return false; // Forward it
|
||||
}
|
||||
|
||||
// ACTION: Modify the transaction's request data before forwarding
|
||||
Parcel final_request;
|
||||
if (action == intercept::kActionOverrideData) {
|
||||
size_t size = pre_resp.readUint64();
|
||||
final_request.appendFrom(&pre_resp, pre_resp.dataPosition(), size);
|
||||
} else {
|
||||
// Default (kActionContinue): Use original data
|
||||
final_request.appendFrom(&request, 0, request.dataSize());
|
||||
}
|
||||
|
||||
// --- Phase 2: Execute Original Transaction ---
|
||||
result = target->transact(code, final_request, reply, flags);
|
||||
|
||||
// --- Phase 3: Post-Transaction Callback ---
|
||||
Parcel post_req, post_resp;
|
||||
writeTransactionData(post_req, tx_id, target, code, flags, final_request);
|
||||
|
||||
// Append the result of the execution for the callback to see
|
||||
VALIDATE_STATUS(tx_id, post_req.writeInt32(result));
|
||||
size_t reply_size = (reply) ? reply->dataSize() : 0;
|
||||
VALIDATE_STATUS(tx_id, post_req.writeUint64(reply_size));
|
||||
if (reply && reply_size > 0) {
|
||||
VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size));
|
||||
}
|
||||
|
||||
status_t post_status = callback->transact(intercept::kPostTransact, post_req, &post_resp);
|
||||
if (post_status == OK) {
|
||||
int32_t post_action = post_resp.readInt32();
|
||||
if (post_action == intercept::kActionOverrideReply && reply) {
|
||||
result = post_resp.readInt32(); // Read new status
|
||||
size_t new_size = post_resp.readUint64();
|
||||
reply->setDataSize(0); // Clear original reply
|
||||
VALIDATE_STATUS(tx_id, reply->appendFrom(&post_resp, post_resp.dataPosition(), new_size));
|
||||
}
|
||||
}
|
||||
|
||||
return true; // We handled the flow, even if we just forwarded it
|
||||
}
|
||||
|
||||
// =============================================================================================
|
||||
// Initialization and Entry Point
|
||||
// =============================================================================================
|
||||
|
||||
bool initialize_hooks() {
|
||||
auto maps = lsplt::MapInfo::Scan();
|
||||
|
||||
dev_t binder_dev = 0;
|
||||
ino_t binder_ino = 0;
|
||||
bool found = false;
|
||||
|
||||
for (const auto &map : maps) {
|
||||
if (map.path.ends_with(intercept::kBinderLibName)) {
|
||||
binder_dev = map.dev;
|
||||
binder_ino = map.inode;
|
||||
found = true;
|
||||
LOGD("Found libbinder at: %s", map.path.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOGE("Could not find libbinder.so in memory maps.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Instantiate Singleton components
|
||||
g_interceptor_instance = sp<BinderInterceptor>::make();
|
||||
g_stub_instance = sp<BinderStub>::make();
|
||||
|
||||
// Register the ioctl hook with LSPLT
|
||||
lsplt::RegisterHook(binder_dev, binder_ino, intercept::kIoctlSymbol.data(),
|
||||
reinterpret_cast<void *>(intercepted_ioctl), reinterpret_cast<void **>(&g_original_ioctl));
|
||||
|
||||
if (!lsplt::CommitHook()) {
|
||||
LOGE("lsplt::CommitHook failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Binder interception initialized successfully.");
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||
bool entry(void *handle) {
|
||||
LOGI("Binder Interceptor library loaded (handle: %p)", handle);
|
||||
return initialize_hooks();
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// DO NOT INCLUDE OTHER LIBBASE HEADERS HERE!
|
||||
// This file gets used in libbinder, and libbinder is used everywhere.
|
||||
// Including other headers from libbase frequently results in inclusion of
|
||||
// android-base/macros.h, which causes macro collisions.
|
||||
|
||||
#if defined(__BIONIC__)
|
||||
#include <android/fdsan.h>
|
||||
#endif
|
||||
#if !defined(_WIN32) && !defined(__TRUSTY__)
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
namespace base {
|
||||
|
||||
// Container for a file descriptor that automatically closes the descriptor as
|
||||
// it goes out of scope.
|
||||
//
|
||||
// unique_fd ufd(open("/some/path", "r"));
|
||||
// if (ufd.get() == -1) return error;
|
||||
//
|
||||
// // Do something useful, possibly including 'return'.
|
||||
//
|
||||
// return 0; // Descriptor is closed for you.
|
||||
//
|
||||
// See also the Pipe()/Socketpair()/Fdopen()/Fdopendir() functions in this file
|
||||
// that provide interoperability with the libc functions with the same (but
|
||||
// lowercase) names.
|
||||
//
|
||||
// unique_fd is also known as ScopedFd/ScopedFD/scoped_fd; mentioned here to help
|
||||
// you find this class if you're searching for one of those names.
|
||||
//
|
||||
// unique_fd itself is a specialization of unique_fd_impl with a default closer.
|
||||
template <typename Closer>
|
||||
class unique_fd_impl final {
|
||||
public:
|
||||
unique_fd_impl() {}
|
||||
|
||||
explicit unique_fd_impl(int fd) { reset(fd); }
|
||||
~unique_fd_impl() { reset(); }
|
||||
|
||||
unique_fd_impl(const unique_fd_impl&) = delete;
|
||||
void operator=(const unique_fd_impl&) = delete;
|
||||
unique_fd_impl(unique_fd_impl&& other) noexcept { reset(other.release()); }
|
||||
unique_fd_impl& operator=(unique_fd_impl&& s) noexcept {
|
||||
int fd = s.fd_;
|
||||
s.fd_ = -1;
|
||||
reset(fd, &s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[clang::reinitializes]] void reset(int new_value = -1) { reset(new_value, nullptr); }
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
#if !defined(ANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION)
|
||||
// unique_fd's operator int is dangerous, but we have way too much code that
|
||||
// depends on it, so make this opt-in at first.
|
||||
operator int() const { return get(); } // NOLINT
|
||||
#endif
|
||||
|
||||
bool operator>=(int rhs) const { return get() >= rhs; }
|
||||
bool operator<(int rhs) const { return get() < rhs; }
|
||||
bool operator==(int rhs) const { return get() == rhs; }
|
||||
bool operator!=(int rhs) const { return get() != rhs; }
|
||||
bool operator==(const unique_fd_impl& rhs) const { return get() == rhs.get(); }
|
||||
bool operator!=(const unique_fd_impl& rhs) const { return get() != rhs.get(); }
|
||||
|
||||
// Catch bogus error checks (i.e.: "!fd" instead of "fd != -1").
|
||||
bool operator!() const = delete;
|
||||
|
||||
bool ok() const { return get() >= 0; }
|
||||
|
||||
int release() __attribute__((warn_unused_result)) {
|
||||
tag(fd_, this, nullptr);
|
||||
int ret = fd_;
|
||||
fd_ = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
void reset(int new_value, void* previous_tag) {
|
||||
int previous_errno = errno;
|
||||
|
||||
if (fd_ != -1) {
|
||||
close(fd_, this);
|
||||
}
|
||||
|
||||
fd_ = new_value;
|
||||
if (new_value != -1) {
|
||||
tag(new_value, previous_tag, this);
|
||||
}
|
||||
|
||||
errno = previous_errno;
|
||||
}
|
||||
|
||||
int fd_ = -1;
|
||||
|
||||
// Template magic to use Closer::Tag if available, and do nothing if not.
|
||||
// If Closer::Tag exists, this implementation is preferred, because int is a better match.
|
||||
// If not, this implementation is SFINAEd away, and the no-op below is the only one that exists.
|
||||
template <typename T = Closer>
|
||||
static auto tag(int fd, void* old_tag, void* new_tag)
|
||||
-> decltype(T::Tag(fd, old_tag, new_tag), void()) {
|
||||
T::Tag(fd, old_tag, new_tag);
|
||||
}
|
||||
|
||||
template <typename T = Closer>
|
||||
static void tag(long, void*, void*) {
|
||||
// No-op.
|
||||
}
|
||||
|
||||
// Same as above, to select between Closer::Close(int) and Closer::Close(int, void*).
|
||||
template <typename T = Closer>
|
||||
static auto close(int fd, void* tag_value) -> decltype(T::Close(fd, tag_value), void()) {
|
||||
T::Close(fd, tag_value);
|
||||
}
|
||||
|
||||
template <typename T = Closer>
|
||||
static auto close(int fd, void*) -> decltype(T::Close(fd), void()) {
|
||||
T::Close(fd);
|
||||
}
|
||||
};
|
||||
|
||||
// The actual details of closing are factored out to support unusual cases.
|
||||
// Almost everyone will want this DefaultCloser, which handles fdsan on bionic.
|
||||
struct DefaultCloser {
|
||||
#if defined(__BIONIC__)
|
||||
static void Tag(int fd, void* old_addr, void* new_addr) {
|
||||
if (android_fdsan_exchange_owner_tag) {
|
||||
uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(old_addr));
|
||||
uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(new_addr));
|
||||
android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
|
||||
}
|
||||
}
|
||||
static void Close(int fd, void* addr) {
|
||||
if (android_fdsan_close_with_tag) {
|
||||
uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
|
||||
reinterpret_cast<uint64_t>(addr));
|
||||
android_fdsan_close_with_tag(fd, tag);
|
||||
} else {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void Close(int fd) {
|
||||
// Even if close(2) fails with EINTR, the fd will have been closed.
|
||||
// Using TEMP_FAILURE_RETRY will either lead to EBADF or closing someone
|
||||
// else's fd.
|
||||
// http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
using unique_fd = unique_fd_impl<DefaultCloser>;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__TRUSTY__)
|
||||
|
||||
// Inline functions, so that they can be used header-only.
|
||||
|
||||
// See pipe(2).
|
||||
// This helper hides the details of converting to unique_fd, and also hides the
|
||||
// fact that macOS doesn't support O_CLOEXEC or O_NONBLOCK directly.
|
||||
template <typename Closer>
|
||||
inline bool Pipe(unique_fd_impl<Closer>* read, unique_fd_impl<Closer>* write,
|
||||
int flags = O_CLOEXEC) {
|
||||
int pipefd[2];
|
||||
|
||||
#if defined(__linux__)
|
||||
if (pipe2(pipefd, flags) != 0) {
|
||||
return false;
|
||||
}
|
||||
#else // defined(__APPLE__)
|
||||
if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
|
||||
return false;
|
||||
}
|
||||
if (pipe(pipefd) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags & O_CLOEXEC) {
|
||||
if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (flags & O_NONBLOCK) {
|
||||
if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
read->reset(pipefd[0]);
|
||||
write->reset(pipefd[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// See socketpair(2).
|
||||
// This helper hides the details of converting to unique_fd.
|
||||
template <typename Closer>
|
||||
inline bool Socketpair(int domain, int type, int protocol, unique_fd_impl<Closer>* left,
|
||||
unique_fd_impl<Closer>* right) {
|
||||
int sockfd[2];
|
||||
if (socketpair(domain, type, protocol, sockfd) != 0) {
|
||||
return false;
|
||||
}
|
||||
left->reset(sockfd[0]);
|
||||
right->reset(sockfd[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// See socketpair(2).
|
||||
// This helper hides the details of converting to unique_fd.
|
||||
template <typename Closer>
|
||||
inline bool Socketpair(int type, unique_fd_impl<Closer>* left, unique_fd_impl<Closer>* right) {
|
||||
return Socketpair(AF_UNIX, type, 0, left, right);
|
||||
}
|
||||
|
||||
// See fdopen(3).
|
||||
// Using fdopen with unique_fd correctly is more annoying than it should be,
|
||||
// because fdopen doesn't close the file descriptor received upon failure.
|
||||
inline FILE* Fdopen(unique_fd&& ufd, const char* mode) {
|
||||
int fd = ufd.release();
|
||||
FILE* file = fdopen(fd, mode);
|
||||
if (!file) {
|
||||
close(fd);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
// See fdopendir(3).
|
||||
// Using fdopendir with unique_fd correctly is more annoying than it should be,
|
||||
// because fdopen doesn't close the file descriptor received upon failure.
|
||||
inline DIR* Fdopendir(unique_fd&& ufd) {
|
||||
int fd = ufd.release();
|
||||
DIR* dir = fdopendir(fd);
|
||||
if (dir == nullptr) {
|
||||
close(fd);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
#endif // !defined(_WIN32) && !defined(__TRUSTY__)
|
||||
|
||||
// A wrapper type that can be implicitly constructed from either int or
|
||||
// unique_fd. This supports cases where you don't actually own the file
|
||||
// descriptor, and can't take ownership, but are temporarily acting as if
|
||||
// you're the owner.
|
||||
//
|
||||
// One example would be a function that needs to also allow
|
||||
// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
|
||||
// that's using a file descriptor that's actually owned by a
|
||||
// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
|
||||
// would like to enforce this weaker sense of "temporary ownership".
|
||||
//
|
||||
// If you think of unique_fd as being like std::string in that represents
|
||||
// ownership, borrowed_fd is like std::string_view (and int is like const
|
||||
// char*).
|
||||
struct borrowed_fd {
|
||||
/* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT
|
||||
template <typename T>
|
||||
/* implicit */ borrowed_fd(const unique_fd_impl<T>& ufd) : fd_(ufd.get()) {} // NOLINT
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
bool operator>=(int rhs) const { return get() >= rhs; }
|
||||
bool operator<(int rhs) const { return get() < rhs; }
|
||||
bool operator==(int rhs) const { return get() == rhs; }
|
||||
bool operator!=(int rhs) const { return get() != rhs; }
|
||||
|
||||
private:
|
||||
int fd_ = -1;
|
||||
};
|
||||
} // namespace base
|
||||
} // namespace android
|
||||
|
||||
template <typename T>
|
||||
int close(const android::base::unique_fd_impl<T>&)
|
||||
__attribute__((__unavailable__("close called on unique_fd")));
|
||||
|
||||
template <typename T>
|
||||
FILE* fdopen(const android::base::unique_fd_impl<T>&, const char* mode)
|
||||
__attribute__((__unavailable__("fdopen takes ownership of the fd passed in; either dup the "
|
||||
"unique_fd, or use android::base::Fdopen to pass ownership")));
|
||||
|
||||
template <typename T>
|
||||
DIR* fdopendir(const android::base::unique_fd_impl<T>&) __attribute__((
|
||||
__unavailable__("fdopendir takes ownership of the fd passed in; either dup the "
|
||||
"unique_fd, or use android::base::Fdopendir to pass ownership")));
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <stdint.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
namespace internal {
|
||||
class Stability;
|
||||
}
|
||||
|
||||
class BBinder : public IBinder {
|
||||
public:
|
||||
LIBBINDER_EXPORTED BBinder();
|
||||
|
||||
LIBBINDER_EXPORTED virtual const String16& getInterfaceDescriptor() const;
|
||||
LIBBINDER_EXPORTED virtual bool isBinderAlive() const;
|
||||
LIBBINDER_EXPORTED virtual status_t pingBinder();
|
||||
LIBBINDER_EXPORTED virtual status_t dump(int fd, const Vector<String16>& args);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply,
|
||||
uint32_t flags = 0) final;
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0,
|
||||
wp<DeathRecipient>* outRecipient = nullptr);
|
||||
|
||||
LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
|
||||
void* cleanupCookie,
|
||||
object_cleanup_func func) final;
|
||||
LIBBINDER_EXPORTED virtual void* findObject(const void* objectID) const final;
|
||||
LIBBINDER_EXPORTED virtual void* detachObject(const void* objectID) final;
|
||||
LIBBINDER_EXPORTED void withLock(const std::function<void()>& doWithLock);
|
||||
LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
|
||||
IBinder::object_make_func make,
|
||||
const void* makeArgs);
|
||||
|
||||
LIBBINDER_EXPORTED virtual BBinder* localBinder();
|
||||
|
||||
LIBBINDER_EXPORTED bool isRequestingSid();
|
||||
// This must be called before the object is sent to another process. Not thread safe.
|
||||
LIBBINDER_EXPORTED void setRequestingSid(bool requestSid);
|
||||
|
||||
LIBBINDER_EXPORTED sp<IBinder> getExtension();
|
||||
// This must be called before the object is sent to another process. Not thread safe.
|
||||
LIBBINDER_EXPORTED void setExtension(const sp<IBinder>& extension);
|
||||
|
||||
// This must be called before the object is sent to another process. Not thread safe.
|
||||
//
|
||||
// This function will abort if improper parameters are set. This is like
|
||||
// sched_setscheduler. However, it sets the minimum scheduling policy
|
||||
// only for the duration that this specific binder object is handling the
|
||||
// call in a threadpool. By default, this API is set to SCHED_NORMAL/0. In
|
||||
// this case, the scheduling priority will not actually be modified from
|
||||
// binder defaults. See also IPCThreadState::disableBackgroundScheduling.
|
||||
//
|
||||
// Appropriate values are:
|
||||
// SCHED_NORMAL: -20 <= priority <= 19
|
||||
// SCHED_RR/SCHED_FIFO: 1 <= priority <= 99
|
||||
LIBBINDER_EXPORTED void setMinSchedulerPolicy(int policy, int priority);
|
||||
LIBBINDER_EXPORTED int getMinSchedulerPolicy();
|
||||
LIBBINDER_EXPORTED int getMinSchedulerPriority();
|
||||
|
||||
// Whether realtime scheduling policies are inherited.
|
||||
LIBBINDER_EXPORTED bool isInheritRt();
|
||||
// This must be called before the object is sent to another process. Not thread safe.
|
||||
LIBBINDER_EXPORTED void setInheritRt(bool inheritRt);
|
||||
|
||||
LIBBINDER_EXPORTED pid_t getDebugPid();
|
||||
|
||||
// Whether this binder has been sent to another process.
|
||||
LIBBINDER_EXPORTED bool wasParceled();
|
||||
// Consider this binder as parceled (setup/init-related calls should no
|
||||
// longer by called. This is automatically set by when this binder is sent
|
||||
// to another process.
|
||||
LIBBINDER_EXPORTED void setParceled();
|
||||
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setRpcClientDebug(binder::unique_fd clientFd,
|
||||
const sp<IBinder>& keepAliveBinder);
|
||||
|
||||
protected:
|
||||
LIBBINDER_EXPORTED virtual ~BBinder();
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
|
||||
private:
|
||||
BBinder(const BBinder& o);
|
||||
BBinder& operator=(const BBinder& o);
|
||||
|
||||
class RpcServerLink;
|
||||
class Extras;
|
||||
|
||||
Extras* getOrCreateExtras();
|
||||
|
||||
[[nodiscard]] status_t setRpcClientDebug(const Parcel& data);
|
||||
void removeRpcServerLink(const sp<RpcServerLink>& link);
|
||||
[[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
|
||||
[[nodiscard]] status_t stopRecordingTransactions();
|
||||
|
||||
std::atomic<Extras*> mExtras;
|
||||
|
||||
friend ::android::internal::Stability;
|
||||
int16_t mStability;
|
||||
bool mParceled;
|
||||
bool mRecordingOn;
|
||||
|
||||
#ifdef __LP64__
|
||||
int32_t mReserved1;
|
||||
#endif
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class BpRefBase : public virtual RefBase {
|
||||
protected:
|
||||
LIBBINDER_EXPORTED explicit BpRefBase(const sp<IBinder>& o);
|
||||
LIBBINDER_EXPORTED virtual ~BpRefBase();
|
||||
LIBBINDER_EXPORTED virtual void onFirstRef();
|
||||
LIBBINDER_EXPORTED virtual void onLastStrongRef(const void* id);
|
||||
LIBBINDER_EXPORTED virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
|
||||
|
||||
LIBBINDER_EXPORTED inline IBinder* remote() const { return mRemote; }
|
||||
LIBBINDER_EXPORTED inline sp<IBinder> remoteStrong() const {
|
||||
return sp<IBinder>::fromExisting(mRemote);
|
||||
}
|
||||
|
||||
private:
|
||||
BpRefBase(const BpRefBase& o);
|
||||
BpRefBase& operator=(const BpRefBase& o);
|
||||
|
||||
IBinder* const mRemote;
|
||||
RefBase::weakref_type* mRefs;
|
||||
std::atomic<int32_t> mState;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String16.h>
|
||||
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
|
||||
// WARNING: deprecated - DO NOT USE - prefer to setup service directly.
|
||||
//
|
||||
// This class embellishes a class with a few static methods which can be used in
|
||||
// limited circumstances (when one service needs to be registered and
|
||||
// published). However, this is an anti-pattern:
|
||||
// - these methods are aliases of existing methods, and as such, represent an
|
||||
// incremental amount of information required to understand the system but
|
||||
// which does not actually save in terms of lines of code. For instance, users
|
||||
// of this class should be surprised to know that this will start up to 16
|
||||
// threads in the binder threadpool.
|
||||
// - the template instantiation costs need to be paid, even though everything
|
||||
// done here is generic.
|
||||
// - the getServiceName API here is undocumented and non-local (for instance,
|
||||
// this unnecessarily assumes a single service type will only be instantiated
|
||||
// once with no arguments).
|
||||
//
|
||||
// So, DO NOT USE.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
template<typename SERVICE>
|
||||
class BinderService
|
||||
{
|
||||
public:
|
||||
static status_t publish(bool allowIsolated = false,
|
||||
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
|
||||
sp<IServiceManager> sm(defaultServiceManager());
|
||||
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
|
||||
dumpFlags);
|
||||
}
|
||||
|
||||
static void publishAndJoinThreadPool(
|
||||
bool allowIsolated = false,
|
||||
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
|
||||
publish(allowIsolated, dumpFlags);
|
||||
joinThreadPool();
|
||||
}
|
||||
|
||||
static void instantiate() { publish(); }
|
||||
|
||||
static status_t shutdown() { return NO_ERROR; }
|
||||
|
||||
private:
|
||||
static void joinThreadPool() {
|
||||
sp<ProcessState> ps(ProcessState::self());
|
||||
ps->startThreadPool();
|
||||
ps->giveThreadPoolName();
|
||||
IPCThreadState::self()->joinThreadPool();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace android
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/RpcThreads.h>
|
||||
#include <binder/unique_fd.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class IPCThreadState;
|
||||
class RpcSession;
|
||||
class RpcState;
|
||||
namespace internal {
|
||||
class Stability;
|
||||
}
|
||||
class ProcessState;
|
||||
|
||||
using binder_proxy_limit_callback = std::function<void(int)>;
|
||||
using binder_proxy_warning_callback = std::function<void(int)>;
|
||||
|
||||
class BpBinder : public IBinder {
|
||||
public:
|
||||
/**
|
||||
* Return value:
|
||||
* true - this is associated with a socket RpcSession
|
||||
* false - (usual) binder over e.g. /dev/binder
|
||||
*/
|
||||
LIBBINDER_EXPORTED bool isRpcBinder() const;
|
||||
|
||||
LIBBINDER_EXPORTED virtual const String16& getInterfaceDescriptor() const;
|
||||
LIBBINDER_EXPORTED virtual bool isBinderAlive() const;
|
||||
LIBBINDER_EXPORTED virtual status_t pingBinder();
|
||||
LIBBINDER_EXPORTED virtual status_t dump(int fd, const Vector<String16>& args);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply,
|
||||
uint32_t flags = 0) final;
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
LIBBINDER_EXPORTED virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr, uint32_t flags = 0,
|
||||
wp<DeathRecipient>* outRecipient = nullptr);
|
||||
|
||||
[[nodiscard]] status_t addFrozenStateChangeCallback(
|
||||
const wp<FrozenStateChangeCallback>& recipient);
|
||||
|
||||
[[nodiscard]] status_t removeFrozenStateChangeCallback(
|
||||
const wp<FrozenStateChangeCallback>& recipient);
|
||||
|
||||
LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
|
||||
void* cleanupCookie,
|
||||
object_cleanup_func func) final;
|
||||
LIBBINDER_EXPORTED virtual void* findObject(const void* objectID) const final;
|
||||
LIBBINDER_EXPORTED virtual void* detachObject(const void* objectID) final;
|
||||
LIBBINDER_EXPORTED void withLock(const std::function<void()>& doWithLock);
|
||||
LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
|
||||
IBinder::object_make_func make,
|
||||
const void* makeArgs);
|
||||
LIBBINDER_EXPORTED virtual BpBinder* remoteBinder();
|
||||
|
||||
LIBBINDER_EXPORTED void sendObituary();
|
||||
|
||||
LIBBINDER_EXPORTED static uint32_t getBinderProxyCount(uint32_t uid);
|
||||
LIBBINDER_EXPORTED static void getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts);
|
||||
LIBBINDER_EXPORTED static void enableCountByUid();
|
||||
LIBBINDER_EXPORTED static void disableCountByUid();
|
||||
LIBBINDER_EXPORTED static void setCountByUidEnabled(bool enable);
|
||||
LIBBINDER_EXPORTED static void setBinderProxyCountEventCallback(
|
||||
binder_proxy_limit_callback cbl, binder_proxy_warning_callback cbw);
|
||||
LIBBINDER_EXPORTED static void setBinderProxyCountWatermarks(int high, int low, int warning);
|
||||
LIBBINDER_EXPORTED static uint32_t getBinderProxyCount();
|
||||
|
||||
LIBBINDER_EXPORTED std::optional<int32_t> getDebugBinderHandle() const;
|
||||
|
||||
// Start recording transactions to the unique_fd.
|
||||
// See RecordedTransaction.h for more details.
|
||||
LIBBINDER_EXPORTED status_t startRecordingBinder(const binder::unique_fd& fd);
|
||||
// Stop the current recording.
|
||||
LIBBINDER_EXPORTED status_t stopRecordingBinder();
|
||||
|
||||
// Note: This class is not thread safe so protect uses of it when necessary
|
||||
class ObjectManager {
|
||||
public:
|
||||
ObjectManager();
|
||||
~ObjectManager();
|
||||
|
||||
void* attach(const void* objectID, void* object, void* cleanupCookie,
|
||||
IBinder::object_cleanup_func func);
|
||||
void* find(const void* objectID) const;
|
||||
void* detach(const void* objectID);
|
||||
sp<IBinder> lookupOrCreateWeak(const void* objectID, IBinder::object_make_func make,
|
||||
const void* makeArgs);
|
||||
|
||||
private:
|
||||
ObjectManager(const ObjectManager&);
|
||||
ObjectManager& operator=(const ObjectManager&);
|
||||
|
||||
struct entry_t {
|
||||
void* object = nullptr;
|
||||
void* cleanupCookie = nullptr;
|
||||
IBinder::object_cleanup_func func = nullptr;
|
||||
};
|
||||
|
||||
std::map<const void*, entry_t> mObjects;
|
||||
};
|
||||
|
||||
class PrivateAccessor {
|
||||
private:
|
||||
friend class BpBinder;
|
||||
friend class ::android::Parcel;
|
||||
friend class ::android::ProcessState;
|
||||
friend class ::android::RpcSession;
|
||||
friend class ::android::RpcState;
|
||||
friend class ::android::IPCThreadState;
|
||||
explicit PrivateAccessor(const BpBinder* binder)
|
||||
: mBinder(binder), mMutableBinder(nullptr) {}
|
||||
explicit PrivateAccessor(BpBinder* binder) : mBinder(binder), mMutableBinder(binder) {}
|
||||
|
||||
static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask) {
|
||||
return BpBinder::create(handle, postTask);
|
||||
}
|
||||
static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address) {
|
||||
return BpBinder::create(session, address);
|
||||
}
|
||||
|
||||
// valid if !isRpcBinder
|
||||
int32_t binderHandle() const { return mBinder->binderHandle(); }
|
||||
|
||||
// valid if isRpcBinder
|
||||
uint64_t rpcAddress() const { return mBinder->rpcAddress(); }
|
||||
const sp<RpcSession>& rpcSession() const { return mBinder->rpcSession(); }
|
||||
|
||||
void onFrozenStateChanged(bool isFrozen) { mMutableBinder->onFrozenStateChanged(isFrozen); }
|
||||
const BpBinder* mBinder;
|
||||
BpBinder* mMutableBinder;
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED const PrivateAccessor getPrivateAccessor() const {
|
||||
return PrivateAccessor(this);
|
||||
}
|
||||
|
||||
PrivateAccessor getPrivateAccessor() { return PrivateAccessor(this); }
|
||||
|
||||
private:
|
||||
friend PrivateAccessor;
|
||||
friend class sp<BpBinder>;
|
||||
|
||||
static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask);
|
||||
static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address);
|
||||
|
||||
struct BinderHandle {
|
||||
int32_t handle;
|
||||
};
|
||||
struct RpcHandle {
|
||||
sp<RpcSession> session;
|
||||
uint64_t address;
|
||||
};
|
||||
using Handle = std::variant<BinderHandle, RpcHandle>;
|
||||
|
||||
int32_t binderHandle() const;
|
||||
uint64_t rpcAddress() const;
|
||||
const sp<RpcSession>& rpcSession() const;
|
||||
|
||||
explicit BpBinder(Handle&& handle);
|
||||
BpBinder(BinderHandle&& handle, int32_t trackedUid);
|
||||
explicit BpBinder(RpcHandle&& handle);
|
||||
|
||||
virtual ~BpBinder();
|
||||
virtual void onFirstRef();
|
||||
virtual void onLastStrongRef(const void* id);
|
||||
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
|
||||
|
||||
friend ::android::internal::Stability;
|
||||
|
||||
int32_t mStability;
|
||||
Handle mHandle;
|
||||
|
||||
struct Obituary {
|
||||
wp<DeathRecipient> recipient;
|
||||
void* cookie;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
void onFrozenStateChanged(bool isFrozen);
|
||||
|
||||
struct FrozenStateChange {
|
||||
bool isFrozen = false;
|
||||
Vector<wp<FrozenStateChangeCallback>> callbacks;
|
||||
bool initialStateReceived = false;
|
||||
};
|
||||
|
||||
void reportOneDeath(const Obituary& obit);
|
||||
bool isDescriptorCached() const;
|
||||
|
||||
mutable RpcMutex mLock;
|
||||
volatile int32_t mAlive;
|
||||
volatile int32_t mObitsSent;
|
||||
Vector<Obituary>* mObituaries;
|
||||
std::unique_ptr<FrozenStateChange> mFrozen;
|
||||
ObjectManager mObjectMgr;
|
||||
mutable String16 mDescriptorCache;
|
||||
int32_t mTrackedUid;
|
||||
|
||||
static RpcMutex sTrackingLock;
|
||||
static std::unordered_map<int32_t, uint32_t> sTrackingMap;
|
||||
static int sNumTrackedUids;
|
||||
static std::atomic_bool sCountByUidEnabled;
|
||||
static binder_proxy_limit_callback sLimitCallback;
|
||||
static uint32_t sBinderProxyCountHighWatermark;
|
||||
static uint32_t sBinderProxyCountLowWatermark;
|
||||
static bool sBinderProxyThrottleCreate;
|
||||
static std::unordered_map<int32_t, uint32_t> sLastLimitCallbackMap;
|
||||
static std::atomic<uint32_t> sBinderProxyCount;
|
||||
static std::atomic<uint32_t> sBinderProxyCountWarned;
|
||||
static binder_proxy_warning_callback sWarningCallback;
|
||||
static uint32_t sBinderProxyCountWarningWatermark;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// libbinder is built with symbol hidden by default. To add a new symbol to the
|
||||
// ABI, you must annotate it with this LIBBINDER_EXPORTED macro. When not
|
||||
// building libbinder (e.g. when another binary includes a libbinder header),
|
||||
// this macro is a no-op.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// // Export a function.
|
||||
// LIBBINDER_EXPORTED void someFunction();
|
||||
//
|
||||
// // Export a subset of the symbols for a class.
|
||||
// class SomeClassA {
|
||||
// public:
|
||||
// LIBBINDER_EXPORTED SomeClassA();
|
||||
//
|
||||
// LIBBINDER_EXPORTED SomeMethod();
|
||||
// }
|
||||
//
|
||||
// // Export all the symbols for a class, even private symbols.
|
||||
// class LIBBINDER_EXPORTED SomeClassB {};
|
||||
//
|
||||
// For a more detailed explanation of this strategy, see
|
||||
// https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html
|
||||
#if BUILDING_LIBBINDER
|
||||
#define LIBBINDER_EXPORTED __attribute__((__visibility__("default")))
|
||||
#else
|
||||
#define LIBBINDER_EXPORTED
|
||||
#endif
|
||||
|
||||
// For stuff that is exported but probably shouldn't be. It behaves the exact
|
||||
// same way as LIBBINDER_EXPORTED, only exists to help track what we want
|
||||
// eventually remove.
|
||||
//
|
||||
// Needed, at least in part, because the test binaries are using internal
|
||||
// headers and accessing these symbols directly.
|
||||
#define LIBBINDER_INTERNAL_EXPORTED LIBBINDER_EXPORTED
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
|
||||
#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
|
||||
#include <log/log.h>
|
||||
#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
|
||||
#endif
|
||||
|
||||
#ifndef __BIONIC__
|
||||
#ifndef __assert
|
||||
|
||||
// defined differently by liblog
|
||||
#pragma push_macro("LOG_PRI")
|
||||
#ifdef LOG_PRI
|
||||
#undef LOG_PRI
|
||||
#endif
|
||||
#include <syslog.h>
|
||||
#pragma pop_macro("LOG_PRI")
|
||||
|
||||
#define __assert(a, b, c) \
|
||||
do { \
|
||||
syslog(LOG_ERR, a ": " c); \
|
||||
abort(); \
|
||||
} while (false)
|
||||
#endif // __assert
|
||||
#endif // __BIONIC__
|
||||
|
||||
namespace android {
|
||||
|
||||
/*
|
||||
* Used to manage AIDL's *Delegator types.
|
||||
* This is used to:
|
||||
* - create a new *Delegator object that delegates to the binder argument.
|
||||
* - or return an existing *Delegator object that already delegates to the
|
||||
* binder argument.
|
||||
* - or return the underlying delegate binder if the binder argument is a
|
||||
* *Delegator itself.
|
||||
*
|
||||
* @param binder - the binder to delegate to or unwrap
|
||||
*
|
||||
* @return pointer to the *Delegator object or the unwrapped binder object
|
||||
*/
|
||||
template <typename T>
|
||||
sp<T> delegate(const sp<T>& binder) {
|
||||
const void* isDelegatorId = &T::descriptor;
|
||||
const void* hasDelegatorId = &T::descriptor + 1;
|
||||
// is binder itself a delegator?
|
||||
if (T::asBinder(binder)->findObject(isDelegatorId)) {
|
||||
if (T::asBinder(binder)->findObject(hasDelegatorId)) {
|
||||
__assert(__FILE__, __LINE__,
|
||||
"This binder has a delegator and is also delegator itself! This is "
|
||||
"likely an unintended mixing of binders.");
|
||||
return nullptr;
|
||||
}
|
||||
// unwrap the delegator
|
||||
return static_cast<typename T::DefaultDelegator*>(binder.get())->getImpl();
|
||||
}
|
||||
|
||||
struct MakeArgs {
|
||||
const sp<T>* binder;
|
||||
const void* id;
|
||||
} makeArgs;
|
||||
makeArgs.binder = &binder;
|
||||
makeArgs.id = isDelegatorId;
|
||||
|
||||
// the binder is not a delegator, so construct one
|
||||
sp<IBinder> newDelegator = T::asBinder(binder)->lookupOrCreateWeak(
|
||||
hasDelegatorId,
|
||||
[](const void* args) -> sp<IBinder> {
|
||||
auto delegator = sp<typename T::DefaultDelegator>::make(
|
||||
*static_cast<const MakeArgs*>(args)->binder);
|
||||
// make sure we know this binder is a delegator by attaching a unique ID
|
||||
(void)delegator->attachObject(static_cast<const MakeArgs*>(args)->id,
|
||||
reinterpret_cast<void*>(0x1), nullptr, nullptr);
|
||||
return delegator;
|
||||
},
|
||||
static_cast<const void*>(&makeArgs));
|
||||
return sp<typename T::DefaultDelegator>::cast(newDelegator);
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Never instantiated. Used as a placeholder for template variables.
|
||||
template <typename T>
|
||||
struct invalid_type;
|
||||
|
||||
// AIDL generates specializations of this for enums.
|
||||
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
|
||||
constexpr invalid_type<EnumType> enum_values;
|
||||
} // namespace internal
|
||||
|
||||
// Usage: for (const auto v : enum_range<EnumType>() ) { ... }
|
||||
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
|
||||
struct enum_range {
|
||||
constexpr auto begin() const { return std::begin(internal::enum_values<EnumType>); }
|
||||
constexpr auto end() const { return std::end(internal::enum_values<EnumType>); }
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace android::binder::impl {
|
||||
|
||||
template <typename F>
|
||||
class scope_guard;
|
||||
|
||||
template <typename F>
|
||||
scope_guard<F> make_scope_guard(F f);
|
||||
|
||||
template <typename F>
|
||||
class scope_guard {
|
||||
public:
|
||||
inline ~scope_guard() {
|
||||
if (f_.has_value()) std::move(f_.value())();
|
||||
}
|
||||
inline void release() { f_.reset(); }
|
||||
|
||||
private:
|
||||
friend scope_guard<F> android::binder::impl::make_scope_guard<>(F);
|
||||
|
||||
inline scope_guard(F&& f) : f_(std::move(f)) {}
|
||||
|
||||
std::optional<F> f_;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
inline scope_guard<F> make_scope_guard(F f) {
|
||||
return scope_guard<F>(std::move(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
constexpr void assert_small_callable() {
|
||||
// While this buffer (std::function::__func::__buf_) is an implementation detail generally not
|
||||
// accessible to users, it's a good bet to assume its size to be around 3 pointers.
|
||||
constexpr size_t kFunctionBufferSize = 3 * sizeof(void*);
|
||||
|
||||
static_assert(sizeof(F) <= kFunctionBufferSize,
|
||||
"Supplied callable is larger than std::function optimization buffer. "
|
||||
"Try using std::ref, but make sure lambda lives long enough to be called.");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class SmallFunction : public std::function<T> {
|
||||
public:
|
||||
template <typename F>
|
||||
SmallFunction(F&& f) : std::function<T>(f) {
|
||||
assert_small_callable<F>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android::binder::impl
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/unique_fd.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
// linux/binder.h defines this, but we don't want to include it here in order to
|
||||
// avoid exporting the kernel headers
|
||||
#ifndef B_PACK_CHARS
|
||||
#define B_PACK_CHARS(c1, c2, c3, c4) \
|
||||
((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
|
||||
#endif // B_PACK_CHARS
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class BBinder;
|
||||
class BpBinder;
|
||||
class IInterface;
|
||||
class Parcel;
|
||||
class IResultReceiver;
|
||||
class IShellCallback;
|
||||
|
||||
/**
|
||||
* Base class and low-level protocol for a remotable object.
|
||||
* You can derive from this class to create an object for which other
|
||||
* processes can hold references to it. Communication between processes
|
||||
* (method calls, property get and set) is down through a low-level
|
||||
* protocol implemented on top of the transact() API.
|
||||
*/
|
||||
class [[clang::lto_visibility_public]] LIBBINDER_EXPORTED IBinder : public virtual RefBase {
|
||||
public:
|
||||
enum {
|
||||
FIRST_CALL_TRANSACTION = 0x00000001,
|
||||
LAST_CALL_TRANSACTION = 0x00ffffff,
|
||||
|
||||
PING_TRANSACTION = B_PACK_CHARS('_', 'P', 'N', 'G'),
|
||||
START_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'S', 'R', 'D'),
|
||||
STOP_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'E', 'R', 'D'),
|
||||
DUMP_TRANSACTION = B_PACK_CHARS('_', 'D', 'M', 'P'),
|
||||
SHELL_COMMAND_TRANSACTION = B_PACK_CHARS('_', 'C', 'M', 'D'),
|
||||
INTERFACE_TRANSACTION = B_PACK_CHARS('_', 'N', 'T', 'F'),
|
||||
SYSPROPS_TRANSACTION = B_PACK_CHARS('_', 'S', 'P', 'R'),
|
||||
EXTENSION_TRANSACTION = B_PACK_CHARS('_', 'E', 'X', 'T'),
|
||||
DEBUG_PID_TRANSACTION = B_PACK_CHARS('_', 'P', 'I', 'D'),
|
||||
SET_RPC_CLIENT_TRANSACTION = B_PACK_CHARS('_', 'R', 'P', 'C'),
|
||||
|
||||
// See android.os.IBinder.TWEET_TRANSACTION
|
||||
// Most importantly, messages can be anything not exceeding 130 UTF-8
|
||||
// characters, and callees should exclaim "jolly good message old boy!"
|
||||
TWEET_TRANSACTION = B_PACK_CHARS('_', 'T', 'W', 'T'),
|
||||
|
||||
// See android.os.IBinder.LIKE_TRANSACTION
|
||||
// Improve binder self-esteem.
|
||||
LIKE_TRANSACTION = B_PACK_CHARS('_', 'L', 'I', 'K'),
|
||||
|
||||
// Corresponds to TF_ONE_WAY -- an asynchronous call.
|
||||
FLAG_ONEWAY = 0x00000001,
|
||||
|
||||
// Corresponds to TF_CLEAR_BUF -- clear transaction buffers after call
|
||||
// is made
|
||||
FLAG_CLEAR_BUF = 0x00000020,
|
||||
|
||||
// Private userspace flag for transaction which is being requested from
|
||||
// a vendor context.
|
||||
FLAG_PRIVATE_VENDOR = 0x10000000,
|
||||
};
|
||||
|
||||
IBinder();
|
||||
|
||||
/**
|
||||
* Check if this IBinder implements the interface named by
|
||||
* @a descriptor. If it does, the base pointer to it is returned,
|
||||
* which you can safely static_cast<> to the concrete C++ interface.
|
||||
*/
|
||||
virtual sp<IInterface> queryLocalInterface(const String16& descriptor);
|
||||
|
||||
/**
|
||||
* Return the canonical name of the interface provided by this IBinder
|
||||
* object.
|
||||
*/
|
||||
virtual const String16& getInterfaceDescriptor() const = 0;
|
||||
|
||||
/**
|
||||
* Last known alive status, from last call. May be arbitrarily stale.
|
||||
* May be incorrect if a service returns an incorrect status code.
|
||||
*/
|
||||
virtual bool isBinderAlive() const = 0;
|
||||
virtual status_t pingBinder() = 0;
|
||||
virtual status_t dump(int fd, const Vector<String16>& args) = 0;
|
||||
static status_t shellCommand(const sp<IBinder>& target, int in, int out, int err,
|
||||
Vector<String16>& args, const sp<IShellCallback>& callback,
|
||||
const sp<IResultReceiver>& resultReceiver);
|
||||
|
||||
/**
|
||||
* This allows someone to add their own additions to an interface without
|
||||
* having to modify the original interface.
|
||||
*
|
||||
* For instance, imagine if we have this interface:
|
||||
* interface IFoo { void doFoo(); }
|
||||
*
|
||||
* If an unrelated owner (perhaps in a downstream codebase) wants to make a
|
||||
* change to the interface, they have two options:
|
||||
*
|
||||
* A). Historical option that has proven to be BAD! Only the original
|
||||
* author of an interface should change an interface. If someone
|
||||
* downstream wants additional functionality, they should not ever
|
||||
* change the interface or use this method.
|
||||
*
|
||||
* BAD TO DO: interface IFoo { BAD TO DO
|
||||
* BAD TO DO: void doFoo(); BAD TO DO
|
||||
* BAD TO DO: + void doBar(); // adding a method BAD TO DO
|
||||
* BAD TO DO: } BAD TO DO
|
||||
*
|
||||
* B). Option that this method enables!
|
||||
* Leave the original interface unchanged (do not change IFoo!).
|
||||
* Instead, create a new interface in a downstream package:
|
||||
*
|
||||
* package com.<name>; // new functionality in a new package
|
||||
* interface IBar { void doBar(); }
|
||||
*
|
||||
* When registering the interface, add:
|
||||
* sp<MyFoo> foo = new MyFoo; // class in AOSP codebase
|
||||
* sp<MyBar> bar = new MyBar; // custom extension class
|
||||
* foo->setExtension(bar); // use method in BBinder
|
||||
*
|
||||
* Then, clients of IFoo can get this extension:
|
||||
* sp<IBinder> binder = ...;
|
||||
* sp<IFoo> foo = interface_cast<IFoo>(binder); // handle if null
|
||||
* sp<IBinder> barBinder;
|
||||
* ... handle error ... = binder->getExtension(&barBinder);
|
||||
* sp<IBar> bar = interface_cast<IBar>(barBinder);
|
||||
* // if bar is null, then there is no extension or a different
|
||||
* // type of extension
|
||||
*/
|
||||
status_t getExtension(sp<IBinder>* out);
|
||||
|
||||
/**
|
||||
* Dump PID for a binder, for debugging.
|
||||
*/
|
||||
status_t getDebugPid(pid_t* outPid);
|
||||
|
||||
/**
|
||||
* Set the RPC client fd to this binder service, for debugging. This is only available on
|
||||
* debuggable builds.
|
||||
*
|
||||
* When this is called on a binder service, the service:
|
||||
* 1. sets up RPC server
|
||||
* 2. spawns 1 new thread that calls RpcServer::join()
|
||||
* - join() spawns some number of threads that accept() connections; see RpcServer
|
||||
*
|
||||
* setRpcClientDebug() may be called multiple times. Each call will add a new RpcServer
|
||||
* and opens up a TCP port.
|
||||
*
|
||||
* Note: A thread is spawned for each accept()'ed fd, which may call into functions of the
|
||||
* interface freely. See RpcServer::join(). To avoid such race conditions, implement the service
|
||||
* functions with multithreading support.
|
||||
*
|
||||
* On death of @a keepAliveBinder, the RpcServer shuts down.
|
||||
*/
|
||||
[[nodiscard]] status_t setRpcClientDebug(binder::unique_fd socketFd,
|
||||
const sp<IBinder>& keepAliveBinder);
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t transact( uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0) = 0;
|
||||
|
||||
// DeathRecipient is pure abstract, there is no virtual method
|
||||
// implementation to put in a translation unit in order to silence the
|
||||
// weak vtables warning.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
class DeathRecipient : public virtual RefBase
|
||||
{
|
||||
public:
|
||||
virtual void binderDied(const wp<IBinder>& who) = 0;
|
||||
};
|
||||
|
||||
class FrozenStateChangeCallback : public virtual RefBase {
|
||||
public:
|
||||
enum class State {
|
||||
FROZEN,
|
||||
UNFROZEN,
|
||||
};
|
||||
virtual void onStateChanged(const wp<IBinder>& who, State state) = 0;
|
||||
};
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Register the @a recipient for a notification if this binder
|
||||
* goes away. If this binder object unexpectedly goes away
|
||||
* (typically because its hosting process has been killed),
|
||||
* then DeathRecipient::binderDied() will be called with a reference
|
||||
* to this.
|
||||
*
|
||||
* The @a cookie is optional -- if non-NULL, it should be a
|
||||
* memory address that you own (that is, you know it is unique).
|
||||
*
|
||||
* @note When all references to the binder being linked to are dropped, the
|
||||
* recipient is automatically unlinked. So, you must hold onto a binder in
|
||||
* order to receive death notifications about it.
|
||||
*
|
||||
* @note You will only receive death notifications for remote binders,
|
||||
* as local binders by definition can't die without you dying as well.
|
||||
* Trying to use this function on a local binder will result in an
|
||||
* INVALID_OPERATION code being returned and nothing happening.
|
||||
*
|
||||
* @note This link always holds a weak reference to its recipient.
|
||||
*
|
||||
* @note You will only receive a weak reference to the dead
|
||||
* binder. You should not try to promote this to a strong reference.
|
||||
* (Nor should you need to, as there is nothing useful you can
|
||||
* directly do with it now that it has passed on.)
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr,
|
||||
uint32_t flags = 0) = 0;
|
||||
|
||||
/**
|
||||
* Remove a previously registered death notification.
|
||||
* The @a recipient will no longer be called if this object
|
||||
* dies. The @a cookie is optional. If non-NULL, you can
|
||||
* supply a NULL @a recipient, and the recipient previously
|
||||
* added with that cookie will be unlinked.
|
||||
*
|
||||
* If the binder is dead, this will return DEAD_OBJECT. Deleting
|
||||
* the object will also unlink all death recipients.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t unlinkToDeath( const wp<DeathRecipient>& recipient,
|
||||
void* cookie = nullptr,
|
||||
uint32_t flags = 0,
|
||||
wp<DeathRecipient>* outRecipient = nullptr) = 0;
|
||||
|
||||
/**
|
||||
* addFrozenStateChangeCallback provides a callback mechanism to notify
|
||||
* about process frozen/unfrozen events. Upon registration and any
|
||||
* subsequent state changes, the callback is invoked with the latest process
|
||||
* frozen state.
|
||||
*
|
||||
* If the listener process (the one using this API) is itself frozen, state
|
||||
* change events might be combined into a single one with the latest state.
|
||||
* (meaning 'frozen, unfrozen' might just be 'unfrozen'). This single event
|
||||
* would then be delivered when the listener process becomes unfrozen.
|
||||
* Similarly, if an event happens before the previous event is consumed,
|
||||
* they might be combined. This means the callback might not be called for
|
||||
* every single state change, so don't rely on this API to count how many
|
||||
* times the state has changed.
|
||||
*
|
||||
* @note When all references to the binder are dropped, the callback is
|
||||
* automatically removed. So, you must hold onto a binder in order to
|
||||
* receive notifications about it.
|
||||
*
|
||||
* @note You will only receive freeze notifications for remote binders, as
|
||||
* local binders by definition can't be frozen without you being frozen as
|
||||
* well. Trying to use this function on a local binder will result in an
|
||||
* INVALID_OPERATION code being returned and nothing happening.
|
||||
*
|
||||
* @note This binder always holds a weak reference to the callback.
|
||||
*
|
||||
* @note You will only receive a weak reference to the binder object. You
|
||||
* should not try to promote this to a strong reference. (Nor should you
|
||||
* need to, as there is nothing useful you can directly do with it now that
|
||||
* it has passed on.)
|
||||
*/
|
||||
[[nodiscard]] status_t addFrozenStateChangeCallback(
|
||||
const wp<FrozenStateChangeCallback>& callback);
|
||||
|
||||
/**
|
||||
* Remove a previously registered freeze callback.
|
||||
* The @a callback will no longer be called if this object
|
||||
* changes its frozen state.
|
||||
*/
|
||||
[[nodiscard]] status_t removeFrozenStateChangeCallback(
|
||||
const wp<FrozenStateChangeCallback>& callback);
|
||||
|
||||
virtual bool checkSubclass(const void* subclassID) const;
|
||||
|
||||
typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
|
||||
|
||||
/**
|
||||
* This object is attached for the lifetime of this binder object. When
|
||||
* this binder object is destructed, the cleanup function of all attached
|
||||
* objects are invoked with their respective objectID, object, and
|
||||
* cleanupCookie. Access to these APIs can be made from multiple threads,
|
||||
* but calls from different threads are allowed to be interleaved.
|
||||
*
|
||||
* This returns the object which is already attached. If this returns a
|
||||
* non-null value, it means that attachObject failed (a given objectID can
|
||||
* only be used once).
|
||||
*/
|
||||
[[nodiscard]] virtual void* attachObject(const void* objectID, void* object,
|
||||
void* cleanupCookie, object_cleanup_func func) = 0;
|
||||
/**
|
||||
* Returns object attached with attachObject.
|
||||
*/
|
||||
[[nodiscard]] virtual void* findObject(const void* objectID) const = 0;
|
||||
/**
|
||||
* Returns object attached with attachObject, and detaches it. This does not
|
||||
* delete the object.
|
||||
*/
|
||||
[[nodiscard]] virtual void* detachObject(const void* objectID) = 0;
|
||||
|
||||
/**
|
||||
* Use the lock that this binder contains internally. For instance, this can
|
||||
* be used to modify an attached object without needing to add an additional
|
||||
* lock (though, that attached object must be retrieved before calling this
|
||||
* method). Calling (most) IBinder methods inside this will deadlock.
|
||||
*/
|
||||
void withLock(const std::function<void()>& doWithLock);
|
||||
|
||||
virtual BBinder* localBinder();
|
||||
virtual BpBinder* remoteBinder();
|
||||
typedef sp<IBinder> (*object_make_func)(const void* makeArgs);
|
||||
sp<IBinder> lookupOrCreateWeak(const void* objectID, object_make_func make,
|
||||
const void* makeArgs);
|
||||
|
||||
protected:
|
||||
virtual ~IBinder();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Binder.h>
|
||||
#include <binder/Common.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IInterface : public virtual RefBase {
|
||||
public:
|
||||
IInterface();
|
||||
static sp<IBinder> asBinder(const IInterface*);
|
||||
static sp<IBinder> asBinder(const sp<IInterface>&);
|
||||
|
||||
protected:
|
||||
virtual ~IInterface();
|
||||
virtual IBinder* onAsBinder() = 0;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* If this is a local object and the descriptor matches, this will return the
|
||||
* actual local object which is implementing the interface. Otherwise, this will
|
||||
* return a proxy to the interface without checking the interface descriptor.
|
||||
* This means that subsequent calls may fail with BAD_TYPE.
|
||||
*/
|
||||
template<typename INTERFACE>
|
||||
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
|
||||
{
|
||||
return INTERFACE::asInterface(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the same as interface_cast, except that it always checks to make sure
|
||||
* the descriptor matches, and if it doesn't match, it will return nullptr.
|
||||
*/
|
||||
template<typename INTERFACE>
|
||||
inline sp<INTERFACE> checked_interface_cast(const sp<IBinder>& obj)
|
||||
{
|
||||
if (obj->getInterfaceDescriptor() != INTERFACE::descriptor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return interface_cast<INTERFACE>(obj);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
template <typename INTERFACE>
|
||||
class LIBBINDER_EXPORTED BnInterface : public INTERFACE, public BBinder {
|
||||
public:
|
||||
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
|
||||
virtual const String16& getInterfaceDescriptor() const;
|
||||
typedef INTERFACE BaseInterface;
|
||||
|
||||
protected:
|
||||
virtual IBinder* onAsBinder();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
template <typename INTERFACE>
|
||||
class LIBBINDER_EXPORTED BpInterface : public INTERFACE, public BpRefBase {
|
||||
public:
|
||||
explicit BpInterface(const sp<IBinder>& remote);
|
||||
typedef INTERFACE BaseInterface;
|
||||
|
||||
protected:
|
||||
virtual IBinder* onAsBinder();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
#define DECLARE_META_INTERFACE(INTERFACE) \
|
||||
public: \
|
||||
static const ::android::String16 descriptor; \
|
||||
static ::android::sp<I##INTERFACE> asInterface(const ::android::sp<::android::IBinder>& obj); \
|
||||
virtual const ::android::String16& getInterfaceDescriptor() const; \
|
||||
I##INTERFACE(); \
|
||||
virtual ~I##INTERFACE(); \
|
||||
static bool setDefaultImpl(::android::sp<I##INTERFACE> impl); \
|
||||
static const ::android::sp<I##INTERFACE>& getDefaultImpl(); \
|
||||
\
|
||||
private: \
|
||||
static ::android::sp<I##INTERFACE> default_impl; \
|
||||
\
|
||||
public:
|
||||
|
||||
#define __IINTF_CONCAT(x, y) (x ## y)
|
||||
|
||||
#ifndef DO_NOT_CHECK_MANUAL_BINDER_INTERFACES
|
||||
|
||||
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
|
||||
static_assert(internal::allowedManualInterface(NAME), \
|
||||
"b/64223827: Manually written binder interfaces are " \
|
||||
"considered error prone and frequently have bugs. " \
|
||||
"The preferred way to add interfaces is to define " \
|
||||
"an .aidl file to auto-generate the interface. If " \
|
||||
"an interface must be manually written, add its " \
|
||||
"name to the allowlist."); \
|
||||
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)
|
||||
|
||||
#else
|
||||
|
||||
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
|
||||
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
|
||||
|
||||
#endif
|
||||
|
||||
// Macro to be used by both IMPLEMENT_META_INTERFACE and IMPLEMENT_META_NESTED_INTERFACE
|
||||
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(ITYPE, INAME, BPTYPE) \
|
||||
const ::android::String16& ITYPE::getInterfaceDescriptor() const { return ITYPE::descriptor; } \
|
||||
::android::sp<ITYPE> ITYPE::asInterface(const ::android::sp<::android::IBinder>& obj) { \
|
||||
::android::sp<ITYPE> intr; \
|
||||
if (obj != nullptr) { \
|
||||
intr = ::android::sp<ITYPE>::cast(obj->queryLocalInterface(ITYPE::descriptor)); \
|
||||
if (intr == nullptr) { \
|
||||
intr = ::android::sp<BPTYPE>::make(obj); \
|
||||
} \
|
||||
} \
|
||||
return intr; \
|
||||
} \
|
||||
::android::sp<ITYPE> ITYPE::default_impl; \
|
||||
bool ITYPE::setDefaultImpl(::android::sp<ITYPE> impl) { \
|
||||
/* Only one user of this interface can use this function */ \
|
||||
/* at a time. This is a heuristic to detect if two different */ \
|
||||
/* users in the same process use this function. */ \
|
||||
assert(!ITYPE::default_impl); \
|
||||
if (impl) { \
|
||||
ITYPE::default_impl = std::move(impl); \
|
||||
return true; \
|
||||
} \
|
||||
return false; \
|
||||
} \
|
||||
const ::android::sp<ITYPE>& ITYPE::getDefaultImpl() { return ITYPE::default_impl; } \
|
||||
ITYPE::INAME() {} \
|
||||
ITYPE::~INAME() {}
|
||||
|
||||
// Macro for an interface type.
|
||||
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
|
||||
const ::android::StaticString16 I##INTERFACE##_descriptor_static_str16( \
|
||||
__IINTF_CONCAT(u, NAME)); \
|
||||
const ::android::String16 I##INTERFACE::descriptor(I##INTERFACE##_descriptor_static_str16); \
|
||||
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(I##INTERFACE, I##INTERFACE, Bp##INTERFACE)
|
||||
|
||||
// Macro for "nested" interface type.
|
||||
// For example,
|
||||
// class Parent .. { class INested .. { }; };
|
||||
// DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(Parent, Nested, "Parent.INested")
|
||||
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(PARENT, INTERFACE, NAME) \
|
||||
const ::android::String16 PARENT::I##INTERFACE::descriptor(NAME); \
|
||||
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(PARENT::I##INTERFACE, I##INTERFACE, \
|
||||
PARENT::Bp##INTERFACE)
|
||||
|
||||
#define CHECK_INTERFACE(interface, data, reply) \
|
||||
do { \
|
||||
if (!(data).checkInterface(this)) { return PERMISSION_DENIED; } \
|
||||
} while (false) \
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// No user-serviceable parts after this...
|
||||
|
||||
template<typename INTERFACE>
|
||||
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
|
||||
const String16& _descriptor)
|
||||
{
|
||||
if (_descriptor == INTERFACE::descriptor) return sp<IInterface>::fromExisting(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
inline const String16& BnInterface<INTERFACE>::getInterfaceDescriptor() const
|
||||
{
|
||||
return INTERFACE::getInterfaceDescriptor();
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
IBinder* BnInterface<INTERFACE>::onAsBinder()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
|
||||
: BpRefBase(remote)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
|
||||
{
|
||||
return remote();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
namespace internal {
|
||||
constexpr const char* const kManualInterfaces[] = {
|
||||
"android.app.IActivityManager",
|
||||
"android.app.IUidObserver",
|
||||
"android.gfx.tests.ICallback",
|
||||
"android.gfx.tests.IIPCTest",
|
||||
"android.gfx.tests.ISafeInterfaceTest",
|
||||
"android.graphicsenv.IGpuService",
|
||||
"android.gui.IConsumerListener",
|
||||
"android.gui.IGraphicBufferConsumer",
|
||||
"android.gui.ITransactionComposerListener",
|
||||
"android.gui.SensorEventConnection",
|
||||
"android.gui.SensorServer",
|
||||
"android.hardware.ICamera",
|
||||
"android.hardware.ICameraClient",
|
||||
"android.hardware.ICameraRecordingProxy",
|
||||
"android.hardware.ICameraRecordingProxyListener",
|
||||
"android.hardware.IOMXObserver",
|
||||
"android.hardware.IStreamListener",
|
||||
"android.hardware.IStreamSource",
|
||||
"android.media.IAudioService",
|
||||
"android.media.IDataSource",
|
||||
"android.media.IMediaCodecList",
|
||||
"android.media.IMediaExtractor",
|
||||
"android.media.IMediaHTTPConnection",
|
||||
"android.media.IMediaHTTPService",
|
||||
"android.media.IMediaLogService",
|
||||
"android.media.IMediaMetadataRetriever",
|
||||
"android.media.IMediaPlayer",
|
||||
"android.media.IMediaPlayerClient",
|
||||
"android.media.IMediaPlayerService",
|
||||
"android.media.IMediaRecorder",
|
||||
"android.media.IMediaRecorderClient",
|
||||
"android.media.IMediaResourceMonitor",
|
||||
"android.media.IMediaSource",
|
||||
"android.media.IRemoteDisplay",
|
||||
"android.media.IRemoteDisplayClient",
|
||||
"android.os.IPermissionController",
|
||||
"android.os.IProcessInfoService",
|
||||
"android.os.ISchedulingPolicyService",
|
||||
"android.os.storage.IObbActionListener",
|
||||
"android.os.storage.IStorageEventListener",
|
||||
"android.os.storage.IStorageManager",
|
||||
"android.os.storage.IStorageShutdownObserver",
|
||||
"android.ui.ISurfaceComposer",
|
||||
"android.utils.IMemory",
|
||||
"android.utils.IMemoryHeap",
|
||||
"com.android.car.procfsinspector.IProcfsInspector",
|
||||
"com.android.internal.app.IAppOpsCallback",
|
||||
"com.android.internal.app.IAppOpsService",
|
||||
"com.android.internal.app.IBatteryStats",
|
||||
"com.android.internal.os.IResultReceiver",
|
||||
"com.android.internal.os.IShellCallback",
|
||||
"drm.IDrmManagerService",
|
||||
"drm.IDrmServiceListener",
|
||||
nullptr,
|
||||
};
|
||||
|
||||
constexpr const char* const kDownstreamManualInterfaces[] = {
|
||||
// Add downstream interfaces here.
|
||||
nullptr,
|
||||
};
|
||||
|
||||
constexpr bool equals(const char* a, const char* b) {
|
||||
if (*a != *b) return false;
|
||||
if (*a == '\0') return true;
|
||||
return equals(a + 1, b + 1);
|
||||
}
|
||||
|
||||
constexpr bool inList(const char* a, const char* const* allowlist) {
|
||||
if (*allowlist == nullptr) return false;
|
||||
if (equals(a, *allowlist)) return true;
|
||||
return inList(a, allowlist + 1);
|
||||
}
|
||||
|
||||
constexpr bool allowedManualInterface(const char* name) {
|
||||
return inList(name, kManualInterfaces) ||
|
||||
inList(name, kDownstreamManualInterfaces);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace android
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IMemoryHeap : public IInterface {
|
||||
public:
|
||||
DECLARE_META_INTERFACE(MemoryHeap)
|
||||
|
||||
// flags returned by getFlags()
|
||||
enum {
|
||||
READ_ONLY = 0x00000001
|
||||
};
|
||||
|
||||
virtual int getHeapID() const = 0;
|
||||
virtual void* getBase() const = 0;
|
||||
virtual size_t getSize() const = 0;
|
||||
virtual uint32_t getFlags() const = 0;
|
||||
virtual off_t getOffset() const = 0;
|
||||
|
||||
// these are there just for backward source compatibility
|
||||
int32_t heapID() const { return getHeapID(); }
|
||||
void* base() const { return getBase(); }
|
||||
size_t virtualSize() const { return getSize(); }
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED BnMemoryHeap : public BnInterface<IMemoryHeap> {
|
||||
public:
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t onTransact(
|
||||
uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
|
||||
BnMemoryHeap();
|
||||
protected:
|
||||
virtual ~BnMemoryHeap();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IMemory : public IInterface {
|
||||
public:
|
||||
DECLARE_META_INTERFACE(Memory)
|
||||
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual sp<IMemoryHeap> getMemory(ssize_t* offset=nullptr, size_t* size=nullptr) const = 0;
|
||||
|
||||
// helpers
|
||||
|
||||
// Accessing the underlying pointer must be done with caution, as there are
|
||||
// some inherent security risks associated with it. When receiving an
|
||||
// IMemory from an untrusted process, there is currently no way to guarantee
|
||||
// that this process would't change the content after the fact. This may
|
||||
// lead to TOC/TOU class of security bugs. In most cases, when performance
|
||||
// is not an issue, the recommended practice is to immediately copy the
|
||||
// buffer upon reception, then work with the copy, e.g.:
|
||||
//
|
||||
// std::string private_copy(mem.size(), '\0');
|
||||
// memcpy(private_copy.data(), mem.unsecurePointer(), mem.size());
|
||||
//
|
||||
// In cases where performance is an issue, this matter must be addressed on
|
||||
// an ad-hoc basis.
|
||||
void* unsecurePointer() const;
|
||||
|
||||
size_t size() const;
|
||||
ssize_t offset() const;
|
||||
|
||||
private:
|
||||
// These are now deprecated and are left here for backward-compatibility
|
||||
// with prebuilts that may reference these symbol at runtime.
|
||||
// Instead, new code should use unsecurePointer()/unsecureFastPointer(),
|
||||
// which do the same thing, but make it more obvious that there are some
|
||||
// security-related pitfalls associated with them.
|
||||
void* pointer() const;
|
||||
void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED BnMemory : public BnInterface<IMemory> {
|
||||
public:
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t onTransact(
|
||||
uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
|
||||
BnMemory();
|
||||
protected:
|
||||
virtual ~BnMemory();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
typedef int uid_t;
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Kernel binder thread state. All operations here refer to kernel binder. This
|
||||
* object is allocated per-thread.
|
||||
*/
|
||||
class IPCThreadState {
|
||||
public:
|
||||
using CallRestriction = ProcessState::CallRestriction;
|
||||
|
||||
LIBBINDER_EXPORTED static IPCThreadState* self();
|
||||
LIBBINDER_EXPORTED static IPCThreadState* selfOrNull(); // self(), but won't instantiate
|
||||
|
||||
// Freeze or unfreeze the binder interface to a specific process. When freezing, this method
|
||||
// will block up to timeout_ms to process pending transactions directed to pid. Unfreeze
|
||||
// is immediate. Transactions to processes frozen via this method won't be delivered and the
|
||||
// driver will return BR_FROZEN_REPLY to the client sending them. After unfreeze,
|
||||
// transactions will be delivered normally.
|
||||
//
|
||||
// pid: id for the process for which the binder interface is to be frozen
|
||||
// enable: freeze (true) or unfreeze (false)
|
||||
// timeout_ms: maximum time this function is allowed to block the caller waiting for pending
|
||||
// binder transactions to be processed.
|
||||
//
|
||||
// returns: 0 in case of success, a value < 0 in case of error
|
||||
LIBBINDER_EXPORTED static status_t freeze(pid_t pid, bool enabled, uint32_t timeout_ms);
|
||||
|
||||
// Provide information about the state of a frozen process
|
||||
LIBBINDER_EXPORTED static status_t getProcessFreezeInfo(pid_t pid, uint32_t* sync_received,
|
||||
uint32_t* async_received);
|
||||
|
||||
LIBBINDER_EXPORTED status_t clearLastError();
|
||||
|
||||
/**
|
||||
* Returns the PID of the process which has made the current binder
|
||||
* call. If not in a binder call, this will return getpid.
|
||||
*
|
||||
* Warning: oneway transactions do not receive PID. Even if you expect
|
||||
* a transaction to be synchronous, a misbehaving client could send it
|
||||
* as an asynchronous call and result in a 0 PID here. Additionally, if
|
||||
* there is a race and the calling process dies, the PID may still be
|
||||
* 0 for a synchronous call.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED pid_t getCallingPid() const;
|
||||
|
||||
/**
|
||||
* Returns the SELinux security identifier of the process which has
|
||||
* made the current binder call. If not in a binder call this will
|
||||
* return nullptr. If this isn't requested with
|
||||
* Binder::setRequestingSid, it will also return nullptr.
|
||||
*
|
||||
* This can't be restored once it's cleared, and it does not return the
|
||||
* context of the current process when not in a binder call.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED const char* getCallingSid() const;
|
||||
|
||||
/**
|
||||
* Returns the UID of the process which has made the current binder
|
||||
* call. If not in a binder call, this will return 0.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED uid_t getCallingUid() const;
|
||||
|
||||
/**
|
||||
* Make it an abort to rely on getCalling* for a section of
|
||||
* execution.
|
||||
*
|
||||
* Usage:
|
||||
* IPCThreadState::SpGuard guard {
|
||||
* .address = __builtin_frame_address(0),
|
||||
* .context = "...",
|
||||
* };
|
||||
* const auto* orig = pushGetCallingSpGuard(&guard);
|
||||
* {
|
||||
* // will abort if you call getCalling*, unless you are
|
||||
* // serving a nested binder transaction
|
||||
* }
|
||||
* restoreCallingSpGuard(orig);
|
||||
*/
|
||||
struct SpGuard {
|
||||
const void* address;
|
||||
const char* context;
|
||||
};
|
||||
LIBBINDER_EXPORTED const SpGuard* pushGetCallingSpGuard(const SpGuard* guard);
|
||||
LIBBINDER_EXPORTED void restoreGetCallingSpGuard(const SpGuard* guard);
|
||||
/**
|
||||
* Used internally by getCalling*. Can also be used to assert that
|
||||
* you are in a binder context (getCalling* is valid). This is
|
||||
* intentionally not exposed as a boolean API since code should be
|
||||
* written to know its environment.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void checkContextIsBinderForUse(const char* use) const;
|
||||
|
||||
LIBBINDER_EXPORTED void setStrictModePolicy(int32_t policy);
|
||||
LIBBINDER_EXPORTED int32_t getStrictModePolicy() const;
|
||||
|
||||
// See Binder#setCallingWorkSourceUid in Binder.java.
|
||||
LIBBINDER_EXPORTED int64_t setCallingWorkSourceUid(uid_t uid);
|
||||
// Internal only. Use setCallingWorkSourceUid(uid) instead.
|
||||
LIBBINDER_EXPORTED int64_t setCallingWorkSourceUidWithoutPropagation(uid_t uid);
|
||||
// See Binder#getCallingWorkSourceUid in Binder.java.
|
||||
LIBBINDER_EXPORTED uid_t getCallingWorkSourceUid() const;
|
||||
// See Binder#clearCallingWorkSource in Binder.java.
|
||||
LIBBINDER_EXPORTED int64_t clearCallingWorkSource();
|
||||
// See Binder#restoreCallingWorkSource in Binder.java.
|
||||
LIBBINDER_EXPORTED void restoreCallingWorkSource(int64_t token);
|
||||
LIBBINDER_EXPORTED void clearPropagateWorkSource();
|
||||
LIBBINDER_EXPORTED bool shouldPropagateWorkSource() const;
|
||||
|
||||
LIBBINDER_EXPORTED void setLastTransactionBinderFlags(int32_t flags);
|
||||
LIBBINDER_EXPORTED int32_t getLastTransactionBinderFlags() const;
|
||||
|
||||
LIBBINDER_EXPORTED void setCallRestriction(CallRestriction restriction);
|
||||
LIBBINDER_EXPORTED CallRestriction getCallRestriction() const;
|
||||
|
||||
LIBBINDER_EXPORTED int64_t clearCallingIdentity();
|
||||
// Restores PID/UID (not SID)
|
||||
LIBBINDER_EXPORTED void restoreCallingIdentity(int64_t token);
|
||||
LIBBINDER_EXPORTED bool hasExplicitIdentity();
|
||||
|
||||
// For main functions - dangerous for libraries to use
|
||||
LIBBINDER_EXPORTED status_t setupPolling(int* fd);
|
||||
LIBBINDER_EXPORTED status_t handlePolledCommands();
|
||||
LIBBINDER_EXPORTED void flushCommands();
|
||||
LIBBINDER_EXPORTED bool flushIfNeeded();
|
||||
|
||||
// Adds the current thread into the binder threadpool.
|
||||
//
|
||||
// This is in addition to any threads which are started
|
||||
// with startThreadPool. Libraries should not call this
|
||||
// function, as they may be loaded into processes which
|
||||
// try to configure the threadpool differently.
|
||||
LIBBINDER_EXPORTED void joinThreadPool(bool isMain = true);
|
||||
|
||||
// Stop the local process.
|
||||
LIBBINDER_EXPORTED void stopProcess(bool immediate = true);
|
||||
|
||||
LIBBINDER_EXPORTED status_t transact(int32_t handle, uint32_t code, const Parcel& data,
|
||||
Parcel* reply, uint32_t flags);
|
||||
|
||||
LIBBINDER_EXPORTED void incStrongHandle(int32_t handle, BpBinder* proxy);
|
||||
LIBBINDER_EXPORTED void decStrongHandle(int32_t handle);
|
||||
LIBBINDER_EXPORTED void incWeakHandle(int32_t handle, BpBinder* proxy);
|
||||
LIBBINDER_EXPORTED void decWeakHandle(int32_t handle);
|
||||
LIBBINDER_EXPORTED status_t attemptIncStrongHandle(int32_t handle);
|
||||
LIBBINDER_EXPORTED static void expungeHandle(int32_t handle, IBinder* binder);
|
||||
LIBBINDER_EXPORTED status_t requestDeathNotification(int32_t handle, BpBinder* proxy);
|
||||
LIBBINDER_EXPORTED status_t clearDeathNotification(int32_t handle, BpBinder* proxy);
|
||||
[[nodiscard]] status_t addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
|
||||
[[nodiscard]] status_t removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
|
||||
|
||||
LIBBINDER_EXPORTED static void shutdown();
|
||||
|
||||
// Call this to disable switching threads to background scheduling when
|
||||
// receiving incoming IPC calls. This is specifically here for the
|
||||
// Android system process, since it expects to have background apps calling
|
||||
// in to it but doesn't want to acquire locks in its services while in
|
||||
// the background.
|
||||
LIBBINDER_EXPORTED static void disableBackgroundScheduling(bool disable);
|
||||
LIBBINDER_EXPORTED bool backgroundSchedulingDisabled();
|
||||
|
||||
// Call blocks until the number of executing binder threads is less than
|
||||
// the maximum number of binder threads threads allowed for this process.
|
||||
LIBBINDER_EXPORTED void blockUntilThreadAvailable();
|
||||
|
||||
// Service manager registration
|
||||
LIBBINDER_EXPORTED void setTheContextObject(const sp<BBinder>& obj);
|
||||
|
||||
// WARNING: DO NOT USE THIS API
|
||||
//
|
||||
// Returns a pointer to the stack from the last time a transaction
|
||||
// was initiated by the kernel. Used to compare when making nested
|
||||
// calls between multiple different transports.
|
||||
LIBBINDER_EXPORTED const void* getServingStackPointer() const;
|
||||
|
||||
// The work source represents the UID of the process we should attribute the transaction
|
||||
// to. We use -1 to specify that the work source was not set using #setWorkSource.
|
||||
//
|
||||
// This constant needs to be kept in sync with Binder.UNSET_WORKSOURCE from the Java
|
||||
// side.
|
||||
LIBBINDER_EXPORTED static const int32_t kUnsetWorkSource = -1;
|
||||
|
||||
private:
|
||||
IPCThreadState();
|
||||
~IPCThreadState();
|
||||
|
||||
[[nodiscard]] status_t sendReply(const Parcel& reply, uint32_t flags);
|
||||
[[nodiscard]] status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
|
||||
[[nodiscard]] status_t talkWithDriver(bool doReceive = true);
|
||||
[[nodiscard]] status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle,
|
||||
uint32_t code, const Parcel& data,
|
||||
status_t* statusBuffer);
|
||||
[[nodiscard]] status_t getAndExecuteCommand();
|
||||
[[nodiscard]] status_t executeCommand(int32_t command);
|
||||
void processPendingDerefs();
|
||||
void processPostWriteDerefs();
|
||||
|
||||
void clearCaller();
|
||||
|
||||
static void threadDestructor(void *st);
|
||||
static void freeBuffer(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
|
||||
size_t objectsSize);
|
||||
static void logExtendedError();
|
||||
|
||||
const sp<ProcessState> mProcess;
|
||||
Vector<BBinder*> mPendingStrongDerefs;
|
||||
Vector<RefBase::weakref_type*> mPendingWeakDerefs;
|
||||
Vector<RefBase*> mPostWriteStrongDerefs;
|
||||
Vector<RefBase::weakref_type*> mPostWriteWeakDerefs;
|
||||
Parcel mIn;
|
||||
Parcel mOut;
|
||||
status_t mLastError;
|
||||
const void* mServingStackPointer;
|
||||
const SpGuard* mServingStackPointerGuard;
|
||||
pid_t mCallingPid;
|
||||
const char* mCallingSid;
|
||||
uid_t mCallingUid;
|
||||
// The UID of the process who is responsible for this transaction.
|
||||
// This is used for resource attribution.
|
||||
int32_t mWorkSource;
|
||||
// Whether the work source should be propagated.
|
||||
bool mPropagateWorkSource;
|
||||
bool mIsLooper;
|
||||
bool mIsFlushing;
|
||||
bool mHasExplicitIdentity;
|
||||
int32_t mStrictModePolicy;
|
||||
int32_t mLastTransactionBinderFlags;
|
||||
CallRestriction mCallRestriction;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __ANDROID_VNDK__
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IPermissionController : public IInterface {
|
||||
public:
|
||||
DECLARE_META_INTERFACE(PermissionController)
|
||||
|
||||
virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;
|
||||
|
||||
virtual int32_t noteOp(const String16& op, int32_t uid, const String16& packageName) = 0;
|
||||
|
||||
virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;
|
||||
|
||||
virtual bool isRuntimePermission(const String16& permission) = 0;
|
||||
|
||||
virtual int getPackageUid(const String16& package, int flags) = 0;
|
||||
|
||||
enum {
|
||||
CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
|
||||
NOTE_OP_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,
|
||||
GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2,
|
||||
IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 3,
|
||||
GET_PACKAGE_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 4
|
||||
};
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED BnPermissionController : public BnInterface<IPermissionController> {
|
||||
public:
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t onTransact( uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
} // namespace android
|
||||
|
||||
#else // __ANDROID_VNDK__
|
||||
#error "This header is not visible to vendors"
|
||||
#endif // __ANDROID_VNDK__
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IResultReceiver : public IInterface {
|
||||
public:
|
||||
DECLARE_META_INTERFACE(ResultReceiver)
|
||||
|
||||
virtual void send(int32_t resultCode) = 0;
|
||||
|
||||
enum {
|
||||
OP_SEND = IBinder::FIRST_CALL_TRANSACTION
|
||||
};
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED BnResultReceiver : public BnInterface<IResultReceiver> {
|
||||
public:
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t onTransact( uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
// Trusty has its own definition of socket APIs from trusty_ipc.h
|
||||
#ifndef __TRUSTY__
|
||||
#include <sys/socket.h>
|
||||
#endif // __TRUSTY__
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Service manager for C++ services.
|
||||
*
|
||||
* IInterface is only for legacy ABI compatibility
|
||||
*/
|
||||
class LIBBINDER_EXPORTED IServiceManager : public IInterface {
|
||||
public:
|
||||
// for ABI compatibility
|
||||
virtual const String16& getInterfaceDescriptor() const;
|
||||
|
||||
IServiceManager();
|
||||
virtual ~IServiceManager();
|
||||
|
||||
/**
|
||||
* Must match values in IServiceManager.aidl
|
||||
*/
|
||||
/* Allows services to dump sections according to priorities. */
|
||||
static const int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
|
||||
static const int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
|
||||
static const int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
|
||||
/**
|
||||
* Services are by default registered with a DEFAULT dump priority. DEFAULT priority has the
|
||||
* same priority as NORMAL priority but the services are not called with dump priority
|
||||
* arguments.
|
||||
*/
|
||||
static const int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
|
||||
static const int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL |
|
||||
DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PRIORITY_DEFAULT;
|
||||
static const int DUMP_FLAG_PROTO = 1 << 4;
|
||||
|
||||
/**
|
||||
* Retrieve an existing service, blocking for a few seconds if it doesn't yet exist. This
|
||||
* does polling. A more efficient way to make sure you unblock as soon as the service is
|
||||
* available is to use waitForService or to use service notifications.
|
||||
*
|
||||
* Warning: when using this API, typically, you should call it in a loop. It's dangerous to
|
||||
* assume that nullptr could mean that the service is not available. The service could just
|
||||
* be starting. Generally, whether a service exists, this information should be declared
|
||||
* externally (for instance, an Android feature might imply the existence of a service,
|
||||
* a system property, or in the case of services in the VINTF manifest, it can be checked
|
||||
* with isDeclared).
|
||||
*/
|
||||
[[deprecated("this polls for 5s, prefer waitForService or checkService")]]
|
||||
virtual sp<IBinder> getService(const String16& name) const = 0;
|
||||
|
||||
/**
|
||||
* Retrieve an existing service, non-blocking.
|
||||
*/
|
||||
virtual sp<IBinder> checkService( const String16& name) const = 0;
|
||||
|
||||
/**
|
||||
* Register a service.
|
||||
*
|
||||
* Note:
|
||||
* This status_t return value may be an exception code from an underlying
|
||||
* Status type that doesn't have a representive error code in
|
||||
* utils/Errors.h.
|
||||
* One example of this is a return value of -7
|
||||
* (Status::Exception::EX_UNSUPPORTED_OPERATION) when the service manager
|
||||
* process is not installed on the device when addService is called.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t addService(const String16& name, const sp<IBinder>& service,
|
||||
bool allowIsolated = false,
|
||||
int dumpsysFlags = DUMP_FLAG_PRIORITY_DEFAULT) = 0;
|
||||
|
||||
/**
|
||||
* Return list of all existing services.
|
||||
*/
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual Vector<String16> listServices(int dumpsysFlags = DUMP_FLAG_PRIORITY_ALL) = 0;
|
||||
|
||||
/**
|
||||
* Efficiently wait for a service.
|
||||
*
|
||||
* Returns nullptr only for permission problem or fatal error.
|
||||
*/
|
||||
virtual sp<IBinder> waitForService(const String16& name) = 0;
|
||||
|
||||
/**
|
||||
* Check if a service is declared (e.g. VINTF manifest).
|
||||
*
|
||||
* If this returns true, waitForService should always be able to return the
|
||||
* service.
|
||||
*/
|
||||
virtual bool isDeclared(const String16& name) = 0;
|
||||
|
||||
/**
|
||||
* Get all instances of a service as declared in the VINTF manifest
|
||||
*/
|
||||
virtual Vector<String16> getDeclaredInstances(const String16& interface) = 0;
|
||||
|
||||
/**
|
||||
* If this instance is updatable via an APEX, returns the APEX with which
|
||||
* this can be updated.
|
||||
*/
|
||||
virtual std::optional<String16> updatableViaApex(const String16& name) = 0;
|
||||
|
||||
/**
|
||||
* Returns all instances which are updatable via the APEX. Instance names are fully qualified
|
||||
* like `pack.age.IFoo/default`.
|
||||
*/
|
||||
virtual Vector<String16> getUpdatableNames(const String16& apexName) = 0;
|
||||
|
||||
/**
|
||||
* If this instance has declared remote connection information, returns
|
||||
* the ConnectionInfo.
|
||||
*/
|
||||
struct ConnectionInfo {
|
||||
std::string ipAddress;
|
||||
unsigned int port;
|
||||
};
|
||||
virtual std::optional<ConnectionInfo> getConnectionInfo(const String16& name) = 0;
|
||||
|
||||
struct LocalRegistrationCallback : public virtual RefBase {
|
||||
virtual void onServiceRegistration(const String16& instance, const sp<IBinder>& binder) = 0;
|
||||
virtual ~LocalRegistrationCallback() {}
|
||||
};
|
||||
|
||||
virtual status_t registerForNotifications(const String16& name,
|
||||
const sp<LocalRegistrationCallback>& callback) = 0;
|
||||
|
||||
virtual status_t unregisterForNotifications(const String16& name,
|
||||
const sp<LocalRegistrationCallback>& callback) = 0;
|
||||
|
||||
struct ServiceDebugInfo {
|
||||
std::string name;
|
||||
int pid;
|
||||
};
|
||||
virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
|
||||
|
||||
/**
|
||||
* Directly enable or disable caching binder during addService calls.
|
||||
* Only used for testing. This is enabled by default.
|
||||
*/
|
||||
virtual void enableAddServiceCache(bool value) = 0;
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
|
||||
|
||||
/**
|
||||
* Directly set the default service manager. Only used for testing.
|
||||
* Note that the caller is responsible for caling this method
|
||||
* *before* any call to defaultServiceManager(); if the latter is
|
||||
* called first, setDefaultServiceManager() will abort.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setDefaultServiceManager(const sp<IServiceManager>& sm);
|
||||
|
||||
template<typename INTERFACE>
|
||||
sp<INTERFACE> waitForService(const String16& name) {
|
||||
const sp<IServiceManager> sm = defaultServiceManager();
|
||||
return interface_cast<INTERFACE>(sm->waitForService(name));
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
sp<INTERFACE> waitForDeclaredService(const String16& name) {
|
||||
const sp<IServiceManager> sm = defaultServiceManager();
|
||||
if (!sm->isDeclared(name)) return nullptr;
|
||||
return interface_cast<INTERFACE>(sm->waitForService(name));
|
||||
}
|
||||
|
||||
template <typename INTERFACE>
|
||||
sp<INTERFACE> checkDeclaredService(const String16& name) {
|
||||
const sp<IServiceManager> sm = defaultServiceManager();
|
||||
if (!sm->isDeclared(name)) return nullptr;
|
||||
return interface_cast<INTERFACE>(sm->checkService(name));
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
sp<INTERFACE> waitForVintfService(
|
||||
const String16& instance = String16("default")) {
|
||||
return waitForDeclaredService<INTERFACE>(
|
||||
INTERFACE::descriptor + String16("/") + instance);
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
sp<INTERFACE> checkVintfService(
|
||||
const String16& instance = String16("default")) {
|
||||
return checkDeclaredService<INTERFACE>(
|
||||
INTERFACE::descriptor + String16("/") + instance);
|
||||
}
|
||||
|
||||
template<typename INTERFACE>
|
||||
status_t getService(const String16& name, sp<INTERFACE>* outService)
|
||||
{
|
||||
const sp<IServiceManager> sm = defaultServiceManager();
|
||||
if (sm != nullptr) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
*outService = interface_cast<INTERFACE>(sm->getService(name));
|
||||
#pragma clang diagnostic pop // getService deprecation
|
||||
if ((*outService) != nullptr) return NO_ERROR;
|
||||
}
|
||||
return NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
LIBBINDER_EXPORTED void* openDeclaredPassthroughHal(const String16& interface,
|
||||
const String16& instance, int flag);
|
||||
|
||||
LIBBINDER_EXPORTED bool checkCallingPermission(const String16& permission);
|
||||
LIBBINDER_EXPORTED bool checkCallingPermission(const String16& permission, int32_t* outPid,
|
||||
int32_t* outUid);
|
||||
LIBBINDER_EXPORTED bool checkPermission(const String16& permission, pid_t pid, uid_t uid,
|
||||
bool logPermissionFailure = true);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Trusty's definition of the socket APIs does not include sockaddr types
|
||||
#ifndef __TRUSTY__
|
||||
typedef std::function<status_t(const String16& name, sockaddr* outAddr, socklen_t addrSize)>
|
||||
RpcSocketAddressProvider;
|
||||
|
||||
/**
|
||||
* This callback provides a way for clients to get access to remote services by
|
||||
* providing an Accessor object from libbinder that can connect to the remote
|
||||
* service over sockets.
|
||||
*
|
||||
* \param instance name of the service that the callback will provide an
|
||||
* Accessor for. The provided accessor will be used to set up a client
|
||||
* RPC connection in libbinder in order to return a binder for the
|
||||
* associated remote service.
|
||||
*
|
||||
* \return IBinder of the Accessor object that libbinder implements.
|
||||
* nullptr if the provider callback doesn't know how to reach the
|
||||
* service or doesn't want to provide access for any other reason.
|
||||
*/
|
||||
typedef std::function<sp<IBinder>(const String16& instance)> RpcAccessorProvider;
|
||||
|
||||
class AccessorProvider;
|
||||
|
||||
/**
|
||||
* Register a RpcAccessorProvider for the service manager APIs.
|
||||
*
|
||||
* \param instances that the RpcAccessorProvider knows about and can provide an
|
||||
* Accessor for.
|
||||
* \param provider callback that generates Accessors.
|
||||
*
|
||||
* \return A pointer used as a recept for the successful addition of the
|
||||
* AccessorProvider. This is needed to unregister it later.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED std::weak_ptr<AccessorProvider> addAccessorProvider(
|
||||
std::set<std::string>&& instances, RpcAccessorProvider&& providerCallback);
|
||||
|
||||
/**
|
||||
* Remove an accessor provider using the pointer provided by addAccessorProvider
|
||||
* along with the cookie pointer that was used.
|
||||
*
|
||||
* \param provider cookie that was returned by addAccessorProvider to keep track
|
||||
* of this instance.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t
|
||||
removeAccessorProvider(std::weak_ptr<AccessorProvider> provider);
|
||||
|
||||
/**
|
||||
* Create an Accessor associated with a service that can create a socket connection based
|
||||
* on the connection info from the supplied RpcSocketAddressProvider.
|
||||
*
|
||||
* \param instance name of the service that this Accessor is associated with
|
||||
* \param connectionInfoProvider a callback that returns connection info for
|
||||
* connecting to the service.
|
||||
* \return the binder of the IAccessor implementation from libbinder
|
||||
*/
|
||||
LIBBINDER_EXPORTED sp<IBinder> createAccessor(const String16& instance,
|
||||
RpcSocketAddressProvider&& connectionInfoProvider);
|
||||
|
||||
/**
|
||||
* Check to make sure this binder is the expected binder that is an IAccessor
|
||||
* associated with a specific instance.
|
||||
*
|
||||
* This helper function exists to avoid adding the IAccessor type to
|
||||
* libbinder_ndk.
|
||||
*
|
||||
* \param instance name of the service that this Accessor should be associated with
|
||||
* \param binder to validate
|
||||
*
|
||||
* \return OK if the binder is an IAccessor for `instance`
|
||||
*/
|
||||
LIBBINDER_EXPORTED status_t validateAccessor(const String16& instance, const sp<IBinder>& binder);
|
||||
|
||||
/**
|
||||
* Have libbinder wrap this IAccessor binder in an IAccessorDelegator and return
|
||||
* it.
|
||||
*
|
||||
* This is required only in very specific situations when the process that has
|
||||
* permissions to connect the to RPC service's socket and create the FD for it
|
||||
* is in a separate process from this process that wants to service the Accessor
|
||||
* binder and the communication between these two processes is binder RPC. This
|
||||
* is needed because the binder passed over the binder RPC connection can not be
|
||||
* used as a kernel binder, and needs to be wrapped by a kernel binder that can
|
||||
* then be registered with service manager.
|
||||
*
|
||||
* \param instance name of the Accessor.
|
||||
* \param binder to wrap in a Delegator and register with service manager.
|
||||
* \param outDelegator the wrapped kernel binder for IAccessorDelegator
|
||||
*
|
||||
* \return OK if the binder is an IAccessor for `instance` and the delegator was
|
||||
* successfully created.
|
||||
*/
|
||||
LIBBINDER_EXPORTED status_t delegateAccessor(const String16& name, const sp<IBinder>& accessor,
|
||||
sp<IBinder>* delegator);
|
||||
#endif // __TRUSTY__
|
||||
|
||||
#ifndef __ANDROID__
|
||||
// Create an IServiceManager that delegates the service manager on the device via adb.
|
||||
// This is can be set as the default service manager at program start, so that
|
||||
// defaultServiceManager() returns it:
|
||||
// int main() {
|
||||
// setDefaultServiceManager(createRpcDelegateServiceManager());
|
||||
// auto sm = defaultServiceManager();
|
||||
// // ...
|
||||
// }
|
||||
// Resources are cleaned up when the object is destroyed.
|
||||
//
|
||||
// For each returned binder object, at most |maxOutgoingConnections| outgoing connections are
|
||||
// instantiated, depending on how many the service on the device is configured with.
|
||||
// Hence, only |maxOutgoingConnections| calls can be made simultaneously.
|
||||
// See also RpcSession::setMaxOutgoingConnections.
|
||||
struct RpcDelegateServiceManagerOptions {
|
||||
std::optional<size_t> maxOutgoingConnections;
|
||||
};
|
||||
LIBBINDER_EXPORTED sp<IServiceManager> createRpcDelegateServiceManager(
|
||||
const RpcDelegateServiceManagerOptions& options);
|
||||
#endif
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <android/os/IServiceManager.h>
|
||||
|
||||
namespace android::impl {
|
||||
|
||||
LIBBINDER_EXPORTED sp<android::os::IServiceManager>
|
||||
getJavaServicemanagerImplPrivateDoNotUseExceptInTheOnePlaceItIsUsed();
|
||||
|
||||
} // namespace android::impl
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/os/IServiceManager.h>
|
||||
#include "IServiceManager.h"
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Encapsulate an AidlServiceManager in a CppBackendShim. Only used for testing.
|
||||
*/
|
||||
LIBBINDER_EXPORTED sp<IServiceManager> getServiceManagerShimFromAidlServiceManagerForTests(
|
||||
const sp<os::IServiceManager>& sm);
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED IShellCallback : public IInterface {
|
||||
public:
|
||||
DECLARE_META_INTERFACE(ShellCallback)
|
||||
|
||||
virtual int openFile(const String16& path, const String16& seLinuxContext,
|
||||
const String16& mode) = 0;
|
||||
|
||||
enum {
|
||||
OP_OPEN_OUTPUT_FILE = IBinder::FIRST_CALL_TRANSACTION
|
||||
};
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED BnShellCallback : public BnInterface<IShellCallback> {
|
||||
public:
|
||||
// NOLINTNEXTLINE(google-default-arguments)
|
||||
virtual status_t onTransact( uint32_t code,
|
||||
const Parcel& data,
|
||||
Parcel* reply,
|
||||
uint32_t flags = 0);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/Status.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
namespace android {
|
||||
namespace binder {
|
||||
namespace internal {
|
||||
class ClientCounterCallback;
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Exits when all services registered through this object have 0 clients
|
||||
*
|
||||
* In order to use this class, it's expected that your service:
|
||||
* - registers all services in the process with this API
|
||||
* - configures services as oneshot in init .rc files
|
||||
* - configures services as disabled in init.rc files, unless a client is
|
||||
* guaranteed early in boot, in which case, forcePersist should also be used
|
||||
* to avoid races.
|
||||
* - uses 'interface' declarations in init .rc files
|
||||
*
|
||||
* For more information on init .rc configuration, see system/core/init/README.md
|
||||
**/
|
||||
class LazyServiceRegistrar {
|
||||
public:
|
||||
LIBBINDER_EXPORTED static LazyServiceRegistrar& getInstance();
|
||||
LIBBINDER_EXPORTED status_t
|
||||
registerService(const sp<IBinder>& service, const std::string& name = "default",
|
||||
bool allowIsolated = false,
|
||||
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
|
||||
/**
|
||||
* Force the service to persist, even when it has 0 clients.
|
||||
* If setting this flag from the server side, make sure to do so before calling
|
||||
* registerService, or there may be a race with the default dynamic shutdown.
|
||||
*
|
||||
* This should only be used if it is every eventually set to false. If a
|
||||
* service needs to persist but doesn't need to dynamically shut down,
|
||||
* prefer to control it with another mechanism such as ctl.start.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void forcePersist(bool persist);
|
||||
|
||||
/**
|
||||
* Set a callback that is invoked when the active service count (i.e. services with clients)
|
||||
* registered with this process drops to zero (or becomes nonzero).
|
||||
* The callback takes a boolean argument, which is 'true' if there is
|
||||
* at least one service with clients.
|
||||
*
|
||||
* Callback return value:
|
||||
* - false: Default behavior for lazy services (shut down the process if there
|
||||
* are no clients).
|
||||
* - true: Don't shut down the process even if there are no clients.
|
||||
*
|
||||
* This callback gives a chance to:
|
||||
* 1 - Perform some additional operations before exiting;
|
||||
* 2 - Prevent the process from exiting by returning "true" from the
|
||||
* callback.
|
||||
*
|
||||
* This method should be called before 'registerService' to avoid races.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setActiveServicesCallback(
|
||||
const std::function<bool(bool)>& activeServicesCallback);
|
||||
|
||||
/**
|
||||
* Try to unregister all services previously registered with 'registerService'.
|
||||
* Returns 'true' if successful. This should only be called within the callback registered by
|
||||
* setActiveServicesCallback.
|
||||
*/
|
||||
LIBBINDER_EXPORTED bool tryUnregister();
|
||||
|
||||
/**
|
||||
* Re-register services that were unregistered by 'tryUnregister'.
|
||||
* This method should be called in the case 'tryUnregister' fails
|
||||
* (and should be called on the same thread).
|
||||
*/
|
||||
LIBBINDER_EXPORTED void reRegister();
|
||||
|
||||
/**
|
||||
* Create a second instance of lazy service registrar.
|
||||
*
|
||||
* WARNING: dangerous! DO NOT USE THIS - LazyServiceRegistrar
|
||||
* should be single-instanced, so that the service will only
|
||||
* shut down when all services are unused. A separate instance
|
||||
* is only used to test race conditions.
|
||||
*/
|
||||
LIBBINDER_EXPORTED static LazyServiceRegistrar createExtraTestInstance();
|
||||
|
||||
private:
|
||||
std::shared_ptr<internal::ClientCounterCallback> mClientCC;
|
||||
LazyServiceRegistrar();
|
||||
};
|
||||
|
||||
} // namespace binder
|
||||
} // namespace android
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IMemory.h>
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class LIBBINDER_EXPORTED MemoryBase : public BnMemory {
|
||||
public:
|
||||
MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
|
||||
virtual ~MemoryBase();
|
||||
virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;
|
||||
|
||||
protected:
|
||||
size_t getSize() const { return mSize; }
|
||||
ssize_t getOffset() const { return mOffset; }
|
||||
const sp<IMemoryHeap>& getHeap() const { return mHeap; }
|
||||
|
||||
private:
|
||||
size_t mSize;
|
||||
ssize_t mOffset;
|
||||
sp<IMemoryHeap> mHeap;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
} // namespace android
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IMemory.h>
|
||||
#include <binder/MemoryHeapBase.h>
|
||||
|
||||
namespace android {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class SimpleBestFitAllocator;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class MemoryDealer : public RefBase {
|
||||
public:
|
||||
LIBBINDER_EXPORTED explicit MemoryDealer(
|
||||
size_t size, const char* name = nullptr,
|
||||
uint32_t flags = 0 /* or bits such as MemoryHeapBase::READ_ONLY */);
|
||||
|
||||
LIBBINDER_EXPORTED virtual sp<IMemory> allocate(size_t size);
|
||||
LIBBINDER_EXPORTED virtual void dump(const char* what) const;
|
||||
|
||||
// allocations are aligned to some value. return that value so clients can account for it.
|
||||
LIBBINDER_EXPORTED static size_t getAllocationAlignment();
|
||||
|
||||
sp<IMemoryHeap> getMemoryHeap() const { return heap(); }
|
||||
|
||||
protected:
|
||||
LIBBINDER_EXPORTED virtual ~MemoryDealer();
|
||||
|
||||
private:
|
||||
friend class Allocation;
|
||||
virtual void deallocate(size_t offset);
|
||||
LIBBINDER_EXPORTED const sp<IMemoryHeap>& heap() const;
|
||||
SimpleBestFitAllocator* allocator() const;
|
||||
|
||||
sp<IMemoryHeap> mHeap;
|
||||
SimpleBestFitAllocator* mAllocator;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
} // namespace android
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IMemory.h>
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class MemoryHeapBase : public BnMemoryHeap {
|
||||
public:
|
||||
static constexpr auto MEMFD_ALLOW_SEALING_FLAG = 0x00000800;
|
||||
enum {
|
||||
READ_ONLY = IMemoryHeap::READ_ONLY,
|
||||
// memory won't be mapped locally, but will be mapped in the remote
|
||||
// process.
|
||||
DONT_MAP_LOCALLY = 0x00000100,
|
||||
NO_CACHING = 0x00000200,
|
||||
// Bypass ashmem-libcutils to create a memfd shared region.
|
||||
// Ashmem-libcutils will eventually migrate to memfd.
|
||||
// Memfd has security benefits and supports file sealing.
|
||||
// Calling process will need to modify selinux permissions to
|
||||
// open access to tmpfs files. See audioserver for examples.
|
||||
// This is only valid for size constructor.
|
||||
// For host compilation targets, memfd is stubbed in favor of /tmp
|
||||
// files so sealing is not enforced.
|
||||
FORCE_MEMFD = 0x00000400,
|
||||
// Default opt-out of sealing behavior in memfd to avoid potential DOS.
|
||||
// Clients of shared files can seal at anytime via syscall, leading to
|
||||
// TOC/TOU issues if additional seals prevent access from the creating
|
||||
// process. Alternatively, seccomp fcntl().
|
||||
MEMFD_ALLOW_SEALING = FORCE_MEMFD | MEMFD_ALLOW_SEALING_FLAG
|
||||
};
|
||||
|
||||
/*
|
||||
* maps the memory referenced by fd. but DOESN'T take ownership
|
||||
* of the filedescriptor (it makes a copy with dup()
|
||||
*/
|
||||
LIBBINDER_EXPORTED MemoryHeapBase(int fd, size_t size, uint32_t flags = 0, off_t offset = 0);
|
||||
|
||||
/*
|
||||
* maps memory from the given device
|
||||
*/
|
||||
LIBBINDER_EXPORTED explicit MemoryHeapBase(const char* device, size_t size = 0,
|
||||
uint32_t flags = 0);
|
||||
|
||||
/*
|
||||
* maps memory from ashmem, with the given name for debugging
|
||||
* if the READ_ONLY flag is set, the memory will be writeable by the calling process,
|
||||
* but not by others. this is NOT the case with the other ctors.
|
||||
*/
|
||||
LIBBINDER_EXPORTED explicit MemoryHeapBase(size_t size, uint32_t flags = 0,
|
||||
char const* name = nullptr);
|
||||
|
||||
LIBBINDER_EXPORTED virtual ~MemoryHeapBase();
|
||||
|
||||
/* implement IMemoryHeap interface */
|
||||
LIBBINDER_EXPORTED int getHeapID() const override;
|
||||
|
||||
/* virtual address of the heap. returns MAP_FAILED in case of error */
|
||||
LIBBINDER_EXPORTED void* getBase() const override;
|
||||
|
||||
LIBBINDER_EXPORTED size_t getSize() const override;
|
||||
LIBBINDER_EXPORTED uint32_t getFlags() const override;
|
||||
LIBBINDER_EXPORTED off_t getOffset() const override;
|
||||
|
||||
LIBBINDER_EXPORTED const char* getDevice() const;
|
||||
|
||||
/* this closes this heap -- use carefully */
|
||||
LIBBINDER_EXPORTED void dispose();
|
||||
|
||||
protected:
|
||||
LIBBINDER_EXPORTED MemoryHeapBase();
|
||||
// init() takes ownership of fd
|
||||
LIBBINDER_EXPORTED status_t init(int fd, void* base, size_t size, int flags = 0,
|
||||
const char* device = nullptr);
|
||||
|
||||
private:
|
||||
status_t mapfd(int fd, bool writeableByCaller, size_t size, off_t offset = 0);
|
||||
|
||||
int mFD;
|
||||
size_t mSize;
|
||||
void* mBase;
|
||||
uint32_t mFlags;
|
||||
const char* mDevice;
|
||||
bool mNeedUnmap;
|
||||
off_t mOffset;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
} // namespace android
|
||||
+1669
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/Parcelable.h>
|
||||
#include <binder/unique_fd.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
|
||||
/*
|
||||
* C++ implementation of the Java class android.os.ParcelFileDescriptor
|
||||
*/
|
||||
class LIBBINDER_EXPORTED ParcelFileDescriptor : public android::Parcelable {
|
||||
public:
|
||||
ParcelFileDescriptor();
|
||||
explicit ParcelFileDescriptor(binder::unique_fd fd);
|
||||
ParcelFileDescriptor(ParcelFileDescriptor&& other) noexcept : mFd(std::move(other.mFd)) { }
|
||||
ParcelFileDescriptor& operator=(ParcelFileDescriptor&& other) noexcept = default;
|
||||
~ParcelFileDescriptor() override;
|
||||
|
||||
int get() const { return mFd.get(); }
|
||||
binder::unique_fd release() { return std::move(mFd); }
|
||||
void reset(binder::unique_fd fd = binder::unique_fd()) { mFd = std::move(fd); }
|
||||
|
||||
// android::Parcelable override:
|
||||
android::status_t writeToParcel(android::Parcel* parcel) const override;
|
||||
android::status_t readFromParcel(const android::Parcel* parcel) override;
|
||||
|
||||
inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
|
||||
inline bool operator!=(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() != rhs.mFd.get();
|
||||
}
|
||||
inline bool operator<(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() < rhs.mFd.get();
|
||||
}
|
||||
inline bool operator<=(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() <= rhs.mFd.get();
|
||||
}
|
||||
inline bool operator==(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() == rhs.mFd.get();
|
||||
}
|
||||
inline bool operator>(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() > rhs.mFd.get();
|
||||
}
|
||||
inline bool operator>=(const ParcelFileDescriptor& rhs) const {
|
||||
return mFd.get() >= rhs.mFd.get();
|
||||
}
|
||||
private:
|
||||
binder::unique_fd mFd;
|
||||
};
|
||||
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String16.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class Parcel;
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
// Abstract interface of all parcelables.
|
||||
class LIBBINDER_EXPORTED Parcelable {
|
||||
public:
|
||||
virtual ~Parcelable() = default;
|
||||
|
||||
Parcelable() = default;
|
||||
Parcelable(const Parcelable&) = default;
|
||||
|
||||
// Write |this| parcelable to the given |parcel|. Keep in mind that
|
||||
// implementations of writeToParcel must be manually kept in sync
|
||||
// with readFromParcel and the Java equivalent versions of these methods.
|
||||
//
|
||||
// Returns android::OK on success and an appropriate error otherwise.
|
||||
virtual status_t writeToParcel(Parcel* parcel) const = 0;
|
||||
|
||||
// Read data from the given |parcel| into |this|. After readFromParcel
|
||||
// completes, |this| should have equivalent state to the object that
|
||||
// wrote itself to the parcel.
|
||||
//
|
||||
// Returns android::OK on success and an appropriate error otherwise.
|
||||
virtual status_t readFromParcel(const Parcel* parcel) = 0;
|
||||
|
||||
// WARNING: for use by auto-generated code only (AIDL). Should not be used
|
||||
// manually, or there is a risk of breaking CTS, GTS, VTS, or CTS-on-GSI
|
||||
// tests.
|
||||
enum class Stability : int32_t {
|
||||
STABILITY_LOCAL,
|
||||
STABILITY_VINTF, // corresponds to @VintfStability
|
||||
};
|
||||
|
||||
// 'Stable' means this parcelable is guaranteed to be stable for multiple
|
||||
// years.
|
||||
// It must be guaranteed by setting stability field in aidl_interface.
|
||||
// WARNING: getStability() is only expected to be overridden by auto-generated
|
||||
// code. Returns true if this parcelable is stable.
|
||||
virtual Stability getStability() const { return Stability::STABILITY_LOCAL; }
|
||||
}; // class Parcelable
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/Parcelable.h>
|
||||
#include <utils/String16.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
/*
|
||||
* C++ implementation of the Java class android.os.ParcelableHolder
|
||||
*/
|
||||
class LIBBINDER_EXPORTED ParcelableHolder : public android::Parcelable {
|
||||
public:
|
||||
ParcelableHolder() = delete;
|
||||
explicit ParcelableHolder(Stability stability) : mStability(stability){}
|
||||
virtual ~ParcelableHolder() = default;
|
||||
ParcelableHolder(const ParcelableHolder& other) {
|
||||
mParcelable = other.mParcelable;
|
||||
mParcelableName = other.mParcelableName;
|
||||
if (other.mParcelPtr) {
|
||||
mParcelPtr = std::make_unique<Parcel>();
|
||||
mParcelPtr->appendFrom(other.mParcelPtr.get(), 0, other.mParcelPtr->dataSize());
|
||||
}
|
||||
mStability = other.mStability;
|
||||
}
|
||||
ParcelableHolder(ParcelableHolder&& other) = default;
|
||||
|
||||
status_t writeToParcel(Parcel* parcel) const override;
|
||||
status_t readFromParcel(const Parcel* parcel) override;
|
||||
|
||||
void reset() {
|
||||
this->mParcelable = nullptr;
|
||||
this->mParcelableName = std::nullopt;
|
||||
this->mParcelPtr = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
status_t setParcelable(T&& p) {
|
||||
using Tt = typename std::decay<T>::type;
|
||||
return setParcelable<Tt>(std::make_shared<Tt>(std::forward<T>(p)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
status_t setParcelable(std::shared_ptr<T> p) {
|
||||
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
|
||||
if (p && this->getStability() > p->getStability()) {
|
||||
return android::BAD_VALUE;
|
||||
}
|
||||
this->mParcelable = p;
|
||||
this->mParcelableName = T::getParcelableDescriptor();
|
||||
this->mParcelPtr = nullptr;
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
status_t getParcelable(std::shared_ptr<T>* ret) const {
|
||||
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
|
||||
const String16& parcelableDesc = T::getParcelableDescriptor();
|
||||
if (!this->mParcelPtr) {
|
||||
if (!this->mParcelable || !this->mParcelableName) {
|
||||
ALOGD("empty ParcelableHolder");
|
||||
*ret = nullptr;
|
||||
return android::OK;
|
||||
} else if (parcelableDesc != *mParcelableName) {
|
||||
ALOGD("extension class name mismatch expected:%s actual:%s",
|
||||
String8(*mParcelableName).c_str(), String8(parcelableDesc).c_str());
|
||||
*ret = nullptr;
|
||||
return android::BAD_VALUE;
|
||||
}
|
||||
*ret = std::static_pointer_cast<T>(mParcelable);
|
||||
return android::OK;
|
||||
}
|
||||
this->mParcelPtr->setDataPosition(0);
|
||||
status_t status = this->mParcelPtr->readString16(&this->mParcelableName);
|
||||
if (status != android::OK || parcelableDesc != this->mParcelableName) {
|
||||
this->mParcelableName = std::nullopt;
|
||||
*ret = nullptr;
|
||||
return status;
|
||||
}
|
||||
this->mParcelable = std::make_shared<T>();
|
||||
status = mParcelable.get()->readFromParcel(this->mParcelPtr.get());
|
||||
if (status != android::OK) {
|
||||
this->mParcelableName = std::nullopt;
|
||||
this->mParcelable = nullptr;
|
||||
*ret = nullptr;
|
||||
return status;
|
||||
}
|
||||
this->mParcelPtr = nullptr;
|
||||
*ret = std::static_pointer_cast<T>(mParcelable);
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
Stability getStability() const override { return mStability; }
|
||||
|
||||
inline std::string toString() const {
|
||||
return "ParcelableHolder:" +
|
||||
(mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
|
||||
: "<parceled>");
|
||||
}
|
||||
inline bool operator!=(const ParcelableHolder& rhs) const {
|
||||
return this != &rhs;
|
||||
}
|
||||
inline bool operator<(const ParcelableHolder& rhs) const {
|
||||
return this < &rhs;
|
||||
}
|
||||
inline bool operator<=(const ParcelableHolder& rhs) const {
|
||||
return this <= &rhs;
|
||||
}
|
||||
inline bool operator==(const ParcelableHolder& rhs) const {
|
||||
return this == &rhs;
|
||||
}
|
||||
inline bool operator>(const ParcelableHolder& rhs) const {
|
||||
return this > &rhs;
|
||||
}
|
||||
inline bool operator>=(const ParcelableHolder& rhs) const {
|
||||
return this >= &rhs;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<Parcelable> mParcelable;
|
||||
mutable std::optional<String16> mParcelableName;
|
||||
mutable std::unique_ptr<Parcel> mParcelPtr;
|
||||
Stability mStability;
|
||||
};
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __ANDROID_VNDK__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Singleton.h>
|
||||
#include <utils/SortedVector.h>
|
||||
#include <binder/Common.h>
|
||||
|
||||
namespace android {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* PermissionCache caches permission checks for a given uid.
|
||||
*
|
||||
* Currently the cache is not updated when there is a permission change,
|
||||
* for instance when an application is uninstalled.
|
||||
*
|
||||
* IMPORTANT: for the reason stated above, only system permissions are safe
|
||||
* to cache. This restriction may be lifted at a later time.
|
||||
*
|
||||
*/
|
||||
|
||||
class PermissionCache : Singleton<PermissionCache> {
|
||||
struct Entry {
|
||||
String16 name;
|
||||
uid_t uid;
|
||||
bool granted;
|
||||
inline bool operator < (const Entry& e) const {
|
||||
return (uid == e.uid) ? (name < e.name) : (uid < e.uid);
|
||||
}
|
||||
};
|
||||
mutable Mutex mLock;
|
||||
// we pool all the permission names we see, as many permissions checks
|
||||
// will have identical names
|
||||
SortedVector< String16 > mPermissionNamesPool;
|
||||
// this is our cache per say. it stores pooled names.
|
||||
SortedVector< Entry > mCache;
|
||||
|
||||
// free the whole cache, but keep the permission name pool
|
||||
void purge();
|
||||
|
||||
status_t check(bool* granted,
|
||||
const String16& permission, uid_t uid) const;
|
||||
|
||||
void cache(const String16& permission, uid_t uid, bool granted);
|
||||
|
||||
public:
|
||||
LIBBINDER_EXPORTED PermissionCache();
|
||||
|
||||
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission);
|
||||
|
||||
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission,
|
||||
int32_t* outPid, int32_t* outUid);
|
||||
|
||||
LIBBINDER_EXPORTED static bool checkPermission(const String16& permission, pid_t pid,
|
||||
uid_t uid);
|
||||
|
||||
LIBBINDER_EXPORTED static void purgeCache();
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
} // namespace android
|
||||
|
||||
#else // __ANDROID_VNDK__
|
||||
#error "This header is not visible to vendors"
|
||||
#endif // __ANDROID_VNDK__
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __ANDROID_VNDK__
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IPermissionController.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class PermissionController {
|
||||
public:
|
||||
|
||||
enum {
|
||||
MATCH_SYSTEM_ONLY = 1<<16,
|
||||
MATCH_UNINSTALLED_PACKAGES = 1<<13,
|
||||
MATCH_FACTORY_ONLY = 1<<21,
|
||||
MATCH_INSTANT = 1<<23
|
||||
};
|
||||
|
||||
enum {
|
||||
MODE_ALLOWED = 0,
|
||||
MODE_IGNORED = 1,
|
||||
MODE_ERRORED = 2,
|
||||
MODE_DEFAULT = 3,
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED PermissionController();
|
||||
|
||||
LIBBINDER_EXPORTED bool checkPermission(const String16& permission, int32_t pid, int32_t uid);
|
||||
LIBBINDER_EXPORTED int32_t noteOp(const String16& op, int32_t uid, const String16& packageName);
|
||||
LIBBINDER_EXPORTED void getPackagesForUid(const uid_t uid, Vector<String16>& packages);
|
||||
LIBBINDER_EXPORTED bool isRuntimePermission(const String16& permission);
|
||||
LIBBINDER_EXPORTED int getPackageUid(const String16& package, int flags);
|
||||
|
||||
private:
|
||||
Mutex mLock;
|
||||
sp<IPermissionController> mService;
|
||||
|
||||
sp<IPermissionController> getService();
|
||||
};
|
||||
|
||||
|
||||
} // namespace android
|
||||
// ---------------------------------------------------------------------------
|
||||
#else // __ANDROID_VNDK__
|
||||
#error "This header is not visible to vendors"
|
||||
#endif // __ANDROID_VNDK__
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcelable.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace os {
|
||||
|
||||
/*
|
||||
* C++ implementation of PersistableBundle, a mapping from String values to
|
||||
* various types that can be saved to persistent and later restored.
|
||||
*/
|
||||
class LIBBINDER_EXPORTED PersistableBundle : public Parcelable {
|
||||
public:
|
||||
PersistableBundle() = default;
|
||||
virtual ~PersistableBundle() = default;
|
||||
PersistableBundle(const PersistableBundle& bundle) = default;
|
||||
|
||||
status_t writeToParcel(Parcel* parcel) const override;
|
||||
status_t readFromParcel(const Parcel* parcel) override;
|
||||
|
||||
bool empty() const;
|
||||
size_t size() const;
|
||||
size_t erase(const String16& key);
|
||||
|
||||
/*
|
||||
* Setters for PersistableBundle. Adds a a key-value pair instantiated with
|
||||
* |key| and |value| into the member map appropriate for the type of |value|.
|
||||
* If there is already an existing value for |key|, |value| will replace it.
|
||||
*/
|
||||
void putBoolean(const String16& key, bool value);
|
||||
void putInt(const String16& key, int32_t value);
|
||||
void putLong(const String16& key, int64_t value);
|
||||
void putDouble(const String16& key, double value);
|
||||
void putString(const String16& key, const String16& value);
|
||||
void putBooleanVector(const String16& key, const std::vector<bool>& value);
|
||||
void putIntVector(const String16& key, const std::vector<int32_t>& value);
|
||||
void putLongVector(const String16& key, const std::vector<int64_t>& value);
|
||||
void putDoubleVector(const String16& key, const std::vector<double>& value);
|
||||
void putStringVector(const String16& key, const std::vector<String16>& value);
|
||||
void putPersistableBundle(const String16& key, const PersistableBundle& value);
|
||||
|
||||
/*
|
||||
* Getters for PersistableBundle. If |key| exists, these methods write the
|
||||
* value associated with |key| into |out|, and return true. Otherwise, these
|
||||
* methods return false.
|
||||
*/
|
||||
bool getBoolean(const String16& key, bool* out) const;
|
||||
bool getInt(const String16& key, int32_t* out) const;
|
||||
bool getLong(const String16& key, int64_t* out) const;
|
||||
bool getDouble(const String16& key, double* out) const;
|
||||
bool getString(const String16& key, String16* out) const;
|
||||
bool getBooleanVector(const String16& key, std::vector<bool>* out) const;
|
||||
bool getIntVector(const String16& key, std::vector<int32_t>* out) const;
|
||||
bool getLongVector(const String16& key, std::vector<int64_t>* out) const;
|
||||
bool getDoubleVector(const String16& key, std::vector<double>* out) const;
|
||||
bool getStringVector(const String16& key, std::vector<String16>* out) const;
|
||||
bool getPersistableBundle(const String16& key, PersistableBundle* out) const;
|
||||
|
||||
/* Getters for all keys for each value type */
|
||||
std::set<String16> getBooleanKeys() const;
|
||||
std::set<String16> getIntKeys() const;
|
||||
std::set<String16> getLongKeys() const;
|
||||
std::set<String16> getDoubleKeys() const;
|
||||
std::set<String16> getStringKeys() const;
|
||||
std::set<String16> getBooleanVectorKeys() const;
|
||||
std::set<String16> getIntVectorKeys() const;
|
||||
std::set<String16> getLongVectorKeys() const;
|
||||
std::set<String16> getDoubleVectorKeys() const;
|
||||
std::set<String16> getStringVectorKeys() const;
|
||||
std::set<String16> getPersistableBundleKeys() const;
|
||||
|
||||
friend bool operator==(const PersistableBundle& lhs, const PersistableBundle& rhs) {
|
||||
return (lhs.mBoolMap == rhs.mBoolMap && lhs.mIntMap == rhs.mIntMap &&
|
||||
lhs.mLongMap == rhs.mLongMap && lhs.mDoubleMap == rhs.mDoubleMap &&
|
||||
lhs.mStringMap == rhs.mStringMap && lhs.mBoolVectorMap == rhs.mBoolVectorMap &&
|
||||
lhs.mIntVectorMap == rhs.mIntVectorMap &&
|
||||
lhs.mLongVectorMap == rhs.mLongVectorMap &&
|
||||
lhs.mDoubleVectorMap == rhs.mDoubleVectorMap &&
|
||||
lhs.mStringVectorMap == rhs.mStringVectorMap &&
|
||||
lhs.mPersistableBundleMap == rhs.mPersistableBundleMap);
|
||||
}
|
||||
|
||||
friend bool operator!=(const PersistableBundle& lhs, const PersistableBundle& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
status_t writeToParcelInner(Parcel* parcel) const;
|
||||
status_t readFromParcelInner(const Parcel* parcel, size_t length);
|
||||
|
||||
std::map<String16, bool> mBoolMap;
|
||||
std::map<String16, int32_t> mIntMap;
|
||||
std::map<String16, int64_t> mLongMap;
|
||||
std::map<String16, double> mDoubleMap;
|
||||
std::map<String16, String16> mStringMap;
|
||||
std::map<String16, std::vector<bool>> mBoolVectorMap;
|
||||
std::map<String16, std::vector<int32_t>> mIntVectorMap;
|
||||
std::map<String16, std::vector<int64_t>> mLongVectorMap;
|
||||
std::map<String16, std::vector<double>> mDoubleVectorMap;
|
||||
std::map<String16, std::vector<String16>> mStringVectorMap;
|
||||
std::map<String16, PersistableBundle> mPersistableBundleMap;
|
||||
};
|
||||
|
||||
} // namespace os
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class IPCThreadState;
|
||||
|
||||
/**
|
||||
* Kernel binder process state. All operations here refer to kernel binder. This
|
||||
* object is allocated per process.
|
||||
*/
|
||||
class ProcessState : public virtual RefBase {
|
||||
public:
|
||||
LIBBINDER_EXPORTED static sp<ProcessState> self();
|
||||
LIBBINDER_EXPORTED static sp<ProcessState> selfOrNull();
|
||||
|
||||
LIBBINDER_EXPORTED static bool isVndservicemanagerEnabled();
|
||||
|
||||
/* initWithDriver() can be used to configure libbinder to use
|
||||
* a different binder driver dev node. It must be called *before*
|
||||
* any call to ProcessState::self(). The default is /dev/vndbinder
|
||||
* for processes built with the VNDK and /dev/binder for those
|
||||
* which are not.
|
||||
*
|
||||
* If this is called with nullptr, the behavior is the same as selfOrNull.
|
||||
*/
|
||||
LIBBINDER_EXPORTED static sp<ProcessState> initWithDriver(const char* driver);
|
||||
|
||||
LIBBINDER_EXPORTED sp<IBinder> getContextObject(const sp<IBinder>& caller);
|
||||
|
||||
// This should be called before startThreadPool at the beginning
|
||||
// of a program, and libraries should never call it because programs
|
||||
// should configure their own threadpools. The threadpool size can
|
||||
// never be decreased.
|
||||
//
|
||||
// The 'maxThreads' value refers to the total number of threads
|
||||
// that will be started by the kernel. This is in addition to any
|
||||
// threads started by 'startThreadPool' or 'joinRpcThreadpool'.
|
||||
LIBBINDER_EXPORTED status_t setThreadPoolMaxThreadCount(size_t maxThreads);
|
||||
|
||||
// Libraries should not call this, as processes should configure
|
||||
// threadpools themselves. Should be called in the main function
|
||||
// directly before any code executes or joins the threadpool.
|
||||
//
|
||||
// Starts one thread, PLUS those requested in setThreadPoolMaxThreadCount,
|
||||
// PLUS those manually requested in joinThreadPool.
|
||||
//
|
||||
// For instance, if setThreadPoolMaxCount(3) is called and
|
||||
// startThreadpPool (+1 thread) and joinThreadPool (+1 thread)
|
||||
// are all called, then up to 5 threads can be started.
|
||||
LIBBINDER_EXPORTED void startThreadPool();
|
||||
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool becomeContextManager();
|
||||
|
||||
LIBBINDER_EXPORTED sp<IBinder> getStrongProxyForHandle(int32_t handle);
|
||||
LIBBINDER_EXPORTED void expungeHandle(int32_t handle, IBinder* binder);
|
||||
|
||||
// TODO: deprecate.
|
||||
LIBBINDER_EXPORTED void spawnPooledThread(bool isMain);
|
||||
|
||||
LIBBINDER_EXPORTED status_t enableOnewaySpamDetection(bool enable);
|
||||
|
||||
// Set the name of the current thread to look like a threadpool
|
||||
// thread. Typically this is called before joinThreadPool.
|
||||
//
|
||||
// TODO: remove this API, and automatically set it intelligently.
|
||||
LIBBINDER_EXPORTED void giveThreadPoolName();
|
||||
|
||||
LIBBINDER_EXPORTED String8 getDriverName();
|
||||
|
||||
LIBBINDER_EXPORTED ssize_t getKernelReferences(size_t count, uintptr_t* buf);
|
||||
|
||||
// Only usable by the context manager.
|
||||
// This refcount includes:
|
||||
// 1. Strong references to the node by this and other processes
|
||||
// 2. Temporary strong references held by the kernel during a
|
||||
// transaction on the node.
|
||||
// It does NOT include local strong references to the node
|
||||
LIBBINDER_EXPORTED ssize_t getStrongRefCountForNode(const sp<BpBinder>& binder);
|
||||
|
||||
enum class CallRestriction {
|
||||
// all calls okay
|
||||
NONE,
|
||||
// log when calls are blocking
|
||||
ERROR_IF_NOT_ONEWAY,
|
||||
// abort process on blocking calls
|
||||
FATAL_IF_NOT_ONEWAY,
|
||||
};
|
||||
// Sets calling restrictions for all transactions in this process. This must be called
|
||||
// before any threads are spawned.
|
||||
LIBBINDER_EXPORTED void setCallRestriction(CallRestriction restriction);
|
||||
|
||||
/**
|
||||
* Get the max number of threads that have joined the thread pool.
|
||||
* This includes kernel started threads, user joined threads and polling
|
||||
* threads if used.
|
||||
*/
|
||||
LIBBINDER_EXPORTED size_t getThreadPoolMaxTotalThreadCount() const;
|
||||
|
||||
/**
|
||||
* Check to see if the thread pool has started.
|
||||
*/
|
||||
LIBBINDER_EXPORTED bool isThreadPoolStarted() const;
|
||||
|
||||
enum class DriverFeature {
|
||||
ONEWAY_SPAM_DETECTION,
|
||||
EXTENDED_ERROR,
|
||||
FREEZE_NOTIFICATION,
|
||||
};
|
||||
// Determine whether a feature is supported by the binder driver.
|
||||
LIBBINDER_EXPORTED static bool isDriverFeatureEnabled(const DriverFeature feature);
|
||||
|
||||
private:
|
||||
static sp<ProcessState> init(const char* defaultDriver, bool requireDefault);
|
||||
|
||||
void checkExpectingThreadPoolStart() const;
|
||||
|
||||
static void onFork();
|
||||
static void parentPostFork();
|
||||
static void childPostFork();
|
||||
|
||||
friend class IPCThreadState;
|
||||
friend class sp<ProcessState>;
|
||||
|
||||
explicit ProcessState(const char* driver);
|
||||
~ProcessState();
|
||||
|
||||
ProcessState(const ProcessState& o);
|
||||
ProcessState& operator=(const ProcessState& o);
|
||||
String8 makeBinderThreadName();
|
||||
|
||||
struct handle_entry {
|
||||
IBinder* binder;
|
||||
RefBase::weakref_type* refs;
|
||||
};
|
||||
|
||||
handle_entry* lookupHandleLocked(int32_t handle);
|
||||
|
||||
String8 mDriverName;
|
||||
int mDriverFD;
|
||||
void* mVMStart;
|
||||
|
||||
mutable std::mutex mOnThreadAvailableLock;
|
||||
std::condition_variable mOnThreadAvailableCondVar;
|
||||
// Number of threads waiting on `mOnThreadAvailableCondVar`.
|
||||
std::atomic_int64_t mOnThreadAvailableWaiting = 0;
|
||||
|
||||
// Number of binder threads current executing a command.
|
||||
std::atomic_size_t mExecutingThreadsCount;
|
||||
// Maximum number of lazy threads to be started in the threadpool by the kernel.
|
||||
std::atomic_size_t mMaxThreads;
|
||||
// Current number of threads inside the thread pool.
|
||||
std::atomic_size_t mCurrentThreads;
|
||||
// Current number of pooled threads inside the thread pool.
|
||||
std::atomic_size_t mKernelStartedThreads;
|
||||
// Time when thread pool was emptied
|
||||
std::atomic<std::chrono::steady_clock::time_point> mStarvationStartTime;
|
||||
|
||||
static constexpr auto never = &std::chrono::steady_clock::time_point::min;
|
||||
|
||||
mutable std::mutex mLock; // protects everything below.
|
||||
|
||||
Vector<handle_entry> mHandleToObject;
|
||||
|
||||
bool mForked;
|
||||
std::atomic_bool mThreadPoolStarted;
|
||||
std::atomic_int32_t mThreadPoolSeq;
|
||||
|
||||
CallRestriction mCallRestriction;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2022, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/unique_fd.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace binder::debug {
|
||||
|
||||
// Warning: Transactions are sequentially recorded to the file descriptor in a
|
||||
// non-stable format. A detailed description of the recording format can be found in
|
||||
// RecordedTransaction.cpp.
|
||||
|
||||
class RecordedTransaction {
|
||||
public:
|
||||
// Filled with the first transaction from fd.
|
||||
|
||||
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromFile(
|
||||
const binder::unique_fd& fd);
|
||||
// Filled with the arguments.
|
||||
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromDetails(
|
||||
const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp,
|
||||
const Parcel& data, const Parcel& reply, status_t err);
|
||||
LIBBINDER_EXPORTED RecordedTransaction(RecordedTransaction&& t) noexcept;
|
||||
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t dumpToFile(const binder::unique_fd& fd) const;
|
||||
|
||||
LIBBINDER_EXPORTED const std::string& getInterfaceName() const;
|
||||
LIBBINDER_EXPORTED uint32_t getCode() const;
|
||||
LIBBINDER_EXPORTED uint32_t getFlags() const;
|
||||
LIBBINDER_EXPORTED int32_t getReturnedStatus() const;
|
||||
LIBBINDER_EXPORTED timespec getTimestamp() const;
|
||||
LIBBINDER_EXPORTED uint32_t getVersion() const;
|
||||
LIBBINDER_EXPORTED const Parcel& getDataParcel() const;
|
||||
LIBBINDER_EXPORTED const Parcel& getReplyParcel() const;
|
||||
LIBBINDER_EXPORTED const std::vector<uint64_t>& getObjectOffsets() const;
|
||||
|
||||
private:
|
||||
RecordedTransaction() = default;
|
||||
|
||||
android::status_t writeChunk(const binder::borrowed_fd, uint32_t chunkType, size_t byteCount,
|
||||
const uint8_t* data) const;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic error "-Wpadded"
|
||||
struct TransactionHeader {
|
||||
uint32_t code = 0;
|
||||
uint32_t flags = 0;
|
||||
int32_t statusReturned = 0;
|
||||
uint32_t version = 0; // !0 iff Rpc
|
||||
int64_t timestampSeconds = 0;
|
||||
int32_t timestampNanoseconds = 0;
|
||||
int32_t reserved = 0;
|
||||
};
|
||||
#pragma clang diagnostic pop
|
||||
static_assert(sizeof(TransactionHeader) == 32);
|
||||
static_assert(sizeof(TransactionHeader) % 8 == 0);
|
||||
|
||||
struct MovableData { // movable
|
||||
TransactionHeader mHeader;
|
||||
std::string mInterfaceName;
|
||||
std::vector<uint64_t> mSentObjectData; /* Object Offsets */
|
||||
};
|
||||
MovableData mData;
|
||||
Parcel mSentDataOnly;
|
||||
Parcel mReplyDataOnly;
|
||||
};
|
||||
|
||||
} // namespace binder::debug
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Formats for serializing TLS certificate.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
|
||||
enum class RpcCertificateFormat {
|
||||
PEM,
|
||||
DER,
|
||||
};
|
||||
|
||||
static inline std::string PrintToString(RpcCertificateFormat format) {
|
||||
switch (format) {
|
||||
case RpcCertificateFormat::PEM:
|
||||
return "PEM";
|
||||
case RpcCertificateFormat::DER:
|
||||
return "DER";
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Formats for serializing TLS private keys.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
|
||||
enum class RpcKeyFormat {
|
||||
PEM,
|
||||
DER,
|
||||
};
|
||||
|
||||
static inline std::string PrintToString(RpcKeyFormat format) {
|
||||
switch (format) {
|
||||
case RpcKeyFormat::PEM:
|
||||
return "PEM";
|
||||
case RpcKeyFormat::DER:
|
||||
return "DER";
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/RpcSession.h>
|
||||
#include <binder/RpcThreads.h>
|
||||
#include <binder/RpcTransport.h>
|
||||
#include <binder/unique_fd.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <bitset>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace android {
|
||||
|
||||
class FdTrigger;
|
||||
class RpcServerTrusty;
|
||||
class RpcSocketAddress;
|
||||
|
||||
/**
|
||||
* This represents a server of an interface, which may be connected to by any
|
||||
* number of clients over sockets.
|
||||
*
|
||||
* Usage:
|
||||
* auto server = RpcServer::make();
|
||||
* // only supports one now
|
||||
* if (!server->setup*Server(...)) {
|
||||
* :(
|
||||
* }
|
||||
* server->join();
|
||||
*/
|
||||
class RpcServer final : public virtual RefBase, private RpcSession::EventListener {
|
||||
public:
|
||||
LIBBINDER_EXPORTED static sp<RpcServer> make(
|
||||
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
|
||||
|
||||
/**
|
||||
* Creates an RPC server that bootstraps sessions using an existing
|
||||
* Unix domain socket pair.
|
||||
*
|
||||
* Callers should create a pair of SOCK_STREAM Unix domain sockets, pass
|
||||
* one to RpcServer::setupUnixDomainSocketBootstrapServer and the other
|
||||
* to RpcSession::setupUnixDomainSocketBootstrapClient. Multiple client
|
||||
* session can be created from the client end of the pair.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t
|
||||
setupUnixDomainSocketBootstrapServer(binder::unique_fd serverFd);
|
||||
|
||||
/**
|
||||
* This represents a session for responses, e.g.:
|
||||
*
|
||||
* process A serves binder a
|
||||
* process B opens a session to process A
|
||||
* process B makes binder b and sends it to A
|
||||
* A uses this 'back session' to send things back to B
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainServer(const char* path);
|
||||
|
||||
/**
|
||||
* Sets up an RPC server with a raw socket file descriptor.
|
||||
* The socket should be created and bound to a socket address already, e.g.
|
||||
* the socket can be created in init.rc.
|
||||
*
|
||||
* This method is used in the libbinder_rpc_unstable API
|
||||
* RunInitUnixDomainRpcServer().
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupRawSocketServer(binder::unique_fd socket_fd);
|
||||
|
||||
/**
|
||||
* Creates an RPC server binding to the given CID at the given port.
|
||||
*
|
||||
* Set |port| to VMADDR_PORT_ANY to pick an ephemeral port. In this case, |assignedPort|
|
||||
* will be set to the picked port number, if it is not null.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockServer(unsigned bindCid, unsigned port,
|
||||
unsigned* assignedPort = nullptr);
|
||||
|
||||
/**
|
||||
* Creates an RPC server at the current port using IPv4.
|
||||
*
|
||||
* TODO(b/182914638): IPv6 support
|
||||
*
|
||||
* Set |port| to 0 to pick an ephemeral port; see discussion of
|
||||
* /proc/sys/net/ipv4/ip_local_port_range in ip(7). In this case, |assignedPort|
|
||||
* will be set to the picked port number, if it is not null.
|
||||
*
|
||||
* Set the IPv4 address for the socket to be listening on.
|
||||
* "127.0.0.1" allows for local connections from the same device.
|
||||
* "0.0.0.0" allows for connections on any IP address that the device may
|
||||
* have
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetServer(const char* address,
|
||||
unsigned int port,
|
||||
unsigned int* assignedPort = nullptr);
|
||||
|
||||
/**
|
||||
* If setup*Server has been successful, return true. Otherwise return false.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool hasServer();
|
||||
|
||||
/**
|
||||
* If hasServer(), return the server FD. Otherwise return invalid FD.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED binder::unique_fd releaseServer();
|
||||
|
||||
/**
|
||||
* Set up server using an external FD previously set up by releaseServer().
|
||||
* Return false if there's already a server.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupExternalServer(binder::unique_fd serverFd);
|
||||
|
||||
/**
|
||||
* This must be called before adding a client session. This corresponds
|
||||
* to the number of incoming connections to RpcSession objects in the
|
||||
* server, which will correspond to the number of outgoing connections
|
||||
* in client RpcSession objects.
|
||||
*
|
||||
* If this is not specified, this will be a single-threaded server.
|
||||
*
|
||||
* TODO(b/167966510): these are currently created per client, but these
|
||||
* should be shared.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setMaxThreads(size_t threads);
|
||||
LIBBINDER_EXPORTED size_t getMaxThreads();
|
||||
|
||||
/**
|
||||
* By default, the latest protocol version which is supported by a client is
|
||||
* used. However, this can be used in order to prevent newer protocol
|
||||
* versions from ever being used. This is expected to be useful for testing.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
|
||||
|
||||
/**
|
||||
* Set the supported transports for sending and receiving file descriptors.
|
||||
*
|
||||
* Clients will propose a mode when connecting. If the mode is not in the
|
||||
* provided list, the connection will be rejected.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setSupportedFileDescriptorTransportModes(
|
||||
const std::vector<RpcSession::FileDescriptorTransportMode>& modes);
|
||||
|
||||
/**
|
||||
* The root object can be retrieved by any client, without any
|
||||
* authentication. TODO(b/183988761)
|
||||
*
|
||||
* Holds a strong reference to the root object.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setRootObject(const sp<IBinder>& binder);
|
||||
/**
|
||||
* Holds a weak reference to the root object.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setRootObjectWeak(const wp<IBinder>& binder);
|
||||
/**
|
||||
* Allows a root object to be created for each session.
|
||||
*
|
||||
* Takes one argument: a callable that is invoked once per new session.
|
||||
* The callable takes three arguments:
|
||||
* - a weak pointer to the session. If you want to hold onto this in the root object, then
|
||||
* you should keep a weak pointer, and promote it when needed. For instance, if you refer
|
||||
* to this from the root object, then you could get ahold of transport-specific information.
|
||||
* - a type-erased pointer to an OS- and transport-specific address structure, e.g.,
|
||||
* sockaddr_vm for vsock
|
||||
* - an integer representing the size in bytes of that structure. The callable should
|
||||
* validate the size, then cast the type-erased pointer to a pointer to the actual type of the
|
||||
* address, e.g., const void* to const sockaddr_vm*.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setPerSessionRootObject(
|
||||
std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& object);
|
||||
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
|
||||
|
||||
/**
|
||||
* Set optional filter of incoming connections based on the peer's address.
|
||||
*
|
||||
* Takes one argument: a callable that is invoked on each accept()-ed
|
||||
* connection and returns false if the connection should be dropped.
|
||||
* See the description of setPerSessionRootObject() for details about
|
||||
* the callable's arguments.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setConnectionFilter(std::function<bool(const void*, size_t)>&& filter);
|
||||
|
||||
/**
|
||||
* Set optional modifier of each newly created server socket.
|
||||
*
|
||||
* The only argument is a successfully created file descriptor, not bound to an address yet.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setServerSocketModifier(
|
||||
std::function<void(binder::borrowed_fd)>&& modifier);
|
||||
|
||||
/**
|
||||
* See RpcTransportCtx::getCertificate
|
||||
*/
|
||||
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
|
||||
|
||||
/**
|
||||
* Runs join() in a background thread. Immediately returns.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void start();
|
||||
|
||||
/**
|
||||
* You must have at least one client session before calling this.
|
||||
*
|
||||
* If a client needs to actively terminate join, call shutdown() in a separate thread.
|
||||
*
|
||||
* At any given point, there can only be one thread calling join().
|
||||
*
|
||||
* Warning: if shutdown is called, this will return while the shutdown is
|
||||
* still occurring. To ensure that the service is fully shutdown, you might
|
||||
* want to call shutdown after 'join' returns.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void join();
|
||||
|
||||
/**
|
||||
* Shut down any existing join(). Return true if successfully shut down, false otherwise
|
||||
* (e.g. no join() is running). Will wait for the server to be fully
|
||||
* shutdown.
|
||||
*
|
||||
* Warning: this will hang if it is called from its own thread.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool shutdown();
|
||||
|
||||
/**
|
||||
* For debugging!
|
||||
*/
|
||||
LIBBINDER_EXPORTED std::vector<sp<RpcSession>> listSessions();
|
||||
LIBBINDER_EXPORTED size_t numUninitializedSessions();
|
||||
|
||||
/**
|
||||
* Whether any requests are currently being processed.
|
||||
*/
|
||||
LIBBINDER_EXPORTED bool hasActiveRequests();
|
||||
|
||||
LIBBINDER_EXPORTED ~RpcServer();
|
||||
|
||||
private:
|
||||
friend RpcServerTrusty;
|
||||
friend sp<RpcServer>;
|
||||
explicit RpcServer(std::unique_ptr<RpcTransportCtx> ctx);
|
||||
|
||||
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
|
||||
void onSessionIncomingThreadEnded() override;
|
||||
|
||||
status_t setupExternalServer(
|
||||
binder::unique_fd serverFd,
|
||||
std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn);
|
||||
|
||||
static constexpr size_t kRpcAddressSize = 128;
|
||||
static void establishConnection(
|
||||
sp<RpcServer>&& server, RpcTransportFd clientFd,
|
||||
std::array<uint8_t, kRpcAddressSize> addr, size_t addrLen,
|
||||
std::function<void(sp<RpcSession>&&, RpcSession::PreJoinSetupResult&&)>&& joinFn);
|
||||
static status_t acceptSocketConnection(const RpcServer& server, RpcTransportFd* out);
|
||||
static status_t recvmsgSocketConnection(const RpcServer& server, RpcTransportFd* out);
|
||||
|
||||
[[nodiscard]] status_t setupSocketServer(const RpcSocketAddress& address);
|
||||
|
||||
const std::unique_ptr<RpcTransportCtx> mCtx;
|
||||
size_t mMaxThreads = 1;
|
||||
std::optional<uint32_t> mProtocolVersion;
|
||||
// A mode is supported if the N'th bit is on, where N is the mode enum's value.
|
||||
std::bitset<8> mSupportedFileDescriptorTransportModes = std::bitset<8>().set(
|
||||
static_cast<size_t>(RpcSession::FileDescriptorTransportMode::NONE));
|
||||
RpcTransportFd mServer; // socket we are accepting sessions on
|
||||
|
||||
RpcMutex mLock; // for below
|
||||
std::unique_ptr<RpcMaybeThread> mJoinThread;
|
||||
bool mJoinThreadRunning = false;
|
||||
std::map<RpcMaybeThread::id, RpcMaybeThread> mConnectingThreads;
|
||||
|
||||
sp<IBinder> mRootObject;
|
||||
wp<IBinder> mRootObjectWeak;
|
||||
std::function<sp<IBinder>(wp<RpcSession>, const void*, size_t)> mRootObjectFactory;
|
||||
std::function<bool(const void*, size_t)> mConnectionFilter;
|
||||
std::function<void(binder::borrowed_fd)> mServerSocketModifier;
|
||||
std::map<std::vector<uint8_t>, sp<RpcSession>> mSessions;
|
||||
std::unique_ptr<FdTrigger> mShutdownTrigger;
|
||||
RpcConditionVariable mShutdownCv;
|
||||
std::function<status_t(const RpcServer& server, RpcTransportFd* out)> mAcceptFn;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <binder/RpcThreads.h>
|
||||
#include <binder/RpcTransport.h>
|
||||
#include <binder/unique_fd.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
|
||||
class Parcel;
|
||||
class RpcServer;
|
||||
class RpcServerTrusty;
|
||||
class RpcSocketAddress;
|
||||
class RpcState;
|
||||
class RpcTransport;
|
||||
class FdTrigger;
|
||||
|
||||
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2;
|
||||
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
|
||||
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1;
|
||||
|
||||
// Starting with this version:
|
||||
//
|
||||
// * RpcWireReply is larger (4 bytes -> 20).
|
||||
// * RpcWireTransaction and RpcWireReplyV1 include the parcel data size.
|
||||
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE = 1;
|
||||
|
||||
/**
|
||||
* This represents a session (group of connections) between a client
|
||||
* and a server. Multiple connections are needed for multiple parallel "binder"
|
||||
* calls which may also have nested calls.
|
||||
*
|
||||
* Once a binder exists in the session, if all references to all binders are dropped,
|
||||
* the session shuts down.
|
||||
*/
|
||||
class RpcSession final : public virtual RefBase {
|
||||
public:
|
||||
// Create an RpcSession with default configuration (raw sockets).
|
||||
LIBBINDER_EXPORTED static sp<RpcSession> make();
|
||||
|
||||
// Create an RpcSession with the given configuration. |serverRpcCertificateFormat| and
|
||||
// |serverCertificate| must have values or be nullopt simultaneously. If they have values, set
|
||||
// server certificate.
|
||||
LIBBINDER_EXPORTED static sp<RpcSession> make(
|
||||
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory);
|
||||
|
||||
/**
|
||||
* Set the maximum number of incoming threads allowed to be made (for things like callbacks).
|
||||
* By default, this is 0. This must be called before setting up this connection as a client.
|
||||
* Server sessions will inherits this value from RpcServer. Each thread will serve a
|
||||
* connection to the remote RpcSession.
|
||||
*
|
||||
* If this is called, 'shutdown' on this session must also be called.
|
||||
* Otherwise, a threadpool will leak.
|
||||
*
|
||||
* TODO(b/189955605): start these lazily - currently all are started
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setMaxIncomingThreads(size_t threads);
|
||||
LIBBINDER_EXPORTED size_t getMaxIncomingThreads();
|
||||
|
||||
/**
|
||||
* Set the maximum number of outgoing connections allowed to be made.
|
||||
* By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up
|
||||
* this connection as a client.
|
||||
*
|
||||
* For an RpcSession client, if you are connecting to a server which starts N threads,
|
||||
* then this must be set to >= N. If you set the maximum number of outgoing connections
|
||||
* to 1, but the server requests 10, then it would be considered an error. If you set a
|
||||
* maximum number of connections to 10, and the server requests 1, then only 1 will be
|
||||
* created. This API is used to limit the amount of resources a server can request you
|
||||
* create.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setMaxOutgoingConnections(size_t connections);
|
||||
LIBBINDER_EXPORTED size_t getMaxOutgoingThreads();
|
||||
|
||||
/**
|
||||
* By default, the minimum of the supported versions of the client and the
|
||||
* server will be used. Usually, this API should only be used for debugging.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
|
||||
LIBBINDER_EXPORTED std::optional<uint32_t> getProtocolVersion();
|
||||
|
||||
enum class FileDescriptorTransportMode : uint8_t {
|
||||
NONE = 0,
|
||||
// Send file descriptors via unix domain socket ancillary data.
|
||||
UNIX = 1,
|
||||
// Send file descriptors as Trusty IPC handles.
|
||||
TRUSTY = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the transport for sending and receiving file descriptors.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setFileDescriptorTransportMode(FileDescriptorTransportMode mode);
|
||||
LIBBINDER_EXPORTED FileDescriptorTransportMode getFileDescriptorTransportMode();
|
||||
|
||||
/**
|
||||
* This should be called once per thread, matching 'join' in the remote
|
||||
* process.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainClient(const char* path);
|
||||
|
||||
/**
|
||||
* Connects to an RPC server over a nameless Unix domain socket pair.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t
|
||||
setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
|
||||
|
||||
/**
|
||||
* Connects to an RPC server at the CID & port.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockClient(unsigned int cid, unsigned int port);
|
||||
|
||||
/**
|
||||
* Connects to an RPC server at the given address and port.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetClient(const char* addr, unsigned int port);
|
||||
|
||||
/**
|
||||
* Starts talking to an RPC server which has already been connected to. This
|
||||
* is expected to be used when another process has permission to connect to
|
||||
* a binder RPC service, but this process only has permission to talk to
|
||||
* that service.
|
||||
*
|
||||
* For convenience, if 'fd' is -1, 'request' will be called.
|
||||
*
|
||||
* For future compatibility, 'request' should not reference any stack data.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t
|
||||
setupPreconnectedClient(binder::unique_fd fd, std::function<binder::unique_fd()>&& request);
|
||||
|
||||
/**
|
||||
* For debugging!
|
||||
*
|
||||
* Sets up an empty connection. All queries to this connection which require a
|
||||
* response will never be satisfied. All data sent here will be
|
||||
* unceremoniously cast down the bottomless pit, /dev/null.
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t addNullDebuggingClient();
|
||||
|
||||
/**
|
||||
* Query the other side of the session for the root object hosted by that
|
||||
* process's RpcServer (if one exists)
|
||||
*/
|
||||
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
|
||||
|
||||
/**
|
||||
* Query the other side of the session for the maximum number of threads
|
||||
* it supports (maximum number of concurrent non-nested synchronous transactions)
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t getRemoteMaxThreads(size_t* maxThreads);
|
||||
|
||||
/**
|
||||
* See RpcTransportCtx::getCertificate
|
||||
*/
|
||||
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
|
||||
|
||||
/**
|
||||
* Shuts down the service.
|
||||
*
|
||||
* For client sessions, wait can be true or false. For server sessions,
|
||||
* waiting is not currently supported (will abort).
|
||||
*
|
||||
* Warning: this is currently not active/nice (the server isn't told we're
|
||||
* shutting down). Being nicer to the server could potentially make it
|
||||
* reclaim resources faster.
|
||||
*
|
||||
* If this is called w/ 'wait' true, then this will wait for shutdown to
|
||||
* complete before returning. This will hang if it is called from the
|
||||
* session threadpool (when processing received calls).
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED bool shutdownAndWait(bool wait);
|
||||
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t transact(const sp<IBinder>& binder, uint32_t code,
|
||||
const Parcel& data, Parcel* reply,
|
||||
uint32_t flags);
|
||||
|
||||
/**
|
||||
* Generally, you should not call this, unless you are testing error
|
||||
* conditions, as this is called automatically by BpBinders when they are
|
||||
* deleted (this is also why a raw pointer is used here)
|
||||
*/
|
||||
[[nodiscard]] LIBBINDER_EXPORTED status_t sendDecStrong(const BpBinder* binder);
|
||||
|
||||
/**
|
||||
* Whether any requests are currently being processed.
|
||||
*/
|
||||
LIBBINDER_EXPORTED bool hasActiveRequests();
|
||||
|
||||
LIBBINDER_EXPORTED ~RpcSession();
|
||||
|
||||
/**
|
||||
* Server if this session is created as part of a server (symmetrical to
|
||||
* client servers). Otherwise, nullptr.
|
||||
*/
|
||||
LIBBINDER_EXPORTED sp<RpcServer> server();
|
||||
|
||||
// internal only
|
||||
LIBBINDER_EXPORTED const std::unique_ptr<RpcState>& state() { return mRpcBinderState; }
|
||||
|
||||
/**
|
||||
* Sets the session-specific root object. This is the object that will be used to attach
|
||||
* the IAccessor binder to the RpcSession when a binder is set up via accessor.
|
||||
*/
|
||||
LIBBINDER_EXPORTED void setSessionSpecificRoot(const sp<IBinder>& sessionSpecificRoot);
|
||||
|
||||
private:
|
||||
friend sp<RpcSession>;
|
||||
friend RpcServer;
|
||||
friend RpcServerTrusty;
|
||||
friend RpcState;
|
||||
explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx);
|
||||
|
||||
static constexpr size_t kDefaultMaxOutgoingConnections = 10;
|
||||
|
||||
// internal version of setProtocolVersion that
|
||||
// optionally skips the mStartedSetup check
|
||||
[[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted);
|
||||
|
||||
// for 'target', see RpcState::sendDecStrongToTarget
|
||||
[[nodiscard]] status_t sendDecStrongToTarget(uint64_t address, size_t target);
|
||||
|
||||
class EventListener : public virtual RefBase {
|
||||
public:
|
||||
virtual void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) = 0;
|
||||
virtual void onSessionIncomingThreadEnded() = 0;
|
||||
};
|
||||
|
||||
class WaitForShutdownListener : public EventListener {
|
||||
public:
|
||||
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
|
||||
void onSessionIncomingThreadEnded() override;
|
||||
void waitForShutdown(RpcMutexUniqueLock& lock, const sp<RpcSession>& session);
|
||||
|
||||
private:
|
||||
RpcConditionVariable mCv;
|
||||
std::atomic<size_t> mShutdownCount = 0;
|
||||
};
|
||||
friend WaitForShutdownListener;
|
||||
|
||||
struct RpcConnection : public RefBase {
|
||||
std::unique_ptr<RpcTransport> rpcTransport;
|
||||
|
||||
// whether this or another thread is currently using this fd to make
|
||||
// or receive transactions.
|
||||
std::optional<uint64_t> exclusiveTid;
|
||||
|
||||
bool allowNested = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] status_t readId();
|
||||
|
||||
// A thread joining a server must always call these functions in order, and
|
||||
// cleanup is only programmed once into join. These are in separate
|
||||
// functions in order to allow for different locks to be taken during
|
||||
// different parts of setup.
|
||||
//
|
||||
// transfer ownership of thread (usually done while a lock is taken on the
|
||||
// structure which originally owns the thread)
|
||||
void preJoinThreadOwnership(RpcMaybeThread thread);
|
||||
// pass FD to thread and read initial connection information
|
||||
struct PreJoinSetupResult {
|
||||
// Server connection object associated with this
|
||||
sp<RpcConnection> connection;
|
||||
// Status of setup
|
||||
status_t status;
|
||||
};
|
||||
PreJoinSetupResult preJoinSetup(std::unique_ptr<RpcTransport> rpcTransport);
|
||||
// join on thread passed to preJoinThreadOwnership
|
||||
static void join(sp<RpcSession>&& session, PreJoinSetupResult&& result);
|
||||
|
||||
// This is a workaround to support move-only functors.
|
||||
// TODO: use std::move_only_function when it becomes available.
|
||||
template <typename Fn,
|
||||
// Fn must be a callable type taking (const std::vector<uint8_t>&, bool) and returning
|
||||
// status_t
|
||||
typename = std::enable_if_t<
|
||||
std::is_invocable_r_v<status_t, Fn, const std::vector<uint8_t>&, bool>>>
|
||||
[[nodiscard]] status_t setupClient(Fn&& connectAndInit);
|
||||
[[nodiscard]] status_t setupSocketClient(const RpcSocketAddress& address);
|
||||
[[nodiscard]] status_t setupOneSocketConnection(const RpcSocketAddress& address,
|
||||
const std::vector<uint8_t>& sessionId,
|
||||
bool incoming);
|
||||
[[nodiscard]] status_t initAndAddConnection(RpcTransportFd fd,
|
||||
const std::vector<uint8_t>& sessionId,
|
||||
bool incoming);
|
||||
[[nodiscard]] status_t addIncomingConnection(std::unique_ptr<RpcTransport> rpcTransport);
|
||||
[[nodiscard]] status_t addOutgoingConnection(std::unique_ptr<RpcTransport> rpcTransport,
|
||||
bool init);
|
||||
[[nodiscard]] bool setForServer(const wp<RpcServer>& server,
|
||||
const wp<RpcSession::EventListener>& eventListener,
|
||||
const std::vector<uint8_t>& sessionId,
|
||||
const sp<IBinder>& sessionSpecificRoot);
|
||||
sp<RpcConnection> assignIncomingConnectionToThisThread(
|
||||
std::unique_ptr<RpcTransport> rpcTransport);
|
||||
[[nodiscard]] bool removeIncomingConnection(const sp<RpcConnection>& connection);
|
||||
void clearConnectionTid(const sp<RpcConnection>& connection);
|
||||
|
||||
[[nodiscard]] status_t initShutdownTrigger();
|
||||
|
||||
/**
|
||||
* Checks whether any connection is active (Not polling on fd)
|
||||
*/
|
||||
bool hasActiveConnection(const std::vector<sp<RpcConnection>>& connections);
|
||||
|
||||
enum class ConnectionUse {
|
||||
CLIENT,
|
||||
CLIENT_ASYNC,
|
||||
CLIENT_REFCOUNT,
|
||||
};
|
||||
|
||||
// Object representing exclusive access to a connection.
|
||||
class ExclusiveConnection {
|
||||
public:
|
||||
[[nodiscard]] static status_t find(const sp<RpcSession>& session, ConnectionUse use,
|
||||
ExclusiveConnection* connection);
|
||||
|
||||
~ExclusiveConnection();
|
||||
const sp<RpcConnection>& get() { return mConnection; }
|
||||
|
||||
private:
|
||||
static void findConnection(uint64_t tid, sp<RpcConnection>* exclusive,
|
||||
sp<RpcConnection>* available,
|
||||
std::vector<sp<RpcConnection>>& sockets,
|
||||
size_t socketsIndexHint);
|
||||
|
||||
sp<RpcSession> mSession; // avoid deallocation
|
||||
sp<RpcConnection> mConnection;
|
||||
|
||||
// whether this is being used for a nested transaction (being on the same
|
||||
// thread guarantees we won't write in the middle of a message, the way
|
||||
// the wire protocol is constructed guarantees this is safe).
|
||||
bool mReentrant = false;
|
||||
};
|
||||
|
||||
const std::unique_ptr<RpcTransportCtx> mCtx;
|
||||
|
||||
// On the other side of a session, for each of mOutgoing here, there should
|
||||
// be one of mIncoming on the other side (and vice versa).
|
||||
//
|
||||
// For the simplest session, a single server with one client, you would
|
||||
// have:
|
||||
// - the server has a single 'mIncoming' and a thread listening on this
|
||||
// - the client has a single 'mOutgoing' and makes calls to this
|
||||
// - here, when the client makes a call, the server can call back into it
|
||||
// (nested calls), but outside of this, the client will only ever read
|
||||
// calls from the server when it makes a call itself.
|
||||
//
|
||||
// For a more complicated case, the client might itself open up a thread to
|
||||
// serve calls to the server at all times (e.g. if it hosts a callback)
|
||||
|
||||
wp<RpcServer> mForServer; // maybe null, for client sessions
|
||||
sp<WaitForShutdownListener> mShutdownListener; // used for client sessions
|
||||
wp<EventListener> mEventListener; // mForServer if server, mShutdownListener if client
|
||||
|
||||
// session-specific root object (if a different root is used for each
|
||||
// session)
|
||||
sp<IBinder> mSessionSpecificRootObject;
|
||||
|
||||
std::vector<uint8_t> mId;
|
||||
|
||||
std::unique_ptr<FdTrigger> mShutdownTrigger;
|
||||
|
||||
std::unique_ptr<RpcState> mRpcBinderState;
|
||||
|
||||
RpcMutex mMutex; // for all below
|
||||
|
||||
bool mStartedSetup = false;
|
||||
size_t mMaxIncomingThreads = 0;
|
||||
size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections;
|
||||
std::optional<uint32_t> mProtocolVersion;
|
||||
FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE;
|
||||
|
||||
RpcConditionVariable mAvailableConnectionCv; // for mWaitingThreads
|
||||
|
||||
std::unique_ptr<RpcTransport> mBootstrapTransport;
|
||||
|
||||
struct ThreadState {
|
||||
size_t mWaitingThreads = 0;
|
||||
// hint index into clients, ++ when sending an async transaction
|
||||
size_t mOutgoingOffset = 0;
|
||||
std::vector<sp<RpcConnection>> mOutgoing;
|
||||
// max size of mIncoming. Once any thread starts down, no more can be started.
|
||||
size_t mMaxIncoming = 0;
|
||||
std::vector<sp<RpcConnection>> mIncoming;
|
||||
std::map<RpcMaybeThread::id, RpcMaybeThread> mThreads;
|
||||
} mConnections;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <binder/Common.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
#ifdef BINDER_RPC_SINGLE_THREADED
|
||||
class LIBBINDER_EXPORTED RpcMutex {
|
||||
public:
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED RpcMutexUniqueLock {
|
||||
public:
|
||||
RpcMutexUniqueLock(RpcMutex&) {}
|
||||
void unlock() {}
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED RpcMutexLockGuard {
|
||||
public:
|
||||
RpcMutexLockGuard(RpcMutex&) {}
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED RpcConditionVariable {
|
||||
public:
|
||||
void notify_one() {}
|
||||
void notify_all() {}
|
||||
|
||||
void wait(RpcMutexUniqueLock&) {}
|
||||
|
||||
template <typename Predicate>
|
||||
void wait(RpcMutexUniqueLock&, Predicate stop_waiting) {
|
||||
LOG_ALWAYS_FATAL_IF(!stop_waiting(), "RpcConditionVariable::wait condition not met");
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
std::cv_status wait_for(RpcMutexUniqueLock&, const Duration&) {
|
||||
return std::cv_status::no_timeout;
|
||||
}
|
||||
|
||||
template <typename Duration, typename Predicate>
|
||||
bool wait_for(RpcMutexUniqueLock&, const Duration&, Predicate stop_waiting) {
|
||||
return stop_waiting();
|
||||
}
|
||||
};
|
||||
|
||||
class LIBBINDER_EXPORTED RpcMaybeThread {
|
||||
public:
|
||||
RpcMaybeThread() = default;
|
||||
|
||||
template <typename Function, typename... Args>
|
||||
RpcMaybeThread(Function&& f, Args&&... args) {
|
||||
// std::function requires a copy-constructible closure,
|
||||
// so we need to wrap both the function and its arguments
|
||||
// in a shared pointer that std::function can copy internally
|
||||
struct Vars {
|
||||
std::decay_t<Function> f;
|
||||
std::tuple<std::decay_t<Args>...> args;
|
||||
|
||||
explicit Vars(Function&& f, Args&&... args)
|
||||
: f(std::move(f)), args(std::move(args)...) {}
|
||||
};
|
||||
auto vars = std::make_shared<Vars>(std::forward<Function>(f), std::forward<Args>(args)...);
|
||||
mFunc = [vars]() { std::apply(std::move(vars->f), std::move(vars->args)); };
|
||||
}
|
||||
|
||||
void join() {
|
||||
if (mFunc) {
|
||||
// Move mFunc into a temporary so we can clear mFunc before
|
||||
// executing the callback. This avoids infinite recursion if
|
||||
// the callee then calls join() again directly or indirectly.
|
||||
decltype(mFunc) func = nullptr;
|
||||
mFunc.swap(func);
|
||||
func();
|
||||
}
|
||||
}
|
||||
void detach() { join(); }
|
||||
|
||||
class id {
|
||||
public:
|
||||
bool operator==(const id&) const { return true; }
|
||||
bool operator!=(const id&) const { return false; }
|
||||
bool operator<(const id&) const { return false; }
|
||||
bool operator<=(const id&) const { return true; }
|
||||
bool operator>(const id&) const { return false; }
|
||||
bool operator>=(const id&) const { return true; }
|
||||
};
|
||||
|
||||
id get_id() const { return id(); }
|
||||
|
||||
private:
|
||||
std::function<void(void)> mFunc;
|
||||
};
|
||||
|
||||
namespace rpc_this_thread {
|
||||
static inline RpcMaybeThread::id get_id() {
|
||||
return RpcMaybeThread::id();
|
||||
}
|
||||
} // namespace rpc_this_thread
|
||||
|
||||
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread& t) {
|
||||
t.join();
|
||||
}
|
||||
#else // BINDER_RPC_SINGLE_THREADED
|
||||
using RpcMutex = std::mutex;
|
||||
using RpcMutexUniqueLock = std::unique_lock<std::mutex>;
|
||||
using RpcMutexLockGuard = std::lock_guard<std::mutex>;
|
||||
using RpcConditionVariable = std::condition_variable;
|
||||
using RpcMaybeThread = std::thread;
|
||||
namespace rpc_this_thread = std::this_thread;
|
||||
|
||||
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread&) {}
|
||||
#endif // BINDER_RPC_SINGLE_THREADED
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Wraps the transport layer of RPC. Implementation may use plain sockets or TLS.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <utils/Errors.h>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Functional.h>
|
||||
#include <binder/RpcCertificateFormat.h>
|
||||
#include <binder/RpcThreads.h>
|
||||
#include <binder/unique_fd.h>
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class FdTrigger;
|
||||
struct RpcTransportFd;
|
||||
|
||||
// for 'friend'
|
||||
class RpcTransportRaw;
|
||||
class RpcTransportTls;
|
||||
class RpcTransportTipcAndroid;
|
||||
class RpcTransportTipcTrusty;
|
||||
class RpcTransportCtxRaw;
|
||||
class RpcTransportCtxTls;
|
||||
class RpcTransportCtxTipcAndroid;
|
||||
class RpcTransportCtxTipcTrusty;
|
||||
|
||||
// Represents a socket connection.
|
||||
// No thread-safety is guaranteed for these APIs.
|
||||
class LIBBINDER_EXPORTED RpcTransport {
|
||||
public:
|
||||
virtual ~RpcTransport() = default;
|
||||
|
||||
/**
|
||||
* Poll the transport to check whether there is any data ready to read.
|
||||
*
|
||||
* Return:
|
||||
* OK - There is data available on this transport
|
||||
* WOULDBLOCK - No data is available
|
||||
* error - any other error
|
||||
*/
|
||||
[[nodiscard]] virtual status_t pollRead(void) = 0;
|
||||
|
||||
/**
|
||||
* Read (or write), but allow to be interrupted by a trigger.
|
||||
*
|
||||
* iovs - array of iovecs to perform the operation on. The elements
|
||||
* of the array may be modified by this method.
|
||||
*
|
||||
* altPoll - function to be called instead of polling, when needing to wait
|
||||
* to read/write data. If this returns an error, that error is returned from
|
||||
* this function.
|
||||
*
|
||||
* ancillaryFds - FDs to be sent via UNIX domain dockets or Trusty IPC. When
|
||||
* reading, if `ancillaryFds` is null, any received FDs will be silently
|
||||
* dropped and closed (by the OS). Appended values will always be unique_fd,
|
||||
* the variant type is used to avoid extra copies elsewhere.
|
||||
*
|
||||
* Return:
|
||||
* OK - succeeded in completely processing 'size'
|
||||
* error - interrupted (failure or trigger)
|
||||
*/
|
||||
[[nodiscard]] virtual status_t interruptableWriteFully(
|
||||
FdTrigger* fdTrigger, iovec* iovs, int niovs,
|
||||
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
|
||||
const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
|
||||
ancillaryFds) = 0;
|
||||
[[nodiscard]] virtual status_t interruptableReadFully(
|
||||
FdTrigger* fdTrigger, iovec* iovs, int niovs,
|
||||
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
|
||||
std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds) = 0;
|
||||
|
||||
/**
|
||||
* Check whether any threads are blocked while polling the transport
|
||||
* for read operations
|
||||
* Return:
|
||||
* True - Specifies that there is active polling on transport.
|
||||
* False - No active polling on transport
|
||||
*/
|
||||
[[nodiscard]] virtual bool isWaiting() = 0;
|
||||
|
||||
private:
|
||||
// limit the classes which can implement RpcTransport. Being able to change this
|
||||
// interface is important to allow development of RPC binder. In the past, we
|
||||
// changed this interface to use iovec for efficiency, and we added FDs to the
|
||||
// interface. If another transport is needed, it should be added directly here.
|
||||
// non-socket FDs likely also need changes in RpcSession in order to get
|
||||
// connected, and similarly to how addrinfo was type-erased from RPC binder
|
||||
// interfaces when RpcTransportTipc* was added, other changes may be needed
|
||||
// to add more transports.
|
||||
|
||||
friend class ::android::RpcTransportRaw;
|
||||
friend class ::android::RpcTransportTls;
|
||||
friend class ::android::RpcTransportTipcAndroid;
|
||||
friend class ::android::RpcTransportTipcTrusty;
|
||||
|
||||
RpcTransport() = default;
|
||||
};
|
||||
|
||||
// Represents the context that generates the socket connection.
|
||||
// All APIs are thread-safe. See RpcTransportCtxRaw and RpcTransportCtxTls for details.
|
||||
class LIBBINDER_EXPORTED RpcTransportCtx {
|
||||
public:
|
||||
virtual ~RpcTransportCtx() = default;
|
||||
|
||||
// Create a new RpcTransport object.
|
||||
//
|
||||
// Implementation details: for TLS, this function may incur I/O. |fdTrigger| may be used
|
||||
// to interrupt I/O. This function blocks until handshake is finished.
|
||||
[[nodiscard]] virtual std::unique_ptr<RpcTransport> newTransport(
|
||||
android::RpcTransportFd fd, FdTrigger *fdTrigger) const = 0;
|
||||
|
||||
// Return the preconfigured certificate of this context.
|
||||
//
|
||||
// Implementation details:
|
||||
// - For raw sockets, this always returns empty string.
|
||||
// - For TLS, this returns the certificate. See RpcTransportTls for details.
|
||||
[[nodiscard]] virtual std::vector<uint8_t> getCertificate(
|
||||
RpcCertificateFormat format) const = 0;
|
||||
|
||||
private:
|
||||
// see comment on RpcTransport
|
||||
friend class ::android::RpcTransportCtxRaw;
|
||||
friend class ::android::RpcTransportCtxTls;
|
||||
friend class ::android::RpcTransportCtxTipcAndroid;
|
||||
friend class ::android::RpcTransportCtxTipcTrusty;
|
||||
|
||||
RpcTransportCtx() = default;
|
||||
};
|
||||
|
||||
// A factory class that generates RpcTransportCtx.
|
||||
// All APIs are thread-safe.
|
||||
class LIBBINDER_EXPORTED RpcTransportCtxFactory {
|
||||
public:
|
||||
virtual ~RpcTransportCtxFactory() = default;
|
||||
// Creates server context.
|
||||
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newServerCtx() const = 0;
|
||||
|
||||
// Creates client context.
|
||||
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newClientCtx() const = 0;
|
||||
|
||||
// Return a short description of this transport (e.g. "raw"). For logging / debugging / testing
|
||||
// only.
|
||||
[[nodiscard]] virtual const char *toCString() const = 0;
|
||||
|
||||
protected:
|
||||
RpcTransportCtxFactory() = default;
|
||||
};
|
||||
|
||||
struct LIBBINDER_EXPORTED RpcTransportFd final {
|
||||
private:
|
||||
mutable bool isPolling{false};
|
||||
|
||||
void setPollingState(bool state) const { isPolling = state; }
|
||||
|
||||
public:
|
||||
binder::unique_fd fd;
|
||||
|
||||
RpcTransportFd() = default;
|
||||
explicit RpcTransportFd(binder::unique_fd&& descriptor)
|
||||
: isPolling(false), fd(std::move(descriptor)) {}
|
||||
|
||||
RpcTransportFd(RpcTransportFd &&transportFd) noexcept
|
||||
: isPolling(transportFd.isPolling), fd(std::move(transportFd.fd)) {}
|
||||
|
||||
RpcTransportFd &operator=(RpcTransportFd &&transportFd) noexcept {
|
||||
fd = std::move(transportFd.fd);
|
||||
isPolling = transportFd.isPolling;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RpcTransportFd& operator=(binder::unique_fd&& descriptor) noexcept {
|
||||
fd = std::move(descriptor);
|
||||
isPolling = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isInPollingState() const { return isPolling; }
|
||||
friend class FdTrigger;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Wraps the transport layer of RPC. Implementation uses plain sockets.
|
||||
// Note: don't use directly. You probably want newServerRpcTransportCtx / newClientRpcTransportCtx.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/RpcTransport.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
// RpcTransportCtxFactory with TLS disabled.
|
||||
class RpcTransportCtxFactoryRaw : public RpcTransportCtxFactory {
|
||||
public:
|
||||
LIBBINDER_EXPORTED static std::unique_ptr<RpcTransportCtxFactory> make();
|
||||
|
||||
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newServerCtx() const override;
|
||||
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newClientCtx() const override;
|
||||
LIBBINDER_EXPORTED const char* toCString() const override;
|
||||
|
||||
private:
|
||||
RpcTransportCtxFactoryRaw() = default;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
* Copyright 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IInterface.h>
|
||||
#include <binder/Parcel.h>
|
||||
|
||||
// Set to 1 to enable CallStacks when logging errors
|
||||
#define SI_DUMP_CALLSTACKS 0
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
#include <utils/CallStack.h>
|
||||
#endif
|
||||
|
||||
#include <utils/NativeHandle.h>
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace android {
|
||||
namespace SafeInterface {
|
||||
|
||||
/**
|
||||
* WARNING: Prefer to use AIDL-generated interfaces. Using SafeInterface to generate interfaces
|
||||
* does not support tracing, and many other AIDL features out of the box. The general direction
|
||||
* we should go is to migrate safe interface users to AIDL and then remove this so that there
|
||||
* is only one thing to learn/use/test/integrate, not this as well.
|
||||
*/
|
||||
|
||||
// ParcelHandler is responsible for writing/reading various types to/from a Parcel in a generic way
|
||||
class LIBBINDER_EXPORTED ParcelHandler {
|
||||
public:
|
||||
explicit ParcelHandler(const char* logTag) : mLogTag(logTag) {}
|
||||
|
||||
// Specializations for types with dedicated handling in Parcel
|
||||
status_t read(const Parcel& parcel, bool* b) const {
|
||||
return callParcel("readBool", [&]() { return parcel.readBool(b); });
|
||||
}
|
||||
status_t write(Parcel* parcel, bool b) const {
|
||||
return callParcel("writeBool", [&]() { return parcel->writeBool(b); });
|
||||
}
|
||||
template <typename E>
|
||||
typename std::enable_if<std::is_enum<E>::value, status_t>::type read(const Parcel& parcel,
|
||||
E* e) const {
|
||||
typename std::underlying_type<E>::type u{};
|
||||
status_t result = read(parcel, &u);
|
||||
*e = static_cast<E>(u);
|
||||
return result;
|
||||
}
|
||||
template <typename E>
|
||||
typename std::enable_if<std::is_enum<E>::value, status_t>::type write(Parcel* parcel,
|
||||
E e) const {
|
||||
return write(parcel, static_cast<typename std::underlying_type<E>::type>(e));
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, T* t) const {
|
||||
return callParcel("read(Flattenable)", [&]() { return parcel.read(*t); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const T& t) const {
|
||||
return callParcel("write(Flattenable)", [&]() { return parcel->write(t); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, sp<T>* t) const {
|
||||
*t = new T{};
|
||||
return callParcel("read(sp<Flattenable>)", [&]() { return parcel.read(*(t->get())); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const sp<T>& t) const {
|
||||
return callParcel("write(sp<Flattenable>)", [&]() { return parcel->write(*(t.get())); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, T* t) const {
|
||||
return callParcel("read(LightFlattenable)", [&]() { return parcel.read(*t); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const T& t) const {
|
||||
return callParcel("write(LightFlattenable)", [&]() { return parcel->write(t); });
|
||||
}
|
||||
template <typename NH>
|
||||
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type read(
|
||||
const Parcel& parcel, NH* nh) {
|
||||
*nh = NativeHandle::create(parcel.readNativeHandle(), true);
|
||||
return NO_ERROR;
|
||||
}
|
||||
template <typename NH>
|
||||
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type write(
|
||||
Parcel* parcel, const NH& nh) {
|
||||
return callParcel("write(sp<NativeHandle>)",
|
||||
[&]() { return parcel->writeNativeHandle(nh->handle()); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, T* t) const {
|
||||
return callParcel("readParcelable", [&]() { return parcel.readParcelable(t); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const T& t) const {
|
||||
return callParcel("writeParcelable", [&]() { return parcel->writeParcelable(t); });
|
||||
}
|
||||
status_t read(const Parcel& parcel, String8* str) const {
|
||||
return callParcel("readString8", [&]() { return parcel.readString8(str); });
|
||||
}
|
||||
status_t write(Parcel* parcel, const String8& str) const {
|
||||
return callParcel("writeString8", [&]() { return parcel->writeString8(str); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, sp<T>* pointer) const {
|
||||
return callParcel("readNullableStrongBinder",
|
||||
[&]() { return parcel.readNullableStrongBinder(pointer); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const sp<T>& pointer) const {
|
||||
return callParcel("writeStrongBinder",
|
||||
[&]() { return parcel->writeStrongBinder(pointer); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, sp<T>* pointer) const {
|
||||
return callParcel("readNullableStrongBinder[IInterface]",
|
||||
[&]() { return parcel.readNullableStrongBinder(pointer); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const sp<T>& interface) const {
|
||||
return write(parcel, IInterface::asBinder(interface));
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
|
||||
const Parcel& parcel, std::vector<T>* v) const {
|
||||
return callParcel("readParcelableVector", [&]() { return parcel.readParcelableVector(v); });
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
|
||||
Parcel* parcel, const std::vector<T>& v) const {
|
||||
return callParcel("writeParcelableVector",
|
||||
[&]() { return parcel->writeParcelableVector(v); });
|
||||
}
|
||||
|
||||
status_t read(const Parcel& parcel, std::vector<bool>* v) const {
|
||||
return callParcel("readBoolVector", [&]() { return parcel.readBoolVector(v); });
|
||||
}
|
||||
status_t write(Parcel* parcel, const std::vector<bool>& v) const {
|
||||
return callParcel("writeBoolVector", [&]() { return parcel->writeBoolVector(v); });
|
||||
}
|
||||
|
||||
status_t read(const Parcel& parcel, float* f) const {
|
||||
return callParcel("readFloat", [&]() { return parcel.readFloat(f); });
|
||||
}
|
||||
status_t write(Parcel* parcel, float f) const {
|
||||
return callParcel("writeFloat", [&]() { return parcel->writeFloat(f); });
|
||||
}
|
||||
|
||||
// Templates to handle integral types. We use a struct template to require that the called
|
||||
// function exactly matches the signedness and size of the argument (e.g., the argument isn't
|
||||
// silently widened).
|
||||
template <bool isSigned, size_t size, typename I>
|
||||
struct HandleInt;
|
||||
template <typename I>
|
||||
struct HandleInt<true, 4, I> {
|
||||
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
|
||||
return handler.callParcel("readInt32", [&]() { return parcel.readInt32(i); });
|
||||
}
|
||||
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
|
||||
return handler.callParcel("writeInt32", [&]() { return parcel->writeInt32(i); });
|
||||
}
|
||||
};
|
||||
template <typename I>
|
||||
struct HandleInt<false, 4, I> {
|
||||
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
|
||||
return handler.callParcel("readUint32", [&]() { return parcel.readUint32(i); });
|
||||
}
|
||||
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
|
||||
return handler.callParcel("writeUint32", [&]() { return parcel->writeUint32(i); });
|
||||
}
|
||||
};
|
||||
template <typename I>
|
||||
struct HandleInt<true, 8, I> {
|
||||
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
|
||||
return handler.callParcel("readInt64", [&]() { return parcel.readInt64(i); });
|
||||
}
|
||||
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
|
||||
return handler.callParcel("writeInt64", [&]() { return parcel->writeInt64(i); });
|
||||
}
|
||||
};
|
||||
template <typename I>
|
||||
struct HandleInt<false, 8, I> {
|
||||
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
|
||||
return handler.callParcel("readUint64", [&]() { return parcel.readUint64(i); });
|
||||
}
|
||||
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
|
||||
return handler.callParcel("writeUint64", [&]() { return parcel->writeUint64(i); });
|
||||
}
|
||||
};
|
||||
template <typename I>
|
||||
typename std::enable_if<std::is_integral<I>::value, status_t>::type read(const Parcel& parcel,
|
||||
I* i) const {
|
||||
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::read(*this, parcel, i);
|
||||
}
|
||||
template <typename I>
|
||||
typename std::enable_if<std::is_integral<I>::value, status_t>::type write(Parcel* parcel,
|
||||
I i) const {
|
||||
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::write(*this, parcel, i);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const mLogTag;
|
||||
|
||||
// Helper to encapsulate error handling while calling the various Parcel methods
|
||||
template <typename Function>
|
||||
status_t callParcel(const char* name, Function f) const {
|
||||
status_t error = f();
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
ALOG(LOG_ERROR, mLogTag, "Failed to %s, (%d: %s)", name, error, strerror(-error));
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
CallStack callStack(mLogTag);
|
||||
#endif
|
||||
}
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
// Utility struct template which allows us to retrieve the types of the parameters of a member
|
||||
// function pointer
|
||||
template <typename T>
|
||||
struct ParamExtractor;
|
||||
template <typename Class, typename Return, typename... Params>
|
||||
struct ParamExtractor<Return (Class::*)(Params...)> {
|
||||
using ParamTuple = std::tuple<Params...>;
|
||||
};
|
||||
template <typename Class, typename Return, typename... Params>
|
||||
struct ParamExtractor<Return (Class::*)(Params...) const> {
|
||||
using ParamTuple = std::tuple<Params...>;
|
||||
};
|
||||
|
||||
} // namespace SafeInterface
|
||||
|
||||
template <typename Interface>
|
||||
class LIBBINDER_EXPORTED SafeBpInterface : public BpInterface<Interface> {
|
||||
protected:
|
||||
SafeBpInterface(const sp<IBinder>& impl, const char* logTag)
|
||||
: BpInterface<Interface>(impl), mLogTag(logTag) {}
|
||||
~SafeBpInterface() override = default;
|
||||
|
||||
// callRemote is used to invoke a synchronous procedure call over Binder
|
||||
template <typename Method, typename TagType, typename... Args>
|
||||
status_t callRemote(TagType tag, Args&&... args) const {
|
||||
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
|
||||
|
||||
// Verify that the arguments are compatible with the parameters
|
||||
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
|
||||
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
|
||||
"Invalid argument type");
|
||||
|
||||
// Write the input arguments to the data Parcel
|
||||
Parcel data;
|
||||
data.writeInterfaceToken(this->getInterfaceDescriptor());
|
||||
|
||||
status_t error = writeInputs(&data, std::forward<Args>(args)...);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by writeInputs
|
||||
return error;
|
||||
}
|
||||
|
||||
// Send the data Parcel to the remote and retrieve the reply parcel
|
||||
Parcel reply;
|
||||
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
CallStack callStack(mLogTag);
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
|
||||
// Read the outputs from the reply Parcel into the output arguments
|
||||
error = readOutputs(reply, std::forward<Args>(args)...);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by readOutputs
|
||||
return error;
|
||||
}
|
||||
|
||||
// Retrieve the result code from the reply Parcel
|
||||
status_t result = NO_ERROR;
|
||||
error = reply.readInt32(&result);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
ALOG(LOG_ERROR, mLogTag, "Failed to obtain result");
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
CallStack callStack(mLogTag);
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// callRemoteAsync is used to invoke an asynchronous procedure call over Binder
|
||||
template <typename Method, typename TagType, typename... Args>
|
||||
void callRemoteAsync(TagType tag, Args&&... args) const {
|
||||
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
|
||||
|
||||
// Verify that the arguments are compatible with the parameters
|
||||
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
|
||||
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
|
||||
"Invalid argument type");
|
||||
|
||||
// Write the input arguments to the data Parcel
|
||||
Parcel data;
|
||||
data.writeInterfaceToken(this->getInterfaceDescriptor());
|
||||
status_t error = writeInputs(&data, std::forward<Args>(args)...);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by writeInputs
|
||||
return;
|
||||
}
|
||||
|
||||
// There will be no data in the reply Parcel since the call is one-way
|
||||
Parcel reply;
|
||||
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply,
|
||||
IBinder::FLAG_ONEWAY);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
CallStack callStack(mLogTag);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const mLogTag;
|
||||
|
||||
// This struct provides information on whether the decayed types of the elements at Index in the
|
||||
// tuple types T and U (that is, the types after stripping cv-qualifiers, removing references,
|
||||
// and a few other less common operations) are the same
|
||||
template <size_t Index, typename T, typename U>
|
||||
struct DecayedElementsMatch {
|
||||
private:
|
||||
using FirstT = typename std::tuple_element<Index, T>::type;
|
||||
using DecayedT = typename std::decay<FirstT>::type;
|
||||
using FirstU = typename std::tuple_element<Index, U>::type;
|
||||
using DecayedU = typename std::decay<FirstU>::type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = std::is_same<DecayedT, DecayedU>::value;
|
||||
};
|
||||
|
||||
// When comparing whether the argument types match the parameter types, we first decay them (see
|
||||
// DecayedElementsMatch) to avoid falsely flagging, say, T&& against T even though they are
|
||||
// equivalent enough for our purposes
|
||||
template <typename T, typename U>
|
||||
struct ArgsMatchParams {};
|
||||
template <typename... Args, typename... Params>
|
||||
struct ArgsMatchParams<std::tuple<Args...>, std::tuple<Params...>> {
|
||||
static_assert(sizeof...(Args) <= sizeof...(Params), "Too many arguments");
|
||||
static_assert(sizeof...(Args) >= sizeof...(Params), "Not enough arguments");
|
||||
|
||||
private:
|
||||
template <size_t Index>
|
||||
static constexpr typename std::enable_if<(Index < sizeof...(Args)), bool>::type
|
||||
elementsMatch() {
|
||||
if (!DecayedElementsMatch<Index, std::tuple<Args...>, std::tuple<Params...>>::value) {
|
||||
return false;
|
||||
}
|
||||
return elementsMatch<Index + 1>();
|
||||
}
|
||||
template <size_t Index>
|
||||
static constexpr typename std::enable_if<(Index >= sizeof...(Args)), bool>::type
|
||||
elementsMatch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr bool value = elementsMatch<0>();
|
||||
};
|
||||
|
||||
// Since we assume that pointer arguments are outputs, we can use this template struct to
|
||||
// determine whether or not a given argument is fundamentally a pointer type and thus an output
|
||||
template <typename T>
|
||||
struct IsPointerIfDecayed {
|
||||
private:
|
||||
using Decayed = typename std::decay<T>::type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = std::is_pointer<Decayed>::value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
|
||||
Parcel* data, T&& t) const {
|
||||
return SafeInterface::ParcelHandler{mLogTag}.write(data, std::forward<T>(t));
|
||||
}
|
||||
template <typename T>
|
||||
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
|
||||
Parcel* /*data*/, T&& /*t*/) const {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// This method iterates through all of the arguments, writing them to the data Parcel if they
|
||||
// are an input (i.e., if they are not a pointer type)
|
||||
template <typename T, typename... Remaining>
|
||||
status_t writeInputs(Parcel* data, T&& t, Remaining&&... remaining) const {
|
||||
status_t error = writeIfInput(data, std::forward<T>(t));
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by writeIfInput
|
||||
return error;
|
||||
}
|
||||
return writeInputs(data, std::forward<Remaining>(remaining)...);
|
||||
}
|
||||
static status_t writeInputs(Parcel* /*data*/) { return NO_ERROR; }
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
|
||||
const Parcel& reply, T&& t) const {
|
||||
return SafeInterface::ParcelHandler{mLogTag}.read(reply, std::forward<T>(t));
|
||||
}
|
||||
template <typename T>
|
||||
static typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
|
||||
const Parcel& /*reply*/, T&& /*t*/) {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Similar to writeInputs except that it reads output arguments from the reply Parcel
|
||||
template <typename T, typename... Remaining>
|
||||
status_t readOutputs(const Parcel& reply, T&& t, Remaining&&... remaining) const {
|
||||
status_t error = readIfOutput(reply, std::forward<T>(t));
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by readIfOutput
|
||||
return error;
|
||||
}
|
||||
return readOutputs(reply, std::forward<Remaining>(remaining)...);
|
||||
}
|
||||
static status_t readOutputs(const Parcel& /*data*/) { return NO_ERROR; }
|
||||
};
|
||||
|
||||
template <typename Interface>
|
||||
class LIBBINDER_EXPORTED SafeBnInterface : public BnInterface<Interface> {
|
||||
public:
|
||||
explicit SafeBnInterface(const char* logTag) : mLogTag(logTag) {}
|
||||
|
||||
protected:
|
||||
template <typename Method>
|
||||
status_t callLocal(const Parcel& data, Parcel* reply, Method method) {
|
||||
CHECK_INTERFACE(this, data, reply);
|
||||
|
||||
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
|
||||
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
|
||||
// outputs. When we ultimately call into the method, we will pass the addresses of the
|
||||
// output arguments instead of their tuple members directly, but the storage will live in
|
||||
// the tuple.
|
||||
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
|
||||
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
|
||||
|
||||
// Read the inputs from the data Parcel into the argument tuple
|
||||
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by read
|
||||
return error;
|
||||
}
|
||||
|
||||
// Call the local method
|
||||
status_t result = MethodCaller<ParamTuple>::call(this, method, &rawArgs);
|
||||
|
||||
// Extract the outputs from the argument tuple and write them into the reply Parcel
|
||||
error = OutputWriter<ParamTuple>{mLogTag}.writeOutputs(reply, &rawArgs);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by write
|
||||
return error;
|
||||
}
|
||||
|
||||
// Return the result code in the reply Parcel
|
||||
error = reply->writeInt32(result);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
ALOG(LOG_ERROR, mLogTag, "Failed to write result");
|
||||
#if SI_DUMP_CALLSTACKS
|
||||
CallStack callStack(mLogTag);
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
template <typename Method>
|
||||
status_t callLocalAsync(const Parcel& data, Parcel* /*reply*/, Method method) {
|
||||
// reply is not actually used by CHECK_INTERFACE
|
||||
CHECK_INTERFACE(this, data, reply);
|
||||
|
||||
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
|
||||
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
|
||||
// outputs. When we ultimately call into the method, we will pass the addresses of the
|
||||
// output arguments instead of their tuple members directly, but the storage will live in
|
||||
// the tuple.
|
||||
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
|
||||
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
|
||||
|
||||
// Read the inputs from the data Parcel into the argument tuple
|
||||
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged by read
|
||||
return error;
|
||||
}
|
||||
|
||||
// Call the local method
|
||||
MethodCaller<ParamTuple>::callVoid(this, method, &rawArgs);
|
||||
|
||||
// After calling, there is nothing more to do since asynchronous calls do not return a value
|
||||
// to the caller
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const mLogTag;
|
||||
|
||||
// RemoveFirst strips the first element from a tuple.
|
||||
// For example, given T = std::tuple<A, B, C>, RemoveFirst<T>::type = std::tuple<B, C>
|
||||
template <typename T, typename... Args>
|
||||
struct RemoveFirst;
|
||||
template <typename T, typename... Args>
|
||||
struct RemoveFirst<std::tuple<T, Args...>> {
|
||||
using type = std::tuple<Args...>;
|
||||
};
|
||||
|
||||
// RawConverter strips a tuple down to its fundamental types, discarding both pointers and
|
||||
// references. This allows us to allocate storage for both input (non-pointer) arguments and
|
||||
// output (pointer) arguments in one tuple.
|
||||
// For example, given T = std::tuple<const A&, B*>, RawConverter<T>::type = std::tuple<A, B>
|
||||
template <typename Unconverted, typename... Converted>
|
||||
struct RawConverter;
|
||||
template <typename Unconverted, typename... Converted>
|
||||
struct RawConverter<std::tuple<Converted...>, Unconverted> {
|
||||
private:
|
||||
using ElementType = typename std::tuple_element<0, Unconverted>::type;
|
||||
using Decayed = typename std::decay<ElementType>::type;
|
||||
using WithoutPointer = typename std::remove_pointer<Decayed>::type;
|
||||
|
||||
public:
|
||||
using type = typename RawConverter<std::tuple<Converted..., WithoutPointer>,
|
||||
typename RemoveFirst<Unconverted>::type>::type;
|
||||
};
|
||||
template <typename... Converted>
|
||||
struct RawConverter<std::tuple<Converted...>, std::tuple<>> {
|
||||
using type = std::tuple<Converted...>;
|
||||
};
|
||||
|
||||
// This provides a simple way to determine whether the indexed element of Args... is a pointer
|
||||
template <size_t I, typename... Args>
|
||||
struct ElementIsPointer {
|
||||
private:
|
||||
using ElementType = typename std::tuple_element<I, std::tuple<Args...>>::type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = std::is_pointer<ElementType>::value;
|
||||
};
|
||||
|
||||
// This class iterates over the parameter types, and if a given parameter is an input
|
||||
// (i.e., is not a pointer), reads the corresponding argument tuple element from the data Parcel
|
||||
template <typename... Params>
|
||||
class InputReader;
|
||||
template <typename... Params>
|
||||
class InputReader<std::tuple<Params...>> {
|
||||
public:
|
||||
explicit InputReader(const char* logTag) : mLogTag(logTag) {}
|
||||
|
||||
// Note that in this case (as opposed to in SafeBpInterface), we iterate using an explicit
|
||||
// index (starting with 0 here) instead of using recursion and stripping the first element.
|
||||
// This is because in SafeBpInterface we aren't actually operating on a real tuple, but are
|
||||
// instead just using a tuple as a convenient container for variadic types, whereas here we
|
||||
// can't modify the argument tuple without causing unnecessary copies or moves of the data
|
||||
// contained therein.
|
||||
template <typename RawTuple>
|
||||
status_t readInputs(const Parcel& data, RawTuple* args) {
|
||||
return dispatchArg<0>(data, args);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const mLogTag;
|
||||
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
|
||||
const Parcel& data, RawTuple* args) {
|
||||
return SafeInterface::ParcelHandler{mLogTag}.read(data, &std::get<I>(*args));
|
||||
}
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
|
||||
const Parcel& /*data*/, RawTuple* /*args*/) {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Recursively iterate through the arguments
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
|
||||
const Parcel& data, RawTuple* args) {
|
||||
status_t error = readIfInput<I>(data, args);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged in read
|
||||
return error;
|
||||
}
|
||||
return dispatchArg<I + 1>(data, args);
|
||||
}
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
|
||||
const Parcel& /*data*/, RawTuple* /*args*/) {
|
||||
return NO_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
// getForCall uses the types of the parameters to determine whether a given element of the
|
||||
// argument tuple is an input, which should be passed directly into the call, or an output, for
|
||||
// which its address should be passed into the call
|
||||
template <size_t I, typename RawTuple, typename... Params>
|
||||
static typename std::enable_if<
|
||||
ElementIsPointer<I, Params...>::value,
|
||||
typename std::tuple_element<I, std::tuple<Params...>>::type>::type
|
||||
getForCall(RawTuple* args) {
|
||||
return &std::get<I>(*args);
|
||||
}
|
||||
template <size_t I, typename RawTuple, typename... Params>
|
||||
static typename std::enable_if<
|
||||
!ElementIsPointer<I, Params...>::value,
|
||||
typename std::tuple_element<I, std::tuple<Params...>>::type>::type&
|
||||
getForCall(RawTuple* args) {
|
||||
return std::get<I>(*args);
|
||||
}
|
||||
|
||||
// This template class uses std::index_sequence and parameter pack expansion to call the given
|
||||
// method using the elements of the argument tuple (after those arguments are passed through
|
||||
// getForCall to get addresses instead of values for output arguments)
|
||||
template <typename... Params>
|
||||
struct MethodCaller;
|
||||
template <typename... Params>
|
||||
struct MethodCaller<std::tuple<Params...>> {
|
||||
public:
|
||||
// The calls through these to the helper methods are necessary to generate the
|
||||
// std::index_sequences used to unpack the argument tuple into the method call
|
||||
template <typename Class, typename MemberFunction, typename RawTuple>
|
||||
static status_t call(Class* instance, MemberFunction function, RawTuple* args) {
|
||||
return callHelper(instance, function, args, std::index_sequence_for<Params...>{});
|
||||
}
|
||||
template <typename Class, typename MemberFunction, typename RawTuple>
|
||||
static void callVoid(Class* instance, MemberFunction function, RawTuple* args) {
|
||||
callVoidHelper(instance, function, args, std::index_sequence_for<Params...>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
|
||||
static status_t callHelper(Class* instance, MemberFunction function, RawTuple* args,
|
||||
std::index_sequence<I...> /*unused*/) {
|
||||
return (instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
|
||||
}
|
||||
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
|
||||
static void callVoidHelper(Class* instance, MemberFunction function, RawTuple* args,
|
||||
std::index_sequence<I...> /*unused*/) {
|
||||
(instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
// This class iterates over the parameter types, and if a given parameter is an output
|
||||
// (i.e., is a pointer), writes the corresponding argument tuple element into the reply Parcel
|
||||
template <typename... Params>
|
||||
struct OutputWriter;
|
||||
template <typename... Params>
|
||||
struct OutputWriter<std::tuple<Params...>> {
|
||||
public:
|
||||
explicit OutputWriter(const char* logTag) : mLogTag(logTag) {}
|
||||
|
||||
// See the note on InputReader::readInputs for why this differs from the arguably simpler
|
||||
// RemoveFirst approach in SafeBpInterface
|
||||
template <typename RawTuple>
|
||||
status_t writeOutputs(Parcel* reply, RawTuple* args) {
|
||||
return dispatchArg<0>(reply, args);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* const mLogTag;
|
||||
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type
|
||||
writeIfOutput(Parcel* reply, RawTuple* args) {
|
||||
return SafeInterface::ParcelHandler{mLogTag}.write(reply, std::get<I>(*args));
|
||||
}
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type
|
||||
writeIfOutput(Parcel* /*reply*/, RawTuple* /*args*/) {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Recursively iterate through the arguments
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
|
||||
Parcel* reply, RawTuple* args) {
|
||||
status_t error = writeIfOutput<I>(reply, args);
|
||||
if (error != NO_ERROR) [[unlikely]] {
|
||||
// A message will have been logged in read
|
||||
return error;
|
||||
}
|
||||
return dispatchArg<I + 1>(reply, args);
|
||||
}
|
||||
template <std::size_t I, typename RawTuple>
|
||||
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
|
||||
Parcel* /*reply*/, RawTuple* /*args*/) {
|
||||
return NO_ERROR;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/IBinder.h>
|
||||
#include <string>
|
||||
|
||||
class BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
|
||||
|
||||
namespace android {
|
||||
|
||||
class BpBinder;
|
||||
class ProcessState;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Stability encodes how a binder changes over time. There are two levels of
|
||||
// stability:
|
||||
// 1). the interface stability - this is how a particular set of API calls (a
|
||||
// particular ordering of things like writeInt32/readInt32) are changed over
|
||||
// time. If one release, we have 'writeInt32' and the next release, we have
|
||||
// 'writeInt64', then this interface doesn't have a very stable
|
||||
// Stability::Level. Usually this ordering is controlled by a .aidl file.
|
||||
// 2). the wire format stability - this is how these API calls map to actual
|
||||
// bytes that are written to the wire (literally, this is how they are written
|
||||
// to the kernel inside of IBinder::transact, but it may be expanded to other
|
||||
// wires in the future). For instance, writeInt32 in binder translates to
|
||||
// writing a 4-byte little-endian integer in two's complement. You can imagine
|
||||
// in the future, we change writeInt32/readInt32 to instead write 8-bytes with
|
||||
// that integer and some check bits. In this case, the wire format changes,
|
||||
// but as long as a client libbinder knows to keep on writing a 4-byte value
|
||||
// to old servers, and new servers know how to interpret the 8-byte result,
|
||||
// they can still communicate.
|
||||
//
|
||||
// This class is specifically about (1). (2) is not currently tracked by
|
||||
// libbinder for regular binder calls, and everything on the system uses the
|
||||
// same copy of libbinder.
|
||||
|
||||
class Stability final {
|
||||
public:
|
||||
// Given a binder interface at a certain stability, there may be some
|
||||
// requirements associated with that higher stability level. For instance, a
|
||||
// VINTF stability binder is required to be in the VINTF manifest. This API
|
||||
// can be called to use that same interface within the local partition.
|
||||
LIBBINDER_EXPORTED static void forceDowngradeToLocalStability(const sp<IBinder>& binder);
|
||||
|
||||
// WARNING: Below APIs are only ever expected to be called by auto-generated code.
|
||||
// Instead of calling them, you should set the stability of a .aidl interface
|
||||
|
||||
// WARNING: The only client of
|
||||
// - forceDowngradeToSystemStability() and;
|
||||
// - korceDowngradeToVendorStability()
|
||||
// should be AIBinder_forceDowngradeToLocalStability().
|
||||
//
|
||||
// getLocalLevel() in libbinder returns Level::SYSTEM when called
|
||||
// from libbinder_ndk (even on vendor partition). So we explicitly provide
|
||||
// these methods for use by the NDK API:
|
||||
// AIBinder_forceDowngradeToLocalStability().
|
||||
//
|
||||
// This allows correctly downgrading the binder's stability to either system/vendor,
|
||||
// depending on the partition.
|
||||
|
||||
// Given a binder interface at a certain stability, there may be some
|
||||
// requirements associated with that higher stability level. For instance, a
|
||||
// VINTF stability binder is required to be in the VINTF manifest. This API
|
||||
// can be called to use that same interface within the vendor partition.
|
||||
LIBBINDER_EXPORTED static void forceDowngradeToVendorStability(const sp<IBinder>& binder);
|
||||
|
||||
// Given a binder interface at a certain stability, there may be some
|
||||
// requirements associated with that higher stability level. For instance, a
|
||||
// VINTF stability binder is required to be in the VINTF manifest. This API
|
||||
// can be called to use that same interface within the system partition.
|
||||
LIBBINDER_EXPORTED static void forceDowngradeToSystemStability(const sp<IBinder>& binder);
|
||||
|
||||
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
|
||||
// change or modify the stability class of the interface you are using.
|
||||
// This must be called as soon as the binder in question is constructed. No thread safety
|
||||
// is provided.
|
||||
// E.g. stability is according to libbinder compilation unit
|
||||
LIBBINDER_EXPORTED static void markCompilationUnit(IBinder* binder);
|
||||
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
|
||||
// change or modify the stability class of the interface you are using.
|
||||
// This must be called as soon as the binder in question is constructed. No thread safety
|
||||
// is provided.
|
||||
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
|
||||
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
|
||||
LIBBINDER_EXPORTED static void markVintf(IBinder* binder);
|
||||
|
||||
// WARNING: for debugging only
|
||||
LIBBINDER_EXPORTED static std::string debugToString(const sp<IBinder>& binder);
|
||||
|
||||
// WARNING: This is only ever expected to be called by auto-generated code or tests.
|
||||
// You likely want to change or modify the stability of the interface you are using.
|
||||
// This must be called as soon as the binder in question is constructed. No thread safety
|
||||
// is provided.
|
||||
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
|
||||
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
|
||||
// If this is called when __ANDROID_VNDK__ is not defined, then it is UB and will likely
|
||||
// break the device during GSI or other tests.
|
||||
LIBBINDER_EXPORTED static void markVndk(IBinder* binder);
|
||||
|
||||
// Returns true if the binder needs to be declared in the VINTF manifest or
|
||||
// else false if the binder is local to the current partition.
|
||||
LIBBINDER_EXPORTED static bool requiresVintfDeclaration(const sp<IBinder>& binder);
|
||||
|
||||
private:
|
||||
// Parcel needs to read/write stability level in an unstable format.
|
||||
friend ::android::Parcel;
|
||||
|
||||
// only expose internal APIs inside of libbinder, for checking stability
|
||||
friend ::android::BpBinder;
|
||||
|
||||
// so that it can mark the context object (only the root object doesn't go
|
||||
// through Parcel)
|
||||
friend ::android::ProcessState;
|
||||
|
||||
friend ::BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
|
||||
|
||||
static void tryMarkCompilationUnit(IBinder* binder);
|
||||
|
||||
// Currently, we use int16_t for Level so that it can fit in BBinder.
|
||||
// However, on the wire, we have 4 bytes reserved for stability, so whenever
|
||||
// we ingest a Level, we always accept an int32_t.
|
||||
enum Level : int16_t {
|
||||
UNDECLARED = 0,
|
||||
|
||||
VENDOR = 0b000011,
|
||||
SYSTEM = 0b001100,
|
||||
VINTF = 0b111111,
|
||||
};
|
||||
|
||||
// returns the stability according to how this was built
|
||||
static Level getLocalLevel();
|
||||
|
||||
// Downgrades binder stability to the specified level.
|
||||
static void forceDowngradeToStability(const sp<IBinder>& binder, Level level);
|
||||
|
||||
enum {
|
||||
REPR_NONE = 0,
|
||||
REPR_LOG = 1,
|
||||
REPR_ALLOW_DOWNGRADE = 2,
|
||||
};
|
||||
// applies stability to binder if stability level is known
|
||||
__attribute__((warn_unused_result)) static status_t setRepr(IBinder* binder, int32_t setting,
|
||||
uint32_t flags);
|
||||
|
||||
// get stability information as encoded on the wire
|
||||
LIBBINDER_EXPORTED static int16_t getRepr(IBinder* binder);
|
||||
|
||||
// whether a transaction on binder is allowed, if the transaction
|
||||
// is done from a context with a specific stability level
|
||||
LIBBINDER_EXPORTED static bool check(int16_t provided, Level required);
|
||||
|
||||
static bool isDeclaredLevel(int32_t level);
|
||||
static std::string levelString(int32_t level);
|
||||
|
||||
Stability();
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace android
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_BINDER_STATUS_H
|
||||
#define ANDROID_BINDER_STATUS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream> // historical
|
||||
#include <ostream>
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <utils/String8.h>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace binder {
|
||||
|
||||
// An object similar in function to a status_t except that it understands
|
||||
// how exceptions are encoded in the prefix of a Parcel. Used like:
|
||||
//
|
||||
// Parcel data;
|
||||
// Parcel reply;
|
||||
// status_t status;
|
||||
// binder::Status remote_exception;
|
||||
// if ((status = data.writeInterfaceToken(interface_descriptor)) != OK ||
|
||||
// (status = data.writeInt32(function_input)) != OK) {
|
||||
// // We failed to write into the memory of our local parcel?
|
||||
// }
|
||||
// if ((status = remote()->transact(transaction, data, &reply)) != OK) {
|
||||
// // Something has gone wrong in the binder driver or libbinder.
|
||||
// }
|
||||
// if ((status = remote_exception.readFromParcel(reply)) != OK) {
|
||||
// // The remote didn't correctly write the exception header to the
|
||||
// // reply.
|
||||
// }
|
||||
// if (!remote_exception.isOk()) {
|
||||
// // The transaction went through correctly, but the remote reported an
|
||||
// // exception during handling.
|
||||
// }
|
||||
//
|
||||
class LIBBINDER_EXPORTED Status final {
|
||||
public:
|
||||
// Keep the exception codes in sync with android/os/Parcel.java.
|
||||
enum Exception {
|
||||
EX_NONE = 0,
|
||||
EX_SECURITY = -1,
|
||||
EX_BAD_PARCELABLE = -2,
|
||||
EX_ILLEGAL_ARGUMENT = -3,
|
||||
EX_NULL_POINTER = -4,
|
||||
EX_ILLEGAL_STATE = -5,
|
||||
EX_NETWORK_MAIN_THREAD = -6,
|
||||
EX_UNSUPPORTED_OPERATION = -7,
|
||||
EX_SERVICE_SPECIFIC = -8,
|
||||
EX_PARCELABLE = -9,
|
||||
|
||||
// See android/os/Parcel.java. We need to handle this in native code.
|
||||
EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127,
|
||||
|
||||
// This is special and Java specific; see Parcel.java.
|
||||
EX_HAS_REPLY_HEADER = -128,
|
||||
// This is special, and indicates to C++ binder proxies that the
|
||||
// transaction has failed at a low level.
|
||||
EX_TRANSACTION_FAILED = -129,
|
||||
};
|
||||
|
||||
// A more readable alias for the default constructor.
|
||||
static Status ok();
|
||||
|
||||
// Authors should explicitly pick whether their integer is:
|
||||
// - an exception code (EX_* above)
|
||||
// - service specific error code
|
||||
// - status_t
|
||||
//
|
||||
// Prefer a generic exception code when possible, then a service specific
|
||||
// code, and finally a status_t for low level failures or legacy support.
|
||||
// Exception codes and service specific errors map to nicer exceptions for
|
||||
// Java clients.
|
||||
static Status fromExceptionCode(int32_t exceptionCode);
|
||||
static Status fromExceptionCode(int32_t exceptionCode,
|
||||
const String8& message);
|
||||
static Status fromExceptionCode(int32_t exceptionCode,
|
||||
const char* message);
|
||||
|
||||
// warning: this is still considered an error if it is constructed with a
|
||||
// zero value error code. Please use Status::ok() instead and avoid zero
|
||||
// error codes
|
||||
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode);
|
||||
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode,
|
||||
const String8& message);
|
||||
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode,
|
||||
const char* message);
|
||||
|
||||
static Status fromStatusT(status_t status);
|
||||
|
||||
static std::string exceptionToString(status_t exceptionCode);
|
||||
|
||||
Status() = default;
|
||||
~Status() = default;
|
||||
|
||||
// Status objects are copyable and contain just simple data.
|
||||
Status(const Status& status) = default;
|
||||
Status(Status&& status) = default;
|
||||
Status& operator=(const Status& status) = default;
|
||||
|
||||
// Bear in mind that if the client or service is a Java endpoint, this
|
||||
// is not the logic which will provide/interpret the data here.
|
||||
status_t readFromParcel(const Parcel& parcel);
|
||||
status_t writeToParcel(Parcel* parcel) const;
|
||||
|
||||
// Convenience API to replace a Parcel with a status value, w/o requiring
|
||||
// calling multiple APIs (makes generated code smaller).
|
||||
status_t writeOverParcel(Parcel* parcel) const;
|
||||
|
||||
// Set one of the pre-defined exception types defined above.
|
||||
void setException(int32_t ex, const String8& message);
|
||||
// Set a service specific exception with error code.
|
||||
void setServiceSpecificError(int32_t errorCode, const String8& message);
|
||||
// Setting a |status| != OK causes generated code to return |status|
|
||||
// from Binder transactions, rather than writing an exception into the
|
||||
// reply Parcel. This is the least preferable way of reporting errors.
|
||||
void setFromStatusT(status_t status);
|
||||
|
||||
// Get information about an exception.
|
||||
int32_t exceptionCode() const { return mException; }
|
||||
const String8& exceptionMessage() const { return mMessage; }
|
||||
status_t transactionError() const {
|
||||
return mException == EX_TRANSACTION_FAILED ? mErrorCode : OK;
|
||||
}
|
||||
int32_t serviceSpecificErrorCode() const {
|
||||
return mException == EX_SERVICE_SPECIFIC ? mErrorCode : 0;
|
||||
}
|
||||
|
||||
bool isOk() const { return mException == EX_NONE; }
|
||||
|
||||
// For logging.
|
||||
String8 toString8() const;
|
||||
|
||||
private:
|
||||
Status(int32_t exceptionCode, int32_t errorCode);
|
||||
Status(int32_t exceptionCode, int32_t errorCode, const String8& message);
|
||||
|
||||
status_t skipUnusedHeader(const Parcel& parcel);
|
||||
|
||||
// If |mException| == EX_TRANSACTION_FAILED, generated code will return
|
||||
// |mErrorCode| as the result of the transaction rather than write an
|
||||
// exception to the reply parcel.
|
||||
//
|
||||
// Otherwise, we always write |mException| to the parcel.
|
||||
// If |mException| != EX_NONE, we write |mMessage| as well.
|
||||
// If |mException| == EX_SERVICE_SPECIFIC we write |mErrorCode| as well.
|
||||
int32_t mException = EX_NONE;
|
||||
int32_t mErrorCode = 0;
|
||||
String8 mMessage;
|
||||
}; // class Status
|
||||
|
||||
static inline std::ostream& operator<< (std::ostream& o, const Status& s) {
|
||||
return o << s.toString8();
|
||||
}
|
||||
|
||||
} // namespace binder
|
||||
} // namespace android
|
||||
|
||||
#endif // ANDROID_BINDER_STATUS_H
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sstream>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
class LIBBINDER_EXPORTED TextOutput {
|
||||
public:
|
||||
TextOutput();
|
||||
virtual ~TextOutput();
|
||||
|
||||
virtual status_t print(const char* txt, size_t len) = 0;
|
||||
virtual void moveIndent(int delta) = 0;
|
||||
|
||||
class Bundle {
|
||||
public:
|
||||
inline explicit Bundle(TextOutput& to) : mTO(to) { to.pushBundle(); }
|
||||
inline ~Bundle() { mTO.popBundle(); }
|
||||
private:
|
||||
TextOutput& mTO;
|
||||
};
|
||||
|
||||
virtual void pushBundle() = 0;
|
||||
virtual void popBundle() = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
|
||||
// be allocated.
|
||||
// Text output stream for printing to the log (via utils/Log.h).
|
||||
extern LIBBINDER_EXPORTED TextOutput& alog;
|
||||
|
||||
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
|
||||
// be allocated.
|
||||
// Text output stream for printing to stdout.
|
||||
extern LIBBINDER_EXPORTED TextOutput& aout;
|
||||
|
||||
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
|
||||
// be allocated.
|
||||
// Text output stream for printing to stderr.
|
||||
extern LIBBINDER_EXPORTED TextOutput& aerr;
|
||||
|
||||
typedef TextOutput& (*TextOutputManipFunc)(TextOutput&);
|
||||
|
||||
TextOutput& endl(TextOutput& to);
|
||||
TextOutput& indent(TextOutput& to);
|
||||
TextOutput& dedent(TextOutput& to);
|
||||
|
||||
template<typename T>
|
||||
TextOutput& operator<<(TextOutput& to, const T& val)
|
||||
{
|
||||
std::stringstream strbuf;
|
||||
strbuf << val;
|
||||
std::string str = strbuf.str();
|
||||
to.print(str.c_str(), str.size());
|
||||
return to;
|
||||
}
|
||||
|
||||
LIBBINDER_EXPORTED TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func);
|
||||
|
||||
class LIBBINDER_EXPORTED TypeCode {
|
||||
public:
|
||||
inline explicit TypeCode(uint32_t code);
|
||||
inline ~TypeCode();
|
||||
|
||||
inline uint32_t typeCode() const;
|
||||
|
||||
private:
|
||||
uint32_t mCode;
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const TypeCode& val);
|
||||
|
||||
class LIBBINDER_EXPORTED HexDump {
|
||||
public:
|
||||
HexDump(const void *buf, size_t size, size_t bytesPerLine=16);
|
||||
inline ~HexDump();
|
||||
|
||||
inline HexDump& setBytesPerLine(size_t bytesPerLine);
|
||||
inline HexDump& setSingleLineCutoff(int32_t bytes);
|
||||
inline HexDump& setAlignment(size_t alignment);
|
||||
inline HexDump& setCArrayStyle(bool enabled);
|
||||
|
||||
inline const void* buffer() const;
|
||||
inline size_t size() const;
|
||||
inline size_t bytesPerLine() const;
|
||||
inline int32_t singleLineCutoff() const;
|
||||
inline size_t alignment() const;
|
||||
inline bool carrayStyle() const;
|
||||
|
||||
private:
|
||||
const void* mBuffer;
|
||||
size_t mSize;
|
||||
size_t mBytesPerLine;
|
||||
int32_t mSingleLineCutoff;
|
||||
size_t mAlignment;
|
||||
bool mCArrayStyle;
|
||||
};
|
||||
|
||||
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const HexDump& val);
|
||||
inline TextOutput& operator<<(TextOutput& to,
|
||||
decltype(std::endl<char,
|
||||
std::char_traits<char>>)
|
||||
/*val*/) {
|
||||
endl(to);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& operator<<(TextOutput& to, const char &c)
|
||||
{
|
||||
to.print(&c, 1);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& operator<<(TextOutput& to, const bool &val)
|
||||
{
|
||||
if (val) to.print("true", 4);
|
||||
else to.print("false", 5);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& operator<<(TextOutput& to, const String16& val)
|
||||
{
|
||||
to << String8(val).c_str();
|
||||
return to;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user servicable parts below.
|
||||
|
||||
inline TextOutput& endl(TextOutput& to)
|
||||
{
|
||||
to.print("\n", 1);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& indent(TextOutput& to)
|
||||
{
|
||||
to.moveIndent(1);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& dedent(TextOutput& to)
|
||||
{
|
||||
to.moveIndent(-1);
|
||||
return to;
|
||||
}
|
||||
|
||||
inline TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func)
|
||||
{
|
||||
return (*func)(to);
|
||||
}
|
||||
|
||||
inline TypeCode::TypeCode(uint32_t code) : mCode(code) { }
|
||||
inline TypeCode::~TypeCode() { }
|
||||
inline uint32_t TypeCode::typeCode() const { return mCode; }
|
||||
|
||||
inline HexDump::~HexDump() { }
|
||||
|
||||
inline HexDump& HexDump::setBytesPerLine(size_t bytesPerLine) {
|
||||
mBytesPerLine = bytesPerLine; return *this;
|
||||
}
|
||||
inline HexDump& HexDump::setSingleLineCutoff(int32_t bytes) {
|
||||
mSingleLineCutoff = bytes; return *this;
|
||||
}
|
||||
inline HexDump& HexDump::setAlignment(size_t alignment) {
|
||||
mAlignment = alignment; return *this;
|
||||
}
|
||||
inline HexDump& HexDump::setCArrayStyle(bool enabled) {
|
||||
mCArrayStyle = enabled; return *this;
|
||||
}
|
||||
|
||||
inline const void* HexDump::buffer() const { return mBuffer; }
|
||||
inline size_t HexDump::size() const { return mSize; }
|
||||
inline size_t HexDump::bytesPerLine() const { return mBytesPerLine; }
|
||||
inline int32_t HexDump::singleLineCutoff() const { return mSingleLineCutoff; }
|
||||
inline size_t HexDump::alignment() const { return mAlignment; }
|
||||
inline bool HexDump::carrayStyle() const { return mCArrayStyle; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
} // namespace android
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __has_include(<cutils/trace.h>)
|
||||
#include <cutils/trace.h>
|
||||
#endif
|
||||
|
||||
#include <binder/Common.h>
|
||||
|
||||
#ifdef ATRACE_TAG_AIDL
|
||||
#if ATRACE_TAG_AIDL != (1 << 24)
|
||||
#error "Mismatched ATRACE_TAG_AIDL definitions"
|
||||
#endif
|
||||
#else
|
||||
#define ATRACE_TAG_AIDL (1 << 24)
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
namespace binder {
|
||||
|
||||
// Forward declarations from internal OS.h
|
||||
namespace os {
|
||||
// Trampoline functions allowing generated aidls to trace binder transactions without depending on
|
||||
// libcutils/libutils
|
||||
void trace_begin(uint64_t tag, const char* name);
|
||||
void trace_end(uint64_t tag);
|
||||
void trace_int(uint64_t tag, const char* name, int32_t value);
|
||||
uint64_t get_trace_enabled_tags();
|
||||
} // namespace os
|
||||
|
||||
class LIBBINDER_EXPORTED ScopedTrace {
|
||||
public:
|
||||
inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { os::trace_begin(mTag, name); }
|
||||
|
||||
inline ~ScopedTrace() { os::trace_end(mTag); }
|
||||
|
||||
private:
|
||||
uint64_t mTag;
|
||||
};
|
||||
|
||||
} // namespace binder
|
||||
} // namespace android
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <binder/Common.h>
|
||||
|
||||
#ifndef BINDER_NO_LIBBASE
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
namespace android::binder {
|
||||
using android::base::borrowed_fd;
|
||||
using android::base::unique_fd;
|
||||
} // namespace android::binder
|
||||
|
||||
#else // BINDER_NO_LIBBASE
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // not needed for unique_fd, but a lot of users depend on open(3)
|
||||
#include <unistd.h>
|
||||
|
||||
namespace android::binder {
|
||||
|
||||
// Container for a file descriptor that automatically closes the descriptor as
|
||||
// it goes out of scope.
|
||||
//
|
||||
// unique_fd ufd(open("/some/path", "r"));
|
||||
// if (!ufd.ok()) return error;
|
||||
//
|
||||
// // Do something useful with ufd.get(), possibly including early 'return'.
|
||||
//
|
||||
// return 0; // Descriptor is closed for you.
|
||||
//
|
||||
class LIBBINDER_EXPORTED unique_fd final {
|
||||
public:
|
||||
unique_fd() {}
|
||||
|
||||
explicit unique_fd(int fd) { reset(fd); }
|
||||
~unique_fd() { reset(); }
|
||||
|
||||
unique_fd(const unique_fd&) = delete;
|
||||
void operator=(const unique_fd&) = delete;
|
||||
unique_fd(unique_fd&& other) noexcept { reset(other.release()); }
|
||||
unique_fd& operator=(unique_fd&& s) noexcept {
|
||||
int fd = s.fd_;
|
||||
s.fd_ = -1;
|
||||
reset(fd);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[clang::reinitializes]] void reset(int new_value = -1) {
|
||||
int previous_errno = errno;
|
||||
|
||||
if (fd_ != -1) {
|
||||
::close(fd_);
|
||||
}
|
||||
|
||||
fd_ = new_value;
|
||||
errno = previous_errno;
|
||||
}
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
bool ok() const { return get() >= 0; }
|
||||
|
||||
[[nodiscard]] int release() {
|
||||
int ret = fd_;
|
||||
fd_ = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_ = -1;
|
||||
};
|
||||
|
||||
// A wrapper type that can be implicitly constructed from either int or
|
||||
// unique_fd. This supports cases where you don't actually own the file
|
||||
// descriptor, and can't take ownership, but are temporarily acting as if
|
||||
// you're the owner.
|
||||
//
|
||||
// One example would be a function that needs to also allow
|
||||
// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
|
||||
// that's using a file descriptor that's actually owned by a
|
||||
// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
|
||||
// would like to enforce this weaker sense of "temporary ownership".
|
||||
//
|
||||
// If you think of unique_fd as being like std::string in that represents
|
||||
// ownership, borrowed_fd is like std::string_view (and int is like const
|
||||
// char*).
|
||||
struct LIBBINDER_EXPORTED borrowed_fd {
|
||||
/* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT
|
||||
/* implicit */ borrowed_fd(const unique_fd& ufd) : fd_(ufd.get()) {} // NOLINT
|
||||
|
||||
int get() const { return fd_; }
|
||||
|
||||
private:
|
||||
int fd_ = -1;
|
||||
};
|
||||
|
||||
} // namespace android::binder
|
||||
|
||||
#endif // BINDER_NO_LIBBASE
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* The type used to return success/failure from frameworks APIs.
|
||||
* See the anonymous enum below for valid values.
|
||||
*/
|
||||
typedef int32_t status_t;
|
||||
|
||||
/*
|
||||
* Error codes.
|
||||
* All error codes are negative values.
|
||||
*/
|
||||
|
||||
enum {
|
||||
OK = 0, // Preferred constant for checking success.
|
||||
#ifndef NO_ERROR
|
||||
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
|
||||
// real conflict, though it's a bit awkward.
|
||||
NO_ERROR = OK, // Deprecated synonym for `OK`. Prefer `OK` because it doesn't conflict with Windows.
|
||||
#endif
|
||||
|
||||
UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value
|
||||
|
||||
NO_MEMORY = -ENOMEM,
|
||||
INVALID_OPERATION = -ENOSYS,
|
||||
BAD_VALUE = -EINVAL,
|
||||
BAD_TYPE = (UNKNOWN_ERROR + 1),
|
||||
NAME_NOT_FOUND = -ENOENT,
|
||||
PERMISSION_DENIED = -EPERM,
|
||||
NO_INIT = -ENODEV,
|
||||
ALREADY_EXISTS = -EEXIST,
|
||||
DEAD_OBJECT = -EPIPE,
|
||||
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
|
||||
#if !defined(_WIN32)
|
||||
BAD_INDEX = -EOVERFLOW,
|
||||
NOT_ENOUGH_DATA = -ENODATA,
|
||||
WOULD_BLOCK = -EWOULDBLOCK,
|
||||
TIMED_OUT = -ETIMEDOUT,
|
||||
UNKNOWN_TRANSACTION = -EBADMSG,
|
||||
#else
|
||||
BAD_INDEX = -E2BIG,
|
||||
NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
|
||||
WOULD_BLOCK = (UNKNOWN_ERROR + 4),
|
||||
TIMED_OUT = (UNKNOWN_ERROR + 5),
|
||||
UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
|
||||
#endif
|
||||
FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
|
||||
UNEXPECTED_NULL = (UNKNOWN_ERROR + 8),
|
||||
};
|
||||
|
||||
// Human readable name of error
|
||||
std::string statusToString(status_t status);
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* See documentation in RefBase.h
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
class ReferenceRenamer;
|
||||
|
||||
void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
|
||||
|
||||
template <class T>
|
||||
class LightRefBase
|
||||
{
|
||||
public:
|
||||
inline LightRefBase() : mCount(0) { }
|
||||
inline void incStrong(__attribute__((unused)) const void* id) const {
|
||||
mCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
|
||||
if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
|
||||
LightRefBase_reportIncStrongRequireStrongFailed(this);
|
||||
}
|
||||
}
|
||||
inline void decStrong(__attribute__((unused)) const void* id) const {
|
||||
if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
delete static_cast<const T*>(this);
|
||||
}
|
||||
}
|
||||
//! DEBUGGING ONLY: Get current strong ref count.
|
||||
inline int32_t getStrongCount() const {
|
||||
return mCount.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
protected:
|
||||
inline ~LightRefBase() { }
|
||||
|
||||
private:
|
||||
friend class ReferenceMover;
|
||||
inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { }
|
||||
inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { }
|
||||
|
||||
private:
|
||||
mutable std::atomic<int32_t> mCount;
|
||||
};
|
||||
|
||||
// This is a wrapper around LightRefBase that simply enforces a virtual
|
||||
// destructor to eliminate the template requirement of LightRefBase
|
||||
class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
|
||||
public:
|
||||
virtual ~VirtualLightRefBase() = default;
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,818 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
// SOME COMMENTS ABOUT USAGE:
|
||||
|
||||
// This provides primarily wp<> weak pointer types and RefBase, which work
|
||||
// together with sp<> from <StrongPointer.h>.
|
||||
|
||||
// sp<> (and wp<>) are a type of smart pointer that use a well defined protocol
|
||||
// to operate. As long as the object they are templated with implements that
|
||||
// protocol, these smart pointers work. In several places the platform
|
||||
// instantiates sp<> with non-RefBase objects; the two are not tied to each
|
||||
// other.
|
||||
|
||||
// RefBase is such an implementation and it supports strong pointers, weak
|
||||
// pointers and some magic features for the binder.
|
||||
|
||||
// So, when using RefBase objects, you have the ability to use strong and weak
|
||||
// pointers through sp<> and wp<>.
|
||||
|
||||
// Normally, when the last strong pointer goes away, the object is destroyed,
|
||||
// i.e. it's destructor is called. HOWEVER, parts of its associated memory is not
|
||||
// freed until the last weak pointer is released.
|
||||
|
||||
// Weak pointers are essentially "safe" pointers. They are always safe to
|
||||
// access through promote(). They may return nullptr if the object was
|
||||
// destroyed because it ran out of strong pointers. This makes them good candidates
|
||||
// for keys in a cache for instance.
|
||||
|
||||
// Weak pointers remain valid for comparison purposes even after the underlying
|
||||
// object has been destroyed. Even if object A is destroyed and its memory reused
|
||||
// for B, A remaining weak pointer to A will not compare equal to one to B.
|
||||
// This again makes them attractive for use as keys.
|
||||
|
||||
// How is this supposed / intended to be used?
|
||||
|
||||
// Our recommendation is to use strong references (sp<>) when there is an
|
||||
// ownership relation. e.g. when an object "owns" another one, use a strong
|
||||
// ref. And of course use strong refs as arguments of functions (it's extremely
|
||||
// rare that a function will take a wp<>).
|
||||
|
||||
// Typically a newly allocated object will immediately be used to initialize
|
||||
// a strong pointer, which may then be used to construct or assign to other
|
||||
// strong and weak pointers.
|
||||
|
||||
// Use weak references when there are no ownership relation. e.g. the keys in a
|
||||
// cache (you cannot use plain pointers because there is no safe way to acquire
|
||||
// a strong reference from a vanilla pointer).
|
||||
|
||||
// This implies that two objects should never (or very rarely) have sp<> on
|
||||
// each other, because they can't both own each other.
|
||||
|
||||
|
||||
// Caveats with reference counting
|
||||
|
||||
// Obviously, circular strong references are a big problem; this creates leaks
|
||||
// and it's hard to debug -- except it's in fact really easy because RefBase has
|
||||
// tons of debugging code for that. It can basically tell you exactly where the
|
||||
// leak is.
|
||||
|
||||
// Another problem has to do with destructors with side effects. You must
|
||||
// assume that the destructor of reference counted objects can be called AT ANY
|
||||
// TIME. For instance code as simple as this:
|
||||
|
||||
// void setStuff(const sp<Stuff>& stuff) {
|
||||
// std::lock_guard<std::mutex> lock(mMutex);
|
||||
// mStuff = stuff;
|
||||
// }
|
||||
|
||||
// is very dangerous. This code WILL deadlock one day or another.
|
||||
|
||||
// What isn't obvious is that ~Stuff() can be called as a result of the
|
||||
// assignment. And it gets called with the lock held. First of all, the lock is
|
||||
// protecting mStuff, not ~Stuff(). Secondly, if ~Stuff() uses its own internal
|
||||
// mutex, now you have mutex ordering issues. Even worse, if ~Stuff() is
|
||||
// virtual, now you're calling into "user" code (potentially), by that, I mean,
|
||||
// code you didn't even write.
|
||||
|
||||
// A correct way to write this code is something like:
|
||||
|
||||
// void setStuff(const sp<Stuff>& stuff) {
|
||||
// std::unique_lock<std::mutex> lock(mMutex);
|
||||
// sp<Stuff> hold = mStuff;
|
||||
// mStuff = stuff;
|
||||
// lock.unlock();
|
||||
// }
|
||||
|
||||
// More importantly, reference counted objects should do as little work as
|
||||
// possible in their destructor, or at least be mindful that their destructor
|
||||
// could be called from very weird and unintended places.
|
||||
|
||||
// Other more specific restrictions for wp<> and sp<>:
|
||||
|
||||
// Do not construct a strong pointer to "this" in an object's constructor.
|
||||
// The onFirstRef() callback would be made on an incompletely constructed
|
||||
// object.
|
||||
// Construction of a weak pointer to "this" in an object's constructor is also
|
||||
// discouraged. But the implementation was recently changed so that, in the
|
||||
// absence of extendObjectLifetime() calls, weak pointers no longer impact
|
||||
// object lifetime, and hence this no longer risks premature deallocation,
|
||||
// and hence usually works correctly.
|
||||
|
||||
// Such strong or weak pointers can be safely created in the RefBase onFirstRef()
|
||||
// callback.
|
||||
|
||||
// Use of wp::unsafe_get() for any purpose other than debugging is almost
|
||||
// always wrong. Unless you somehow know that there is a longer-lived sp<> to
|
||||
// the same object, it may well return a pointer to a deallocated object that
|
||||
// has since been reallocated for a different purpose. (And if you know there
|
||||
// is a longer-lived sp<>, why not use an sp<> directly?) A wp<> should only be
|
||||
// dereferenced by using promote().
|
||||
|
||||
// Any object inheriting from RefBase should always be destroyed as the result
|
||||
// of a reference count decrement, not via any other means. Such objects
|
||||
// should never be stack allocated, or appear directly as data members in other
|
||||
// objects. Objects inheriting from RefBase should have their strong reference
|
||||
// count incremented as soon as possible after construction. Usually this
|
||||
// will be done via construction of an sp<> to the object, but may instead
|
||||
// involve other means of calling RefBase::incStrong().
|
||||
// Explicitly deleting or otherwise destroying a RefBase object with outstanding
|
||||
// wp<> or sp<> pointers to it will result in an abort or heap corruption.
|
||||
|
||||
// It is particularly important not to mix sp<> and direct storage management
|
||||
// since the sp from raw pointer constructor is implicit. Thus if a RefBase-
|
||||
// -derived object of type T is managed without ever incrementing its strong
|
||||
// count, and accidentally passed to f(sp<T>), a strong pointer to the object
|
||||
// will be temporarily constructed and destroyed, prematurely deallocating the
|
||||
// object, and resulting in heap corruption. None of this would be easily
|
||||
// visible in the source. See below on
|
||||
// ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION for a compile time
|
||||
// option which helps avoid this case.
|
||||
|
||||
// Extra Features:
|
||||
|
||||
// RefBase::extendObjectLifetime() can be used to prevent destruction of the
|
||||
// object while there are still weak references. This is really special purpose
|
||||
// functionality to support Binder.
|
||||
|
||||
// Wp::promote(), implemented via the attemptIncStrong() member function, is
|
||||
// used to try to convert a weak pointer back to a strong pointer. It's the
|
||||
// normal way to try to access the fields of an object referenced only through
|
||||
// a wp<>. Binder code also sometimes uses attemptIncStrong() directly.
|
||||
|
||||
// RefBase provides a number of additional callbacks for certain reference count
|
||||
// events, as well as some debugging facilities.
|
||||
|
||||
// Debugging support can be enabled by turning on DEBUG_REFS in RefBase.cpp.
|
||||
// Otherwise little checking is provided.
|
||||
|
||||
// Thread safety:
|
||||
|
||||
// Like std::shared_ptr, sp<> and wp<> allow concurrent accesses to DIFFERENT
|
||||
// sp<> and wp<> instances that happen to refer to the same underlying object.
|
||||
// They do NOT support concurrent access (where at least one access is a write)
|
||||
// to THE SAME sp<> or wp<>. In effect, their thread-safety properties are
|
||||
// exactly like those of T*, NOT atomic<T*>.
|
||||
|
||||
// Safety option: ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION
|
||||
//
|
||||
// This flag makes the semantics for using a RefBase object with wp<> and sp<>
|
||||
// much stricter by disabling implicit conversion from raw pointers to these
|
||||
// objects. In order to use this, apply this flag in Android.bp like so:
|
||||
//
|
||||
// cflags: [
|
||||
// "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
|
||||
// ],
|
||||
//
|
||||
// REGARDLESS of whether this flag is on, best usage of sp<> is shown below. If
|
||||
// this flag is on, no other usage is possible (directly calling RefBase methods
|
||||
// is possible, but seeing code using 'incStrong' instead of 'sp<>', for
|
||||
// instance, should already set off big alarm bells. With carefully constructed
|
||||
// data structures, it should NEVER be necessary to directly use RefBase
|
||||
// methods). Proper RefBase usage:
|
||||
//
|
||||
// class Foo : virtual public RefBase { ... };
|
||||
//
|
||||
// // always construct an sp object with sp::make
|
||||
// sp<Foo> myFoo = sp<Foo>::make(/*args*/);
|
||||
//
|
||||
// // if you need a weak pointer, it must be constructed from a strong
|
||||
// // pointer
|
||||
// wp<Foo> weakFoo = myFoo; // NOT myFoo.get()
|
||||
//
|
||||
// // If you are inside of a method of Foo and need access to a strong
|
||||
// // explicitly call this function. This documents your intention to code
|
||||
// // readers, and it will give a runtime error for what otherwise would
|
||||
// // be potential double ownership
|
||||
// .... Foo::someMethod(...) {
|
||||
// // asserts if there is a memory issue
|
||||
// sp<Foo> thiz = sp<Foo>::fromExisting(this);
|
||||
// }
|
||||
//
|
||||
|
||||
#ifndef ANDROID_REF_BASE_H
|
||||
#define ANDROID_REF_BASE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits> // for common_type.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// LightRefBase used to be declared in this header, so we have to include it
|
||||
#include <utils/LightRefBase.h>
|
||||
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define COMPARE_WEAK(_op_) \
|
||||
template<typename U> \
|
||||
inline bool operator _op_ (const U* o) const { \
|
||||
return m_ptr _op_ o; \
|
||||
} \
|
||||
/* Needed to handle type inference for nullptr: */ \
|
||||
inline bool operator _op_ (const T* o) const { \
|
||||
return m_ptr _op_ o; \
|
||||
}
|
||||
|
||||
template<template<typename C> class comparator, typename T, typename U>
|
||||
static inline bool _wp_compare_(T* a, U* b) {
|
||||
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
|
||||
}
|
||||
|
||||
// Use std::less and friends to avoid undefined behavior when ordering pointers
|
||||
// to different objects.
|
||||
#define COMPARE_WEAK_FUNCTIONAL(_op_, _compare_) \
|
||||
template<typename U> \
|
||||
inline bool operator _op_ (const U* o) const { \
|
||||
return _wp_compare_<_compare_>(m_ptr, o); \
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// RefererenceRenamer is pure abstract, there is no virtual method
|
||||
// implementation to put in a translation unit in order to silence the
|
||||
// weak vtables warning.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
class ReferenceRenamer {
|
||||
protected:
|
||||
// destructor is purposely not virtual so we avoid code overhead from
|
||||
// subclasses; we have to make it protected to guarantee that it
|
||||
// cannot be called from this base class (and to make strict compilers
|
||||
// happy).
|
||||
~ReferenceRenamer() { }
|
||||
public:
|
||||
virtual void operator()(size_t i) const = 0;
|
||||
};
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class RefBase
|
||||
{
|
||||
public:
|
||||
void incStrong(const void* id) const;
|
||||
void incStrongRequireStrong(const void* id) const;
|
||||
void decStrong(const void* id) const;
|
||||
|
||||
void forceIncStrong(const void* id) const;
|
||||
|
||||
//! DEBUGGING ONLY: Get current strong ref count.
|
||||
int32_t getStrongCount() const;
|
||||
|
||||
class weakref_type
|
||||
{
|
||||
public:
|
||||
RefBase* refBase() const;
|
||||
|
||||
void incWeak(const void* id);
|
||||
void incWeakRequireWeak(const void* id);
|
||||
void decWeak(const void* id);
|
||||
|
||||
// acquires a strong reference if there is already one.
|
||||
bool attemptIncStrong(const void* id);
|
||||
|
||||
// acquires a weak reference if there is already one.
|
||||
// This is not always safe. see ProcessState.cpp and BpBinder.cpp
|
||||
// for proper use.
|
||||
bool attemptIncWeak(const void* id);
|
||||
|
||||
//! DEBUGGING ONLY: Get current weak ref count.
|
||||
int32_t getWeakCount() const;
|
||||
|
||||
//! DEBUGGING ONLY: Print references held on object.
|
||||
void printRefs() const;
|
||||
|
||||
//! DEBUGGING ONLY: Enable tracking for this object.
|
||||
// enable -- enable/disable tracking
|
||||
// retain -- when tracking is enable, if true, then we save a stack trace
|
||||
// for each reference and dereference; when retain == false, we
|
||||
// match up references and dereferences and keep only the
|
||||
// outstanding ones.
|
||||
|
||||
void trackMe(bool enable, bool retain);
|
||||
};
|
||||
|
||||
weakref_type* createWeak(const void* id) const;
|
||||
|
||||
weakref_type* getWeakRefs() const;
|
||||
|
||||
//! DEBUGGING ONLY: Print references held on object.
|
||||
inline void printRefs() const { getWeakRefs()->printRefs(); }
|
||||
|
||||
//! DEBUGGING ONLY: Enable tracking of object.
|
||||
inline void trackMe(bool enable, bool retain)
|
||||
{
|
||||
getWeakRefs()->trackMe(enable, retain);
|
||||
}
|
||||
|
||||
protected:
|
||||
// When constructing these objects, prefer using sp::make<>. Using a RefBase
|
||||
// object on the stack or with other refcount mechanisms (e.g.
|
||||
// std::shared_ptr) is inherently wrong. RefBase types have an implicit
|
||||
// ownership model and cannot be safely used with other ownership models.
|
||||
|
||||
RefBase();
|
||||
virtual ~RefBase();
|
||||
|
||||
//! Flags for extendObjectLifetime()
|
||||
enum {
|
||||
OBJECT_LIFETIME_STRONG = 0x0000,
|
||||
OBJECT_LIFETIME_WEAK = 0x0001,
|
||||
OBJECT_LIFETIME_MASK = 0x0001
|
||||
};
|
||||
|
||||
void extendObjectLifetime(int32_t mode);
|
||||
|
||||
//! Flags for onIncStrongAttempted()
|
||||
enum {
|
||||
FIRST_INC_STRONG = 0x0001
|
||||
};
|
||||
|
||||
// Invoked after creation of initial strong pointer/reference.
|
||||
virtual void onFirstRef();
|
||||
// Invoked when either the last strong reference goes away, or we need to undo
|
||||
// the effect of an unnecessary onIncStrongAttempted.
|
||||
virtual void onLastStrongRef(const void* id);
|
||||
// Only called in OBJECT_LIFETIME_WEAK case. Returns true if OK to promote to
|
||||
// strong reference. May have side effects if it returns true.
|
||||
// The first flags argument is always FIRST_INC_STRONG.
|
||||
// TODO: Remove initial flag argument.
|
||||
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
|
||||
// Invoked in the OBJECT_LIFETIME_WEAK case when the last reference of either
|
||||
// kind goes away. Unused.
|
||||
// TODO: Remove.
|
||||
virtual void onLastWeakRef(const void* id);
|
||||
|
||||
private:
|
||||
friend class weakref_type;
|
||||
class weakref_impl;
|
||||
|
||||
RefBase(const RefBase& o);
|
||||
RefBase& operator=(const RefBase& o);
|
||||
|
||||
private:
|
||||
friend class ReferenceMover;
|
||||
|
||||
static void renameRefs(size_t n, const ReferenceRenamer& renamer);
|
||||
|
||||
static void renameRefId(weakref_type* ref,
|
||||
const void* old_id, const void* new_id);
|
||||
|
||||
static void renameRefId(RefBase* ref,
|
||||
const void* old_id, const void* new_id);
|
||||
|
||||
weakref_impl* const mRefs;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template <typename T>
|
||||
class wp
|
||||
{
|
||||
public:
|
||||
typedef typename RefBase::weakref_type weakref_type;
|
||||
|
||||
inline constexpr wp() : m_ptr(nullptr), m_refs(nullptr) { }
|
||||
|
||||
// if nullptr, returns nullptr
|
||||
//
|
||||
// if a weak pointer is already available, this will retrieve it,
|
||||
// otherwise, this will abort
|
||||
static inline wp<T> fromExisting(T* other);
|
||||
|
||||
// for more information about this flag, see above
|
||||
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
wp(std::nullptr_t) : wp() {}
|
||||
#else
|
||||
wp(T* other); // NOLINT(implicit)
|
||||
template <typename U>
|
||||
wp(U* other); // NOLINT(implicit)
|
||||
wp& operator=(T* other);
|
||||
template <typename U>
|
||||
wp& operator=(U* other);
|
||||
#endif
|
||||
|
||||
wp(const wp<T>& other);
|
||||
explicit wp(const sp<T>& other);
|
||||
|
||||
template<typename U> wp(const sp<U>& other); // NOLINT(implicit)
|
||||
template<typename U> wp(const wp<U>& other); // NOLINT(implicit)
|
||||
|
||||
~wp();
|
||||
|
||||
// Assignment
|
||||
|
||||
wp& operator = (const wp<T>& other);
|
||||
wp& operator = (const sp<T>& other);
|
||||
|
||||
template<typename U> wp& operator = (const wp<U>& other);
|
||||
template<typename U> wp& operator = (const sp<U>& other);
|
||||
|
||||
void set_object_and_refs(T* other, weakref_type* refs);
|
||||
|
||||
// promotion to sp
|
||||
|
||||
sp<T> promote() const;
|
||||
|
||||
// Reset
|
||||
|
||||
void clear();
|
||||
|
||||
// Accessors
|
||||
|
||||
inline weakref_type* get_refs() const { return m_refs; }
|
||||
|
||||
inline T* unsafe_get() const { return m_ptr; }
|
||||
|
||||
// Operators
|
||||
|
||||
COMPARE_WEAK(==)
|
||||
COMPARE_WEAK(!=)
|
||||
COMPARE_WEAK_FUNCTIONAL(>, std::greater)
|
||||
COMPARE_WEAK_FUNCTIONAL(<, std::less)
|
||||
COMPARE_WEAK_FUNCTIONAL(<=, std::less_equal)
|
||||
COMPARE_WEAK_FUNCTIONAL(>=, std::greater_equal)
|
||||
|
||||
template<typename U>
|
||||
inline bool operator == (const wp<U>& o) const {
|
||||
return m_refs == o.m_refs; // Implies m_ptr == o.mptr; see invariants below.
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator == (const sp<U>& o) const {
|
||||
// Just comparing m_ptr fields is often dangerous, since wp<> may refer to an older
|
||||
// object at the same address.
|
||||
if (o == nullptr) {
|
||||
return m_ptr == nullptr;
|
||||
} else {
|
||||
return m_refs == o->getWeakRefs(); // Implies m_ptr == o.mptr.
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator != (const sp<U>& o) const {
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator > (const wp<U>& o) const {
|
||||
if (m_ptr == o.m_ptr) {
|
||||
return _wp_compare_<std::greater>(m_refs, o.m_refs);
|
||||
} else {
|
||||
return _wp_compare_<std::greater>(m_ptr, o.m_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator < (const wp<U>& o) const {
|
||||
if (m_ptr == o.m_ptr) {
|
||||
return _wp_compare_<std::less>(m_refs, o.m_refs);
|
||||
} else {
|
||||
return _wp_compare_<std::less>(m_ptr, o.m_ptr);
|
||||
}
|
||||
}
|
||||
template<typename U> inline bool operator != (const wp<U>& o) const { return !operator == (o); }
|
||||
template<typename U> inline bool operator <= (const wp<U>& o) const { return !operator > (o); }
|
||||
template<typename U> inline bool operator >= (const wp<U>& o) const { return !operator < (o); }
|
||||
|
||||
private:
|
||||
template<typename Y> friend class sp;
|
||||
template<typename Y> friend class wp;
|
||||
|
||||
T* m_ptr;
|
||||
weakref_type* m_refs;
|
||||
};
|
||||
|
||||
#undef COMPARE_WEAK
|
||||
#undef COMPARE_WEAK_FUNCTIONAL
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts below here.
|
||||
|
||||
// Implementation invariants:
|
||||
// Either
|
||||
// 1) m_ptr and m_refs are both null, or
|
||||
// 2) m_refs == m_ptr->mRefs, or
|
||||
// 3) *m_ptr is no longer live, and m_refs points to the weakref_type object that corresponded
|
||||
// to m_ptr while it was live. *m_refs remains live while a wp<> refers to it.
|
||||
//
|
||||
// The m_refs field in a RefBase object is allocated on construction, unique to that RefBase
|
||||
// object, and never changes. Thus if two wp's have identical m_refs fields, they are either both
|
||||
// null or point to the same object. If two wp's have identical m_ptr fields, they either both
|
||||
// point to the same live object and thus have the same m_ref fields, or at least one of the
|
||||
// objects is no longer live.
|
||||
//
|
||||
// Note that the above comparison operations go out of their way to provide an ordering consistent
|
||||
// with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs.
|
||||
|
||||
template <typename T>
|
||||
wp<T> wp<T>::fromExisting(T* other) {
|
||||
if (!other) return nullptr;
|
||||
|
||||
auto refs = other->getWeakRefs();
|
||||
refs->incWeakRequireWeak(other);
|
||||
|
||||
wp<T> ret;
|
||||
ret.m_ptr = other;
|
||||
ret.m_refs = refs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T>
|
||||
wp<T>::wp(T* other)
|
||||
: m_ptr(other)
|
||||
{
|
||||
m_refs = other ? other->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
wp<T>::wp(U* other) : m_ptr(other) {
|
||||
m_refs = other ? other->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
wp<T>& wp<T>::operator=(T* other) {
|
||||
weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
wp<T>& wp<T>::operator=(U* other) {
|
||||
weakref_type* newRefs = other ? other->createWeak(this) : 0;
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
wp<T>::wp(const wp<T>& other)
|
||||
: m_ptr(other.m_ptr), m_refs(other.m_refs)
|
||||
{
|
||||
if (m_ptr) m_refs->incWeak(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>::wp(const sp<T>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>::wp(const wp<U>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
if (m_ptr) {
|
||||
m_refs = other.m_refs;
|
||||
m_refs->incWeak(this);
|
||||
} else {
|
||||
m_refs = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>::wp(const sp<U>& other)
|
||||
: m_ptr(other.m_ptr)
|
||||
{
|
||||
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>::~wp()
|
||||
{
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>& wp<T>::operator = (const wp<T>& other)
|
||||
{
|
||||
weakref_type* otherRefs(other.m_refs);
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherRefs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = otherRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
wp<T>& wp<T>::operator = (const sp<T>& other)
|
||||
{
|
||||
weakref_type* newRefs =
|
||||
other != nullptr ? other->createWeak(this) : nullptr;
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>& wp<T>::operator = (const wp<U>& other)
|
||||
{
|
||||
weakref_type* otherRefs(other.m_refs);
|
||||
U* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherRefs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = otherRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
wp<T>& wp<T>::operator = (const sp<U>& other)
|
||||
{
|
||||
weakref_type* newRefs = other != nullptr ? other->createWeak(this) : nullptr;
|
||||
U* otherPtr(other.m_ptr);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = otherPtr;
|
||||
m_refs = newRefs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void wp<T>::set_object_and_refs(T* other, weakref_type* refs)
|
||||
{
|
||||
if (other) refs->incWeak(this);
|
||||
if (m_ptr) m_refs->decWeak(this);
|
||||
m_ptr = other;
|
||||
m_refs = refs;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T> wp<T>::promote() const
|
||||
{
|
||||
sp<T> result;
|
||||
if (m_ptr && m_refs->attemptIncStrong(&result)) {
|
||||
result.set_pointer(m_ptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void wp<T>::clear()
|
||||
{
|
||||
if (m_ptr) {
|
||||
m_refs->decWeak(this);
|
||||
m_refs = nullptr;
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// this class just serves as a namespace so TYPE::moveReferences can stay
|
||||
// private.
|
||||
class ReferenceMover {
|
||||
public:
|
||||
// it would be nice if we could make sure no extra code is generated
|
||||
// for sp<TYPE> or wp<TYPE> when TYPE is a descendant of RefBase:
|
||||
// Using a sp<RefBase> override doesn't work; it's a bit like we wanted
|
||||
// a template<typename TYPE inherits RefBase> template...
|
||||
|
||||
template<typename TYPE> static inline
|
||||
void move_references(sp<TYPE>* dest, sp<TYPE> const* src, size_t n) {
|
||||
|
||||
class Renamer : public ReferenceRenamer {
|
||||
sp<TYPE>* d_;
|
||||
sp<TYPE> const* s_;
|
||||
virtual void operator()(size_t i) const {
|
||||
// The id are known to be the sp<>'s this pointer
|
||||
TYPE::renameRefId(d_[i].get(), &s_[i], &d_[i]);
|
||||
}
|
||||
public:
|
||||
Renamer(sp<TYPE>* d, sp<TYPE> const* s) : d_(d), s_(s) { }
|
||||
virtual ~Renamer() { }
|
||||
};
|
||||
|
||||
memmove(dest, src, n*sizeof(sp<TYPE>));
|
||||
TYPE::renameRefs(n, Renamer(dest, src));
|
||||
}
|
||||
|
||||
|
||||
template<typename TYPE> static inline
|
||||
void move_references(wp<TYPE>* dest, wp<TYPE> const* src, size_t n) {
|
||||
|
||||
class Renamer : public ReferenceRenamer {
|
||||
wp<TYPE>* d_;
|
||||
wp<TYPE> const* s_;
|
||||
virtual void operator()(size_t i) const {
|
||||
// The id are known to be the wp<>'s this pointer
|
||||
TYPE::renameRefId(d_[i].get_refs(), &s_[i], &d_[i]);
|
||||
}
|
||||
public:
|
||||
Renamer(wp<TYPE>* rd, wp<TYPE> const* rs) : d_(rd), s_(rs) { }
|
||||
virtual ~Renamer() { }
|
||||
};
|
||||
|
||||
memmove(dest, src, n*sizeof(wp<TYPE>));
|
||||
TYPE::renameRefs(n, Renamer(dest, src));
|
||||
}
|
||||
};
|
||||
|
||||
// specialization for moving sp<> and wp<> types.
|
||||
// these are used by the [Sorted|Keyed]Vector<> implementations
|
||||
// sp<> and wp<> need to be handled specially, because they do not
|
||||
// have trivial copy operation in the general case (see RefBase.cpp
|
||||
// when DEBUG ops are enabled), but can be implemented very
|
||||
// efficiently in most cases.
|
||||
|
||||
template<typename TYPE> inline
|
||||
void move_forward_type(sp<TYPE>* d, sp<TYPE> const* s, size_t n) {
|
||||
ReferenceMover::move_references(d, s, n);
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
void move_backward_type(sp<TYPE>* d, sp<TYPE> const* s, size_t n) {
|
||||
ReferenceMover::move_references(d, s, n);
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
void move_forward_type(wp<TYPE>* d, wp<TYPE> const* s, size_t n) {
|
||||
ReferenceMover::move_references(d, s, n);
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
void move_backward_type(wp<TYPE>* d, wp<TYPE> const* s, size_t n) {
|
||||
ReferenceMover::move_references(d, s, n);
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
namespace libutilsinternal {
|
||||
template <typename T, typename = void>
|
||||
struct is_complete_type : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
|
||||
} // namespace libutilsinternal
|
||||
|
||||
namespace std {
|
||||
|
||||
// Define `RefBase` specific versions of `std::make_shared` and
|
||||
// `std::make_unique` to block people from using them. Using them to allocate
|
||||
// `RefBase` objects results in double ownership. Use
|
||||
// `sp<T>::make(...)` instead.
|
||||
//
|
||||
// Note: We exclude incomplete types because `std::is_base_of` is undefined in
|
||||
// that case.
|
||||
|
||||
template <typename T, typename... Args,
|
||||
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
|
||||
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
|
||||
shared_ptr<T> make_shared(Args...) { // SEE COMMENT ABOVE.
|
||||
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
|
||||
}
|
||||
|
||||
template <typename T, typename... Args,
|
||||
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
|
||||
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
|
||||
unique_ptr<T> make_unique(Args...) { // SEE COMMENT ABOVE.
|
||||
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_REF_BASE_H
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_STRING16_H
|
||||
#define ANDROID_STRING16_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
#include <compare>
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace android {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template <size_t N>
|
||||
class StaticString16;
|
||||
|
||||
// DO NOT USE: please use std::u16string
|
||||
|
||||
//! This is a string holding UTF-16 characters.
|
||||
class String16
|
||||
{
|
||||
public:
|
||||
String16();
|
||||
String16(const String16& o);
|
||||
String16(String16&& o) noexcept;
|
||||
String16(const String16& o,
|
||||
size_t len,
|
||||
size_t begin=0);
|
||||
explicit String16(const char16_t* o);
|
||||
explicit String16(const char16_t* o, size_t len);
|
||||
explicit String16(const String8& o);
|
||||
explicit String16(const char* o);
|
||||
explicit String16(const char* o, size_t len);
|
||||
|
||||
~String16();
|
||||
|
||||
inline const char16_t* c_str() const;
|
||||
|
||||
size_t size() const;
|
||||
inline bool empty() const;
|
||||
|
||||
inline size_t length() const;
|
||||
|
||||
void setTo(const String16& other);
|
||||
status_t setTo(const char16_t* other);
|
||||
status_t setTo(const char16_t* other, size_t len);
|
||||
status_t setTo(const String16& other,
|
||||
size_t len,
|
||||
size_t begin=0);
|
||||
|
||||
status_t append(const String16& other);
|
||||
status_t append(const char16_t* other, size_t len);
|
||||
|
||||
inline String16& operator=(const String16& other);
|
||||
String16& operator=(String16&& other) noexcept;
|
||||
|
||||
inline String16& operator+=(const String16& other);
|
||||
inline String16 operator+(const String16& other) const;
|
||||
|
||||
status_t insert(size_t pos, const char16_t* chrs);
|
||||
status_t insert(size_t pos,
|
||||
const char16_t* chrs, size_t len);
|
||||
|
||||
ssize_t findFirst(char16_t c) const;
|
||||
ssize_t findLast(char16_t c) const;
|
||||
|
||||
bool startsWith(const String16& prefix) const;
|
||||
bool startsWith(const char16_t* prefix) const;
|
||||
|
||||
bool contains(const char16_t* chrs) const;
|
||||
inline bool contains(const String16& other) const;
|
||||
|
||||
status_t replaceAll(char16_t replaceThis,
|
||||
char16_t withThis);
|
||||
|
||||
inline int compare(const String16& other) const;
|
||||
|
||||
inline bool operator<(const String16& other) const;
|
||||
inline bool operator<=(const String16& other) const;
|
||||
inline bool operator==(const String16& other) const;
|
||||
inline bool operator!=(const String16& other) const;
|
||||
inline bool operator>=(const String16& other) const;
|
||||
inline bool operator>(const String16& other) const;
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering operator<=>(const String16& other) const;
|
||||
#endif
|
||||
|
||||
inline bool operator<(const char16_t* other) const;
|
||||
inline bool operator<=(const char16_t* other) const;
|
||||
inline bool operator==(const char16_t* other) const;
|
||||
inline bool operator!=(const char16_t* other) const;
|
||||
inline bool operator>=(const char16_t* other) const;
|
||||
inline bool operator>(const char16_t* other) const;
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering operator<=>(const char16_t* other) const;
|
||||
#endif
|
||||
|
||||
inline operator const char16_t*() const;
|
||||
|
||||
// Implicit cast to std::u16string is not implemented on purpose - u16string_view is much
|
||||
// lighter and if one needs, they can still create u16string from u16string_view.
|
||||
inline operator std::u16string_view() const;
|
||||
|
||||
// Static and non-static String16 behave the same for the users, so
|
||||
// this method isn't of much use for the users. It is public for testing.
|
||||
bool isStaticString() const;
|
||||
|
||||
private:
|
||||
/*
|
||||
* A flag indicating the type of underlying buffer.
|
||||
*/
|
||||
static constexpr uint32_t kIsSharedBufferAllocated = 0x80000000;
|
||||
|
||||
/*
|
||||
* alloc() returns void* so that SharedBuffer class is not exposed.
|
||||
*/
|
||||
static void* alloc(size_t size);
|
||||
static char16_t* allocFromUTF8(const char* u8str, size_t u8len);
|
||||
static char16_t* allocFromUTF16(const char16_t* u16str, size_t u16len);
|
||||
|
||||
/*
|
||||
* edit() and editResize() return void* so that SharedBuffer class
|
||||
* is not exposed.
|
||||
*/
|
||||
void* edit();
|
||||
void* editResize(size_t new_size);
|
||||
|
||||
void acquire();
|
||||
void release();
|
||||
|
||||
size_t staticStringSize() const;
|
||||
|
||||
const char16_t* mString;
|
||||
|
||||
protected:
|
||||
/*
|
||||
* Data structure used to allocate static storage for static String16.
|
||||
*
|
||||
* Note that this data structure and SharedBuffer are used interchangably
|
||||
* as the underlying data structure for a String16. Therefore, the layout
|
||||
* of this data structure must match the part in SharedBuffer that is
|
||||
* visible to String16.
|
||||
*/
|
||||
template <size_t N>
|
||||
struct StaticData {
|
||||
// The high bit of 'size' is used as a flag.
|
||||
static_assert(N - 1 < kIsSharedBufferAllocated, "StaticString16 too long!");
|
||||
constexpr StaticData() : size(N - 1), data{0} {}
|
||||
const uint32_t size;
|
||||
char16_t data[N];
|
||||
|
||||
constexpr StaticData(const StaticData<N>&) = default;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function for constructing a StaticData object.
|
||||
*/
|
||||
template <size_t N>
|
||||
static constexpr const StaticData<N> makeStaticData(const char16_t (&s)[N]) {
|
||||
StaticData<N> r;
|
||||
// The 'size' field is at the same location where mClientMetadata would
|
||||
// be for a SharedBuffer. We do NOT set kIsSharedBufferAllocated flag
|
||||
// here.
|
||||
for (size_t i = 0; i < N - 1; ++i) r.data[i] = s[i];
|
||||
return r;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit constexpr String16(const StaticData<N>& s) : mString(s.data) {}
|
||||
|
||||
// These symbols are for potential backward compatibility with prebuilts. To be removed.
|
||||
#ifdef ENABLE_STRING16_OBSOLETE_METHODS
|
||||
public:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
inline const char16_t* string() const;
|
||||
};
|
||||
|
||||
// String16 can be trivially moved using memcpy() because moving does not
|
||||
// require any change to the underlying SharedBuffer contents or reference count.
|
||||
ANDROID_TRIVIAL_MOVE_TRAIT(String16)
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, const String16& str) {
|
||||
os << String8(str);
|
||||
return os;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* A StaticString16 object is a specialized String16 object. Instead of holding
|
||||
* the string data in a ref counted SharedBuffer object, it holds data in a
|
||||
* buffer within StaticString16 itself. Note that this buffer is NOT ref
|
||||
* counted and is assumed to be available for as long as there is at least a
|
||||
* String16 object using it. Therefore, one must be extra careful to NEVER
|
||||
* assign a StaticString16 to a String16 that outlives the StaticString16
|
||||
* object.
|
||||
*
|
||||
* THE SAFEST APPROACH IS TO USE StaticString16 ONLY AS GLOBAL VARIABLES.
|
||||
*
|
||||
* A StaticString16 SHOULD NEVER APPEAR IN APIs. USE String16 INSTEAD.
|
||||
*/
|
||||
template <size_t N>
|
||||
class StaticString16 : public String16 {
|
||||
public:
|
||||
constexpr StaticString16(const char16_t (&s)[N]) : String16(mData), mData(makeStaticData(s)) {}
|
||||
|
||||
constexpr StaticString16(const StaticString16<N>& other)
|
||||
: String16(mData), mData(other.mData) {}
|
||||
|
||||
constexpr StaticString16(const StaticString16<N>&&) = delete;
|
||||
|
||||
// There is no reason why one would want to 'new' a StaticString16. Delete
|
||||
// it to discourage misuse.
|
||||
static void* operator new(std::size_t) = delete;
|
||||
|
||||
private:
|
||||
const StaticData<N> mData;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
StaticString16(const F&)->StaticString16<sizeof(F) / sizeof(char16_t)>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user servicable parts below.
|
||||
|
||||
inline int compare_type(const String16& lhs, const String16& rhs)
|
||||
{
|
||||
return lhs.compare(rhs);
|
||||
}
|
||||
|
||||
inline int strictly_order_type(const String16& lhs, const String16& rhs)
|
||||
{
|
||||
return compare_type(lhs, rhs) < 0;
|
||||
}
|
||||
|
||||
inline const char16_t* String16::c_str() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline const char16_t* String16::string() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline bool String16::empty() const
|
||||
{
|
||||
return length() == 0;
|
||||
}
|
||||
|
||||
inline size_t String16::length() const
|
||||
{
|
||||
return size();
|
||||
}
|
||||
|
||||
inline bool String16::contains(const String16& other) const
|
||||
{
|
||||
return contains(other.c_str());
|
||||
}
|
||||
|
||||
inline String16& String16::operator=(const String16& other)
|
||||
{
|
||||
setTo(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String16& String16::operator+=(const String16& other)
|
||||
{
|
||||
append(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String16 String16::operator+(const String16& other) const
|
||||
{
|
||||
String16 tmp(*this);
|
||||
tmp += other;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline int String16::compare(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size());
|
||||
}
|
||||
|
||||
inline bool String16::operator<(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) < 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator<=(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) <= 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator==(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator!=(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) != 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator>=(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) >= 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator>(const String16& other) const
|
||||
{
|
||||
return strzcmp16(mString, size(), other.mString, other.size()) > 0;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering String16::operator<=>(const String16& other) const {
|
||||
int result = strzcmp16(mString, size(), other.mString, other.size());
|
||||
if (result == 0) {
|
||||
return std::strong_ordering::equal;
|
||||
} else if (result < 0) {
|
||||
return std::strong_ordering::less;
|
||||
} else {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool String16::operator<(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) < 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator<=(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) <= 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator==(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) == 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator!=(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) != 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator>=(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) >= 0;
|
||||
}
|
||||
|
||||
inline bool String16::operator>(const char16_t* other) const
|
||||
{
|
||||
return strcmp16(mString, other) > 0;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering String16::operator<=>(const char16_t* other) const {
|
||||
int result = strcmp16(mString, other);
|
||||
if (result == 0) {
|
||||
return std::strong_ordering::equal;
|
||||
} else if (result < 0) {
|
||||
return std::strong_ordering::less;
|
||||
} else {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline String16::operator const char16_t*() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline String16::operator std::u16string_view() const
|
||||
{
|
||||
return {mString, length()};
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_STRING16_H
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_STRING8_H
|
||||
#define ANDROID_STRING8_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Unicode.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
|
||||
#include <string.h> // for strcmp
|
||||
#include <stdarg.h>
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
#include <compare>
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace android {
|
||||
|
||||
class String16;
|
||||
|
||||
// DO NOT USE: please use std::string
|
||||
|
||||
//! This is a string holding UTF-8 characters. Does not allow the value more
|
||||
// than 0x10FFFF, which is not valid unicode codepoint.
|
||||
class String8
|
||||
{
|
||||
public:
|
||||
String8();
|
||||
String8(const String8& o);
|
||||
explicit String8(const char* o);
|
||||
explicit String8(const char* o, size_t numChars);
|
||||
explicit String8(std::string_view o);
|
||||
|
||||
explicit String8(const String16& o);
|
||||
explicit String8(const char16_t* o);
|
||||
explicit String8(const char16_t* o, size_t numChars);
|
||||
explicit String8(const char32_t* o);
|
||||
explicit String8(const char32_t* o, size_t numChars);
|
||||
~String8();
|
||||
|
||||
static String8 format(const char* fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
static String8 formatV(const char* fmt, va_list args);
|
||||
|
||||
inline const char* c_str() const;
|
||||
|
||||
inline size_t size() const;
|
||||
inline size_t bytes() const;
|
||||
inline bool empty() const;
|
||||
|
||||
size_t length() const;
|
||||
|
||||
void clear();
|
||||
|
||||
void setTo(const String8& other);
|
||||
status_t setTo(const char* other);
|
||||
status_t setTo(const char* other, size_t numChars);
|
||||
status_t setTo(const char16_t* other, size_t numChars);
|
||||
status_t setTo(const char32_t* other,
|
||||
size_t length);
|
||||
|
||||
status_t append(const String8& other);
|
||||
status_t append(const char* other);
|
||||
status_t append(const char* other, size_t numChars);
|
||||
|
||||
status_t appendFormat(const char* fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
status_t appendFormatV(const char* fmt, va_list args);
|
||||
|
||||
inline String8& operator=(const String8& other);
|
||||
inline String8& operator=(const char* other);
|
||||
|
||||
inline String8& operator+=(const String8& other);
|
||||
inline String8 operator+(const String8& other) const;
|
||||
|
||||
inline String8& operator+=(const char* other);
|
||||
inline String8 operator+(const char* other) const;
|
||||
|
||||
inline int compare(const String8& other) const;
|
||||
|
||||
inline bool operator<(const String8& other) const;
|
||||
inline bool operator<=(const String8& other) const;
|
||||
inline bool operator==(const String8& other) const;
|
||||
inline bool operator!=(const String8& other) const;
|
||||
inline bool operator>=(const String8& other) const;
|
||||
inline bool operator>(const String8& other) const;
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering operator<=>(const String8& other) const;
|
||||
#endif
|
||||
|
||||
inline bool operator<(const char* other) const;
|
||||
inline bool operator<=(const char* other) const;
|
||||
inline bool operator==(const char* other) const;
|
||||
inline bool operator!=(const char* other) const;
|
||||
inline bool operator>=(const char* other) const;
|
||||
inline bool operator>(const char* other) const;
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering operator<=>(const char* other) const;
|
||||
#endif
|
||||
|
||||
inline operator const char*() const;
|
||||
|
||||
inline explicit operator std::string_view() const;
|
||||
|
||||
char* lockBuffer(size_t size);
|
||||
void unlockBuffer();
|
||||
status_t unlockBuffer(size_t size);
|
||||
|
||||
// return the index of the first byte of other in this at or after
|
||||
// start, or -1 if not found
|
||||
ssize_t find(const char* other, size_t start = 0) const;
|
||||
inline ssize_t find(const String8& other, size_t start = 0) const;
|
||||
|
||||
// return true if this string contains the specified substring
|
||||
inline bool contains(const char* other) const;
|
||||
inline bool contains(const String8& other) const;
|
||||
|
||||
// removes all occurrence of the specified substring
|
||||
// returns true if any were found and removed
|
||||
bool removeAll(const char* other);
|
||||
inline bool removeAll(const String8& other);
|
||||
|
||||
void toLower();
|
||||
|
||||
private:
|
||||
String8 getPathDir(void) const;
|
||||
String8 getPathExtension(void) const;
|
||||
|
||||
status_t real_append(const char* other, size_t numChars);
|
||||
|
||||
const char* mString;
|
||||
|
||||
// These symbols are for potential backward compatibility with prebuilts. To be removed.
|
||||
#ifdef ENABLE_STRING8_OBSOLETE_METHODS
|
||||
public:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
inline const char* string() const;
|
||||
inline bool isEmpty() const;
|
||||
};
|
||||
|
||||
// String8 can be trivially moved using memcpy() because moving does not
|
||||
// require any change to the underlying SharedBuffer contents or reference count.
|
||||
ANDROID_TRIVIAL_MOVE_TRAIT(String8)
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, const String8& str) {
|
||||
os << str.c_str();
|
||||
return os;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user servicable parts below.
|
||||
|
||||
inline int compare_type(const String8& lhs, const String8& rhs)
|
||||
{
|
||||
return lhs.compare(rhs);
|
||||
}
|
||||
|
||||
inline int strictly_order_type(const String8& lhs, const String8& rhs)
|
||||
{
|
||||
return compare_type(lhs, rhs) < 0;
|
||||
}
|
||||
|
||||
inline const char* String8::c_str() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
inline const char* String8::string() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline size_t String8::size() const
|
||||
{
|
||||
return length();
|
||||
}
|
||||
|
||||
inline bool String8::empty() const
|
||||
{
|
||||
return length() == 0;
|
||||
}
|
||||
|
||||
inline bool String8::isEmpty() const
|
||||
{
|
||||
return length() == 0;
|
||||
}
|
||||
|
||||
inline size_t String8::bytes() const
|
||||
{
|
||||
return length();
|
||||
}
|
||||
|
||||
inline ssize_t String8::find(const String8& other, size_t start) const
|
||||
{
|
||||
return find(other.c_str(), start);
|
||||
}
|
||||
|
||||
inline bool String8::contains(const char* other) const
|
||||
{
|
||||
return find(other) >= 0;
|
||||
}
|
||||
|
||||
inline bool String8::contains(const String8& other) const
|
||||
{
|
||||
return contains(other.c_str());
|
||||
}
|
||||
|
||||
inline bool String8::removeAll(const String8& other)
|
||||
{
|
||||
return removeAll(other.c_str());
|
||||
}
|
||||
|
||||
inline String8& String8::operator=(const String8& other)
|
||||
{
|
||||
setTo(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String8& String8::operator=(const char* other)
|
||||
{
|
||||
setTo(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String8& String8::operator+=(const String8& other)
|
||||
{
|
||||
append(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String8 String8::operator+(const String8& other) const
|
||||
{
|
||||
String8 tmp(*this);
|
||||
tmp += other;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline String8& String8::operator+=(const char* other)
|
||||
{
|
||||
append(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline String8 String8::operator+(const char* other) const
|
||||
{
|
||||
String8 tmp(*this);
|
||||
tmp += other;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline int String8::compare(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString);
|
||||
}
|
||||
|
||||
inline bool String8::operator<(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) < 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator<=(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) <= 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator==(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) == 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator!=(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) != 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator>=(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) >= 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator>(const String8& other) const
|
||||
{
|
||||
return strcmp(mString, other.mString) > 0;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering String8::operator<=>(const String8& other) const {
|
||||
int result = strcmp(mString, other.mString);
|
||||
if (result == 0) {
|
||||
return std::strong_ordering::equal;
|
||||
} else if (result < 0) {
|
||||
return std::strong_ordering::less;
|
||||
} else {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool String8::operator<(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) < 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator<=(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) <= 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator==(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) == 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator!=(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) != 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator>=(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) >= 0;
|
||||
}
|
||||
|
||||
inline bool String8::operator>(const char* other) const
|
||||
{
|
||||
return strcmp(mString, other) > 0;
|
||||
}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
inline std::strong_ordering String8::operator<=>(const char* other) const {
|
||||
int result = strcmp(mString, other);
|
||||
if (result == 0) {
|
||||
return std::strong_ordering::equal;
|
||||
} else if (result < 0) {
|
||||
return std::strong_ordering::less;
|
||||
} else {
|
||||
return std::strong_ordering::greater;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline String8::operator const char*() const
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline String8::String8(std::string_view o) : String8(o.data(), o.length()) { }
|
||||
|
||||
inline String8::operator std::string_view() const
|
||||
{
|
||||
return {mString, length()};
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_STRING8_H
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_STRONG_POINTER_H
|
||||
#define ANDROID_STRONG_POINTER_H
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits> // for common_type.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace android {
|
||||
|
||||
template<typename T> class wp;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
class sp {
|
||||
public:
|
||||
inline constexpr sp() : m_ptr(nullptr) { }
|
||||
|
||||
// The old way of using sp<> was like this. This is bad because it relies
|
||||
// on implicit conversion to sp<>, which we would like to remove (if an
|
||||
// object is being managed some other way, this is double-ownership). We
|
||||
// want to move away from this:
|
||||
//
|
||||
// sp<Foo> foo = new Foo(...); // DO NOT DO THIS
|
||||
//
|
||||
// Instead, prefer to do this:
|
||||
//
|
||||
// sp<Foo> foo = sp<Foo>::make(...); // DO THIS
|
||||
//
|
||||
// Sometimes, in order to use this, when a constructor is marked as private,
|
||||
// you may need to add this to your class:
|
||||
//
|
||||
// friend class sp<Foo>;
|
||||
template <typename... Args>
|
||||
static inline sp<T> make(Args&&... args);
|
||||
|
||||
// if nullptr, returns nullptr
|
||||
//
|
||||
// if a strong pointer is already available, this will retrieve it,
|
||||
// otherwise, this will abort
|
||||
static inline sp<T> fromExisting(T* other);
|
||||
|
||||
// for more information about this macro and correct RefBase usage, see
|
||||
// the comment at the top of utils/RefBase.h
|
||||
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
sp(std::nullptr_t) : sp() {}
|
||||
#else
|
||||
sp(T* other); // NOLINT(implicit)
|
||||
template <typename U>
|
||||
sp(U* other); // NOLINT(implicit)
|
||||
sp& operator=(T* other);
|
||||
template <typename U>
|
||||
sp& operator=(U* other);
|
||||
#endif
|
||||
|
||||
sp(const sp<T>& other);
|
||||
sp(sp<T>&& other) noexcept;
|
||||
|
||||
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
|
||||
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
|
||||
|
||||
// Cast a strong pointer directly from one type to another. Constructors
|
||||
// allow changing types, but only if they are pointer-compatible. This does
|
||||
// a static_cast internally.
|
||||
template <typename U>
|
||||
static inline sp<T> cast(const sp<U>& other);
|
||||
|
||||
~sp();
|
||||
|
||||
// Assignment
|
||||
|
||||
sp& operator = (const sp<T>& other);
|
||||
sp& operator=(sp<T>&& other) noexcept;
|
||||
|
||||
template<typename U> sp& operator = (const sp<U>& other);
|
||||
template<typename U> sp& operator = (sp<U>&& other);
|
||||
|
||||
//! Special optimization for use by ProcessState (and nobody else).
|
||||
void force_set(T* other);
|
||||
|
||||
// Reset
|
||||
|
||||
void clear();
|
||||
|
||||
// Releases the ownership of the object managed by this instance of sp, if any.
|
||||
// The caller is now responsible for managing it. That is, the caller must ensure
|
||||
// decStrong() is called when the pointer is no longer used.
|
||||
[[nodiscard]] inline T* release() noexcept {
|
||||
auto ret = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Accessors
|
||||
|
||||
inline T& operator* () const { return *m_ptr; }
|
||||
inline T* operator-> () const { return m_ptr; }
|
||||
inline T* get() const { return m_ptr; }
|
||||
inline explicit operator bool () const { return m_ptr != nullptr; }
|
||||
|
||||
// Punt these to the wp<> implementation.
|
||||
template<typename U>
|
||||
inline bool operator == (const wp<U>& o) const {
|
||||
return o == *this;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline bool operator != (const wp<U>& o) const {
|
||||
return o != *this;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Y> friend class sp;
|
||||
template<typename Y> friend class wp;
|
||||
void set_pointer(T* ptr);
|
||||
T* m_ptr;
|
||||
};
|
||||
|
||||
#define COMPARE_STRONG(_op_) \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
|
||||
return t.get() _op_ u.get(); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const T* t, const sp<U>& u) { \
|
||||
return t _op_ u.get(); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const U* u) { \
|
||||
return t.get() _op_ u; \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
|
||||
return t.get() _op_ nullptr; \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
|
||||
return nullptr _op_ t.get(); \
|
||||
}
|
||||
|
||||
template <template <typename C> class comparator, typename T, typename U>
|
||||
static inline bool _sp_compare_(T* a, U* b) {
|
||||
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
|
||||
}
|
||||
|
||||
#define COMPARE_STRONG_FUNCTIONAL(_op_, _compare_) \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
|
||||
return _sp_compare_<_compare_>(t.get(), u.get()); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const T* t, const sp<U>& u) { \
|
||||
return _sp_compare_<_compare_>(t, u.get()); \
|
||||
} \
|
||||
template <typename T, typename U> \
|
||||
static inline bool operator _op_(const sp<T>& t, const U* u) { \
|
||||
return _sp_compare_<_compare_>(t.get(), u); \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
|
||||
return _sp_compare_<_compare_>(t.get(), nullptr); \
|
||||
} \
|
||||
template <typename T> \
|
||||
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
|
||||
return _sp_compare_<_compare_>(nullptr, t.get()); \
|
||||
}
|
||||
|
||||
COMPARE_STRONG(==)
|
||||
COMPARE_STRONG(!=)
|
||||
COMPARE_STRONG_FUNCTIONAL(>, std::greater)
|
||||
COMPARE_STRONG_FUNCTIONAL(<, std::less)
|
||||
COMPARE_STRONG_FUNCTIONAL(<=, std::less_equal)
|
||||
COMPARE_STRONG_FUNCTIONAL(>=, std::greater_equal)
|
||||
|
||||
#undef COMPARE_STRONG
|
||||
#undef COMPARE_STRONG_FUNCTIONAL
|
||||
|
||||
// For code size reasons, we do not want these inlined or templated.
|
||||
void sp_report_race();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts below here.
|
||||
|
||||
// TODO: Ideally we should find a way to increment the reference count before running the
|
||||
// constructor, so that generating an sp<> to this in the constructor is no longer dangerous.
|
||||
template <typename T>
|
||||
template <typename... Args>
|
||||
sp<T> sp<T>::make(Args&&... args) {
|
||||
T* t = new T(std::forward<Args>(args)...);
|
||||
sp<T> result;
|
||||
result.m_ptr = t;
|
||||
t->incStrong(t);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T> sp<T>::fromExisting(T* other) {
|
||||
if (other) {
|
||||
other->incStrongRequireStrong(other);
|
||||
sp<T> result;
|
||||
result.m_ptr = other;
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T>
|
||||
sp<T>::sp(T* other)
|
||||
: m_ptr(other) {
|
||||
if (other) {
|
||||
other->incStrong(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
sp<T>::sp(U* other) : m_ptr(other) {
|
||||
if (other) {
|
||||
(static_cast<T*>(other))->incStrong(this);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>& sp<T>::operator=(T* other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (other) {
|
||||
other->incStrong(this);
|
||||
}
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
sp<T>::sp(const sp<T>& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
if (m_ptr)
|
||||
m_ptr->incStrong(this);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>::sp(sp<T>&& other) noexcept : m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>::sp(const sp<U>& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
if (m_ptr)
|
||||
m_ptr->incStrong(this);
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>::sp(sp<U>&& other)
|
||||
: m_ptr(other.m_ptr) {
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
sp<T> sp<T>::cast(const sp<U>& other) {
|
||||
return sp<T>::fromExisting(static_cast<T*>(other.get()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T>::~sp() {
|
||||
if (m_ptr)
|
||||
m_ptr->decStrong(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
sp<T>& sp<T>::operator =(const sp<T>& other) {
|
||||
// Force m_ptr to be read twice, to heuristically check for data races.
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherPtr->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = otherPtr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
sp<T>& sp<T>::operator=(sp<T>&& other) noexcept {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(const sp<U>& other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
T* otherPtr(other.m_ptr);
|
||||
if (otherPtr) otherPtr->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = otherPtr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(sp<U>&& other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (m_ptr) m_ptr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
|
||||
template<typename T> template<typename U>
|
||||
sp<T>& sp<T>::operator =(U* other) {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (other) (static_cast<T*>(other))->incStrong(this);
|
||||
if (oldPtr) oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = other;
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::force_set(T* other) {
|
||||
other->forceIncStrong(this);
|
||||
m_ptr = other;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::clear() {
|
||||
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
|
||||
if (oldPtr) {
|
||||
oldPtr->decStrong(this);
|
||||
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void sp<T>::set_pointer(T* ptr) {
|
||||
m_ptr = ptr;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_STRONG_POINTER_H
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_TYPE_HELPERS_H
|
||||
#define ANDROID_TYPE_HELPERS_H
|
||||
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace android {
|
||||
|
||||
/*
|
||||
* Types traits
|
||||
*/
|
||||
|
||||
template <typename T> struct trait_trivial_ctor { enum { value = false }; };
|
||||
template <typename T> struct trait_trivial_dtor { enum { value = false }; };
|
||||
template <typename T> struct trait_trivial_copy { enum { value = false }; };
|
||||
template <typename T> struct trait_trivial_move { enum { value = false }; };
|
||||
template <typename T> struct trait_pointer { enum { value = false }; };
|
||||
template <typename T> struct trait_pointer<T*> { enum { value = true }; };
|
||||
|
||||
template <typename TYPE>
|
||||
struct traits {
|
||||
enum {
|
||||
// whether this type is a pointer
|
||||
is_pointer = trait_pointer<TYPE>::value,
|
||||
// whether this type's constructor is a no-op
|
||||
has_trivial_ctor = is_pointer || trait_trivial_ctor<TYPE>::value,
|
||||
// whether this type's destructor is a no-op
|
||||
has_trivial_dtor = is_pointer || trait_trivial_dtor<TYPE>::value,
|
||||
// whether this type type can be copy-constructed with memcpy
|
||||
has_trivial_copy = is_pointer || trait_trivial_copy<TYPE>::value,
|
||||
// whether this type can be moved with memmove
|
||||
has_trivial_move = is_pointer || trait_trivial_move<TYPE>::value
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct aggregate_traits {
|
||||
enum {
|
||||
is_pointer = false,
|
||||
has_trivial_ctor =
|
||||
traits<T>::has_trivial_ctor && traits<U>::has_trivial_ctor,
|
||||
has_trivial_dtor =
|
||||
traits<T>::has_trivial_dtor && traits<U>::has_trivial_dtor,
|
||||
has_trivial_copy =
|
||||
traits<T>::has_trivial_copy && traits<U>::has_trivial_copy,
|
||||
has_trivial_move =
|
||||
traits<T>::has_trivial_move && traits<U>::has_trivial_move
|
||||
};
|
||||
};
|
||||
|
||||
#define ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
|
||||
template<> struct trait_trivial_ctor< T > { enum { value = true }; };
|
||||
|
||||
#define ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
|
||||
template<> struct trait_trivial_dtor< T > { enum { value = true }; };
|
||||
|
||||
#define ANDROID_TRIVIAL_COPY_TRAIT( T ) \
|
||||
template<> struct trait_trivial_copy< T > { enum { value = true }; };
|
||||
|
||||
#define ANDROID_TRIVIAL_MOVE_TRAIT( T ) \
|
||||
template<> struct trait_trivial_move< T > { enum { value = true }; };
|
||||
|
||||
#define ANDROID_BASIC_TYPES_TRAITS( T ) \
|
||||
ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
|
||||
ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
|
||||
ANDROID_TRIVIAL_COPY_TRAIT( T ) \
|
||||
ANDROID_TRIVIAL_MOVE_TRAIT( T )
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* basic types traits
|
||||
*/
|
||||
|
||||
ANDROID_BASIC_TYPES_TRAITS( void )
|
||||
ANDROID_BASIC_TYPES_TRAITS( bool )
|
||||
ANDROID_BASIC_TYPES_TRAITS( char )
|
||||
ANDROID_BASIC_TYPES_TRAITS( unsigned char )
|
||||
ANDROID_BASIC_TYPES_TRAITS( short )
|
||||
ANDROID_BASIC_TYPES_TRAITS( unsigned short )
|
||||
ANDROID_BASIC_TYPES_TRAITS( int )
|
||||
ANDROID_BASIC_TYPES_TRAITS( unsigned int )
|
||||
ANDROID_BASIC_TYPES_TRAITS( long )
|
||||
ANDROID_BASIC_TYPES_TRAITS( unsigned long )
|
||||
ANDROID_BASIC_TYPES_TRAITS( long long )
|
||||
ANDROID_BASIC_TYPES_TRAITS( unsigned long long )
|
||||
ANDROID_BASIC_TYPES_TRAITS( float )
|
||||
ANDROID_BASIC_TYPES_TRAITS( double )
|
||||
|
||||
template<typename T> struct trait_trivial_ctor<T*> { enum { value = true }; };
|
||||
template<typename T> struct trait_trivial_dtor<T*> { enum { value = true }; };
|
||||
template<typename T> struct trait_trivial_copy<T*> { enum { value = true }; };
|
||||
template<typename T> struct trait_trivial_move<T*> { enum { value = true }; };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
/*
|
||||
* compare and order types
|
||||
*/
|
||||
|
||||
template<typename TYPE> inline
|
||||
int strictly_order_type(const TYPE& lhs, const TYPE& rhs) {
|
||||
return (lhs < rhs) ? 1 : 0;
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
int compare_type(const TYPE& lhs, const TYPE& rhs) {
|
||||
return strictly_order_type(rhs, lhs) - strictly_order_type(lhs, rhs);
|
||||
}
|
||||
|
||||
/*
|
||||
* create, destroy, copy and move types...
|
||||
*/
|
||||
|
||||
template<typename TYPE> inline
|
||||
void construct_type(TYPE* p, size_t n) {
|
||||
if (!traits<TYPE>::has_trivial_ctor) {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
new(p++) TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
void destroy_type(TYPE* p, size_t n) {
|
||||
if (!traits<TYPE>::has_trivial_dtor) {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
p->~TYPE();
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<traits<TYPE>::has_trivial_copy>::type
|
||||
inline
|
||||
copy_type(TYPE* d, const TYPE* s, size_t n) {
|
||||
memcpy(d,s,n*sizeof(TYPE));
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<!traits<TYPE>::has_trivial_copy>::type
|
||||
inline
|
||||
copy_type(TYPE* d, const TYPE* s, size_t n) {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
new(d) TYPE(*s);
|
||||
d++, s++;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TYPE> inline
|
||||
void splat_type(TYPE* where, const TYPE* what, size_t n) {
|
||||
if (!traits<TYPE>::has_trivial_copy) {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
new(where) TYPE(*what);
|
||||
where++;
|
||||
}
|
||||
} else {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
*where++ = *what;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
struct use_trivial_move : public std::integral_constant<bool,
|
||||
(traits<TYPE>::has_trivial_dtor && traits<TYPE>::has_trivial_copy)
|
||||
|| traits<TYPE>::has_trivial_move
|
||||
> {};
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<use_trivial_move<TYPE>::value>::type
|
||||
inline
|
||||
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
|
||||
memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
|
||||
inline
|
||||
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
|
||||
d += n;
|
||||
s += n;
|
||||
while (n > 0) {
|
||||
n--;
|
||||
--d, --s;
|
||||
if (!traits<TYPE>::has_trivial_copy) {
|
||||
new(d) TYPE(*s);
|
||||
} else {
|
||||
*d = *s;
|
||||
}
|
||||
if (!traits<TYPE>::has_trivial_dtor) {
|
||||
s->~TYPE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<use_trivial_move<TYPE>::value>::type
|
||||
inline
|
||||
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
|
||||
memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
|
||||
inline
|
||||
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
|
||||
while (n > 0) {
|
||||
n--;
|
||||
if (!traits<TYPE>::has_trivial_copy) {
|
||||
new(d) TYPE(*s);
|
||||
} else {
|
||||
*d = *s;
|
||||
}
|
||||
if (!traits<TYPE>::has_trivial_dtor) {
|
||||
s->~TYPE();
|
||||
}
|
||||
d++, s++;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* a key/value pair
|
||||
*/
|
||||
|
||||
template <typename KEY, typename VALUE>
|
||||
struct key_value_pair_t {
|
||||
typedef KEY key_t;
|
||||
typedef VALUE value_t;
|
||||
|
||||
KEY key;
|
||||
VALUE value;
|
||||
key_value_pair_t() { }
|
||||
key_value_pair_t(const key_value_pair_t& o) : key(o.key), value(o.value) { }
|
||||
key_value_pair_t& operator=(const key_value_pair_t& o) {
|
||||
key = o.key;
|
||||
value = o.value;
|
||||
return *this;
|
||||
}
|
||||
key_value_pair_t(const KEY& k, const VALUE& v) : key(k), value(v) { }
|
||||
explicit key_value_pair_t(const KEY& k) : key(k) { }
|
||||
inline bool operator < (const key_value_pair_t& o) const {
|
||||
return strictly_order_type(key, o.key);
|
||||
}
|
||||
inline const KEY& getKey() const {
|
||||
return key;
|
||||
}
|
||||
inline const VALUE& getValue() const {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename K, typename V>
|
||||
struct trait_trivial_ctor< key_value_pair_t<K, V> >
|
||||
{ enum { value = aggregate_traits<K,V>::has_trivial_ctor }; };
|
||||
template <typename K, typename V>
|
||||
struct trait_trivial_dtor< key_value_pair_t<K, V> >
|
||||
{ enum { value = aggregate_traits<K,V>::has_trivial_dtor }; };
|
||||
template <typename K, typename V>
|
||||
struct trait_trivial_copy< key_value_pair_t<K, V> >
|
||||
{ enum { value = aggregate_traits<K,V>::has_trivial_copy }; };
|
||||
template <typename K, typename V>
|
||||
struct trait_trivial_move< key_value_pair_t<K, V> >
|
||||
{ enum { value = aggregate_traits<K,V>::has_trivial_move }; };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Hash codes.
|
||||
*/
|
||||
typedef uint32_t hash_t;
|
||||
|
||||
template <typename TKey>
|
||||
hash_t hash_type(const TKey& key);
|
||||
|
||||
/* Built-in hash code specializations */
|
||||
#define ANDROID_INT32_HASH(T) \
|
||||
template <> inline hash_t hash_type(const T& value) { return hash_t(value); }
|
||||
#define ANDROID_INT64_HASH(T) \
|
||||
template <> inline hash_t hash_type(const T& value) { \
|
||||
return hash_t((value >> 32) ^ value); }
|
||||
#define ANDROID_REINTERPRET_HASH(T, R) \
|
||||
template <> inline hash_t hash_type(const T& value) { \
|
||||
R newValue; \
|
||||
static_assert(sizeof(newValue) == sizeof(value), "size mismatch"); \
|
||||
memcpy(&newValue, &value, sizeof(newValue)); \
|
||||
return hash_type(newValue); \
|
||||
}
|
||||
|
||||
ANDROID_INT32_HASH(bool)
|
||||
ANDROID_INT32_HASH(int8_t)
|
||||
ANDROID_INT32_HASH(uint8_t)
|
||||
ANDROID_INT32_HASH(int16_t)
|
||||
ANDROID_INT32_HASH(uint16_t)
|
||||
ANDROID_INT32_HASH(int32_t)
|
||||
ANDROID_INT32_HASH(uint32_t)
|
||||
ANDROID_INT64_HASH(int64_t)
|
||||
ANDROID_INT64_HASH(uint64_t)
|
||||
ANDROID_REINTERPRET_HASH(float, uint32_t)
|
||||
ANDROID_REINTERPRET_HASH(double, uint64_t)
|
||||
|
||||
template <typename T> inline hash_t hash_type(T* const & value) {
|
||||
return hash_type(uintptr_t(value));
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_TYPE_HELPERS_H
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_UNICODE_H
|
||||
#define ANDROID_UNICODE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Standard string functions on char16_t strings.
|
||||
int strcmp16(const char16_t *, const char16_t *);
|
||||
int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
|
||||
size_t strlen16(const char16_t *);
|
||||
size_t strnlen16(const char16_t *, size_t);
|
||||
char16_t *strstr16(const char16_t*, const char16_t*);
|
||||
|
||||
// Version of comparison that supports embedded NULs.
|
||||
// This is different than strncmp() because we don't stop
|
||||
// at a nul character and consider the strings to be different
|
||||
// if the lengths are different (thus we need to supply the
|
||||
// lengths of both strings). This can also be used when
|
||||
// your string is not nul-terminated as it will have the
|
||||
// equivalent result as strcmp16 (unlike strncmp16).
|
||||
int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
|
||||
|
||||
/**
|
||||
* Measure the length of a UTF-32 string in UTF-8. If the string is invalid
|
||||
* such as containing a surrogate character, -1 will be returned.
|
||||
*/
|
||||
ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len);
|
||||
|
||||
/**
|
||||
* Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not
|
||||
* large enough to store the string, the part of the "src" string is stored
|
||||
* into "dst" as much as possible. See the examples for more detail.
|
||||
* Returns the size actually used for storing the string.
|
||||
* dst" is not nul-terminated when dst_len is fully used (like strncpy).
|
||||
*
|
||||
* \code
|
||||
* Example 1
|
||||
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
|
||||
* "src_len" == 2
|
||||
* "dst_len" >= 7
|
||||
* ->
|
||||
* Returned value == 6
|
||||
* "dst" becomes \xE3\x81\x82\xE3\x81\x84\0
|
||||
* (note that "dst" is nul-terminated)
|
||||
*
|
||||
* Example 2
|
||||
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
|
||||
* "src_len" == 2
|
||||
* "dst_len" == 5
|
||||
* ->
|
||||
* Returned value == 3
|
||||
* "dst" becomes \xE3\x81\x82\0
|
||||
* (note that "dst" is nul-terminated, but \u3044 is not stored in "dst"
|
||||
* since "dst" does not have enough size to store the character)
|
||||
*
|
||||
* Example 3
|
||||
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
|
||||
* "src_len" == 2
|
||||
* "dst_len" == 6
|
||||
* ->
|
||||
* Returned value == 6
|
||||
* "dst" becomes \xE3\x81\x82\xE3\x81\x84
|
||||
* (note that "dst" is NOT nul-terminated, like strncpy)
|
||||
* \endcode
|
||||
*/
|
||||
void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst, size_t dst_len);
|
||||
|
||||
/**
|
||||
* Returns the unicode value at "index".
|
||||
* Returns -1 when the index is invalid (equals to or more than "src_len").
|
||||
* If returned value is positive, it is able to be converted to char32_t, which
|
||||
* is unsigned. Then, if "next_index" is not NULL, the next index to be used is
|
||||
* stored in "next_index". "next_index" can be NULL.
|
||||
*/
|
||||
int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 length of UTF-16 string "src".
|
||||
*/
|
||||
ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len);
|
||||
|
||||
/**
|
||||
* Converts a UTF-16 string to UTF-8. The destination buffer must be large
|
||||
* enough to fit the UTF-16 as measured by utf16_to_utf8_length with an added
|
||||
* NUL terminator.
|
||||
*/
|
||||
void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst, size_t dst_len);
|
||||
|
||||
/**
|
||||
* Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
|
||||
* it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
|
||||
* can ask to log a message and fail in case the invalid utf8 could have caused an override if no
|
||||
* bound checks were used (otherwise -1 is returned).
|
||||
*/
|
||||
ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen, bool overreadIsFatal = false);
|
||||
|
||||
/**
|
||||
* Convert UTF-8 to UTF-16 including surrogate pairs.
|
||||
* Returns a pointer to the end of the string (where a NUL terminator might go
|
||||
* if you wanted to add one). At most dstLen characters are written; it won't emit half a surrogate
|
||||
* pair. If dstLen == 0 nothing is written and dst is returned. If dstLen > SSIZE_MAX it aborts
|
||||
* (this being probably a negative number returned as an error and casted to unsigned).
|
||||
*/
|
||||
char16_t* utf8_to_utf16_no_null_terminator(
|
||||
const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen);
|
||||
|
||||
/**
|
||||
* Convert UTF-8 to UTF-16 including surrogate pairs. At most dstLen - 1
|
||||
* characters are written; it won't emit half a surrogate pair; and a NUL terminator is appended
|
||||
* after. dstLen - 1 can be measured beforehand using utf8_to_utf16_length. Aborts if dstLen == 0
|
||||
* (at least one character is needed for the NUL terminator) or dstLen > SSIZE_MAX (the latter
|
||||
* case being likely a negative number returned as an error and casted to unsigned) . Returns a
|
||||
* pointer to the NUL terminator.
|
||||
*/
|
||||
char16_t *utf8_to_utf16(
|
||||
const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_VECTOR_H
|
||||
#define ANDROID_VECTOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// #include <log/log.h>
|
||||
#include <utils/TypeHelpers.h>
|
||||
#include <utils/VectorImpl.h>
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Used to exclude some functions from CFI.
|
||||
*/
|
||||
#if __has_attribute(no_sanitize)
|
||||
#define UTILS_VECTOR_NO_CFI __attribute__((no_sanitize("cfi")))
|
||||
#else
|
||||
#define UTILS_VECTOR_NO_CFI
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace android {
|
||||
|
||||
template <typename TYPE>
|
||||
class SortedVector;
|
||||
|
||||
/*!
|
||||
* The main templated vector class ensuring type safety
|
||||
* while making use of VectorImpl.
|
||||
* This is the class users want to use.
|
||||
*
|
||||
* DO NOT USE: please use std::vector
|
||||
*/
|
||||
|
||||
template <class TYPE>
|
||||
class Vector : private VectorImpl
|
||||
{
|
||||
public:
|
||||
typedef TYPE value_type;
|
||||
|
||||
/*!
|
||||
* Constructors and destructors
|
||||
*/
|
||||
|
||||
Vector();
|
||||
Vector(const Vector<TYPE>& rhs);
|
||||
explicit Vector(const SortedVector<TYPE>& rhs);
|
||||
virtual ~Vector();
|
||||
|
||||
/*! copy operator */
|
||||
Vector<TYPE>& operator=(const Vector<TYPE>& rhs); // NOLINT(cert-oop54-cpp)
|
||||
Vector<TYPE>& operator=(const SortedVector<TYPE>& rhs); // NOLINT(cert-oop54-cpp)
|
||||
|
||||
/*
|
||||
* empty the vector
|
||||
*/
|
||||
|
||||
inline void clear() { VectorImpl::clear(); }
|
||||
|
||||
/*!
|
||||
* vector stats
|
||||
*/
|
||||
|
||||
//! returns number of items in the vector
|
||||
inline size_t size() const { return VectorImpl::size(); }
|
||||
//! returns whether or not the vector is empty
|
||||
inline bool isEmpty() const { return VectorImpl::isEmpty(); }
|
||||
//! returns how many items can be stored without reallocating the backing store
|
||||
inline size_t capacity() const { return VectorImpl::capacity(); }
|
||||
//! sets the capacity. capacity can never be reduced less than size()
|
||||
inline ssize_t setCapacity(size_t size) { return VectorImpl::setCapacity(size); }
|
||||
|
||||
/*!
|
||||
* set the size of the vector. items are appended with the default
|
||||
* constructor, or removed from the end as needed.
|
||||
*/
|
||||
inline ssize_t resize(size_t size) { return VectorImpl::resize(size); }
|
||||
|
||||
/*!
|
||||
* C-style array access
|
||||
*/
|
||||
|
||||
//! read-only C-style access
|
||||
inline const TYPE* array() const;
|
||||
//! read-write C-style access
|
||||
TYPE* editArray();
|
||||
|
||||
/*!
|
||||
* accessors
|
||||
*/
|
||||
|
||||
//! read-only access to an item at a given index
|
||||
inline const TYPE& operator [] (size_t index) const;
|
||||
//! alternate name for operator []
|
||||
inline const TYPE& itemAt(size_t index) const;
|
||||
//! stack-usage of the vector. returns the top of the stack (last element)
|
||||
const TYPE& top() const;
|
||||
|
||||
/*!
|
||||
* modifying the array
|
||||
*/
|
||||
|
||||
//! copy-on write support, grants write access to an item
|
||||
TYPE& editItemAt(size_t index);
|
||||
//! grants right access to the top of the stack (last element)
|
||||
TYPE& editTop();
|
||||
|
||||
/*!
|
||||
* append/insert another vector
|
||||
*/
|
||||
|
||||
//! insert another vector at a given index
|
||||
ssize_t insertVectorAt(const Vector<TYPE>& vector, size_t index);
|
||||
|
||||
//! append another vector at the end of this one
|
||||
ssize_t appendVector(const Vector<TYPE>& vector);
|
||||
|
||||
|
||||
//! insert an array at a given index
|
||||
ssize_t insertArrayAt(const TYPE* array, size_t index, size_t length);
|
||||
|
||||
//! append an array at the end of this vector
|
||||
ssize_t appendArray(const TYPE* array, size_t length);
|
||||
|
||||
/*!
|
||||
* add/insert/replace items
|
||||
*/
|
||||
|
||||
//! insert one or several items initialized with their default constructor
|
||||
inline ssize_t insertAt(size_t index, size_t numItems = 1);
|
||||
//! insert one or several items initialized from a prototype item
|
||||
ssize_t insertAt(const TYPE& prototype_item, size_t index, size_t numItems = 1);
|
||||
//! pop the top of the stack (removes the last element). No-op if the stack's empty
|
||||
inline void pop();
|
||||
//! pushes an item initialized with its default constructor
|
||||
inline void push();
|
||||
//! pushes an item on the top of the stack
|
||||
void push(const TYPE& item);
|
||||
//! same as push() but returns the index the item was added at (or an error)
|
||||
inline ssize_t add();
|
||||
//! same as push() but returns the index the item was added at (or an error)
|
||||
ssize_t add(const TYPE& item);
|
||||
//! replace an item with a new one initialized with its default constructor
|
||||
inline ssize_t replaceAt(size_t index);
|
||||
//! replace an item with a new one
|
||||
ssize_t replaceAt(const TYPE& item, size_t index);
|
||||
|
||||
/*!
|
||||
* remove items
|
||||
*/
|
||||
|
||||
//! remove several items
|
||||
inline ssize_t removeItemsAt(size_t index, size_t count = 1);
|
||||
//! remove one item
|
||||
inline ssize_t removeAt(size_t index) { return removeItemsAt(index); }
|
||||
|
||||
/*!
|
||||
* sort (stable) the array
|
||||
*/
|
||||
|
||||
typedef int (*compar_t)(const TYPE* lhs, const TYPE* rhs);
|
||||
typedef int (*compar_r_t)(const TYPE* lhs, const TYPE* rhs, void* state);
|
||||
|
||||
inline status_t sort(compar_t cmp);
|
||||
inline status_t sort(compar_r_t cmp, void* state);
|
||||
|
||||
// for debugging only
|
||||
inline size_t getItemSize() const { return itemSize(); }
|
||||
|
||||
|
||||
/*
|
||||
* these inlines add some level of compatibility with STL. eventually
|
||||
* we should probably turn things around.
|
||||
*/
|
||||
typedef TYPE* iterator;
|
||||
typedef TYPE const* const_iterator;
|
||||
|
||||
inline iterator begin() { return editArray(); }
|
||||
inline iterator end() { return editArray() + size(); }
|
||||
inline const_iterator begin() const { return array(); }
|
||||
inline const_iterator end() const { return array() + size(); }
|
||||
inline void reserve(size_t n) { setCapacity(n); }
|
||||
inline bool empty() const{ return isEmpty(); }
|
||||
inline void push_back(const TYPE& item) { insertAt(item, size(), 1); }
|
||||
inline void push_front(const TYPE& item) { insertAt(item, 0, 1); }
|
||||
inline iterator erase(iterator pos) {
|
||||
ssize_t index = removeItemsAt(static_cast<size_t>(pos-array()));
|
||||
return begin() + index;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void do_construct(void* storage, size_t num) const;
|
||||
virtual void do_destroy(void* storage, size_t num) const;
|
||||
virtual void do_copy(void* dest, const void* from, size_t num) const;
|
||||
virtual void do_splat(void* dest, const void* item, size_t num) const;
|
||||
virtual void do_move_forward(void* dest, const void* from, size_t num) const;
|
||||
virtual void do_move_backward(void* dest, const void* from, size_t num) const;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts from here...
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template<class TYPE> inline
|
||||
Vector<TYPE>::Vector()
|
||||
: VectorImpl(sizeof(TYPE),
|
||||
((traits<TYPE>::has_trivial_ctor ? HAS_TRIVIAL_CTOR : 0)
|
||||
|(traits<TYPE>::has_trivial_dtor ? HAS_TRIVIAL_DTOR : 0)
|
||||
|(traits<TYPE>::has_trivial_copy ? HAS_TRIVIAL_COPY : 0))
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
Vector<TYPE>::Vector(const Vector<TYPE>& rhs)
|
||||
: VectorImpl(rhs) {
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
Vector<TYPE>::Vector(const SortedVector<TYPE>& rhs)
|
||||
: VectorImpl(static_cast<const VectorImpl&>(rhs)) {
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
Vector<TYPE>::~Vector() {
|
||||
finish_vector();
|
||||
}
|
||||
|
||||
template <class TYPE>
|
||||
inline Vector<TYPE>& Vector<TYPE>::operator=(const Vector<TYPE>& rhs) // NOLINT(cert-oop54-cpp)
|
||||
{
|
||||
VectorImpl::operator=(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class TYPE>
|
||||
inline Vector<TYPE>& Vector<TYPE>::operator=(
|
||||
const SortedVector<TYPE>& rhs) // NOLINT(cert-oop54-cpp)
|
||||
{
|
||||
VectorImpl::operator=(static_cast<const VectorImpl&>(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
const TYPE* Vector<TYPE>::array() const {
|
||||
return static_cast<const TYPE *>(arrayImpl());
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
TYPE* Vector<TYPE>::editArray() {
|
||||
return static_cast<TYPE *>(editArrayImpl());
|
||||
}
|
||||
|
||||
|
||||
template<class TYPE> inline
|
||||
const TYPE& Vector<TYPE>::operator[](size_t index) const {
|
||||
LOG_FATAL_IF(index>=size(),
|
||||
"%s: index=%u out of range (%u)", __PRETTY_FUNCTION__,
|
||||
int(index), int(size()));
|
||||
return *(array() + index);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
const TYPE& Vector<TYPE>::itemAt(size_t index) const {
|
||||
return operator[](index);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
const TYPE& Vector<TYPE>::top() const {
|
||||
return *(array() + size() - 1);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
TYPE& Vector<TYPE>::editItemAt(size_t index) {
|
||||
return *( static_cast<TYPE *>(editItemLocation(index)) );
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
TYPE& Vector<TYPE>::editTop() {
|
||||
return *( static_cast<TYPE *>(editItemLocation(size()-1)) );
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::insertVectorAt(const Vector<TYPE>& vector, size_t index) {
|
||||
return VectorImpl::insertVectorAt(reinterpret_cast<const VectorImpl&>(vector), index);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::appendVector(const Vector<TYPE>& vector) {
|
||||
return VectorImpl::appendVector(reinterpret_cast<const VectorImpl&>(vector));
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t length) {
|
||||
return VectorImpl::insertArrayAt(array, index, length);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t length) {
|
||||
return VectorImpl::appendArray(array, length);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::insertAt(const TYPE& item, size_t index, size_t numItems) {
|
||||
return VectorImpl::insertAt(&item, index, numItems);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
void Vector<TYPE>::push(const TYPE& item) {
|
||||
return VectorImpl::push(&item);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::add(const TYPE& item) {
|
||||
return VectorImpl::add(&item);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::replaceAt(const TYPE& item, size_t index) {
|
||||
return VectorImpl::replaceAt(&item, index);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::insertAt(size_t index, size_t numItems) {
|
||||
return VectorImpl::insertAt(index, numItems);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
void Vector<TYPE>::pop() {
|
||||
VectorImpl::pop();
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
void Vector<TYPE>::push() {
|
||||
VectorImpl::push();
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::add() {
|
||||
return VectorImpl::add();
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::replaceAt(size_t index) {
|
||||
return VectorImpl::replaceAt(index);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
ssize_t Vector<TYPE>::removeItemsAt(size_t index, size_t count) {
|
||||
return VectorImpl::removeItemsAt(index, count);
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
status_t Vector<TYPE>::sort(Vector<TYPE>::compar_t cmp) {
|
||||
return VectorImpl::sort(reinterpret_cast<VectorImpl::compar_t>(cmp));
|
||||
}
|
||||
|
||||
template<class TYPE> inline
|
||||
status_t Vector<TYPE>::sort(Vector<TYPE>::compar_r_t cmp, void* state) {
|
||||
return VectorImpl::sort(reinterpret_cast<VectorImpl::compar_r_t>(cmp), state);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
template<class TYPE>
|
||||
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_construct(void* storage, size_t num) const {
|
||||
construct_type( reinterpret_cast<TYPE*>(storage), num );
|
||||
}
|
||||
|
||||
template<class TYPE>
|
||||
void Vector<TYPE>::do_destroy(void* storage, size_t num) const {
|
||||
destroy_type( reinterpret_cast<TYPE*>(storage), num );
|
||||
}
|
||||
|
||||
template<class TYPE>
|
||||
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_copy(void* dest, const void* from, size_t num) const {
|
||||
copy_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
|
||||
}
|
||||
|
||||
template<class TYPE>
|
||||
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_splat(void* dest, const void* item, size_t num) const {
|
||||
splat_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(item), num );
|
||||
}
|
||||
|
||||
template<class TYPE>
|
||||
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_move_forward(void* dest, const void* from, size_t num) const {
|
||||
move_forward_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
|
||||
}
|
||||
|
||||
template<class TYPE>
|
||||
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_move_backward(void* dest, const void* from, size_t num) const {
|
||||
move_backward_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_VECTOR_H
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2005 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_VECTOR_IMPL_H
|
||||
#define ANDROID_VECTOR_IMPL_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <utils/Errors.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// No user serviceable parts in here...
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace android {
|
||||
|
||||
/*!
|
||||
* Implementation of the guts of the vector<> class
|
||||
* this ensures backward binary compatibility and
|
||||
* reduces code size.
|
||||
* For performance reasons, we expose mStorage and mCount
|
||||
* so these fields are set in stone.
|
||||
*
|
||||
*/
|
||||
|
||||
class VectorImpl
|
||||
{
|
||||
public:
|
||||
enum { // flags passed to the ctor
|
||||
HAS_TRIVIAL_CTOR = 0x00000001,
|
||||
HAS_TRIVIAL_DTOR = 0x00000002,
|
||||
HAS_TRIVIAL_COPY = 0x00000004,
|
||||
};
|
||||
|
||||
VectorImpl(size_t itemSize, uint32_t flags);
|
||||
VectorImpl(const VectorImpl& rhs);
|
||||
virtual ~VectorImpl();
|
||||
|
||||
/*! must be called from subclasses destructor */
|
||||
void finish_vector();
|
||||
|
||||
VectorImpl& operator = (const VectorImpl& rhs);
|
||||
|
||||
/*! C-style array access */
|
||||
inline const void* arrayImpl() const { return mStorage; }
|
||||
void* editArrayImpl();
|
||||
|
||||
/*! vector stats */
|
||||
inline size_t size() const { return mCount; }
|
||||
inline bool isEmpty() const { return mCount == 0; }
|
||||
size_t capacity() const;
|
||||
ssize_t setCapacity(size_t size);
|
||||
ssize_t resize(size_t size);
|
||||
|
||||
/*! append/insert another vector or array */
|
||||
ssize_t insertVectorAt(const VectorImpl& vector, size_t index);
|
||||
ssize_t appendVector(const VectorImpl& vector);
|
||||
ssize_t insertArrayAt(const void* array, size_t index, size_t length);
|
||||
ssize_t appendArray(const void* array, size_t length);
|
||||
|
||||
/*! add/insert/replace items */
|
||||
ssize_t insertAt(size_t where, size_t numItems = 1);
|
||||
ssize_t insertAt(const void* item, size_t where, size_t numItems = 1);
|
||||
void pop();
|
||||
void push();
|
||||
void push(const void* item);
|
||||
ssize_t add();
|
||||
ssize_t add(const void* item);
|
||||
ssize_t replaceAt(size_t index);
|
||||
ssize_t replaceAt(const void* item, size_t index);
|
||||
|
||||
/*! remove items */
|
||||
ssize_t removeItemsAt(size_t index, size_t count = 1);
|
||||
void clear();
|
||||
|
||||
const void* itemLocation(size_t index) const;
|
||||
void* editItemLocation(size_t index);
|
||||
|
||||
typedef int (*compar_t)(const void* lhs, const void* rhs);
|
||||
typedef int (*compar_r_t)(const void* lhs, const void* rhs, void* state);
|
||||
status_t sort(compar_t cmp);
|
||||
status_t sort(compar_r_t cmp, void* state);
|
||||
|
||||
protected:
|
||||
size_t itemSize() const;
|
||||
void release_storage();
|
||||
|
||||
virtual void do_construct(void* storage, size_t num) const = 0;
|
||||
virtual void do_destroy(void* storage, size_t num) const = 0;
|
||||
virtual void do_copy(void* dest, const void* from, size_t num) const = 0;
|
||||
virtual void do_splat(void* dest, const void* item, size_t num) const = 0;
|
||||
virtual void do_move_forward(void* dest, const void* from, size_t num) const = 0;
|
||||
virtual void do_move_backward(void* dest, const void* from, size_t num) const = 0;
|
||||
|
||||
private:
|
||||
void* _grow(size_t where, size_t amount);
|
||||
void _shrink(size_t where, size_t amount);
|
||||
|
||||
inline void _do_construct(void* storage, size_t num) const;
|
||||
inline void _do_destroy(void* storage, size_t num) const;
|
||||
inline void _do_copy(void* dest, const void* from, size_t num) const;
|
||||
inline void _do_splat(void* dest, const void* item, size_t num) const;
|
||||
inline void _do_move_forward(void* dest, const void* from, size_t num) const;
|
||||
inline void _do_move_backward(void* dest, const void* from, size_t num) const;
|
||||
|
||||
// These 2 fields are exposed in the inlines below,
|
||||
// so they're set in stone.
|
||||
void * mStorage; // base address of the vector
|
||||
size_t mCount; // number of items
|
||||
|
||||
const uint32_t mFlags;
|
||||
const size_t mItemSize;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SortedVectorImpl : public VectorImpl
|
||||
{
|
||||
public:
|
||||
SortedVectorImpl(size_t itemSize, uint32_t flags);
|
||||
explicit SortedVectorImpl(const VectorImpl& rhs);
|
||||
virtual ~SortedVectorImpl();
|
||||
|
||||
SortedVectorImpl& operator = (const SortedVectorImpl& rhs);
|
||||
|
||||
//! finds the index of an item
|
||||
ssize_t indexOf(const void* item) const;
|
||||
|
||||
//! finds where this item should be inserted
|
||||
size_t orderOf(const void* item) const;
|
||||
|
||||
//! add an item in the right place (or replaces it if there is one)
|
||||
ssize_t add(const void* item);
|
||||
|
||||
//! merges a vector into this one
|
||||
ssize_t merge(const VectorImpl& vector);
|
||||
ssize_t merge(const SortedVectorImpl& vector);
|
||||
|
||||
//! removes an item
|
||||
ssize_t remove(const void* item);
|
||||
|
||||
protected:
|
||||
virtual int do_compare(const void* lhs, const void* rhs) const = 0;
|
||||
|
||||
private:
|
||||
ssize_t _indexOrderOf(const void* item, size_t* order = nullptr) const;
|
||||
|
||||
// these are made private, because they can't be used on a SortedVector
|
||||
// (they don't have an implementation either)
|
||||
ssize_t add();
|
||||
void pop();
|
||||
void push();
|
||||
void push(const void* item);
|
||||
ssize_t insertVectorAt(const VectorImpl& vector, size_t index);
|
||||
ssize_t appendVector(const VectorImpl& vector);
|
||||
ssize_t insertArrayAt(const void* array, size_t index, size_t length);
|
||||
ssize_t appendArray(const void* array, size_t length);
|
||||
ssize_t insertAt(size_t where, size_t numItems = 1);
|
||||
ssize_t insertAt(const void* item, size_t where, size_t numItems = 1);
|
||||
ssize_t replaceAt(size_t index);
|
||||
ssize_t replaceAt(const void* item, size_t index);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#endif // ANDROID_VECTOR_IMPL_H
|
||||
+1
Submodule app/src/main/cpp/external/LSPlt added at 3e29437f03
@@ -0,0 +1,639 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright (C) 2008 Google, Inc.
|
||||
*
|
||||
* Based on, but no longer compatible with, the original
|
||||
* OpenBinder.org binder driver interface, which is:
|
||||
*
|
||||
* Copyright (c) 2005 Palmsource, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_LINUX_BINDER_H
|
||||
#define _UAPI_LINUX_BINDER_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#define B_PACK_CHARS(c1, c2, c3, c4) \
|
||||
((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
|
||||
#define B_TYPE_LARGE 0x85
|
||||
|
||||
enum {
|
||||
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
|
||||
BINDER_TYPE_FDA = B_PACK_CHARS('f', 'd', 'a', B_TYPE_LARGE),
|
||||
BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),
|
||||
};
|
||||
|
||||
/**
|
||||
* enum flat_binder_object_shifts: shift values for flat_binder_object_flags
|
||||
* @FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT: shift for getting scheduler policy.
|
||||
*
|
||||
*/
|
||||
enum flat_binder_object_shifts {
|
||||
FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT = 9,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum flat_binder_object_flags - flags for use in flat_binder_object.flags
|
||||
*/
|
||||
enum flat_binder_object_flags {
|
||||
/**
|
||||
* @FLAT_BINDER_FLAG_PRIORITY_MASK: bit-mask for min scheduler priority
|
||||
*
|
||||
* These bits can be used to set the minimum scheduler priority
|
||||
* at which transactions into this node should run. Valid values
|
||||
* in these bits depend on the scheduler policy encoded in
|
||||
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK.
|
||||
*
|
||||
* For SCHED_NORMAL/SCHED_BATCH, the valid range is between [-20..19]
|
||||
* For SCHED_FIFO/SCHED_RR, the value can run between [1..99]
|
||||
*/
|
||||
FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff,
|
||||
/**
|
||||
* @FLAT_BINDER_FLAG_ACCEPTS_FDS: whether the node accepts fds.
|
||||
*/
|
||||
FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100,
|
||||
|
||||
/**
|
||||
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK: bit-mask for scheduling policy
|
||||
*
|
||||
* These two bits can be used to set the min scheduling policy at which
|
||||
* transactions on this node should run. These match the UAPI
|
||||
* scheduler policy values, eg:
|
||||
* 00b: SCHED_NORMAL
|
||||
* 01b: SCHED_FIFO
|
||||
* 10b: SCHED_RR
|
||||
* 11b: SCHED_BATCH
|
||||
*/
|
||||
FLAT_BINDER_FLAG_SCHED_POLICY_MASK =
|
||||
3U << FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT,
|
||||
|
||||
/**
|
||||
* @FLAT_BINDER_FLAG_INHERIT_RT: whether the node inherits RT policy
|
||||
*
|
||||
* Only when set, calls into this node will inherit a real-time
|
||||
* scheduling policy from the caller (for synchronous transactions).
|
||||
*/
|
||||
FLAT_BINDER_FLAG_INHERIT_RT = 0x800,
|
||||
|
||||
/**
|
||||
* @FLAT_BINDER_FLAG_TXN_SECURITY_CTX: request security contexts
|
||||
*
|
||||
* Only when set, causes senders to include their security
|
||||
* context
|
||||
*/
|
||||
FLAT_BINDER_FLAG_TXN_SECURITY_CTX = 0x1000,
|
||||
};
|
||||
|
||||
#ifdef BINDER_IPC_32BIT
|
||||
typedef __u32 binder_size_t;
|
||||
typedef __u32 binder_uintptr_t;
|
||||
#else
|
||||
typedef __u64 binder_size_t;
|
||||
typedef __u64 binder_uintptr_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* struct binder_object_header - header shared by all binder metadata objects.
|
||||
* @type: type of the object
|
||||
*/
|
||||
struct binder_object_header {
|
||||
__u32 type;
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the flattened representation of a Binder object for transfer
|
||||
* between processes. The 'offsets' supplied as part of a binder transaction
|
||||
* contains offsets into the data where these structures occur. The Binder
|
||||
* driver takes care of re-writing the structure type and data as it moves
|
||||
* between processes.
|
||||
*/
|
||||
struct flat_binder_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 flags;
|
||||
|
||||
/* 8 bytes of data. */
|
||||
union {
|
||||
binder_uintptr_t binder; /* local object */
|
||||
__u32 handle; /* remote object */
|
||||
};
|
||||
|
||||
/* extra data associated with local object */
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct binder_fd_object - describes a filedescriptor to be fixed up.
|
||||
* @hdr: common header structure
|
||||
* @pad_flags: padding to remain compatible with old userspace code
|
||||
* @pad_binder: padding to remain compatible with old userspace code
|
||||
* @fd: file descriptor
|
||||
* @cookie: opaque data, used by user-space
|
||||
*/
|
||||
struct binder_fd_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 pad_flags;
|
||||
union {
|
||||
binder_uintptr_t pad_binder;
|
||||
__u32 fd;
|
||||
};
|
||||
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
|
||||
/* struct binder_buffer_object - object describing a userspace buffer
|
||||
* @hdr: common header structure
|
||||
* @flags: one or more BINDER_BUFFER_* flags
|
||||
* @buffer: address of the buffer
|
||||
* @length: length of the buffer
|
||||
* @parent: index in offset array pointing to parent buffer
|
||||
* @parent_offset: offset in @parent pointing to this buffer
|
||||
*
|
||||
* A binder_buffer object represents an object that the
|
||||
* binder kernel driver can copy verbatim to the target
|
||||
* address space. A buffer itself may be pointed to from
|
||||
* within another buffer, meaning that the pointer inside
|
||||
* that other buffer needs to be fixed up as well. This
|
||||
* can be done by setting the BINDER_BUFFER_FLAG_HAS_PARENT
|
||||
* flag in @flags, by setting @parent buffer to the index
|
||||
* in the offset array pointing to the parent binder_buffer_object,
|
||||
* and by setting @parent_offset to the offset in the parent buffer
|
||||
* at which the pointer to this buffer is located.
|
||||
*/
|
||||
struct binder_buffer_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 flags;
|
||||
binder_uintptr_t buffer;
|
||||
binder_size_t length;
|
||||
binder_size_t parent;
|
||||
binder_size_t parent_offset;
|
||||
};
|
||||
|
||||
enum {
|
||||
BINDER_BUFFER_FLAG_HAS_PARENT = 0x01,
|
||||
};
|
||||
|
||||
/* struct binder_fd_array_object - object describing an array of fds in a buffer
|
||||
* @hdr: common header structure
|
||||
* @pad: padding to ensure correct alignment
|
||||
* @num_fds: number of file descriptors in the buffer
|
||||
* @parent: index in offset array to buffer holding the fd array
|
||||
* @parent_offset: start offset of fd array in the buffer
|
||||
*
|
||||
* A binder_fd_array object represents an array of file
|
||||
* descriptors embedded in a binder_buffer_object. It is
|
||||
* different from a regular binder_buffer_object because it
|
||||
* describes a list of file descriptors to fix up, not an opaque
|
||||
* blob of memory, and hence the kernel needs to treat it differently.
|
||||
*
|
||||
* An example of how this would be used is with Android's
|
||||
* native_handle_t object, which is a struct with a list of integers
|
||||
* and a list of file descriptors. The native_handle_t struct itself
|
||||
* will be represented by a struct binder_buffer_objct, whereas the
|
||||
* embedded list of file descriptors is represented by a
|
||||
* struct binder_fd_array_object with that binder_buffer_object as
|
||||
* a parent.
|
||||
*/
|
||||
struct binder_fd_array_object {
|
||||
struct binder_object_header hdr;
|
||||
__u32 pad;
|
||||
binder_size_t num_fds;
|
||||
binder_size_t parent;
|
||||
binder_size_t parent_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* On 64-bit platforms where user code may run in 32-bits the driver must
|
||||
* translate the buffer (and local binder) addresses appropriately.
|
||||
*/
|
||||
|
||||
struct binder_write_read {
|
||||
binder_size_t write_size; /* bytes to write */
|
||||
binder_size_t write_consumed; /* bytes consumed by driver */
|
||||
binder_uintptr_t write_buffer;
|
||||
binder_size_t read_size; /* bytes to read */
|
||||
binder_size_t read_consumed; /* bytes consumed by driver */
|
||||
binder_uintptr_t read_buffer;
|
||||
};
|
||||
|
||||
/* Use with BINDER_VERSION, driver fills in fields. */
|
||||
struct binder_version {
|
||||
/* driver protocol version -- increment with incompatible change */
|
||||
__s32 protocol_version;
|
||||
};
|
||||
|
||||
/* This is the current protocol version. */
|
||||
#ifdef BINDER_IPC_32BIT
|
||||
#define BINDER_CURRENT_PROTOCOL_VERSION 7
|
||||
#else
|
||||
#define BINDER_CURRENT_PROTOCOL_VERSION 8
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use with BINDER_GET_NODE_DEBUG_INFO, driver reads ptr, writes to all fields.
|
||||
* Set ptr to NULL for the first call to get the info for the first node, and
|
||||
* then repeat the call passing the previously returned value to get the next
|
||||
* nodes. ptr will be 0 when there are no more nodes.
|
||||
*/
|
||||
struct binder_node_debug_info {
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
__u32 has_strong_ref;
|
||||
__u32 has_weak_ref;
|
||||
};
|
||||
|
||||
struct binder_node_info_for_ref {
|
||||
__u32 handle;
|
||||
__u32 strong_count;
|
||||
__u32 weak_count;
|
||||
__u32 reserved1;
|
||||
__u32 reserved2;
|
||||
__u32 reserved3;
|
||||
};
|
||||
|
||||
struct binder_freeze_info {
|
||||
__u32 pid;
|
||||
__u32 enable;
|
||||
__u32 timeout_ms;
|
||||
};
|
||||
|
||||
struct binder_frozen_status_info {
|
||||
__u32 pid;
|
||||
|
||||
/* process received sync transactions since last frozen
|
||||
* bit 0: received sync transaction after being frozen
|
||||
* bit 1: new pending sync transaction during freezing
|
||||
*/
|
||||
__u32 sync_recv;
|
||||
|
||||
/* process received async transactions since last frozen */
|
||||
__u32 async_recv;
|
||||
};
|
||||
|
||||
struct binder_frozen_state_info {
|
||||
binder_uintptr_t cookie;
|
||||
__u32 is_frozen;
|
||||
__u32 reserved;
|
||||
};
|
||||
|
||||
/* struct binder_extened_error - extended error information
|
||||
* @id: identifier for the failed operation
|
||||
* @command: command as defined by binder_driver_return_protocol
|
||||
* @param: parameter holding a negative errno value
|
||||
*
|
||||
* Used with BINDER_GET_EXTENDED_ERROR. This extends the error information
|
||||
* returned by the driver upon a failed operation. Userspace can pull this
|
||||
* data to properly handle specific error scenarios.
|
||||
*/
|
||||
struct binder_extended_error {
|
||||
__u32 id;
|
||||
__u32 command;
|
||||
__s32 param;
|
||||
};
|
||||
|
||||
enum {
|
||||
BINDER_WRITE_READ = _IOWR('b', 1, struct binder_write_read),
|
||||
BINDER_SET_IDLE_TIMEOUT = _IOW('b', 3, __s64),
|
||||
BINDER_SET_MAX_THREADS = _IOW('b', 5, __u32),
|
||||
BINDER_SET_IDLE_PRIORITY = _IOW('b', 6, __s32),
|
||||
BINDER_SET_CONTEXT_MGR = _IOW('b', 7, __s32),
|
||||
BINDER_THREAD_EXIT = _IOW('b', 8, __s32),
|
||||
BINDER_VERSION = _IOWR('b', 9, struct binder_version),
|
||||
BINDER_GET_NODE_DEBUG_INFO = _IOWR('b', 11, struct binder_node_debug_info),
|
||||
BINDER_GET_NODE_INFO_FOR_REF = _IOWR('b', 12, struct binder_node_info_for_ref),
|
||||
BINDER_SET_CONTEXT_MGR_EXT = _IOW('b', 13, struct flat_binder_object),
|
||||
BINDER_FREEZE = _IOW('b', 14, struct binder_freeze_info),
|
||||
BINDER_GET_FROZEN_INFO = _IOWR('b', 15, struct binder_frozen_status_info),
|
||||
BINDER_ENABLE_ONEWAY_SPAM_DETECTION = _IOW('b', 16, __u32),
|
||||
BINDER_GET_EXTENDED_ERROR = _IOWR('b', 17, struct binder_extended_error),
|
||||
};
|
||||
|
||||
/*
|
||||
* NOTE: Two special error codes you should check for when calling
|
||||
* in to the driver are:
|
||||
*
|
||||
* EINTR -- The operation has been interupted. This should be
|
||||
* handled by retrying the ioctl() until a different error code
|
||||
* is returned.
|
||||
*
|
||||
* ECONNREFUSED -- The driver is no longer accepting operations
|
||||
* from your process. That is, the process is being destroyed.
|
||||
* You should handle this by exiting from your process. Note
|
||||
* that once this error code is returned, all further calls to
|
||||
* the driver from any thread will return this same code.
|
||||
*/
|
||||
|
||||
enum transaction_flags {
|
||||
TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */
|
||||
TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */
|
||||
TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */
|
||||
TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */
|
||||
TF_CLEAR_BUF = 0x20, /* clear buffer on txn complete */
|
||||
TF_UPDATE_TXN = 0x40, /* update the outdated pending async txn */
|
||||
};
|
||||
|
||||
struct binder_transaction_data {
|
||||
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
|
||||
* identifying the target and contents of the transaction.
|
||||
*/
|
||||
union {
|
||||
/* target descriptor of command transaction */
|
||||
__u32 handle;
|
||||
/* target descriptor of return transaction */
|
||||
binder_uintptr_t ptr;
|
||||
} target;
|
||||
binder_uintptr_t cookie; /* target object cookie */
|
||||
__u32 code; /* transaction command */
|
||||
|
||||
/* General information about the transaction. */
|
||||
__u32 flags;
|
||||
__kernel_pid_t sender_pid;
|
||||
__kernel_uid32_t sender_euid;
|
||||
binder_size_t data_size; /* number of bytes of data */
|
||||
binder_size_t offsets_size; /* number of bytes of offsets */
|
||||
|
||||
/* If this transaction is inline, the data immediately
|
||||
* follows here; otherwise, it ends with a pointer to
|
||||
* the data buffer.
|
||||
*/
|
||||
union {
|
||||
struct {
|
||||
/* transaction data */
|
||||
binder_uintptr_t buffer;
|
||||
/* offsets from buffer to flat_binder_object structs */
|
||||
binder_uintptr_t offsets;
|
||||
} ptr;
|
||||
__u8 buf[8];
|
||||
} data;
|
||||
};
|
||||
|
||||
struct binder_transaction_data_secctx {
|
||||
struct binder_transaction_data transaction_data;
|
||||
binder_uintptr_t secctx;
|
||||
};
|
||||
|
||||
struct binder_transaction_data_sg {
|
||||
struct binder_transaction_data transaction_data;
|
||||
binder_size_t buffers_size;
|
||||
};
|
||||
|
||||
struct binder_ptr_cookie {
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
|
||||
struct binder_handle_cookie {
|
||||
__u32 handle;
|
||||
binder_uintptr_t cookie;
|
||||
} __packed;
|
||||
|
||||
struct binder_pri_desc {
|
||||
__s32 priority;
|
||||
__u32 desc;
|
||||
};
|
||||
|
||||
struct binder_pri_ptr_cookie {
|
||||
__s32 priority;
|
||||
binder_uintptr_t ptr;
|
||||
binder_uintptr_t cookie;
|
||||
};
|
||||
|
||||
enum binder_driver_return_protocol {
|
||||
BR_ERROR = _IOR('r', 0, __s32),
|
||||
/*
|
||||
* int: error code
|
||||
*/
|
||||
|
||||
BR_OK = _IO('r', 1),
|
||||
/* No parameters! */
|
||||
|
||||
BR_TRANSACTION_SEC_CTX = _IOR('r', 2,
|
||||
struct binder_transaction_data_secctx),
|
||||
/*
|
||||
* binder_transaction_data_secctx: the received command.
|
||||
*/
|
||||
BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data),
|
||||
BR_REPLY = _IOR('r', 3, struct binder_transaction_data),
|
||||
/*
|
||||
* binder_transaction_data: the received command.
|
||||
*/
|
||||
|
||||
BR_ACQUIRE_RESULT = _IOR('r', 4, __s32),
|
||||
/*
|
||||
* not currently supported
|
||||
* int: 0 if the last bcATTEMPT_ACQUIRE was not successful.
|
||||
* Else the remote object has acquired a primary reference.
|
||||
*/
|
||||
|
||||
BR_DEAD_REPLY = _IO('r', 5),
|
||||
/*
|
||||
* The target of the last transaction (either a bcTRANSACTION or
|
||||
* a bcATTEMPT_ACQUIRE) is no longer with us. No parameters.
|
||||
*/
|
||||
|
||||
BR_TRANSACTION_COMPLETE = _IO('r', 6),
|
||||
/*
|
||||
* No parameters... always refers to the last transaction requested
|
||||
* (including replies). Note that this will be sent even for
|
||||
* asynchronous transactions.
|
||||
*/
|
||||
|
||||
BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie),
|
||||
BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie),
|
||||
BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie),
|
||||
BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie),
|
||||
/*
|
||||
* void *: ptr to binder
|
||||
* void *: cookie for binder
|
||||
*/
|
||||
|
||||
BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie),
|
||||
/*
|
||||
* not currently supported
|
||||
* int: priority
|
||||
* void *: ptr to binder
|
||||
* void *: cookie for binder
|
||||
*/
|
||||
|
||||
BR_NOOP = _IO('r', 12),
|
||||
/*
|
||||
* No parameters. Do nothing and examine the next command. It exists
|
||||
* primarily so that we can replace it with a BR_SPAWN_LOOPER command.
|
||||
*/
|
||||
|
||||
BR_SPAWN_LOOPER = _IO('r', 13),
|
||||
/*
|
||||
* No parameters. The driver has determined that a process has no
|
||||
* threads waiting to service incoming transactions. When a process
|
||||
* receives this command, it must spawn a new service thread and
|
||||
* register it via bcENTER_LOOPER.
|
||||
*/
|
||||
|
||||
BR_FINISHED = _IO('r', 14),
|
||||
/*
|
||||
* not currently supported
|
||||
* stop threadpool thread
|
||||
*/
|
||||
|
||||
BR_DEAD_BINDER = _IOR('r', 15, binder_uintptr_t),
|
||||
/*
|
||||
* void *: cookie
|
||||
*/
|
||||
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, binder_uintptr_t),
|
||||
/*
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BR_FAILED_REPLY = _IO('r', 17),
|
||||
/*
|
||||
* The last transaction (either a bcTRANSACTION or
|
||||
* a bcATTEMPT_ACQUIRE) failed (e.g. out of memory). No parameters.
|
||||
*/
|
||||
|
||||
BR_FROZEN_REPLY = _IO('r', 18),
|
||||
/*
|
||||
* The target of the last sync transaction (either a bcTRANSACTION or
|
||||
* a bcATTEMPT_ACQUIRE) is frozen. No parameters.
|
||||
*/
|
||||
|
||||
BR_ONEWAY_SPAM_SUSPECT = _IO('r', 19),
|
||||
/*
|
||||
* Current process sent too many oneway calls to target, and the last
|
||||
* asynchronous transaction makes the allocated async buffer size exceed
|
||||
* detection threshold. No parameters.
|
||||
*/
|
||||
|
||||
BR_TRANSACTION_PENDING_FROZEN = _IO('r', 20),
|
||||
/*
|
||||
* The target of the last async transaction is frozen. No parameters.
|
||||
*/
|
||||
|
||||
BR_FROZEN_BINDER = _IOR('r', 21, struct binder_frozen_state_info),
|
||||
/*
|
||||
* The cookie and a boolean (is_frozen) that indicates whether the process
|
||||
* transitioned into a frozen or an unfrozen state.
|
||||
*/
|
||||
|
||||
BR_CLEAR_FREEZE_NOTIFICATION_DONE = _IOR('r', 22, binder_uintptr_t),
|
||||
/*
|
||||
* void *: cookie
|
||||
*/
|
||||
};
|
||||
|
||||
enum binder_driver_command_protocol {
|
||||
BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
|
||||
BC_REPLY = _IOW('c', 1, struct binder_transaction_data),
|
||||
/*
|
||||
* binder_transaction_data: the sent command.
|
||||
*/
|
||||
|
||||
BC_ACQUIRE_RESULT = _IOW('c', 2, __s32),
|
||||
/*
|
||||
* not currently supported
|
||||
* int: 0 if the last BR_ATTEMPT_ACQUIRE was not successful.
|
||||
* Else you have acquired a primary reference on the object.
|
||||
*/
|
||||
|
||||
BC_FREE_BUFFER = _IOW('c', 3, binder_uintptr_t),
|
||||
/*
|
||||
* void *: ptr to transaction data received on a read
|
||||
*/
|
||||
|
||||
BC_INCREFS = _IOW('c', 4, __u32),
|
||||
BC_ACQUIRE = _IOW('c', 5, __u32),
|
||||
BC_RELEASE = _IOW('c', 6, __u32),
|
||||
BC_DECREFS = _IOW('c', 7, __u32),
|
||||
/*
|
||||
* int: descriptor
|
||||
*/
|
||||
|
||||
BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie),
|
||||
BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie),
|
||||
/*
|
||||
* void *: ptr to binder
|
||||
* void *: cookie for binder
|
||||
*/
|
||||
|
||||
BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc),
|
||||
/*
|
||||
* not currently supported
|
||||
* int: priority
|
||||
* int: descriptor
|
||||
*/
|
||||
|
||||
BC_REGISTER_LOOPER = _IO('c', 11),
|
||||
/*
|
||||
* No parameters.
|
||||
* Register a spawned looper thread with the device.
|
||||
*/
|
||||
|
||||
BC_ENTER_LOOPER = _IO('c', 12),
|
||||
BC_EXIT_LOOPER = _IO('c', 13),
|
||||
/*
|
||||
* No parameters.
|
||||
* These two commands are sent as an application-level thread
|
||||
* enters and exits the binder loop, respectively. They are
|
||||
* used so the binder can have an accurate count of the number
|
||||
* of looping threads it has available.
|
||||
*/
|
||||
|
||||
BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14,
|
||||
struct binder_handle_cookie),
|
||||
/*
|
||||
* int: handle
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15,
|
||||
struct binder_handle_cookie),
|
||||
/*
|
||||
* int: handle
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BC_DEAD_BINDER_DONE = _IOW('c', 16, binder_uintptr_t),
|
||||
/*
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BC_TRANSACTION_SG = _IOW('c', 17, struct binder_transaction_data_sg),
|
||||
BC_REPLY_SG = _IOW('c', 18, struct binder_transaction_data_sg),
|
||||
/*
|
||||
* binder_transaction_data_sg: the sent command.
|
||||
*/
|
||||
|
||||
BC_REQUEST_FREEZE_NOTIFICATION =
|
||||
_IOW('c', 19, struct binder_handle_cookie),
|
||||
/*
|
||||
* int: handle
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BC_CLEAR_FREEZE_NOTIFICATION = _IOW('c', 20,
|
||||
struct binder_handle_cookie),
|
||||
/*
|
||||
* int: handle
|
||||
* void *: cookie
|
||||
*/
|
||||
|
||||
BC_FREEZE_NOTIFICATION_DONE = _IOW('c', 21, binder_uintptr_t),
|
||||
/*
|
||||
* void *: cookie
|
||||
*/
|
||||
};
|
||||
|
||||
#endif /* _UAPI_LINUX_BINDER_H */
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef LOG_TAG
|
||||
#define LOG_TAG "TEESimulator"
|
||||
#endif
|
||||
|
||||
#include "../logging.hpp"
|
||||
@@ -0,0 +1,499 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // For std::swap in UniqueFd
|
||||
#include <limits.h> // For PATH_MAX
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "lsplt.hpp"
|
||||
|
||||
// Macros for syscall error checking. These are typically used after remote
|
||||
// syscall emulation.
|
||||
#define SYSCALL_IS_ERR(e) (((unsigned long)e) > -4096UL) // Checks if a syscall return value indicates an error.
|
||||
#define SYSCALL_ERR(e) (-(int)(e)) // Converts a syscall error value to a negative errno.
|
||||
|
||||
// Architecture-specific register definitions.
|
||||
// These macros abstract away the differences in register names across architectures,
|
||||
// allowing for generic code that manipulates `struct user_regs_struct`.
|
||||
#if defined(__x86_64__)
|
||||
# define REG_SP rsp // Stack pointer register
|
||||
# define REG_IP rip // Instruction pointer register
|
||||
# define REG_RET rax // Return value register
|
||||
# define REG_NR orig_rax // Syscall number register
|
||||
# define REG_SYS_ARG0 rdi // First syscall argument register
|
||||
#elif defined(__i386__)
|
||||
# define REG_SP esp
|
||||
# define REG_IP eip
|
||||
# define REG_RET eax
|
||||
# define REG_NR orig_eax
|
||||
# define REG_SYS_ARG0 ebx
|
||||
#elif defined(__aarch64__)
|
||||
# define REG_SP sp // Stack pointer register (AArch64)
|
||||
# define REG_IP pc // Program counter register (AArch64)
|
||||
# define REG_RET regs[0] // Return value register (x0)
|
||||
# define REG_NR regs[8] // Syscall number register (x8)
|
||||
# define REG_SYS_ARG0 regs[0] // First syscall argument register (x0)
|
||||
#elif defined(__arm__)
|
||||
# define REG_SP uregs[13] // Stack pointer register (R13)
|
||||
# define REG_IP uregs[15] // Program counter register (R15)
|
||||
# define REG_RET uregs[0] // Return value register (R0)
|
||||
# define REG_NR uregs[7] // Syscall number register (R7)
|
||||
# define REG_SYS_ARG0 uregs[0] // First syscall argument register (R0)
|
||||
# define user_regs_struct user_regs // ARM's equivalent to user_regs_struct is user_regs
|
||||
# define SYS_mmap SYS_mmap2 // ARM uses mmap2 syscall
|
||||
#endif
|
||||
|
||||
// --- Remote Memory Operations ---
|
||||
|
||||
/**
|
||||
* @brief Writes data to the remote process's memory.
|
||||
* @param pid The target process ID.
|
||||
* @param remote_addr The target address in the remote process.
|
||||
* @param buf A pointer to the local buffer containing data to write.
|
||||
* @param len The number of bytes to write.
|
||||
* @param use_proc_mem If true, uses /proc/<pid>/mem; otherwise, uses
|
||||
* process_vm_writev.
|
||||
* @return The number of bytes written, or -1 on error.
|
||||
*/
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false);
|
||||
|
||||
/**
|
||||
* @brief Reads data from the remote process's memory.
|
||||
* @param pid The target process ID.
|
||||
* @param remote_addr The source address in the remote process.
|
||||
* @param buf A pointer to the local buffer to store the read data.
|
||||
* @param len The number of bytes to read.
|
||||
* @return The number of bytes read, or -1 on error.
|
||||
*/
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
|
||||
|
||||
// --- Remote Register Operations ---
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current CPU registers of the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to a `user_regs_struct` to store the registers.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool get_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
/**
|
||||
* @brief Sets the CPU registers of the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to a `user_regs_struct` containing the registers to set.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
// --- Module and Symbol Resolution ---
|
||||
|
||||
/**
|
||||
* @brief Gets a descriptive string of the memory region containing a given
|
||||
* address.
|
||||
* @param map_info A vector of `lsplt::MapInfo` for the process.
|
||||
* @param addr The address to look up.
|
||||
* @return A string representing the memory region (e.g., "path perms"), or "<unknown>".
|
||||
*/
|
||||
std::string get_addr_mem_region(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr);
|
||||
|
||||
/**
|
||||
* @brief Finds the base address of a module in a process's memory map.
|
||||
* @param map_info A vector of `lsplt::MapInfo` for the process.
|
||||
* @param module_suffix The suffix of the module path (e.g., "libc.so").
|
||||
* @return The base address of the module, or nullptr if not found.
|
||||
*/
|
||||
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
|
||||
|
||||
/**
|
||||
* @brief Finds the address of a function in a remote process by resolving it
|
||||
* locally and calculating the offset.
|
||||
*
|
||||
* This function opens the module locally, finds the symbol address,
|
||||
* calculates its offset from the local module base, and then adds that offset to the remote module base.
|
||||
*
|
||||
* @param local_map_info Memory map of the local (injector) process.
|
||||
* @param remote_map_info Memory map of the remote (target) process.
|
||||
* @param module_name The name of the module (e.g., "libc.so").
|
||||
* @param function_name The name of the function (e.g., "open").
|
||||
* @return The remote address of the function, or nullptr if not found.
|
||||
*/
|
||||
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info,
|
||||
const std::vector<lsplt::MapInfo> &remote_map_info, std::string_view module_name,
|
||||
std::string_view function_name);
|
||||
|
||||
/**
|
||||
* @brief Finds a suitable return address within a specific module in the remote
|
||||
* process.
|
||||
*
|
||||
* This typically looks for a non-executable segment of the module to return to,
|
||||
* as `PTRACE_CONT` will resume execution at the specified instruction pointer.
|
||||
*
|
||||
* @param map_info A vector of `lsplt::MapInfo` for the remote process.
|
||||
* @param module_suffix The suffix of the module path (e.g., "libc.so").
|
||||
* @return A pointer to a suitable return address, or nullptr if not found.
|
||||
*/
|
||||
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
|
||||
|
||||
// --- Remote Stack Manipulation ---
|
||||
|
||||
/**
|
||||
* @brief Aligns the stack pointer (`REG_SP`) to ensure proper stack frame setup.
|
||||
* @param regs A reference to the `user_regs_struct` to modify.
|
||||
* @param preserve_bytes Number of bytes to preserve below the new stack pointer.
|
||||
*/
|
||||
void align_stack(struct user_regs_struct ®s, uintptr_t preserve_bytes = 0);
|
||||
|
||||
/**
|
||||
* @brief Pushes a block of memory onto the remote process's stack.
|
||||
*
|
||||
* This function decrements the stack pointer, aligns it, and then writes the data.
|
||||
*
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
|
||||
* @param data A pointer to the local data to push.
|
||||
* @param length The number of bytes to push.
|
||||
* @return The remote address where the data was pushed, or 0 on error.
|
||||
*/
|
||||
uintptr_t push_memory(int pid, struct user_regs_struct ®s, const void *data, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Pushes a null-terminated string onto the remote process's stack.
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
|
||||
* @param str The null-terminated C-style string to push.
|
||||
* @return The remote address where the string was pushed, or 0 on error.
|
||||
*/
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str);
|
||||
|
||||
// --- Remote Function Call Emulation ---
|
||||
|
||||
/**
|
||||
* @brief Prepares and initiates a remote function call in the target process.
|
||||
*
|
||||
* This function sets up registers (arguments, return address, instruction pointer) and
|
||||
* then continues the target process execution using PTRACE_CONT.
|
||||
*
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to the `user_regs_struct` (will be modified).
|
||||
* @param func_addr The remote address of the function to call.
|
||||
* @param return_addr The address in the remote process where execution should
|
||||
* resume after the call.
|
||||
* @param args A vector of `uintptr_t` representing the function arguments.
|
||||
* @return True if the remote call was successfully initiated, false otherwise.
|
||||
*/
|
||||
bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args);
|
||||
|
||||
/**
|
||||
* @brief Waits for and finalizes a remote function call, retrieving its return value.
|
||||
*
|
||||
* This function waits for the target process to stop after a remote call and
|
||||
* then retrieves the return value from the appropriate register.
|
||||
*
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to the `user_regs_struct` (will be updated with post-call registers).
|
||||
* @param expected_return_addr The address where the remote call was expected to return to.
|
||||
* Used for error checking (e.g., if a crash occurs elsewhere).
|
||||
* @return The return value of the remote function, or 0 on error.
|
||||
*/
|
||||
uintptr_t remote_post_call(int pid, struct user_regs_struct ®s, uintptr_t expected_return_addr);
|
||||
|
||||
/**
|
||||
* @brief Executes a complete remote function call (pre-call, continue,
|
||||
* post-call).
|
||||
* @param pid The target process ID.
|
||||
* @param regs A reference to the `user_regs_struct` (will be modified).
|
||||
* @param func_addr The remote address of the function to call.
|
||||
* @param return_addr The address in the remote process where execution should resume after the call.
|
||||
* @param args A vector of `uintptr_t` representing the function arguments.
|
||||
* @return The return value of the remote function, or 0 on error.
|
||||
*/
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<uintptr_t> &args);
|
||||
|
||||
// --- Process Management and Ptrace Utilities ---
|
||||
|
||||
/**
|
||||
* @brief Forks twice to create a daemon process, returning 0 in the daemon,
|
||||
* or the child pid in parent.
|
||||
* @return 0 in the grand-child (daemon), PID of first child in parent, or -1 on error.
|
||||
*/
|
||||
int fork_dont_care();
|
||||
|
||||
/**
|
||||
* @brief Waits for the target process to stop due to ptrace.
|
||||
*
|
||||
* This function handles `EINTR` and ensures the process is actually stopped.
|
||||
*
|
||||
* @param pid The target process ID.
|
||||
* @param status A pointer to an integer to store the wait status.
|
||||
* @param flags Flags for `waitpid` (e.g., `__WALL`).
|
||||
* @return True if the process successfully stopped, false otherwise.
|
||||
*/
|
||||
bool wait_for_trace(int pid, int *status, int flags);
|
||||
|
||||
/**
|
||||
* @brief Parses the wait status integer into a human-readable string.
|
||||
* @param status The status integer returned by `waitpid`.
|
||||
* @return A string describing the wait status.
|
||||
*/
|
||||
std::string parse_status(int status);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the executable path of a process.
|
||||
* @param pid The target process ID.
|
||||
* @return The absolute path to the executable, or an empty string on error.
|
||||
*/
|
||||
std::string get_program(int pid);
|
||||
|
||||
/**
|
||||
* @brief Gets the command-line arguments of a process.
|
||||
* @param pid The target process ID.
|
||||
* @return A vector of strings representing the command-line arguments.
|
||||
*/
|
||||
std::vector<std::string> get_cmdline(int pid);
|
||||
|
||||
/**
|
||||
* @brief Parses the `exec` status of a process
|
||||
* @param pid The target process ID.
|
||||
* @return A string representing the `exec` status (placeholder).
|
||||
*/
|
||||
std::string parse_exec(int pid);
|
||||
|
||||
/**
|
||||
* @brief Skips the current syscall in the target process
|
||||
* @param pid The target process ID.
|
||||
* @return True on success, false on failure (placeholder).
|
||||
*/
|
||||
bool skip_syscall(int pid);
|
||||
|
||||
/**
|
||||
* @brief Executes a syscall in the remote process using ptrace.
|
||||
* @param pid The target process ID.
|
||||
* @param ret Reference to store the syscall return value.
|
||||
* @param nr The syscall number.
|
||||
* @param arg0 to arg5 - Syscall arguments.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0,
|
||||
uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0);
|
||||
|
||||
/**
|
||||
* @brief Switches the mount namespace of the current process to that of the target PID, or restores it.
|
||||
* @param pid If non-zero, switches to the namespace of `pid`.
|
||||
* If zero, restores to the namespace stored in `*fd`.
|
||||
* @param fd On entry (pid != 0), points to an int to store the original namespace FD.
|
||||
* On entry (pid == 0), points to the FD of the namespace to restore to.
|
||||
* FD is consumed/set to kInvalidFd on successful restore.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool switch_mnt_ns(int pid, int *fd);
|
||||
|
||||
/**
|
||||
* @brief Remotely calls mmap in the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param addr The preferred starting address for the new mapping.
|
||||
* @param size The length of the mapping.
|
||||
* @param prot Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC).
|
||||
* @param flags Mapping flags (MAP_PRIVATE, MAP_ANONYMOUS, etc.).
|
||||
* @param fd File descriptor to map from (or -1 for anonymous).
|
||||
* @param offset Offset into the file (or 0 for anonymous).
|
||||
* @return The starting address of the new mapping, or MAP_FAILED on error.
|
||||
*/
|
||||
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
|
||||
|
||||
/**
|
||||
* @brief Remotely calls munmap in the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param addr The starting address of the region to unmap.
|
||||
* @param size The length of the region to unmap.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool remote_munmap(int pid, uintptr_t addr, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Remotely calls open in the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param path_addr The remote address of the path string.
|
||||
* @param flags Open flags (O_RDONLY, O_WRONLY, O_CREAT, etc.).
|
||||
* @return The file descriptor in the remote process, or -1 on error.
|
||||
*/
|
||||
int remote_open(int pid, uintptr_t path_addr, int flags);
|
||||
|
||||
/**
|
||||
* @brief Remotely calls close in the target process.
|
||||
* @param pid The target process ID.
|
||||
* @param fd The file descriptor in the remote process to close.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool remote_close(int pid, int fd);
|
||||
|
||||
/**
|
||||
* @brief Waits for a child process to terminate.
|
||||
* @param pid The child process ID.
|
||||
* @return The exit status of the child, or -1 on error.
|
||||
*/
|
||||
int wait_for_child(int pid);
|
||||
|
||||
/**
|
||||
* @brief Determines the ELF class (32-bit or 64-bit) of an executable file.
|
||||
* @param path The path to the ELF file.
|
||||
* @return `ELFCLASS32` for 32-bit, `ELFCLASS64` for 64-bit, or `ELFNONE` on error.
|
||||
*/
|
||||
int get_elf_class(std::string_view path);
|
||||
|
||||
// --- Miscellaneous Utilities ---
|
||||
|
||||
constexpr size_t kMaxPathLength = PATH_MAX; // Max path length, consistent with main.cpp
|
||||
constexpr size_t kDefaultMagicLength = 16; // Default length for generated magic strings.
|
||||
|
||||
/**
|
||||
* @brief Generates a random alphanumeric string.
|
||||
* @param length The desired length of the magic string.
|
||||
* @return The generated magic string.
|
||||
*/
|
||||
std::string generateMagic(size_t length);
|
||||
|
||||
/**
|
||||
* @brief Sets the SELinux security context of a file.
|
||||
* @param file_path The path to the file.
|
||||
* @param security_context The new security context string.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int setfilecon(const char *file_path, const char *security_context);
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper for file descriptors.
|
||||
*
|
||||
* This class automatically closes the file descriptor when it goes out of scope.
|
||||
*/
|
||||
class UniqueFd {
|
||||
using Fd = int; // Alias for file descriptor type.
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor. Initializes with an invalid FD.
|
||||
*/
|
||||
UniqueFd() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructor that takes an existing file descriptor.
|
||||
* @param fd The file descriptor to manage.
|
||||
*/
|
||||
UniqueFd(Fd fd) : fd_(fd) {}
|
||||
|
||||
/**
|
||||
* @brief Destructor. Closes the managed file descriptor if valid.
|
||||
*/
|
||||
~UniqueFd() {
|
||||
if (fd_ >= 0)
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
// Delete copy constructor and assignment operator to prevent double-free issues.
|
||||
UniqueFd(const UniqueFd &) = delete;
|
||||
UniqueFd &operator=(const UniqueFd &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor. Transfers ownership of the file descriptor.
|
||||
* @param other The `UniqueFd` object to move from.
|
||||
*/
|
||||
UniqueFd(UniqueFd &&other) noexcept {
|
||||
std::swap(fd_, other.fd_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator. Transfers ownership of the file descriptor.
|
||||
* @param other The `UniqueFd` object to move from.
|
||||
* @return A reference to this `UniqueFd` object.
|
||||
*/
|
||||
UniqueFd &operator=(UniqueFd &&other) noexcept {
|
||||
if (this != &other) { // Handle self-assignment
|
||||
if (fd_ >= 0)
|
||||
close(fd_); // Close current FD before taking ownership
|
||||
fd_ = -1; // Invalidate current FD before swap
|
||||
std::swap(fd_, other.fd_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assignment from raw int FD. Closes the current FD.
|
||||
*/
|
||||
UniqueFd &operator=(Fd fd) {
|
||||
if (fd_ >= 0) {
|
||||
close(fd_);
|
||||
}
|
||||
fd_ = fd;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allows implicit conversion to the underlying file descriptor type.
|
||||
* @return The managed file descriptor.
|
||||
*/
|
||||
operator const Fd &() const {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
private:
|
||||
Fd fd_ = -1; // The managed file descriptor, initialized to invalid.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sets the SELinux context for newly created sockets.
|
||||
*
|
||||
* This allows the injector to create sockets with a specific security context
|
||||
* that might be required for interaction with target processes under SELinux.
|
||||
* It attempts to write to `/proc/thread-self/attr/sockcreate` or a process-specific fallback.
|
||||
*
|
||||
* @param security_context The SELinux context string to set.
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool set_sockcreate_con(const char *security_context);
|
||||
|
||||
// --- Ptrace Event and Signal Parsing ---
|
||||
|
||||
#define WPTEVENT(x) (x >> 16) // Macro to extract the ptrace event code from wait status.
|
||||
#define CASE_CONST_RETURN(x) \
|
||||
case x: \
|
||||
return #x; // Helper macro for switch-case to return string literal.
|
||||
|
||||
/**
|
||||
* @brief Parses a ptrace event code into a human-readable string.
|
||||
* @param status The wait status containing the ptrace event code.
|
||||
* @return A string representing the ptrace event.
|
||||
*/
|
||||
inline const char *parse_ptrace_event(int status) {
|
||||
status = WPTEVENT(status); // Extract the event code.
|
||||
switch (status) {
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_STOP) // Not a standard event, but sometimes
|
||||
// seen for special stops
|
||||
default:
|
||||
return "(no event)"; // Default for unknown or no event.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the abbreviated name of a signal.
|
||||
* @param sig The signal number.
|
||||
* @return The abbreviated signal name (e.g., "SIGSEGV"), or "(unknown)".
|
||||
*/
|
||||
inline const char *sigabbrev_np(int sig) {
|
||||
// NSIG is the total number of signals, sys_signame array is indexed by signal
|
||||
// number. Note: sys_signame is part of glibc and may require _GNU_SOURCE or
|
||||
// similar. Assuming its availability for professional refactor.
|
||||
if (sig > 0 && sig < NSIG)
|
||||
return sys_signame[sig];
|
||||
return "(unknown)";
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,270 @@
|
||||
#include "binder/Binder.h"
|
||||
#include "binder/BpBinder.h"
|
||||
#include "binder/IInterface.h"
|
||||
#include "binder/IPCThreadState.h"
|
||||
#include "binder/IServiceManager.h"
|
||||
#include "binder/RpcSession.h"
|
||||
#include "binder/Status.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
IInterface::IInterface() {}
|
||||
IInterface::~IInterface() {}
|
||||
|
||||
IBinder::IBinder() {}
|
||||
IBinder::~IBinder() {}
|
||||
sp<IInterface> IBinder::queryLocalInterface(const String16 &) {
|
||||
return nullptr;
|
||||
}
|
||||
BBinder *IBinder::localBinder() {
|
||||
return nullptr;
|
||||
}
|
||||
BpBinder *IBinder::remoteBinder() {
|
||||
return nullptr;
|
||||
}
|
||||
bool IBinder::checkSubclass(const void *) const {
|
||||
return false;
|
||||
}
|
||||
void IBinder::withLock(const std::function<void()> &) {}
|
||||
|
||||
#ifdef __LP64__
|
||||
static_assert(sizeof(IBinder) == 24);
|
||||
static_assert(sizeof(BBinder) == 40);
|
||||
#else
|
||||
static_assert(sizeof(IBinder) == 12);
|
||||
static_assert(sizeof(BBinder) == 20);
|
||||
#endif
|
||||
|
||||
BBinder::BBinder() {}
|
||||
BBinder::~BBinder() {}
|
||||
|
||||
const String16 &BBinder::getInterfaceDescriptor() const {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
bool BBinder::isBinderAlive() const {
|
||||
return false;
|
||||
}
|
||||
status_t BBinder::pingBinder() {
|
||||
return 0;
|
||||
}
|
||||
status_t BBinder::dump(int, const Vector<String16> &) {
|
||||
return 0;
|
||||
}
|
||||
status_t BBinder::transact(uint32_t, const Parcel &, Parcel *, uint32_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t BBinder::linkToDeath(const sp<DeathRecipient> &, void *, uint32_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t BBinder::unlinkToDeath(const wp<DeathRecipient> &, void *, uint32_t, wp<DeathRecipient> *) {
|
||||
return 0;
|
||||
}
|
||||
void *BBinder::attachObject(const void *, void *, void *, object_cleanup_func) {
|
||||
return nullptr;
|
||||
}
|
||||
void *BBinder::findObject(const void *) const {
|
||||
return nullptr;
|
||||
}
|
||||
void *BBinder::detachObject(const void *) {
|
||||
return nullptr;
|
||||
}
|
||||
void BBinder::withLock(const std::function<void()> &) {}
|
||||
BBinder *BBinder::localBinder() {
|
||||
return nullptr;
|
||||
}
|
||||
status_t BBinder::onTransact(uint32_t, const Parcel &, Parcel *, uint32_t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IPCThreadState *IPCThreadState::self() {
|
||||
return nullptr;
|
||||
}
|
||||
IPCThreadState *IPCThreadState::selfOrNull() {
|
||||
return nullptr;
|
||||
}
|
||||
pid_t IPCThreadState::getCallingPid() const {
|
||||
return 0;
|
||||
}
|
||||
const char *IPCThreadState::getCallingSid() const {
|
||||
return nullptr;
|
||||
}
|
||||
uid_t IPCThreadState::getCallingUid() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
static_assert(sizeof(Parcel) == 120);
|
||||
#else
|
||||
static_assert(sizeof(Parcel) == 60);
|
||||
#endif
|
||||
|
||||
Parcel::Parcel() {}
|
||||
Parcel::~Parcel() {}
|
||||
const uint8_t *Parcel::data() const {
|
||||
return nullptr;
|
||||
}
|
||||
size_t Parcel::dataSize() const {
|
||||
return 0;
|
||||
}
|
||||
size_t Parcel::dataAvail() const {
|
||||
return 0;
|
||||
}
|
||||
size_t Parcel::dataPosition() const {
|
||||
return 0;
|
||||
}
|
||||
size_t Parcel::dataCapacity() const {
|
||||
return 0;
|
||||
}
|
||||
size_t Parcel::dataBufferSize() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::setDataSize(size_t) {
|
||||
return 0;
|
||||
}
|
||||
void Parcel::setDataPosition(size_t) const {}
|
||||
status_t Parcel::setDataCapacity(size_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::setData(const uint8_t *, size_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::appendFrom(const Parcel *, size_t, size_t) {
|
||||
return 0;
|
||||
}
|
||||
binder::Status Parcel::enforceNoDataAvail() const {
|
||||
return {};
|
||||
}
|
||||
void Parcel::setEnforceNoDataAvail(bool) {}
|
||||
void Parcel::freeData() {}
|
||||
status_t Parcel::write(const void *, size_t) {
|
||||
return 0;
|
||||
}
|
||||
void *Parcel::writeInplace(size_t) {
|
||||
return nullptr;
|
||||
}
|
||||
status_t Parcel::writeInt32(int32_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeUint32(uint32_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeInt64(int64_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeUint64(uint64_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeFloat(float) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeDouble(double) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeCString(const char *) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeString8(const char *, size_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeStrongBinder(const sp<IBinder> &) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeBool(bool) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeChar(char16_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeByte(int8_t) {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::writeNoException() {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::read(void *, size_t) const {
|
||||
return 0;
|
||||
}
|
||||
const void *Parcel::readInplace(size_t) const {
|
||||
return nullptr;
|
||||
}
|
||||
int32_t Parcel::readInt32() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readInt32(int32_t *) const {
|
||||
return 0;
|
||||
}
|
||||
uint32_t Parcel::readUint32() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readUint32(uint32_t *) const {
|
||||
return 0;
|
||||
}
|
||||
int64_t Parcel::readInt64() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readInt64(int64_t *) const {
|
||||
return 0;
|
||||
}
|
||||
uint64_t Parcel::readUint64() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readUint64(uint64_t *) const {
|
||||
return 0;
|
||||
}
|
||||
float Parcel::readFloat() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readFloat(float *) const {
|
||||
return 0;
|
||||
}
|
||||
double Parcel::readDouble() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readDouble(double *) const {
|
||||
return 0;
|
||||
}
|
||||
bool Parcel::readBool() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readBool(bool *) const {
|
||||
return 0;
|
||||
}
|
||||
char16_t Parcel::readChar() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readChar(char16_t *) const {
|
||||
return 0;
|
||||
}
|
||||
int8_t Parcel::readByte() const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readByte(int8_t *) const {
|
||||
return 0;
|
||||
}
|
||||
sp<IBinder> Parcel::readStrongBinder() const {
|
||||
return nullptr;
|
||||
}
|
||||
status_t Parcel::readStrongBinder(sp<IBinder> *) const {
|
||||
return 0;
|
||||
}
|
||||
status_t Parcel::readNullableStrongBinder(sp<IBinder> *) const {
|
||||
return 0;
|
||||
}
|
||||
int32_t Parcel::readExceptionCode() const {
|
||||
return 0;
|
||||
}
|
||||
int Parcel::readFileDescriptor() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IServiceManager::IServiceManager() {}
|
||||
IServiceManager::~IServiceManager() {}
|
||||
const String16 &IServiceManager::getInterfaceDescriptor() const {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
sp<IServiceManager> defaultServiceManager() {
|
||||
return nullptr;
|
||||
}
|
||||
void setDefaultServiceManager(const sp<IServiceManager> &) {}
|
||||
|
||||
} // namespace android
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "utils/RefBase.h"
|
||||
#include "utils/String16.h"
|
||||
#include "utils/String8.h"
|
||||
#include "utils/StrongPointer.h"
|
||||
|
||||
namespace android {
|
||||
void RefBase::incStrong(const void *id) const {}
|
||||
|
||||
void RefBase::incStrongRequireStrong(const void *id) const {}
|
||||
|
||||
void RefBase::decStrong(const void *id) const {}
|
||||
|
||||
void RefBase::forceIncStrong(const void *id) const {}
|
||||
|
||||
RefBase::weakref_type *RefBase::createWeak(const void *id) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::weakref_type *RefBase::getWeakRefs() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefBase::RefBase() : mRefs(nullptr) {}
|
||||
RefBase::~RefBase() {}
|
||||
|
||||
void RefBase::onFirstRef() {}
|
||||
void RefBase::onLastStrongRef(const void *id) {}
|
||||
bool RefBase::onIncStrongAttempted(uint32_t flags, const void *id) {
|
||||
return false;
|
||||
}
|
||||
void RefBase::onLastWeakRef(const void *id) {}
|
||||
|
||||
RefBase *RefBase::weakref_type::refBase() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RefBase::weakref_type::incWeak(const void *id) {}
|
||||
void RefBase::weakref_type::incWeakRequireWeak(const void *id) {}
|
||||
void RefBase::weakref_type::decWeak(const void *id) {}
|
||||
|
||||
bool RefBase::weakref_type::attemptIncStrong(const void *id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RefBase::weakref_type::attemptIncWeak(const void *id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void sp_report_race() {}
|
||||
|
||||
String8::String8() {}
|
||||
|
||||
String16::String16() {}
|
||||
|
||||
String16::String16(const String16 &o) {}
|
||||
|
||||
String16::String16(String16 &&o) noexcept {}
|
||||
|
||||
String16::String16(const char *o) {}
|
||||
|
||||
String16::~String16() {}
|
||||
} // namespace android
|
||||
@@ -0,0 +1,76 @@
|
||||
// Fork-based supervisor for instant daemon restart
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
static volatile sig_atomic_t should_exit = 0;
|
||||
|
||||
static void signal_handler(int sig) {
|
||||
should_exit = 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <daemon> [args...]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Forward termination signals to exit cleanly
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
const char *daemon_path = argv[1];
|
||||
char **daemon_argv = &argv[1];
|
||||
|
||||
int backoff_ms = 500;
|
||||
|
||||
while (!should_exit) {
|
||||
struct timespec child_start;
|
||||
clock_gettime(CLOCK_MONOTONIC, &child_start);
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid < 0) {
|
||||
perror("fork failed");
|
||||
usleep(100000); // 100ms backoff on fork failure
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child: become the daemon
|
||||
prctl(PR_SET_PDEATHSIG, SIGKILL); // Die if parent dies
|
||||
setpriority(PRIO_PROCESS, 0, 10); // lower CPU priority than foreground
|
||||
execv(daemon_path, daemon_argv);
|
||||
perror("execv failed");
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
// Parent: wait for child to exit
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (should_exit) break;
|
||||
|
||||
// Exponential backoff on rapid crashes, reset if child was stable
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
long lived_ms = (now.tv_sec - child_start.tv_sec) * 1000 +
|
||||
(now.tv_nsec - child_start.tv_nsec) / 1000000;
|
||||
|
||||
if (lived_ms > 30000) {
|
||||
backoff_ms = 500;
|
||||
} else {
|
||||
usleep(backoff_ms * 1000);
|
||||
if (backoff_ms < 30000) backoff_ms *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.matrix.TEESimulator
|
||||
|
||||
import android.app.ActivityThread
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.os.Build
|
||||
import android.os.Looper
|
||||
import java.security.Security
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.NativeCertGen
|
||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||
|
||||
/**
|
||||
* Main application object for TEESimulator. This object manages the application's lifecycle,
|
||||
* including initialization of interceptors and maintaining the service's primary execution loop.
|
||||
*/
|
||||
object App {
|
||||
// The delay in milliseconds before retrying to initialize the interceptor.
|
||||
private const val RETRY_DELAY_MS = 1000L
|
||||
|
||||
/**
|
||||
* The main entry point of the TEESimulator application.
|
||||
*
|
||||
* @param args Command line arguments (not used).
|
||||
*/
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SystemLogger.info("Welcome to TEESimulator!")
|
||||
|
||||
try {
|
||||
// Initialize the Android framework environment
|
||||
prepareEnvironment()
|
||||
// Initialize and start the appropriate keystore interceptors.
|
||||
initializeInterceptors()
|
||||
|
||||
// Load the package configuration.
|
||||
ConfigurationManager.initialize()
|
||||
// Set up the device's boot key and hash, which are crucial for attestation.
|
||||
AndroidDeviceUtils.setupBootKeyAndHash()
|
||||
|
||||
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
||||
// We must remove the system provider first to ensure the full Bouncy Castle library
|
||||
// (packaged with the app) is used.
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
|
||||
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
|
||||
|
||||
// This starts the message queue processing. It blocks here indefinitely
|
||||
// processing messages until Looper.myLooper().quit() is called.
|
||||
Looper.loop()
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
||||
private fun prepareEnvironment() {
|
||||
// 1. Prepare Main Looper
|
||||
if (Looper.getMainLooper() == null) {
|
||||
@Suppress("deprecation") Looper.prepareMainLooper()
|
||||
}
|
||||
|
||||
// 2. Initialize ActivityThread for the current process
|
||||
val activityThread = ActivityThread.systemMain()
|
||||
|
||||
// 3. Get the system context
|
||||
val systemContext = activityThread.getSystemContext()
|
||||
|
||||
// 4. Create a dummy Application object and attach the context
|
||||
val app = Application()
|
||||
val attachMethod =
|
||||
ContextWrapper::class.java.getDeclaredMethod("attachBaseContext", Context::class.java)
|
||||
attachMethod.isAccessible = true
|
||||
attachMethod.invoke(app, systemContext)
|
||||
|
||||
// 5. Inject this application object into ActivityThread's mInitialApplication field.
|
||||
// This is what KeyStore.getApplicationContext() looks for.
|
||||
val mInitialApplicationField =
|
||||
ActivityThread::class.java.getDeclaredField("mInitialApplication")
|
||||
mInitialApplicationField.isAccessible = true
|
||||
mInitialApplicationField.set(activityThread, app)
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
|
||||
* retries initialization until it succeeds.
|
||||
*/
|
||||
private fun initializeInterceptors() {
|
||||
val interceptor = selectKeystoreInterceptor()
|
||||
|
||||
// Continuously try to run the interceptor until it's successfully initialized.
|
||||
while (!interceptor.tryRunKeystoreInterceptor()) {
|
||||
SystemLogger.debug("Retrying interceptor initialization...")
|
||||
Thread.sleep(RETRY_DELAY_MS)
|
||||
}
|
||||
|
||||
SystemLogger.info("Interceptors initialized successfully.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which keystore interceptor to use based on the device's Android version.
|
||||
*
|
||||
* @return The appropriate keystore interceptor instance.
|
||||
*/
|
||||
private fun selectKeystoreInterceptor(): AbstractKeystoreInterceptor =
|
||||
when {
|
||||
// For Android Q (10) and R (11), use the original KeystoreInterceptor.
|
||||
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
|
||||
SystemLogger.info(
|
||||
"Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})"
|
||||
)
|
||||
android.security.keystore.AndroidKeyStoreProvider.install()
|
||||
KeystoreInterceptor
|
||||
}
|
||||
// For Android S (12) and newer, use the Keystore2Interceptor.
|
||||
else -> {
|
||||
SystemLogger.info(
|
||||
"Using Keystore2Interceptor for Android S and later (SDK ${Build.VERSION.SDK_INT})"
|
||||
)
|
||||
android.security.keystore2.AndroidKeyStoreProvider.install()
|
||||
Keystore2Interceptor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import org.bouncycastle.asn1.ASN1Boolean
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.ASN1Enumerated
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.DERNull
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import org.bouncycastle.asn1.DERSet
|
||||
import org.bouncycastle.asn1.DERTaggedObject
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
|
||||
|
||||
/**
|
||||
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
|
||||
* extension.
|
||||
*/
|
||||
object AttestationBuilder {
|
||||
|
||||
/**
|
||||
* Builds the complete X.509 attestation extension.
|
||||
*
|
||||
* @param params The parsed key generation parameters.
|
||||
* @param uid The UID of the application requesting attestation.
|
||||
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
|
||||
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
|
||||
*/
|
||||
fun buildAttestationExtension(
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): Extension {
|
||||
val keyDescription = buildKeyDescription(params, uid, securityLevel)
|
||||
var formattedString =
|
||||
keyDescription.joinToString(separator = ", ") {
|
||||
AttestationPatcher.formatAsn1Primitive(it)
|
||||
}
|
||||
SystemLogger.verbose("Forged attestation data: ${formattedString}")
|
||||
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the `RootOfTrust` ASN.1 sequence. This contains critical boot state information.
|
||||
*
|
||||
* @param originalRootOfTrust An optional, pre-existing RoT to extract the boot hash from.
|
||||
* @return The constructed [DERSequence] for the Root of Trust.
|
||||
*/
|
||||
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
|
||||
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
|
||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
|
||||
DEROctetString(AndroidDeviceUtils.bootKey)
|
||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
|
||||
ASN1Boolean.TRUE // deviceLocked: true, for security
|
||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
|
||||
ASN1Enumerated(0) // verifiedBootState: Verified
|
||||
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
|
||||
DEROctetString(AndroidDeviceUtils.bootHash)
|
||||
|
||||
return DERSequence(rootOfTrustElements)
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
|
||||
* null value for a given tag indicates that it should be removed from the attestation.
|
||||
*
|
||||
* @param uid The UID of the calling application.
|
||||
* @return A map where keys are attestation tag numbers and values are the desired
|
||||
* [DERTaggedObject] or null to signify removal.
|
||||
*/
|
||||
fun getSimulatedHardwareProperties(uid: Int): Map<Int, DERTaggedObject?> {
|
||||
val properties = mutableMapOf<Int, DERTaggedObject?>()
|
||||
|
||||
// OS Version is always present.
|
||||
properties[AttestationConstants.TAG_OS_VERSION] =
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_OS_VERSION,
|
||||
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
|
||||
)
|
||||
|
||||
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
|
||||
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
|
||||
if (osPatch != DO_NOT_REPORT) {
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_OS_PATCHLEVEL,
|
||||
ASN1Integer(osPatch.toLong()),
|
||||
)
|
||||
} else {
|
||||
null // Signal for removal
|
||||
}
|
||||
|
||||
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
|
||||
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
|
||||
if (vendorPatch != DO_NOT_REPORT) {
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||
ASN1Integer(vendorPatch.toLong()),
|
||||
)
|
||||
} else {
|
||||
null // Signal for removal
|
||||
}
|
||||
|
||||
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
|
||||
SystemLogger.info("Attestation patch levels for uid=$uid: os=$osPatch, vendor=$vendorPatch, boot=$bootPatch")
|
||||
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
|
||||
if (bootPatch != DO_NOT_REPORT) {
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_BOOT_PATCHLEVEL,
|
||||
ASN1Integer(bootPatch.toLong()),
|
||||
)
|
||||
} else {
|
||||
null // Signal for removal
|
||||
}
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
|
||||
private fun buildKeyDescription(
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): ASN1Sequence {
|
||||
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
|
||||
val softwareEnforced = buildSoftwareEnforcedList(uid, securityLevel)
|
||||
|
||||
val fields =
|
||||
arrayOf(
|
||||
ASN1Integer(
|
||||
AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
|
||||
), // attestationVersion
|
||||
ASN1Enumerated(securityLevel), // attestationSecurityLevel
|
||||
ASN1Integer(
|
||||
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
|
||||
), // keymasterVersion
|
||||
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
|
||||
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
|
||||
DEROctetString(ByteArray(0)), // uniqueId
|
||||
softwareEnforced,
|
||||
teeEnforced,
|
||||
)
|
||||
return DERSequence(fields)
|
||||
}
|
||||
|
||||
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
|
||||
private fun buildTeeEnforcedList(
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): DERSequence {
|
||||
val list =
|
||||
mutableListOf<ASN1Encodable>(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_PURPOSE,
|
||||
DERSet(params.purpose.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ALGORITHM,
|
||||
ASN1Integer(params.algorithm.toLong()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_KEY_SIZE,
|
||||
ASN1Integer(params.keySize.toLong()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_DIGEST,
|
||||
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_EC_CURVE,
|
||||
ASN1Integer(params.ecCurve.toLong()),
|
||||
),
|
||||
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ORIGIN,
|
||||
ASN1Integer(0L),
|
||||
), // KeyOrigin.GENERATED
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ROOT_OF_TRUST,
|
||||
buildRootOfTrust(null),
|
||||
),
|
||||
)
|
||||
|
||||
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
|
||||
val simulatedProperties = getSimulatedHardwareProperties(uid)
|
||||
simulatedProperties.values.filterNotNull().forEach { list.add(it) }
|
||||
|
||||
// Add optional device identifiers if they were provided.
|
||||
params.brand?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_BRAND,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.device?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_DEVICE,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.product?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_PRODUCT,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.serial?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_SERIAL,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.imei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.meid?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_MEID,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.manufacturer?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_MANUFACTURER,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
params.model?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_MODEL,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 300) {
|
||||
params.secondImei?.let {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
|
||||
DEROctetString(it),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
|
||||
* Keystore.
|
||||
*/
|
||||
private fun buildSoftwareEnforcedList(uid: Int, securityLevel: Int): DERSequence {
|
||||
val list =
|
||||
mutableListOf<ASN1Encodable>(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_CREATION_DATETIME,
|
||||
ASN1Integer(System.currentTimeMillis()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
|
||||
createApplicationId(uid),
|
||||
),
|
||||
)
|
||||
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
|
||||
list.add(
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_MODULE_HASH,
|
||||
DEROctetString(AndroidDeviceUtils.moduleHash),
|
||||
)
|
||||
)
|
||||
}
|
||||
return DERSequence(list.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a byte array that provides content-based equality. This is necessary for using
|
||||
* signature digests in a Set.
|
||||
*/
|
||||
private data class Digest(val digest: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return digest.contentEquals((other as Digest).digest)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = digest.contentHashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the AttestationApplicationId structure. This structure contains information about the
|
||||
* package(s) and their signing certificates.
|
||||
*
|
||||
* @param uid The UID of the application.
|
||||
* @return A DER-encoded octet string containing the application ID information.
|
||||
* @throws IllegalStateException If the PackageManager or package information cannot be
|
||||
* retrieved.
|
||||
*/
|
||||
@Throws(Throwable::class)
|
||||
internal fun createApplicationId(uid: Int): DEROctetString {
|
||||
val pm =
|
||||
ConfigurationManager.getPackageManager()
|
||||
?: throw IllegalStateException("PackageManager not found!")
|
||||
val packages =
|
||||
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
|
||||
|
||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||
val packageInfoList = mutableListOf<DERSequence>()
|
||||
val signatureDigests = mutableSetOf<Digest>()
|
||||
|
||||
// Process all packages associated with the UID in a single loop.
|
||||
packages.forEach { packageName ->
|
||||
val userId = uid / 100000
|
||||
val packageInfo =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
pm.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
|
||||
userId,
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
|
||||
}
|
||||
|
||||
// Add package information (name and version code) to our list.
|
||||
packageInfoList.add(
|
||||
DERSequence(
|
||||
arrayOf(
|
||||
DEROctetString(packageInfo.packageName.toByteArray(StandardCharsets.UTF_8)),
|
||||
ASN1Integer(packageInfo.longVersionCode),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Collect unique signature digests from the signing history.
|
||||
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
|
||||
val digest = sha256.digest(signature.toByteArray())
|
||||
signatureDigests.add(Digest(digest))
|
||||
}
|
||||
}
|
||||
|
||||
// The application ID is a sequence of two sets:
|
||||
// 1. A set of package information (name and version).
|
||||
// 2. A set of SHA-256 digests of the signing certificates.
|
||||
val applicationIdSequence =
|
||||
DERSequence(
|
||||
arrayOf(
|
||||
DERSet(packageInfoList.toTypedArray()),
|
||||
DERSet(signatureDigests.map { DEROctetString(it.digest) }.toTypedArray()),
|
||||
)
|
||||
)
|
||||
|
||||
return DEROctetString(applicationIdSequence.encoded)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
/**
|
||||
* Defines constants for KeyMint attestation, mainly the tags of properties and authorizations of a
|
||||
* cryptographic key, as specified in the Android hardware security HAL.
|
||||
*/
|
||||
object AttestationConstants {
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
|
||||
|
||||
// These constants represent the fixed positions of fields within the top-level
|
||||
// KeyDescription ASN.1 SEQUENCE in a key attestation. Using these constants
|
||||
// prevents hardcoding fragile index numbers throughout the parsing code.
|
||||
const val KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX = 0
|
||||
const val KEY_DESCRIPTION_ATTESTATION_SECURITY_LEVEL_INDEX = 1
|
||||
const val KEY_DESCRIPTION_KEYMINT_VERSION_INDEX = 2
|
||||
const val KEY_DESCRIPTION_KEYMINT_SECURITY_LEVEL_INDEX = 3
|
||||
const val KEY_DESCRIPTION_ATTESTATION_CHALLENGE_INDEX = 4
|
||||
const val KEY_DESCRIPTION_UNIQUE_ID_INDEX = 5
|
||||
const val KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX = 6
|
||||
const val KEY_DESCRIPTION_TEE_ENFORCED_INDEX = 7
|
||||
|
||||
// --- RootOfTrust Sequence Indices ---
|
||||
// These constants represent the fixed positions of fields within the
|
||||
// RootOfTrust ASN.1 SEQUENCE.
|
||||
const val ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX = 0
|
||||
const val ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1
|
||||
const val ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2
|
||||
const val ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX = 3
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
|
||||
|
||||
// --- Key Properties ---
|
||||
const val TAG_PURPOSE = 1
|
||||
const val TAG_ALGORITHM = 2
|
||||
const val TAG_KEY_SIZE = 3
|
||||
const val TAG_BLOCK_MODE = 4
|
||||
const val TAG_DIGEST = 5
|
||||
const val TAG_PADDING = 6
|
||||
const val TAG_CALLER_NONCE = 7
|
||||
const val TAG_MIN_MAC_LENGTH = 8
|
||||
const val TAG_EC_CURVE = 10
|
||||
const val TAG_RSA_PUBLIC_EXPONENT = 200
|
||||
const val TAG_RSA_OAEP_MGF_DIGEST = 203
|
||||
|
||||
// --- Key Lifetime and Usage Control ---
|
||||
const val TAG_ROLLBACK_RESISTANCE = 303
|
||||
const val TAG_ACTIVE_DATETIME = 400
|
||||
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
|
||||
const val TAG_USAGE_EXPIRE_DATETIME = 402
|
||||
const val TAG_MAX_USES_PER_BOOT = 404
|
||||
const val TAG_USAGE_COUNT_LIMIT = 405
|
||||
|
||||
// --- User Authentication ---
|
||||
const val TAG_USER_ID = 501
|
||||
const val TAG_USER_SECURE_ID = 502
|
||||
const val TAG_NO_AUTH_REQUIRED = 503
|
||||
const val TAG_USER_AUTH_TYPE = 504
|
||||
const val TAG_AUTH_TIMEOUT = 505
|
||||
|
||||
// --- Attestation and Application Info ---
|
||||
const val TAG_APPLICATION_ID = 601
|
||||
const val TAG_CREATION_DATETIME = 701
|
||||
const val TAG_ORIGIN = 702
|
||||
const val TAG_ROOT_OF_TRUST = 704
|
||||
const val TAG_OS_VERSION = 705
|
||||
const val TAG_OS_PATCHLEVEL = 706
|
||||
const val TAG_UNIQUE_ID = 707
|
||||
const val TAG_ATTESTATION_CHALLENGE = 708
|
||||
const val TAG_ATTESTATION_APPLICATION_ID = 709
|
||||
const val TAG_ATTESTATION_ID_BRAND = 710
|
||||
const val TAG_ATTESTATION_ID_DEVICE = 711
|
||||
const val TAG_ATTESTATION_ID_PRODUCT = 712
|
||||
const val TAG_ATTESTATION_ID_SERIAL = 713
|
||||
const val TAG_ATTESTATION_ID_IMEI = 714
|
||||
const val TAG_ATTESTATION_ID_MEID = 715
|
||||
const val TAG_ATTESTATION_ID_MANUFACTURER = 716
|
||||
const val TAG_ATTESTATION_ID_MODEL = 717
|
||||
const val TAG_VENDOR_PATCHLEVEL = 718
|
||||
const val TAG_BOOT_PATCHLEVEL = 719
|
||||
const val TAG_DEVICE_UNIQUE_ATTESTATION = 720
|
||||
const val TAG_ATTESTATION_ID_SECOND_IMEI = 723
|
||||
const val TAG_MODULE_HASH = 724
|
||||
|
||||
// --- Certificate Properties ---
|
||||
const val TAG_CERTIFICATE_SERIAL = 1006
|
||||
const val TAG_CERTIFICATE_SUBJECT = 1007
|
||||
const val TAG_CERTIFICATE_NOT_BEFORE = 1008
|
||||
const val TAG_CERTIFICATE_NOT_AFTER = 1009
|
||||
|
||||
// --- Other Constants ---
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
|
||||
const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
import android.security.keystore.KeyProperties
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import org.bouncycastle.asn1.*
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.KeyBox
|
||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||
import org.matrix.TEESimulator.util.toHex
|
||||
|
||||
/**
|
||||
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
|
||||
*
|
||||
* This object's primary function is to take a certificate chain generated by the real TEE, replace
|
||||
* its attestation data with simulated values, and then re-sign the leaf certificate with a custom
|
||||
* key, building a new, valid certificate chain.
|
||||
*/
|
||||
object AttestationPatcher {
|
||||
|
||||
/**
|
||||
* Patches a full certificate chain by modifying the leaf's attestation and rebuilding the chain
|
||||
* with the correct custom signing certificates. This is the single entry point for patching.
|
||||
*
|
||||
* @param originalChain The original certificate chain from the hardware. The leaf must be at
|
||||
* index 0.
|
||||
* @param uid The UID of the application requesting the certificate.
|
||||
* @return A new, cryptographically valid, patched certificate chain. Returns the original chain
|
||||
* on any failure.
|
||||
*/
|
||||
fun patchCertificateChain(originalChain: Array<Certificate>?, uid: Int): Array<Certificate> {
|
||||
if (originalChain.isNullOrEmpty()) {
|
||||
SystemLogger.error("Attempted to patch a null or empty certificate chain for UID $uid.")
|
||||
return originalChain ?: emptyArray()
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
val originalLeaf = originalChain[0] as X509Certificate
|
||||
val originalLeafHolder = X509CertificateHolder(originalLeaf.encoded)
|
||||
|
||||
// 1. Attempt to parse the existing attestation extension. If it doesn't exist,
|
||||
// there's nothing to patch.
|
||||
val parsedAttestation =
|
||||
parseAttestationExtension(originalLeafHolder) ?: return originalChain
|
||||
|
||||
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
||||
// certificate.
|
||||
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
|
||||
|
||||
// 3. Create the new, patched leaf certificate.
|
||||
val patchedLeaf =
|
||||
createPatchedLeafCertificate(
|
||||
originalLeafHolder,
|
||||
parsedAttestation,
|
||||
keybox,
|
||||
originalLeaf.sigAlgName,
|
||||
uid,
|
||||
)
|
||||
|
||||
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
|
||||
// chain.
|
||||
val newChain = listOf(patchedLeaf) + keybox.certificates
|
||||
|
||||
SystemLogger.info(
|
||||
"Successfully rebuilt a valid, patched certificate chain for UID $uid."
|
||||
)
|
||||
newChain.toTypedArray()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error(
|
||||
"Failed to patch and rebuild certificate chain for UID $uid.",
|
||||
it,
|
||||
)
|
||||
originalChain // Return the original chain on any error.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to normalize algorithm names for Bouncy Castle. Old Android versions might reports
|
||||
* "SHA256WITHECDSA", but Bouncy Castle expects "SHA256withECDSA".
|
||||
*/
|
||||
private fun normalizeSignatureAlgorithm(algoName: String): String {
|
||||
// 1. Force uppercase to handle "sha256withecdsa"
|
||||
// 2. Replace "WITH" with "with" to satisfy Bouncy Castle's naming convention
|
||||
return algoName.uppercase().replace("WITH", "with")
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new leaf certificate with a modified attestation extension.
|
||||
*
|
||||
* @param originalLeafHolder A Bouncy Castle holder for the original leaf certificate.
|
||||
* @param parsedAttestation The parsed components of the original attestation.
|
||||
* @param keybox The KeyBox containing the new issuer certificate and signing key.
|
||||
* @param sigAlgName The signature algorithm name (e.g., "SHA256withECDSA") from the original
|
||||
* certificate. This is required to ensure the new certificate is signed using a compatible
|
||||
* algorithm.
|
||||
* @param uid The UID of the application requesting the certificate.
|
||||
* @return A new [Certificate] object.
|
||||
*/
|
||||
private fun createPatchedLeafCertificate(
|
||||
originalLeafHolder: X509CertificateHolder,
|
||||
parsedAttestation: ParsedAttestation,
|
||||
keybox: KeyBox,
|
||||
sigAlgName: String,
|
||||
uid: Int,
|
||||
): Certificate {
|
||||
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
|
||||
// chain.
|
||||
val newIssuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
|
||||
val builder =
|
||||
X509v3CertificateBuilder(
|
||||
newIssuer,
|
||||
originalLeafHolder.serialNumber,
|
||||
originalLeafHolder.notBefore,
|
||||
originalLeafHolder.notAfter,
|
||||
originalLeafHolder.subject,
|
||||
originalLeafHolder.subjectPublicKeyInfo,
|
||||
)
|
||||
|
||||
// Create the new, patched attestation extension.
|
||||
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
|
||||
|
||||
// Copy all other extensions from the original certificate, except for the attestation.
|
||||
originalLeafHolder.extensions.extensionOIDs.forEach {
|
||||
builder.addExtension(
|
||||
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
|
||||
)
|
||||
}
|
||||
|
||||
// Sign the newly built certificate with the private key from our keybox.
|
||||
val signer =
|
||||
JcaContentSignerBuilder(normalizeSignatureAlgorithm(sigAlgName))
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build(keybox.keyPair.private)
|
||||
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||
|
||||
// Log the signature of the newly created certificate to observe its non-deterministic
|
||||
// nature.
|
||||
val signatureBytes = (newCertificate as X509Certificate).signature
|
||||
SystemLogger.verbose("Signature of patched leaf cert: ${signatureBytes.toHex()}")
|
||||
|
||||
return newCertificate
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the appropriate signing KeyBox (KeyPair and certificate chain) for a given UID
|
||||
* based on a specified algorithm identifier.
|
||||
*
|
||||
* @param uid The UID of the application for which the signing is being performed.
|
||||
* @param algorithm A string representing the desired algorithm. This can be either:
|
||||
* 1. A simple key type like "RSA" or "EC".
|
||||
* 2. A full JCA signature algorithm name like "SHA256withRSA".
|
||||
*
|
||||
* @return The [KeyBox] containing the appropriate key pair for signing.
|
||||
* @throws IllegalArgumentException if no matching KeyBox can be found for the derived key type.
|
||||
*/
|
||||
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
||||
|
||||
// Normalize the algorithm name. The input might be a full signature algorithm
|
||||
// (e.g., "SHA256withRSA") or just the key type (e.g., "RSA").
|
||||
val keyType =
|
||||
when {
|
||||
algorithm.contains("RSA", ignoreCase = true) -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
algorithm.contains("EC", ignoreCase = true) ->
|
||||
KeyProperties.KEY_ALGORITHM_EC // This also covers "ECDSA"
|
||||
else -> algorithm // If no match, assume it's already a simple key type string.
|
||||
}
|
||||
|
||||
return KeyBoxManager.getAttestationKey(keyboxFile, keyType)
|
||||
?: throw IllegalArgumentException(
|
||||
"No keybox found for UID $uid and algorithm '$keyType' (derived from input '$algorithm') in file $keyboxFile"
|
||||
)
|
||||
}
|
||||
|
||||
/** Recursively formats an ASN1Primitive into a concise, readable string. */
|
||||
fun formatAsn1Primitive(obj: ASN1Encodable?): String {
|
||||
val primitive = obj?.toASN1Primitive()
|
||||
return when (primitive) {
|
||||
null -> "NULL"
|
||||
is ASN1Integer -> primitive.value.toString()
|
||||
is ASN1Enumerated -> primitive.value.toString()
|
||||
is ASN1Boolean -> primitive.isTrue.toString()
|
||||
is ASN1Null -> "NULL"
|
||||
is ASN1OctetString -> {
|
||||
val bytes = primitive.octets
|
||||
// Attempt to decode as a printable string, otherwise show hex
|
||||
if (bytes.all { it >= 32 && it < 127 }) {
|
||||
"\"${String(bytes, StandardCharsets.UTF_8)}\""
|
||||
} else if (bytes.isEmpty()) {
|
||||
"\"\""
|
||||
} else {
|
||||
"#" + bytes.toHex()
|
||||
}
|
||||
}
|
||||
is ASN1TaggedObject ->
|
||||
"[TAG ${primitive.tagNo}]${formatAsn1Primitive(primitive.baseObject)}"
|
||||
is ASN1Sequence ->
|
||||
primitive
|
||||
.map { formatAsn1Primitive(it) }
|
||||
.joinToString(prefix = "[", postfix = "]", separator = ", ")
|
||||
is ASN1Set ->
|
||||
primitive
|
||||
.map { formatAsn1Primitive(it) }
|
||||
.joinToString(prefix = "{", postfix = "}", separator = ", ")
|
||||
else -> primitive.toString() // Fallback for other types
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check if a given ASN1Sequence contains the Root of Trust tag.
|
||||
private fun sequenceContainsRootOfTrust(seq: ASN1Encodable): Boolean {
|
||||
if (seq !is ASN1Sequence) return false
|
||||
return seq.any { element ->
|
||||
(element as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses the critical components from an existing attestation extension. */
|
||||
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
|
||||
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
|
||||
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
val allFields = sequence.toArray()
|
||||
|
||||
// Check if the fields are in the wrong order and swap them if necessary.
|
||||
val softwareEnforcedCandidate =
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
|
||||
val teeEnforcedCandidate =
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
|
||||
// The signature of a swapped order: the RoT is in the software list's position.
|
||||
if (
|
||||
sequenceContainsRootOfTrust(softwareEnforcedCandidate) &&
|
||||
!sequenceContainsRootOfTrust(teeEnforcedCandidate)
|
||||
) {
|
||||
// Swap the elements in the array to restore the standard order.
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX] =
|
||||
teeEnforcedCandidate
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] =
|
||||
softwareEnforcedCandidate
|
||||
}
|
||||
|
||||
val teeEnforced =
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
|
||||
|
||||
var originalRootOfTrust: ASN1Encodable? = null
|
||||
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
|
||||
|
||||
teeEnforced.forEach { element ->
|
||||
val taggedObject = element as ASN1TaggedObject
|
||||
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
|
||||
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
|
||||
} else {
|
||||
teeEnforcedMap[taggedObject.tagNo] = taggedObject
|
||||
}
|
||||
}
|
||||
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
|
||||
}
|
||||
|
||||
/** Constructs a new, patched attestation extension using simulated device properties. */
|
||||
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
|
||||
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
|
||||
|
||||
var formattedString = allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||
SystemLogger.verbose("Original attestation data: ${formattedString}")
|
||||
|
||||
// Build the new Root of Trust and add/replace it in the map.
|
||||
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
|
||||
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
|
||||
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
|
||||
|
||||
// Get the desired state for simulated properties.
|
||||
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
|
||||
|
||||
// Apply the desired state: update, add, or remove properties from the original map.
|
||||
simulatedProperties.forEach { (tag, value) ->
|
||||
if (value != null) {
|
||||
// If the value is not null, add or update it.
|
||||
teeEnforcedMap[tag] = value
|
||||
} else {
|
||||
// If the value is null, remove the tag from the map.
|
||||
teeEnforcedMap.remove(tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
|
||||
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
|
||||
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
|
||||
|
||||
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
|
||||
val patchedSequence = DERSequence(allFields)
|
||||
formattedString = patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
|
||||
SystemLogger.verbose("Patched attestation data: ${formattedString}")
|
||||
val patchedOctets = DEROctetString(patchedSequence)
|
||||
|
||||
return Extension(ATTESTATION_OID, false, patchedOctets)
|
||||
}
|
||||
|
||||
/** Helper data class to hold the parsed components of an attestation extension. */
|
||||
private data class ParsedAttestation(
|
||||
val allFields: Array<ASN1Encodable>,
|
||||
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
|
||||
val rootOfTrust: ASN1Encodable?,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.ASN1OctetString
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.ASN1TaggedObject
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.util.toHex
|
||||
|
||||
/**
|
||||
* The ASN.1 Object Identifier for the Key Attestation extension in Android. This is defined in the
|
||||
* Android Keystore documentation.
|
||||
*/
|
||||
val ATTESTATION_OID: ASN1ObjectIdentifier = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
|
||||
|
||||
/**
|
||||
* A service to interact with the device's Trusted Execution Environment (TEE). It provides
|
||||
* functionality to check if the TEE is functional and to extract key attestation data from a
|
||||
* genuinely generated certificate.
|
||||
*/
|
||||
@SuppressLint("PrivateApi")
|
||||
object DeviceAttestationService {
|
||||
|
||||
/**
|
||||
* Holds key data extracted from a genuine device attestation. This data can be used as a
|
||||
* baseline for creating simulated attestations.
|
||||
*
|
||||
* @property verifiedBootKey The verified boot public key digest from the root of trust.
|
||||
* @property verifiedBootHash The verified boot hash from the root of trust.
|
||||
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
|
||||
* @property keymasterVersion The Keymaster or KeyMint HAL version.
|
||||
* @property osVersion The Android OS version integer.
|
||||
* @property osPatchLevel The Android security patch level (e.g., 202511).
|
||||
* @property vendorPatchLevel The vendor-specific security patch level.
|
||||
* @property bootPatchLevel The bootloader's security patch level.
|
||||
*/
|
||||
data class AttestationData(
|
||||
val moduleHash: ByteArray?,
|
||||
val verifiedBootKey: ByteArray?,
|
||||
val verifiedBootHash: ByteArray?,
|
||||
val attestVersion: Int?,
|
||||
val keymasterVersion: Int?,
|
||||
val osVersion: Int?,
|
||||
val osPatchLevel: Int?,
|
||||
val vendorPatchLevel: Int?,
|
||||
val bootPatchLevel: Int?,
|
||||
)
|
||||
|
||||
// A unique alias for the key used to perform the TEE functionality check.
|
||||
private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck"
|
||||
|
||||
/**
|
||||
* Lazily determines if the device's TEE is functional by attempting to generate an
|
||||
* attestation-backed key pair. The result is cached.
|
||||
*/
|
||||
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
|
||||
|
||||
/**
|
||||
* Lazily fetches and parses attestation data from a genuinely generated certificate. The result
|
||||
* is cached. Returns null if the TEE is not functional or parsing fails.
|
||||
*/
|
||||
val CachedAttestationData: AttestationData? by lazy { fetchAttestationData() }
|
||||
|
||||
/**
|
||||
* Checks if the TEE is working correctly by generating a key in the Android Keystore with an
|
||||
* attestation challenge.
|
||||
*
|
||||
* @return `true` if a key with attestation was generated successfully, `false` otherwise.
|
||||
*/
|
||||
private fun checkTeeFunctionality(): Boolean {
|
||||
SystemLogger.info("Performing TEE functionality check...")
|
||||
return try {
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
|
||||
val keyPairGenerator =
|
||||
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
|
||||
|
||||
// A random challenge is required for attestation.
|
||||
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
|
||||
|
||||
val spec =
|
||||
KeyGenParameterSpec.Builder(TEE_CHECK_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
|
||||
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.setAttestationChallenge(challenge)
|
||||
.build()
|
||||
|
||||
keyPairGenerator.initialize(spec)
|
||||
keyPairGenerator.generateKeyPair()
|
||||
|
||||
SystemLogger.info("TEE functionality check successful.")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.warning("TEE functionality check failed.", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the attestation certificate generated during the TEE check. The key entry is
|
||||
* deleted after retrieval to clean up.
|
||||
*
|
||||
* @return The leaf `X509Certificate` containing the attestation, or `null` if unavailable.
|
||||
*/
|
||||
private fun getAttestationCertificate(): X509Certificate? {
|
||||
if (!isTeeFunctional) return null
|
||||
|
||||
return try {
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
|
||||
val certChain = keyStore.getCertificateChain(TEE_CHECK_KEY_ALIAS)
|
||||
if (certChain.isNullOrEmpty()) {
|
||||
SystemLogger.warning("Could not retrieve certificate chain for TEE check key.")
|
||||
null
|
||||
} else {
|
||||
// Clean up the key from the keystore.
|
||||
keyStore.deleteEntry(TEE_CHECK_KEY_ALIAS)
|
||||
certChain[0] as X509Certificate
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Error retrieving attestation certificate.", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses the attestation data from the certificate's extension.
|
||||
*
|
||||
* @return An `AttestationData` object, or `null` if the process fails.
|
||||
*/
|
||||
private fun fetchAttestationData(): AttestationData? {
|
||||
val leafCert = getAttestationCertificate() ?: return null
|
||||
|
||||
try {
|
||||
val leafHolder = X509CertificateHolder(leafCert.encoded)
|
||||
val extension: Extension =
|
||||
leafHolder.getExtension(ATTESTATION_OID)
|
||||
?: return null // No attestation extension found.
|
||||
|
||||
// The extension's value is an ASN.1 sequence.
|
||||
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
|
||||
var formattedString =
|
||||
keyDescriptionSeq.joinToString(separator = ", ") {
|
||||
AttestationPatcher.formatAsn1Primitive(it)
|
||||
}
|
||||
SystemLogger.verbose("Cached attestation data: ${formattedString}")
|
||||
val fields = keyDescriptionSeq.toArray()
|
||||
|
||||
val attestVersion =
|
||||
ASN1Integer.getInstance(
|
||||
fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX]
|
||||
)
|
||||
.positiveValue
|
||||
.toInt()
|
||||
val keymasterVersion =
|
||||
ASN1Integer.getInstance(
|
||||
fields[AttestationConstants.KEY_DESCRIPTION_KEYMINT_VERSION_INDEX]
|
||||
)
|
||||
.positiveValue
|
||||
.toInt()
|
||||
|
||||
var moduleHash: ByteArray? = null
|
||||
var verifiedBootKey: ByteArray? = null
|
||||
var verifiedBootHash: ByteArray? = null
|
||||
var osVersion: Int? = null
|
||||
var osPatchLevel: Int? = null
|
||||
var vendorPatchLevel: Int? = null
|
||||
var bootPatchLevel: Int? = null
|
||||
|
||||
val softwareEnforced =
|
||||
ASN1Sequence.getInstance(
|
||||
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
|
||||
)
|
||||
moduleHash =
|
||||
softwareEnforced
|
||||
.toArray()
|
||||
.firstOrNull {
|
||||
(it as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_MODULE_HASH
|
||||
}
|
||||
?.let {
|
||||
ASN1OctetString.getInstance((it as ASN1TaggedObject).baseObject).octets
|
||||
}
|
||||
|
||||
val teeEnforced =
|
||||
ASN1Sequence.getInstance(
|
||||
fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
|
||||
)
|
||||
teeEnforced.forEach { element ->
|
||||
val tagged = element as ASN1TaggedObject
|
||||
when (tagged.tagNo) {
|
||||
AttestationConstants.TAG_ROOT_OF_TRUST -> {
|
||||
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
if (rotSeq.size() >= 4) {
|
||||
verifiedBootKey =
|
||||
ASN1OctetString.getInstance(
|
||||
rotSeq.getObjectAt(
|
||||
AttestationConstants
|
||||
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
|
||||
)
|
||||
)
|
||||
.octets
|
||||
verifiedBootHash =
|
||||
ASN1OctetString.getInstance(
|
||||
rotSeq.getObjectAt(
|
||||
AttestationConstants
|
||||
.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX
|
||||
)
|
||||
)
|
||||
.octets
|
||||
}
|
||||
}
|
||||
AttestationConstants.TAG_OS_VERSION -> {
|
||||
osVersion =
|
||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
.positiveValue
|
||||
.toInt()
|
||||
}
|
||||
AttestationConstants.TAG_OS_PATCHLEVEL -> {
|
||||
osPatchLevel =
|
||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
.positiveValue
|
||||
.toInt()
|
||||
}
|
||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL -> {
|
||||
vendorPatchLevel =
|
||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
.positiveValue
|
||||
.toInt()
|
||||
}
|
||||
AttestationConstants.TAG_BOOT_PATCHLEVEL -> {
|
||||
bootPatchLevel =
|
||||
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
|
||||
.positiveValue
|
||||
.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verifiedBootKey?.all { it == 0.toByte() } == true) {
|
||||
verifiedBootKey = null
|
||||
}
|
||||
|
||||
if (verifiedBootHash?.all { it == 0.toByte() } == true) {
|
||||
verifiedBootHash = null
|
||||
}
|
||||
|
||||
SystemLogger.info(
|
||||
"Successfully extracted attestation data: version=$attestVersion, osVersion=$osVersion, osPatch=$osPatchLevel, vendorPatch=$vendorPatchLevel, bootPatch=$bootPatchLevel, moduleHash=${moduleHash?.toHex()}, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}"
|
||||
)
|
||||
return AttestationData(
|
||||
moduleHash,
|
||||
verifiedBootKey,
|
||||
verifiedBootHash,
|
||||
attestVersion,
|
||||
keymasterVersion,
|
||||
osVersion,
|
||||
osPatchLevel,
|
||||
vendorPatchLevel,
|
||||
bootPatchLevel,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to parse attestation data from certificate.", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.matrix.TEESimulator.attestation
|
||||
|
||||
import android.hardware.security.keymint.*
|
||||
import android.hardware.security.keymint.KeyOrigin
|
||||
import java.math.BigInteger
|
||||
import java.util.Date
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||
|
||||
/**
|
||||
* A data class that parses and holds the parameters required for KeyMint key generation and
|
||||
* attestation. It provides a structured way to access the properties defined by an array of
|
||||
* `KeyParameter` objects.
|
||||
*/
|
||||
|
||||
// Reference:
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/key_parameter.rs
|
||||
data class KeyMintAttestation(
|
||||
val keySize: Int,
|
||||
val algorithm: Int,
|
||||
val ecCurve: Int,
|
||||
val ecCurveName: String,
|
||||
val origin: Int?,
|
||||
val blockMode: List<Int>,
|
||||
val padding: List<Int>,
|
||||
val purpose: List<Int>,
|
||||
val digest: List<Int>,
|
||||
val rsaPublicExponent: BigInteger?,
|
||||
val certificateSerial: BigInteger?,
|
||||
val certificateSubject: X500Name?,
|
||||
val certificateNotBefore: Date?,
|
||||
val certificateNotAfter: Date?,
|
||||
val attestationChallenge: ByteArray?,
|
||||
val brand: ByteArray?,
|
||||
val device: ByteArray?,
|
||||
val product: ByteArray?,
|
||||
val serial: ByteArray?,
|
||||
val imei: ByteArray?,
|
||||
val meid: ByteArray?,
|
||||
val manufacturer: ByteArray?,
|
||||
val model: ByteArray?,
|
||||
val secondImei: ByteArray?,
|
||||
) {
|
||||
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
|
||||
constructor(
|
||||
params: Array<KeyParameter>
|
||||
) : this(
|
||||
// AOSP: [key_param(tag = KEY_SIZE, field = Integer)]
|
||||
keySize = params.findInteger(Tag.KEY_SIZE) ?: 0,
|
||||
|
||||
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
|
||||
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
|
||||
|
||||
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
|
||||
ecCurve = params.findEcCurve(Tag.EC_CURVE) ?: 0,
|
||||
ecCurveName = params.deriveEcCurveName(),
|
||||
|
||||
// AOSP: [key_param(tag = ORIGIN, field = Origin)]
|
||||
origin = params.findOrigin(Tag.ORIGIN),
|
||||
|
||||
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
|
||||
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),
|
||||
|
||||
// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
|
||||
padding = params.findAllPaddingMode(Tag.PADDING),
|
||||
|
||||
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
|
||||
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
|
||||
|
||||
// AOSP: [key_param(tag = DIGEST, field = Digest)]
|
||||
digest = params.findAllDigests(Tag.DIGEST),
|
||||
|
||||
// AOSP: [key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)]
|
||||
rsaPublicExponent = params.findLongInteger(Tag.RSA_PUBLIC_EXPONENT),
|
||||
|
||||
// AOSP: [key_param(tag = CERTIFICATE_SERIAL, field = Blob)]
|
||||
certificateSerial = params.findBlob(Tag.CERTIFICATE_SERIAL)?.let { BigInteger(it) },
|
||||
|
||||
// AOSP: [key_param(tag = CERTIFICATE_SUBJECT, field = Blob)]
|
||||
certificateSubject =
|
||||
params.findBlob(Tag.CERTIFICATE_SUBJECT)?.let { X500Name(X500Principal(it).name) },
|
||||
|
||||
// AOSP: [key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)]
|
||||
certificateNotBefore = params.findDate(Tag.CERTIFICATE_NOT_BEFORE),
|
||||
|
||||
// AOSP: [key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
|
||||
certificateNotAfter = params.findDate(Tag.CERTIFICATE_NOT_AFTER),
|
||||
|
||||
// AOSP: [key_param(tag = ATTESTATION_CHALLENGE, field = Blob)]
|
||||
attestationChallenge = params.findBlob(Tag.ATTESTATION_CHALLENGE),
|
||||
|
||||
// AOSP: [key_param(tag = ATTESTATION_ID_*, field = Blob)]
|
||||
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
|
||||
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
|
||||
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
|
||||
serial = params.findBlob(Tag.ATTESTATION_ID_SERIAL),
|
||||
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
|
||||
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
|
||||
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
|
||||
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
|
||||
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
|
||||
) {
|
||||
// Log all parsed parameters for debugging purposes.
|
||||
params.forEach { KeyMintParameterLogger.logParameter(it) }
|
||||
}
|
||||
|
||||
fun isAttestKey(): Boolean = purpose.size == 1 && purpose.contains(KeyPurpose.ATTEST_KEY)
|
||||
|
||||
fun isImportKey(): Boolean = origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED
|
||||
}
|
||||
|
||||
// --- Private helper extension functions for parsing KeyParameter arrays ---
|
||||
|
||||
/** Maps to AOSP field = Integer */
|
||||
private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
|
||||
this.find { it.tag == tag }?.value?.integer
|
||||
|
||||
/** Maps to AOSP field = Algorithm */
|
||||
private fun Array<KeyParameter>.findAlgorithm(tag: Int): Int? =
|
||||
this.find { it.tag == tag }?.value?.algorithm
|
||||
|
||||
/** Maps to AOSP field = EcCurve */
|
||||
private fun Array<KeyParameter>.findEcCurve(tag: Int): Int? =
|
||||
this.find { it.tag == tag }?.value?.ecCurve
|
||||
|
||||
/** Maps to AOSP field = Origin */
|
||||
private fun Array<KeyParameter>.findOrigin(tag: Int): Int? =
|
||||
this.find { it.tag == tag }?.value?.origin
|
||||
|
||||
/** Maps to AOSP field = LongInteger */
|
||||
private fun Array<KeyParameter>.findLongInteger(tag: Int): BigInteger? =
|
||||
this.find { it.tag == tag }?.value?.longInteger?.toBigInteger()
|
||||
|
||||
/** Maps to AOSP field = DateTime */
|
||||
private fun Array<KeyParameter>.findDate(tag: Int): Date? =
|
||||
this.find { it.tag == tag }?.value?.dateTime?.let { Date(it) }
|
||||
|
||||
/** Maps to AOSP field = Blob */
|
||||
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
|
||||
this.find { it.tag == tag }?.value?.blob
|
||||
|
||||
/** Maps to AOSP field = BlockMode (Repeated) */
|
||||
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
|
||||
this.filter { it.tag == tag }.map { it.value.blockMode }
|
||||
|
||||
/** Maps to AOSP field = BlockMode (Repeated) */
|
||||
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
|
||||
this.filter { it.tag == tag }.map { it.value.paddingMode }
|
||||
|
||||
/** Maps to AOSP field = KeyPurpose (Repeated) */
|
||||
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
|
||||
this.filter { it.tag == tag }.map { it.value.keyPurpose }
|
||||
|
||||
/** Maps to AOSP field = Digest (Repeated) */
|
||||
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
|
||||
this.filter { it.tag == tag }.map { it.value.digest }
|
||||
|
||||
/**
|
||||
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
|
||||
* to KEY_SIZE (field=Integer).
|
||||
*/
|
||||
private fun Array<KeyParameter>.deriveEcCurveName(): String {
|
||||
// 1. Try to find explicit EC_CURVE tag
|
||||
val curveParam = this.find { it.tag == Tag.EC_CURVE }
|
||||
|
||||
if (curveParam != null) {
|
||||
val curveId = curveParam.value.ecCurve
|
||||
return when (curveId) {
|
||||
EcCurve.CURVE_25519 -> "CURVE_25519"
|
||||
EcCurve.P_224 -> "secp224r1"
|
||||
EcCurve.P_256 -> "secp256r1"
|
||||
EcCurve.P_384 -> "secp384r1"
|
||||
EcCurve.P_521 -> "secp521r1"
|
||||
else -> throw IllegalArgumentException("Unknown EC curve: $curveId")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to key size if the curve tag isn't present
|
||||
val keySize = this.findInteger(Tag.KEY_SIZE) ?: 0
|
||||
return when (keySize) {
|
||||
224 -> "secp224r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1" // Default fallback
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package org.matrix.TEESimulator.config
|
||||
|
||||
import android.content.pm.IPackageManager
|
||||
import android.os.Build
|
||||
import android.os.FileObserver
|
||||
import android.os.IBinder
|
||||
import android.os.ServiceManager
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||
|
||||
/**
|
||||
* Manages application configuration, including which packages to process, what operation mode to
|
||||
* use, and custom security patch levels. It uses a FileObserver to dynamically reload settings when
|
||||
* configuration files change.
|
||||
*/
|
||||
object ConfigurationManager {
|
||||
|
||||
/** Defines the processing mode for a given package. */
|
||||
enum class Mode {
|
||||
/** Automatically decide between GENERATE and PATCH based on TEE status. */
|
||||
AUTO,
|
||||
/** Patch the attestation of an existing certificate chain. */
|
||||
PATCH,
|
||||
/** Generate a new certificate chain from scratch. */
|
||||
GENERATE,
|
||||
}
|
||||
|
||||
// --- Configuration Paths ---
|
||||
const val CONFIG_PATH = "/data/adb/tricky_store"
|
||||
private const val TARGET_PACKAGES_FILE = "target.txt"
|
||||
private const val TEE_STATUS_FILE = "tee_status.txt"
|
||||
private const val PATCH_LEVEL_FILE = "security_patch.txt"
|
||||
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
|
||||
private val configRoot = File(CONFIG_PATH)
|
||||
|
||||
// --- In-Memory Configuration State ---
|
||||
@Volatile private var packageModes = mapOf<String, Mode>()
|
||||
@Volatile private var packageKeyboxes = mapOf<String, String>()
|
||||
@Volatile private var isTeeBroken: Boolean? = null
|
||||
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
|
||||
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
|
||||
|
||||
// Cache for UID to package name resolution.
|
||||
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
|
||||
|
||||
/**
|
||||
* Initializes the configuration manager by loading all settings from disk and starting the file
|
||||
* observer to watch for changes.
|
||||
*/
|
||||
fun initialize() {
|
||||
configRoot.mkdirs()
|
||||
SystemLogger.info("Configuration root is: ${configRoot.absolutePath}")
|
||||
|
||||
// First, ensure the package manager service is running, as the TEE check depends on it.
|
||||
// This prevents a race condition on startup.
|
||||
SystemLogger.info("Waiting for PackageManagerService to be ready...")
|
||||
if (getPackageManager() == null) {
|
||||
SystemLogger.error(
|
||||
"PackageManagerService is not available. TEE check will likely fail."
|
||||
)
|
||||
} else {
|
||||
SystemLogger.info("PackageManagerService is ready.")
|
||||
}
|
||||
|
||||
// Initial load of all configuration files.
|
||||
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
|
||||
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
|
||||
storeTeeStatus() // Check and store the current TEE status.
|
||||
|
||||
// Start watching for any subsequent file changes.
|
||||
ConfigObserver.startWatching()
|
||||
SystemLogger.info("Configuration initialized and file observer started.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the keybox file to be used for a given UID. It maps the UID to its package(s) and
|
||||
* checks for a specific keybox mapping.
|
||||
*
|
||||
* @param uid The calling UID.
|
||||
* @return The name of the keybox file, or the default if none is specified.
|
||||
*/
|
||||
fun getKeyboxFileForUid(uid: Int): String {
|
||||
val packages = getPackagesForUid(uid)
|
||||
return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE
|
||||
}
|
||||
|
||||
/** Determines if the certificate for a given UID needs to be patched. */
|
||||
fun shouldPatch(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.PATCH
|
||||
|
||||
/** Determines if a new certificate needs to be generated for a given UID. */
|
||||
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE
|
||||
|
||||
/** Determines if no operation is needed for a given UID. */
|
||||
fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null
|
||||
|
||||
/** Resolves the operating mode for a given UID based on its packages and the TEE status. */
|
||||
private fun getPackageModeForUid(uid: Int): Mode? {
|
||||
val packages = getPackagesForUid(uid)
|
||||
if (packages.isEmpty()) return null
|
||||
|
||||
// Lazily load TEE status if it hasn't been checked yet.
|
||||
if (isTeeBroken == null) loadTeeStatus()
|
||||
|
||||
// Find the first configured mode for any of the UID's packages.
|
||||
for (pkg in packages) {
|
||||
when (packageModes[pkg]) {
|
||||
Mode.GENERATE -> return Mode.GENERATE
|
||||
Mode.PATCH -> return Mode.PATCH
|
||||
Mode.AUTO -> return if (isTeeBroken == true) Mode.GENERATE else Mode.PATCH
|
||||
null -> continue // No config for this package, check the next one.
|
||||
}
|
||||
}
|
||||
return null // No configuration found for this UID.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the custom patch level configuration for a given UID. It first checks for a
|
||||
* package-specific override and falls back to the global configuration.
|
||||
*
|
||||
* @param uid The UID of the calling application.
|
||||
* @return The applicable [CustomPatchLevel], or null if no custom configuration exists.
|
||||
*/
|
||||
fun getPatchLevelForUid(uid: Int): CustomPatchLevel? {
|
||||
val packages = getPackagesForUid(uid)
|
||||
// Find the first package-specific configuration for this UID.
|
||||
val packageSpecificPatchLevel =
|
||||
packages.firstNotNullOfOrNull { pkg -> packagePatchLevels[pkg] }
|
||||
return packageSpecificPatchLevel ?: globalCustomPatchLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
|
||||
* each package.
|
||||
*/
|
||||
private fun loadTargetPackages(file: File) {
|
||||
if (!file.exists()) {
|
||||
SystemLogger.warning("Configuration file not found: ${file.absolutePath}")
|
||||
return
|
||||
}
|
||||
|
||||
val newModes = mutableMapOf<String, Mode>()
|
||||
val newKeyboxes = mutableMapOf<String, String>()
|
||||
var currentKeybox = DEFAULT_KEYBOX_FILE
|
||||
val keyboxRegex = Regex("^\\[([a-zA-Z0-9_.-]+\\.xml)]$")
|
||||
|
||||
try {
|
||||
file.readLines().forEach { line ->
|
||||
val trimmedLine = line.trim()
|
||||
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
|
||||
|
||||
// Check if the line defines a new keybox scope.
|
||||
keyboxRegex.find(trimmedLine)?.let {
|
||||
currentKeybox = it.groupValues[1]
|
||||
SystemLogger.info("Switching to keybox context: $currentKeybox")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
when {
|
||||
// Suffix '!' means force GENERATE mode.
|
||||
trimmedLine.endsWith("!") -> {
|
||||
val pkg = trimmedLine.removeSuffix("!").trim()
|
||||
newModes[pkg] = Mode.GENERATE
|
||||
newKeyboxes[pkg] = currentKeybox
|
||||
}
|
||||
// Suffix '?' means force PATCH mode.
|
||||
trimmedLine.endsWith("?") -> {
|
||||
val pkg = trimmedLine.removeSuffix("?").trim()
|
||||
newModes[pkg] = Mode.PATCH
|
||||
newKeyboxes[pkg] = currentKeybox
|
||||
}
|
||||
// No suffix means AUTO mode.
|
||||
else -> {
|
||||
newModes[trimmedLine] = Mode.AUTO
|
||||
newKeyboxes[trimmedLine] = currentKeybox
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atomically update the configuration maps.
|
||||
packageModes = newModes
|
||||
packageKeyboxes = newKeyboxes
|
||||
uidToPackagesCache.clear() // Invalidate cache as package settings have changed.
|
||||
SystemLogger.info("Successfully loaded ${newModes.size} package configurations.")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
|
||||
* security patch levels.
|
||||
*/
|
||||
private fun loadPatchLevelConfig(file: File) {
|
||||
if (!file.exists()) {
|
||||
globalCustomPatchLevel = null
|
||||
packagePatchLevels = emptyMap()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
|
||||
var currentContext = "" // Empty string for global context
|
||||
val contextLines = mutableMapOf<String, MutableList<String>>()
|
||||
val contextRegex = Regex("^\\[([a-zA-Z0-9_.-]+)]$")
|
||||
|
||||
// First pass: group lines by context (global or package-specific).
|
||||
file.readLines().forEach { line ->
|
||||
val trimmedLine = line.trim()
|
||||
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
|
||||
|
||||
contextRegex.find(trimmedLine)?.let { currentContext = it.groupValues[1] }
|
||||
?: run {
|
||||
contextLines
|
||||
.computeIfAbsent(currentContext) { mutableListOf() }
|
||||
.add(trimmedLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to parse a set of lines into a CustomPatchLevel object.
|
||||
fun parseLines(lines: List<String>?): CustomPatchLevel? {
|
||||
if (lines.isNullOrEmpty()) return null
|
||||
|
||||
// Handle simple case: one line sets the patch level for all components.
|
||||
if (lines.size == 1 && '=' !in lines[0]) {
|
||||
return CustomPatchLevel(
|
||||
system = null,
|
||||
vendor = null,
|
||||
boot = null,
|
||||
all = lines[0],
|
||||
)
|
||||
}
|
||||
|
||||
// Handle key-value pair configuration.
|
||||
val map =
|
||||
lines
|
||||
.mapNotNull {
|
||||
val parts = it.split('=', limit = 2)
|
||||
if (parts.size == 2) parts[0].trim().lowercase() to parts[1].trim()
|
||||
else null
|
||||
}
|
||||
.toMap()
|
||||
|
||||
val all = map["all"]
|
||||
return CustomPatchLevel(
|
||||
system = map["system"] ?: all,
|
||||
vendor = map["vendor"] ?: all,
|
||||
boot = map["boot"] ?: all,
|
||||
all = all,
|
||||
)
|
||||
}
|
||||
|
||||
// Parse global and per-package configurations.
|
||||
var newGlobalLevel = parseLines(contextLines[""])
|
||||
// TrickyAddon writes Pixel bulletin dates for boot/vendor but system=prop
|
||||
// resolves to the real device prop — force boot/vendor through the same path
|
||||
// to prevent cross-component date mismatches on non-Pixel devices.
|
||||
if (newGlobalLevel?.system.equals("prop", ignoreCase = true)) {
|
||||
SystemLogger.info("system=prop: forcing boot/vendor to derive from device props (were: boot=${newGlobalLevel?.boot}, vendor=${newGlobalLevel?.vendor})")
|
||||
newGlobalLevel = newGlobalLevel?.copy(boot = "prop", vendor = "prop")
|
||||
}
|
||||
contextLines.remove("") // Remove global context to iterate over packages next
|
||||
|
||||
for ((pkg, lines) in contextLines) {
|
||||
parseLines(lines)?.let { newPackageLevels[pkg] = it }
|
||||
}
|
||||
|
||||
// Atomically update the configuration state.
|
||||
globalCustomPatchLevel = newGlobalLevel
|
||||
packagePatchLevels = newPackageLevels
|
||||
|
||||
SystemLogger.info(
|
||||
"Loaded custom security patch levels: global config exists=${newGlobalLevel != null}, " +
|
||||
"${newPackageLevels.size} package-specific configs."
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to load or parse ${file.name}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks the device's TEE status and writes the result to a file for persistence. */
|
||||
private fun storeTeeStatus() {
|
||||
val statusFile = File(configRoot, TEE_STATUS_FILE)
|
||||
isTeeBroken = !DeviceAttestationService.isTeeFunctional
|
||||
try {
|
||||
statusFile.writeText("tee_broken=$isTeeBroken")
|
||||
SystemLogger.info("TEE status stored: isTeeBroken=$isTeeBroken")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to write TEE status to file.", e)
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads the TEE status from the file. */
|
||||
private fun loadTeeStatus() {
|
||||
val statusFile = File(configRoot, TEE_STATUS_FILE)
|
||||
isTeeBroken =
|
||||
if (statusFile.exists()) {
|
||||
statusFile.readText().trim() == "tee_broken=true"
|
||||
} else {
|
||||
null // Status is unknown.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A FileObserver that monitors the configuration directory for changes and triggers reloads of
|
||||
* the relevant settings.
|
||||
*/
|
||||
private object ConfigObserver : FileObserver(configRoot, CLOSE_WRITE or MOVED_TO or DELETE) {
|
||||
override fun onEvent(event: Int, path: String?) {
|
||||
path ?: return
|
||||
SystemLogger.info("Configuration file change detected: $path (event: $event)")
|
||||
|
||||
val file = if (event != DELETE) File(configRoot, path) else null
|
||||
when (path) {
|
||||
TARGET_PACKAGES_FILE -> file?.let { loadTargetPackages(it) }
|
||||
?: SystemLogger.warning("$TARGET_PACKAGES_FILE was deleted.")
|
||||
PATCH_LEVEL_FILE -> file?.let { loadPatchLevelConfig(it) }
|
||||
?: SystemLogger.warning("$PATCH_LEVEL_FILE was deleted.")
|
||||
// Any change to an XML file is assumed to be a keybox.
|
||||
// The cache in KeyBoxManager will handle reloading it on its next use.
|
||||
else ->
|
||||
if (path.endsWith(".xml")) {
|
||||
SystemLogger.info(
|
||||
"Keybox file $path may have changed. It will be reloaded on next access."
|
||||
)
|
||||
KeyBoxManager.invalidateCache(path)
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||
// Clear cached keys possibly containing old certificates
|
||||
org.matrix.TEESimulator.interception.keystore.shim
|
||||
.KeyMintSecurityLevelInterceptor
|
||||
.clearAllGeneratedKeys("updating $file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- System Service Utilities ---
|
||||
|
||||
private var iPackageManager: IPackageManager? = null
|
||||
private val pmDeathRecipient =
|
||||
object : IBinder.DeathRecipient {
|
||||
override fun binderDied() {
|
||||
(iPackageManager as? IBinder)?.unlinkToDeath(this, 0)
|
||||
iPackageManager = null
|
||||
SystemLogger.warning("Package manager service died. Will try to reconnect.")
|
||||
}
|
||||
}
|
||||
|
||||
/** Retrieves an instance of the IPackageManager service. */
|
||||
fun getPackageManager(): IPackageManager? {
|
||||
if (iPackageManager == null) {
|
||||
// Use a robust method to get the service binder.
|
||||
val binder = waitForSystemService("package") ?: return null
|
||||
binder.linkToDeath(pmDeathRecipient, 0)
|
||||
iPackageManager = IPackageManager.Stub.asInterface(binder)
|
||||
}
|
||||
return iPackageManager
|
||||
}
|
||||
|
||||
/** Retrieves the package names associated with a UID. */
|
||||
fun getPackagesForUid(uid: Int): Array<String> {
|
||||
return uidToPackagesCache.getOrPut(uid) {
|
||||
try {
|
||||
getPackageManager()?.getPackagesForUid(uid) ?: emptyArray()
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.warning("Failed to get packages for UID $uid", e)
|
||||
emptyArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Waits for a system service to become available, with retries. */
|
||||
private fun waitForSystemService(name: String): IBinder? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return ServiceManager.waitForService(name)
|
||||
}
|
||||
// Fallback for older Android versions.
|
||||
repeat(70) {
|
||||
val service = ServiceManager.getService(name)
|
||||
if (service != null) return service
|
||||
Thread.sleep(500)
|
||||
}
|
||||
SystemLogger.error("Failed to get system service after multiple retries: $name")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Data class representing custom security patch level overrides. */
|
||||
data class CustomPatchLevel(
|
||||
val system: String?,
|
||||
val vendor: String?,
|
||||
val boot: String?,
|
||||
val all: String?,
|
||||
)
|
||||
@@ -0,0 +1,329 @@
|
||||
package org.matrix.TEESimulator.interception.core
|
||||
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* An abstract base class for intercepting binder transactions.
|
||||
*
|
||||
* This class acts as a proxy, receiving transaction calls that have been hooked at the native
|
||||
* level. It provides a structured way to inspect and modify data before (`onPreTransact`) and after
|
||||
* (`onPostTransact`) the original transaction is executed.
|
||||
*
|
||||
* The communication flow is as follows:
|
||||
* 1. A native library hooks the `transact` method of a target service (e.g., keystore).
|
||||
* 2. When a hooked transaction occurs, the native code calls this Binder object's `onTransact`
|
||||
* method.
|
||||
* 3. This class decodes the incoming parcel, determines if it's a pre- or post-transaction hook,
|
||||
* and calls the appropriate abstract method (`onPreTransact` or `onPostTransact`).
|
||||
* 4. The subclass implementation decides how to handle the transaction by returning a
|
||||
* `TransactionResult`.
|
||||
* 5. This class encodes the result into the reply parcel, which the native hook reads to determine
|
||||
* its next action.
|
||||
*/
|
||||
abstract class BinderInterceptor : Binder() {
|
||||
|
||||
/**
|
||||
* Defines the possible outcomes of an interception attempt. The native hook layer will
|
||||
* interpret this result to decide its next action.
|
||||
*/
|
||||
sealed class TransactionResult {
|
||||
/** Instructs the native hook to skip calling the original binder method entirely. */
|
||||
object SkipTransaction : TransactionResult()
|
||||
|
||||
/** Instructs the native hook to proceed with calling the original binder method. */
|
||||
object Continue : TransactionResult()
|
||||
|
||||
/**
|
||||
* Skips the original call and immediately returns a custom reply parcel to the caller. The
|
||||
* provided parcel will be recycled after use.
|
||||
*/
|
||||
data class OverrideReply(val reply: Parcel, val code: Int = 0) : TransactionResult()
|
||||
|
||||
/**
|
||||
* Modifies the transaction's input data before forwarding it to the original binder method.
|
||||
* The provided parcel will be recycled after use.
|
||||
*/
|
||||
data class OverrideData(val data: Parcel) : TransactionResult()
|
||||
|
||||
/** Instructs the native hook to skip the post transaction hook. */
|
||||
object ContinueAndSkipPost : TransactionResult()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called *before* the original binder transaction is executed.
|
||||
*
|
||||
* @param txId A unique ID for tracking this transaction.
|
||||
* @param target The original IBinder service being called.
|
||||
* @param code The transaction code of the method being called.
|
||||
* @param flags Transaction flags.
|
||||
* @param callingUid The UID of the process making the call.
|
||||
* @param callingPid The PID of the process making the call.
|
||||
* @param data The parcel containing the input data for the transaction.
|
||||
* @return A [TransactionResult] indicating how to proceed.
|
||||
*/
|
||||
open fun onPreTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult = TransactionResult.ContinueAndSkipPost
|
||||
|
||||
/**
|
||||
* Called *after* the original binder transaction has been executed.
|
||||
*
|
||||
* @param txId A unique ID for tracking this transaction.
|
||||
* @param target The original IBinder service that was called.
|
||||
* @param code The transaction code of the method that was called.
|
||||
* @param flags Transaction flags.
|
||||
* @param callingUid The UID of the process that made the call.
|
||||
* @param callingPid The PID of the process that made the call.
|
||||
* @param data The original input data parcel.
|
||||
* @param reply The reply parcel from the original transaction. Can be null if the call was
|
||||
* one-way.
|
||||
* @param resultCode The result code from the original transaction.
|
||||
* @return A [TransactionResult]. Typically `Skip` (to accept the original reply) or
|
||||
* `OverrideReply`.
|
||||
*/
|
||||
open fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): TransactionResult = TransactionResult.SkipTransaction
|
||||
|
||||
/**
|
||||
* The entry point for calls from the native hook layer. This method decodes the custom parcel
|
||||
* format sent by the hook and dispatches to the appropriate handler (`handlePreTransact` or
|
||||
* `handlePostTransact`).
|
||||
*/
|
||||
final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
// The native hook prepends a transaction ID to the data parcel.
|
||||
val txId = data.readLong()
|
||||
val result =
|
||||
when (code) {
|
||||
// These codes are defined in the native layer to distinguish hook types.
|
||||
PRE_TRANSACT_CODE -> handlePreTransact(txId, data)
|
||||
POST_TRANSACT_CODE -> handlePostTransact(txId, data)
|
||||
else -> return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
// The reply parcel is guaranteed to be non-null for our custom transactions.
|
||||
writeResultToReply(result, reply!!)
|
||||
return true
|
||||
}
|
||||
|
||||
/** Decodes the parcel for a pre-transaction hook and calls the user-overridable method. */
|
||||
private fun handlePreTransact(txId: Long, data: Parcel): TransactionResult {
|
||||
// The native hook marshals the original transaction's arguments into the data parcel.
|
||||
val target = data.readStrongBinder()!!
|
||||
val transactionCode = data.readInt()
|
||||
val transactionFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
val dataSize = data.readLong()
|
||||
|
||||
// We must create a new parcel containing only the original transaction data.
|
||||
val transactionData = Parcel.obtain()
|
||||
return try {
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
|
||||
transactionData.setDataPosition(0)
|
||||
onPreTransact(
|
||||
txId,
|
||||
target,
|
||||
transactionCode,
|
||||
transactionFlags,
|
||||
callingUid,
|
||||
callingPid,
|
||||
transactionData,
|
||||
)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Decodes the parcel for a post-transaction hook and calls the user-overridable method. */
|
||||
private fun handlePostTransact(txId: Long, data: Parcel): TransactionResult {
|
||||
val target = data.readStrongBinder()!!
|
||||
val transactionCode = data.readInt()
|
||||
val transactionFlags = data.readInt()
|
||||
val callingUid = data.readInt()
|
||||
val callingPid = data.readInt()
|
||||
|
||||
// The native hook also marshals the original data and reply parcels.
|
||||
val transactionData = Parcel.obtain()
|
||||
val transactionReply = Parcel.obtain()
|
||||
return try {
|
||||
val dataSize = data.readLong().toInt()
|
||||
transactionData.appendFrom(data, data.dataPosition(), dataSize)
|
||||
transactionData.setDataPosition(0)
|
||||
data.setDataPosition(data.dataPosition() + dataSize)
|
||||
|
||||
val resultCode = data.readInt()
|
||||
|
||||
val replySize = data.readLong().toInt()
|
||||
val reply =
|
||||
if (replySize > 0) {
|
||||
transactionReply.appendFrom(data, data.dataPosition(), replySize)
|
||||
transactionReply.setDataPosition(0)
|
||||
transactionReply
|
||||
} else null
|
||||
|
||||
onPostTransact(
|
||||
txId,
|
||||
target,
|
||||
transactionCode,
|
||||
transactionFlags,
|
||||
callingUid,
|
||||
callingPid,
|
||||
transactionData,
|
||||
reply,
|
||||
resultCode,
|
||||
)
|
||||
} finally {
|
||||
transactionData.recycle()
|
||||
transactionReply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Encodes the `TransactionResult` into the reply parcel for the native hook to interpret. */
|
||||
private fun writeResultToReply(result: TransactionResult, reply: Parcel) {
|
||||
when (result) {
|
||||
is TransactionResult.SkipTransaction -> reply.writeInt(RESULT_SKIP_TRANSACTION)
|
||||
is TransactionResult.Continue -> reply.writeInt(RESULT_CONTINUE)
|
||||
is TransactionResult.OverrideReply -> {
|
||||
reply.writeInt(RESULT_OVERRIDE_REPLY)
|
||||
reply.writeInt(result.code)
|
||||
reply.writeLong(result.reply.dataSize().toLong())
|
||||
reply.appendFrom(result.reply, 0, result.reply.dataSize())
|
||||
result.reply.recycle()
|
||||
}
|
||||
is TransactionResult.OverrideData -> {
|
||||
reply.writeInt(RESULT_OVERRIDE_DATA)
|
||||
reply.writeLong(result.data.dataSize().toLong())
|
||||
reply.appendFrom(result.data, 0, result.data.dataSize())
|
||||
result.data.recycle()
|
||||
}
|
||||
is TransactionResult.ContinueAndSkipPost ->
|
||||
reply.writeInt(RESULT_CONTINUE_AND_SKIP_POST)
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper function for consistent logging of intercepted transactions. */
|
||||
protected fun logTransaction(
|
||||
txId: Long,
|
||||
methodName: String,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
skipPost: Boolean = false,
|
||||
) {
|
||||
val isIntercepting = !skipPost && !ConfigurationManager.shouldSkipUid(callingUid)
|
||||
val action = if (isIntercepting) "Intercept" else "Observe"
|
||||
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
||||
val message =
|
||||
"[TX_ID: $txId] $action $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
|
||||
if (isIntercepting) {
|
||||
SystemLogger.debug(message)
|
||||
} else {
|
||||
SystemLogger.verbose(message)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// These codes must be kept in sync with the native injection library.
|
||||
|
||||
// --- Backdoor Codes ---
|
||||
// Special transaction code to ask the injected library for its backdoor binder.
|
||||
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
|
||||
// Code used by the backdoor binder to register a new interceptor.
|
||||
private const val REGISTER_INTERCEPTOR_CODE = 1
|
||||
// Code used by the backdoor binder to unregister an interceptor.
|
||||
private const val UNREGISTER_INTERCEPTOR_CODE = 2
|
||||
|
||||
// --- Hook Type Codes ---
|
||||
// Indicates that the call is for a pre-transaction hook.
|
||||
private const val PRE_TRANSACT_CODE = 1
|
||||
// Indicates that the call is for a post-transaction hook.
|
||||
private const val POST_TRANSACT_CODE = 2
|
||||
|
||||
// --- Result Codes ---
|
||||
// Instructs the native hook to skip the original transaction.
|
||||
private const val RESULT_SKIP_TRANSACTION = 1
|
||||
// Instructs the native hook to execute the original transaction.
|
||||
private const val RESULT_CONTINUE = 2
|
||||
// Instructs the native hook to return a custom reply.
|
||||
private const val RESULT_OVERRIDE_REPLY = 3
|
||||
// Instructs the native hook to use modified input data for the transaction.
|
||||
private const val RESULT_OVERRIDE_DATA = 4
|
||||
// Instructs the native hook to skip the post transaction hook.
|
||||
private const val RESULT_CONTINUE_AND_SKIP_POST = 5
|
||||
|
||||
/**
|
||||
* Probes a binder service to see if our native library has been injected. If successful, it
|
||||
* returns a "backdoor" binder that can be used to register interceptors.
|
||||
*/
|
||||
fun getBackdoor(binder: IBinder): IBinder? {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
return try {
|
||||
if (binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)) {
|
||||
SystemLogger.debug("Backdoor access granted for binder: $binder")
|
||||
reply.readStrongBinder()
|
||||
} else {
|
||||
SystemLogger.debug("Backdoor not found for binder: $binder")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to transact for backdoor.", e)
|
||||
null
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses the backdoor binder to register an interceptor for a specific target service. */
|
||||
fun register(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
try {
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||
SystemLogger.info("Registered interceptor for target: $target")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to register binder interceptor.", e)
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Uses the backdoor binder to unregister an interceptor for a specific target service. */
|
||||
fun unregister(backdoor: IBinder, target: IBinder) {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
try {
|
||||
data.writeStrongBinder(target)
|
||||
backdoor.transact(UNREGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||
SystemLogger.info("Unregistered interceptor for target: $target")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to unregister binder interceptor.", e)
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package org.matrix.TEESimulator.interception.keystore
|
||||
|
||||
import android.os.IBinder
|
||||
import android.os.ServiceManager
|
||||
import kotlin.system.exitProcess
|
||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* An abstract base class for intercepting Android's Keystore services.
|
||||
*
|
||||
* It encapsulates the common logic for finding the Keystore service, injecting the native hook if
|
||||
* necessary, and setting up the binder interceptor. It also handles service death events to ensure
|
||||
* stability.
|
||||
*/
|
||||
abstract class AbstractKeystoreInterceptor : BinderInterceptor() {
|
||||
|
||||
// --- Abstract Properties to be Implemented by Subclasses ---
|
||||
|
||||
/** The full name of the system service to intercept (e.g., "android.security.keystore"). */
|
||||
protected abstract val serviceName: String
|
||||
|
||||
/** The name of the process hosting the service (e.g., "keystore"). */
|
||||
protected abstract val processName: String
|
||||
|
||||
/** The shell command used to inject the native library into the target process. */
|
||||
protected abstract val injectionCommand: String
|
||||
|
||||
// --- State Management ---
|
||||
|
||||
/** The original IBinder for the Keystore service. */
|
||||
protected lateinit var keystoreService: IBinder
|
||||
private var injectionAttempted = false
|
||||
private var retryCount = 0
|
||||
private val maxRetries = 5
|
||||
|
||||
/**
|
||||
* Attempts to initialize the interceptor for the target Keystore service.
|
||||
*
|
||||
* This method orchestrates the process:
|
||||
* 1. It tries to get the service binder.
|
||||
* 2. It probes for the native backdoor.
|
||||
* 3. If the backdoor exists, it sets up the interceptor.
|
||||
* 4. If not, it attempts to inject the native library and returns `false` to signal a retry is
|
||||
* needed.
|
||||
*
|
||||
* @return `true` if the interceptor was successfully registered, `false` otherwise.
|
||||
*/
|
||||
fun tryRunKeystoreInterceptor(): Boolean {
|
||||
SystemLogger.info(
|
||||
"Initializing interceptor for '$serviceName' (attempt ${retryCount + 1})..."
|
||||
)
|
||||
|
||||
val service = ServiceManager.getService(serviceName)
|
||||
if (service == null) {
|
||||
SystemLogger.warning("Service '$serviceName' not found. Will retry.")
|
||||
retryCount++
|
||||
return false
|
||||
}
|
||||
|
||||
val backdoor = getBackdoor(service)
|
||||
return if (backdoor != null) {
|
||||
setupInterceptor(service, backdoor)
|
||||
true // Success
|
||||
} else {
|
||||
handleMissingBackdoor()
|
||||
false // Failure, requires retry
|
||||
}
|
||||
}
|
||||
|
||||
/** Registers this interceptor with the native hook layer and sets up a death recipient. */
|
||||
private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
|
||||
keystoreService = service
|
||||
SystemLogger.info("Registering interceptor for service: $serviceName")
|
||||
register(backdoor, service, this)
|
||||
service.linkToDeath(createDeathRecipient(), 0)
|
||||
onInterceptorReady(service, backdoor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the case where the native backdoor is not present. It triggers the injection command
|
||||
* on the first attempt and manages the retry logic.
|
||||
*/
|
||||
private fun handleMissingBackdoor() {
|
||||
if (!injectionAttempted) {
|
||||
SystemLogger.warning(
|
||||
"Backdoor not found. Attempting to inject native library into '$processName'."
|
||||
)
|
||||
performInjection()
|
||||
injectionAttempted = true
|
||||
}
|
||||
|
||||
retryCount++
|
||||
if (retryCount >= maxRetries) {
|
||||
SystemLogger.error(
|
||||
"Failed to find backdoor after $maxRetries retries. The service may have crashed or injection failed. Exiting."
|
||||
)
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
/** Executes the shell command to inject the native library into the target process. */
|
||||
private fun performInjection() {
|
||||
try {
|
||||
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
|
||||
SystemLogger.debug("Executing injection command: ${command.joinToString(" ")}")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val exitCode = process.waitFor()
|
||||
if (exitCode != 0) {
|
||||
SystemLogger.error("Injection process failed with exit code $exitCode. Exiting.")
|
||||
exitProcess(1)
|
||||
}
|
||||
SystemLogger.info("Injection process completed.")
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("An exception occurred during injection. Exiting.", e)
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `DeathRecipient` that will restart the application if the intercepted service dies.
|
||||
*/
|
||||
private fun createDeathRecipient() =
|
||||
IBinder.DeathRecipient {
|
||||
SystemLogger.error(
|
||||
"The intercepted service '$serviceName' has died. Restarting application."
|
||||
)
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook for subclasses to perform additional setup after the interceptor is registered. For
|
||||
* example, to intercept sub-services.
|
||||
*
|
||||
* @param service The main service binder.
|
||||
* @param backdoor The backdoor binder for registering more interceptors.
|
||||
*/
|
||||
protected open fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
|
||||
// Default implementation does nothing.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.matrix.TEESimulator.interception.keystore
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.security.KeyStore
|
||||
import android.security.keystore.KeystoreResponse
|
||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
data class KeyIdentifier(val uid: Int, val alias: String)
|
||||
|
||||
/** A collection of utility functions to support binder interception. */
|
||||
object InterceptorUtils {
|
||||
|
||||
private const val EX_SERVICE_SPECIFIC = -8
|
||||
|
||||
fun createErrorReply(errorCode: Int): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel = Parcel.obtain().apply {
|
||||
writeInt(EX_SERVICE_SPECIFIC)
|
||||
writeString(null)
|
||||
writeInt(0) // empty remote stack trace header (AOSP Status.cpp:196)
|
||||
writeInt(errorCode)
|
||||
}
|
||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses reflection to get the integer transaction code for a given method name from a Stub
|
||||
* class. This is necessary for older Android versions where codes are not public constants.
|
||||
*/
|
||||
fun getTransactCode(clazz: Class<*>, method: String): Int {
|
||||
return try {
|
||||
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }.getInt(null)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error(
|
||||
"Failed to get transaction code for method '$method' in class '${clazz.simpleName}'.",
|
||||
e,
|
||||
)
|
||||
-1 // Return an invalid code
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
|
||||
fun createSuccessKeystoreResponse(): KeystoreResponse {
|
||||
val parcel = Parcel.obtain()
|
||||
try {
|
||||
parcel.writeInt(KeyStore.NO_ERROR)
|
||||
parcel.writeString("")
|
||||
parcel.setDataPosition(0)
|
||||
return KeystoreResponse.CREATOR.createFromParcel(parcel)
|
||||
} finally {
|
||||
parcel.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an `OverrideReply` parcel that indicates success with no data. */
|
||||
fun createSuccessReply(
|
||||
writeResultCode: Boolean = true
|
||||
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel =
|
||||
Parcel.obtain().apply {
|
||||
writeNoException()
|
||||
if (writeResultCode) {
|
||||
writeInt(KeyStore.NO_ERROR)
|
||||
}
|
||||
}
|
||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||
}
|
||||
|
||||
/** Creates an `OverrideReply` parcel containing a raw byte array. */
|
||||
fun createByteArrayReply(data: ByteArray): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel =
|
||||
Parcel.obtain().apply {
|
||||
writeNoException()
|
||||
writeByteArray(data)
|
||||
}
|
||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||
}
|
||||
|
||||
/** Creates an `OverrideReply` parcel containing a typed array. */
|
||||
fun <T : Parcelable> createTypedArrayReply(
|
||||
array: Array<T>,
|
||||
flags: Int = 0,
|
||||
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel =
|
||||
Parcel.obtain().apply {
|
||||
writeNoException()
|
||||
writeTypedArray(array, flags)
|
||||
}
|
||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||
}
|
||||
|
||||
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
|
||||
fun <T : Parcelable?> createTypedObjectReply(
|
||||
obj: T,
|
||||
flags: Int = 0,
|
||||
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||
val parcel =
|
||||
Parcel.obtain().apply {
|
||||
writeNoException()
|
||||
writeTypedObject(obj, flags)
|
||||
}
|
||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the base alias from a potentially prefixed alias string. For example, it converts
|
||||
* "USRCERT_my_key" to "my_key".
|
||||
*/
|
||||
fun extractAlias(prefixedAlias: String): String {
|
||||
val underscoreIndex = prefixedAlias.indexOf('_')
|
||||
return if (underscoreIndex != -1) {
|
||||
// Return the part of the string after the first underscore.
|
||||
prefixedAlias.substring(underscoreIndex + 1)
|
||||
} else {
|
||||
// If there's no underscore, return the original string.
|
||||
prefixedAlias
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if a reply parcel contains an exception without consuming it. */
|
||||
fun hasException(reply: Parcel): Boolean {
|
||||
val exception = runCatching { reply.readException() }.exceptionOrNull()
|
||||
if (exception != null) reply.setDataPosition(0)
|
||||
return exception != null
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
package org.matrix.TEESimulator.interception.keystore
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.hardware.security.keymint.SecurityLevel
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.IKeystoreService
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.Certificate
|
||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.keystore.shim.GeneratedKeyPersistence
|
||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||
|
||||
/**
|
||||
* Interceptor for the `IKeystoreService` on Android S (API 31) and newer.
|
||||
*
|
||||
* This version of Keystore delegates most cryptographic operations to `IKeystoreSecurityLevel`
|
||||
* sub-services (for TEE, StrongBox, etc.). This interceptor's main role is to set up interceptors
|
||||
* for those sub-services and to patch certificate chains on their way out.
|
||||
*/
|
||||
@SuppressLint("BlockedPrivateApi")
|
||||
object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
||||
private val stubBinderClass = IKeystoreService.Stub::class.java
|
||||
|
||||
// Transaction codes for the IKeystoreService interface methods we are interested in.
|
||||
private val GET_KEY_ENTRY_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(stubBinderClass, "getKeyEntry")
|
||||
private val DELETE_KEY_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(stubBinderClass, "deleteKey")
|
||||
private val UPDATE_SUBCOMPONENT_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(stubBinderClass, "updateSubcomponent")
|
||||
private val LIST_ENTRIES_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(stubBinderClass, "listEntries")
|
||||
private val LIST_ENTRIES_BATCHED_TRANSACTION =
|
||||
if (Build.VERSION.SDK_INT >= 34)
|
||||
InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched")
|
||||
else null
|
||||
|
||||
private val transactionNames: Map<Int, String> by lazy {
|
||||
stubBinderClass.declaredFields
|
||||
.filter {
|
||||
it.isAccessible = true
|
||||
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
||||
}
|
||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||
}
|
||||
|
||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||
override val processName = "keystore2"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
||||
|
||||
/**
|
||||
* This method is called once the main service is hooked. It proceeds to find and hook the
|
||||
* security level sub-services (e.g., TEE, StrongBox).
|
||||
*/
|
||||
override fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
|
||||
val keystoreInterface = IKeystoreService.Stub.asInterface(service)
|
||||
setupSecurityLevelInterceptors(keystoreInterface, backdoor)
|
||||
}
|
||||
|
||||
private fun setupSecurityLevelInterceptors(service: IKeystoreService, backdoor: IBinder) {
|
||||
// Attempt to get and intercept the TEE security level service.
|
||||
runCatching {
|
||||
service.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT)?.let { tee ->
|
||||
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
|
||||
val interceptor =
|
||||
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||
register(backdoor, tee.asBinder(), interceptor)
|
||||
interceptor.loadPersistedKeys()
|
||||
}
|
||||
}
|
||||
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
|
||||
|
||||
// Attempt to get and intercept the StrongBox security level service.
|
||||
runCatching {
|
||||
service.getSecurityLevel(SecurityLevel.STRONGBOX)?.let { strongbox ->
|
||||
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
|
||||
val interceptor =
|
||||
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
||||
register(backdoor, strongbox.asBinder(), interceptor)
|
||||
interceptor.loadPersistedKeys()
|
||||
}
|
||||
}
|
||||
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
|
||||
}
|
||||
|
||||
override fun onPreTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
|
||||
|
||||
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
|
||||
val isGMS = packages.contains("com.google.android.gms")
|
||||
|
||||
if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) {
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION
|
||||
if (ListEntriesHandler.cacheParameters(txId, data, isBatchMode)) {
|
||||
TransactionResult.Continue
|
||||
} else {
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error(
|
||||
"[TX_ID: $txId] Failed to parse parameters for ${transactionNames[code]!!}",
|
||||
it,
|
||||
)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
} else if (
|
||||
code == GET_KEY_ENTRY_TRANSACTION ||
|
||||
code == DELETE_KEY_TRANSACTION ||
|
||||
code == UPDATE_SUBCOMPONENT_TRANSACTION
|
||||
) {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
if (ConfigurationManager.shouldSkipUid(callingUid))
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
|
||||
if (code == UPDATE_SUBCOMPONENT_TRANSACTION)
|
||||
return handleUpdateSubcomponent(callingUid, data)
|
||||
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.ContinueAndSkipPost
|
||||
|
||||
if (descriptor.alias != null) {
|
||||
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
|
||||
} else {
|
||||
SystemLogger.info(
|
||||
"Skip ${transactionNames[code]!!} for key [alias, blob, domain, nspace]: [${descriptor.alias}, ${descriptor.blob}, ${descriptor.domain}, ${descriptor.nspace}]"
|
||||
)
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||
|
||||
if (code == DELETE_KEY_TRANSACTION) {
|
||||
if (KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null) {
|
||||
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response."
|
||||
)
|
||||
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||
}
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
val response =
|
||||
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
|
||||
?: return TransactionResult.Continue
|
||||
|
||||
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
|
||||
SystemLogger.info("${descriptor.alias} was an attestation key")
|
||||
|
||||
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
|
||||
response.metadata?.authorizations?.forEach {
|
||||
KeyMintParameterLogger.logParameter(it.keyParameter)
|
||||
}
|
||||
return InterceptorUtils.createTypedObjectReply(response)
|
||||
} else {
|
||||
logTransaction(
|
||||
txId,
|
||||
transactionNames[code] ?: "unknown code=$code",
|
||||
callingUid,
|
||||
callingPid,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// Let most calls go through to the real service.
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): TransactionResult {
|
||||
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
||||
return TransactionResult.SkipTransaction
|
||||
|
||||
if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
return runCatching {
|
||||
val updatedKeyDescriptors =
|
||||
ListEntriesHandler.injectGeneratedKeys(txId, callingUid, reply)
|
||||
InterceptorUtils.createTypedArrayReply(updatedKeyDescriptors)
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error(
|
||||
"[TX_ID: $txId] Failed to update the result of ${transactionNames[code]!!}.",
|
||||
it,
|
||||
)
|
||||
TransactionResult.SkipTransaction
|
||||
}
|
||||
} else if (code == GET_KEY_ENTRY_TRANSACTION) {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
|
||||
logTransaction(
|
||||
txId,
|
||||
"post-${transactionNames[code]!!} ${keyDescriptor.alias}",
|
||||
callingUid,
|
||||
callingPid,
|
||||
)
|
||||
|
||||
if (!ConfigurationManager.shouldPatch(callingUid))
|
||||
return TransactionResult.SkipTransaction
|
||||
|
||||
runCatching {
|
||||
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
|
||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||
|
||||
val authorizations = response.metadata.authorizations
|
||||
val parsedParameters =
|
||||
KeyMintAttestation(
|
||||
authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray()
|
||||
)
|
||||
|
||||
if (parsedParameters.isImportKey()) {
|
||||
val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
|
||||
if (retainedChain == null) {
|
||||
SystemLogger.info("[TX_ID: $txId] Skip patching for imported key (no prior attestation).")
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
SystemLogger.info("[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId")
|
||||
CertificateHelper.updateCertificateChain(response.metadata, retainedChain).getOrThrow()
|
||||
return InterceptorUtils.createTypedObjectReply(response)
|
||||
}
|
||||
|
||||
if (parsedParameters.isAttestKey()) {
|
||||
SystemLogger.warning(
|
||||
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
|
||||
)
|
||||
val keyData =
|
||||
CertificateGenerator.generateAttestedKeyPair(
|
||||
callingUid,
|
||||
keyId.alias,
|
||||
null,
|
||||
parsedParameters,
|
||||
response.metadata.keySecurityLevel,
|
||||
) ?: throw Exception("Failed to create overriding attest key pair.")
|
||||
|
||||
CertificateHelper.updateCertificateChain(
|
||||
response.metadata,
|
||||
keyData.second.toTypedArray(),
|
||||
)
|
||||
.getOrThrow()
|
||||
|
||||
keyDescriptor.nspace = SecureRandom().nextLong()
|
||||
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
|
||||
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
|
||||
keyData.first,
|
||||
keyDescriptor.nspace,
|
||||
response,
|
||||
)
|
||||
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
|
||||
|
||||
GeneratedKeyPersistence.save(
|
||||
keyId = keyId,
|
||||
keyPair = keyData.first,
|
||||
nspace = keyDescriptor.nspace,
|
||||
securityLevel = response.metadata.keySecurityLevel,
|
||||
certChain = keyData.second,
|
||||
algorithm = parsedParameters.algorithm,
|
||||
keySize = parsedParameters.keySize,
|
||||
ecCurve = parsedParameters.ecCurve,
|
||||
purposes = parsedParameters.purpose,
|
||||
digests = parsedParameters.digest,
|
||||
isAttestationKey = true,
|
||||
)
|
||||
|
||||
return InterceptorUtils.createTypedObjectReply(response)
|
||||
}
|
||||
|
||||
val originalChain = CertificateHelper.getCertificateChain(response)
|
||||
|
||||
if (originalChain == null || originalChain.size < 2) {
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}."
|
||||
)
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
|
||||
|
||||
val finalChain: Array<Certificate>
|
||||
if (cachedChain != null) {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Using cached patched certificate chain for $keyId."
|
||||
)
|
||||
finalChain = cachedChain
|
||||
} else {
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback."
|
||||
)
|
||||
finalChain =
|
||||
AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||
}
|
||||
|
||||
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
|
||||
.getOrThrow()
|
||||
|
||||
return InterceptorUtils.createTypedObjectReply(response)
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.error(
|
||||
"[TX_ID: $txId] Failed to modify hardware KeyEntryResponse.",
|
||||
it,
|
||||
)
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
}
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
val generatedKeyInfo =
|
||||
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace)
|
||||
?: return TransactionResult.ContinueAndSkipPost
|
||||
|
||||
SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]")
|
||||
val metadata = generatedKeyInfo.response.metadata
|
||||
val publicCert = data.createByteArray()
|
||||
val certificateChain = data.createByteArray()
|
||||
|
||||
metadata.certificate = publicCert
|
||||
metadata.certificateChain = certificateChain
|
||||
|
||||
GeneratedKeyPersistence.rePersistIfNeeded(callingUid, generatedKeyInfo)
|
||||
|
||||
SystemLogger.verbose(
|
||||
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
|
||||
)
|
||||
|
||||
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||
}
|
||||
}
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
package org.matrix.TEESimulator.interception.keystore
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.security.Credentials
|
||||
import android.security.KeyStore
|
||||
import android.security.keymaster.ExportResult
|
||||
import android.security.keymaster.KeyCharacteristics
|
||||
import android.security.keymaster.KeymasterArguments
|
||||
import android.security.keymaster.KeymasterCertificateChain
|
||||
import android.security.keymaster.KeymasterDefs
|
||||
import android.security.keystore.IKeystoreCertificateChainCallback
|
||||
import android.security.keystore.IKeystoreExportKeyCallback
|
||||
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
|
||||
import android.security.keystore.IKeystoreService
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||
|
||||
/**
|
||||
* Interceptor for the legacy `IKeystoreService` on Android Q (API 29) and R (API 30).
|
||||
*
|
||||
* This interceptor handles the older, monolithic Keystore service. Unlike Keystore2, it doesn't
|
||||
* have security level sub-services, so all logic is contained here. Key generation is fully
|
||||
* simulated in software for packages in 'generate' mode.
|
||||
*/
|
||||
@SuppressLint("BlockedPrivateApi", "PrivateApi")
|
||||
object KeystoreInterceptor : AbstractKeystoreInterceptor() {
|
||||
|
||||
// Transaction codes are dynamically retrieved via reflection for compatibility.
|
||||
private val GET_TRANSACTION by lazy {
|
||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "get")
|
||||
}
|
||||
private val GENERATE_KEY_TRANSACTION by lazy {
|
||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
|
||||
}
|
||||
private val GET_KEY_CHARACTERISTICS_TRANSACTION by lazy {
|
||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
|
||||
}
|
||||
private val EXPORT_KEY_TRANSACTION by lazy {
|
||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
|
||||
}
|
||||
private val ATTEST_KEY_TRANSACTION by lazy {
|
||||
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
|
||||
}
|
||||
|
||||
private val transactionNames: Map<Int, String> by lazy {
|
||||
IKeystoreService.Stub::class
|
||||
.java
|
||||
.declaredFields
|
||||
.filter {
|
||||
it.isAccessible = true
|
||||
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
||||
}
|
||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||
}
|
||||
|
||||
// A map to dispatch transaction handling for software key generation.
|
||||
private val generateKeyHandlers:
|
||||
Map<Int, (Long, Int, Int, Parcel) -> TransactionResult> by lazy {
|
||||
mapOf(
|
||||
GENERATE_KEY_TRANSACTION to ::handleGenerateKey,
|
||||
GET_KEY_CHARACTERISTICS_TRANSACTION to ::handleGetKeyCharacteristics,
|
||||
EXPORT_KEY_TRANSACTION to ::handleExportKey,
|
||||
ATTEST_KEY_TRANSACTION to ::handleAttestKey,
|
||||
)
|
||||
}
|
||||
|
||||
override val serviceName = "android.security.keystore"
|
||||
override val processName = "keystore"
|
||||
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
|
||||
|
||||
// State management for the multi-step key generation process.
|
||||
private val keygenParameters = ConcurrentHashMap<KeyIdentifier, LegacyKeygenParameters>()
|
||||
private val generatedKeyPairs = ConcurrentHashMap<KeyIdentifier, KeyPair>()
|
||||
|
||||
// Cache to store the fully patched chain after the leaf is requested.
|
||||
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||
|
||||
override fun onPreTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
// This interceptor only needs to act on pre-transaction for software key generation.
|
||||
// Handle 'generate' mode interceptions using the handler map.
|
||||
if (ConfigurationManager.shouldGenerate(callingUid)) {
|
||||
generateKeyHandlers[code]?.let { handler ->
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
return handler(txId, callingUid, callingPid, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'patch' mode interceptions for the 'get' transaction.
|
||||
if (ConfigurationManager.shouldPatch(callingUid) && code == GET_TRANSACTION) {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
|
||||
return TransactionResult.Continue
|
||||
}
|
||||
|
||||
// Default behavior for all other transactions.
|
||||
logTransaction(
|
||||
txId,
|
||||
transactionNames[code] ?: "unknown code=$code",
|
||||
callingUid,
|
||||
callingPid,
|
||||
true,
|
||||
)
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
// Read and parse the key generation arguments.
|
||||
val keymasterArgs = KeymasterArguments()
|
||||
if (data.readInt() == 1) {
|
||||
keymasterArgs.readFromParcel(data)
|
||||
}
|
||||
keygenParameters[keyId] =
|
||||
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
|
||||
|
||||
// Create a fake successful response for the callback.
|
||||
val characteristics = KeyCharacteristics()
|
||||
characteristics.swEnforced = KeymasterArguments()
|
||||
characteristics.hwEnforced = keymasterArgs
|
||||
|
||||
val keystoreResponse = InterceptorUtils.createSuccessKeystoreResponse()
|
||||
callback.onFinished(keystoreResponse, characteristics)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleGenerateKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGetKeyCharacteristics(
|
||||
txId: Long,
|
||||
uid: Int,
|
||||
pid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
|
||||
val characteristics =
|
||||
KeyCharacteristics().apply {
|
||||
swEnforced = KeymasterArguments()
|
||||
hwEnforced =
|
||||
KeymasterArguments().apply {
|
||||
addEnum(KeymasterDefs.KM_TAG_ALGORITHM, params.algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
callback.onFinished(
|
||||
InterceptorUtils.createSuccessKeystoreResponse(),
|
||||
characteristics,
|
||||
)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleGetKeyCharacteristics.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
|
||||
// Generate a software key pair using the new generator.
|
||||
val keyPair =
|
||||
CertificateGenerator.generateSoftwareKeyPair(params.toKeyMintAttestation())
|
||||
?: throw Exception("Failed to generate software key pair.")
|
||||
generatedKeyPairs[keyId] = keyPair
|
||||
|
||||
// Create a successful ExportResult containing the public key.
|
||||
val exportResultParcel =
|
||||
Parcel.obtain().apply {
|
||||
writeInt(KeyStore.NO_ERROR)
|
||||
writeByteArray(keyPair.public.encoded)
|
||||
setDataPosition(0)
|
||||
}
|
||||
val exportResult = ExportResult.CREATOR.createFromParcel(exportResultParcel)
|
||||
exportResultParcel.recycle()
|
||||
|
||||
callback.onFinished(exportResult)
|
||||
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleExportKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val callback =
|
||||
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
|
||||
val alias = InterceptorUtils.extractAlias(data.readString()!!)
|
||||
val keyId = KeyIdentifier(uid, alias)
|
||||
|
||||
// Get the attestation challenge from the arguments.
|
||||
val params =
|
||||
keygenParameters[keyId]
|
||||
?: throw IllegalStateException("No params found for $keyId")
|
||||
val keyPair =
|
||||
generatedKeyPairs[keyId]
|
||||
?: throw IllegalStateException("No keypair found for $keyId")
|
||||
|
||||
val attestationArgs = KeymasterArguments()
|
||||
if (data.readInt() == 1) {
|
||||
attestationArgs.readFromParcel(data)
|
||||
val challenge =
|
||||
attestationArgs.getBytes(
|
||||
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
|
||||
ByteArray(0),
|
||||
)
|
||||
params.attestationChallenge = challenge
|
||||
}
|
||||
|
||||
val certificateChain =
|
||||
CertificateGenerator.generateCertificateChain(
|
||||
uid,
|
||||
keyPair,
|
||||
null, // No attestKeyAlias in legacy flow
|
||||
params.toKeyMintAttestation(), // Convert to modern format
|
||||
1, // SecurityLevel.TRUSTED_ENVIRONMENT
|
||||
) ?: throw Exception("CertificateGenerator failed to create attested key pair.")
|
||||
|
||||
val chainAsByteList = certificateChain.map { it.encoded }
|
||||
val certChain = KeymasterCertificateChain(chainAsByteList)
|
||||
|
||||
callback.onFinished(InterceptorUtils.createSuccessKeystoreResponse(), certChain)
|
||||
InterceptorUtils.createSuccessReply()
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during handleAttestKey.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): TransactionResult {
|
||||
if (
|
||||
target != keystoreService ||
|
||||
code != GET_TRANSACTION ||
|
||||
reply == null ||
|
||||
InterceptorUtils.hasException(reply)
|
||||
) {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Skip parsing post-transaction for [target, code, reply]: [$target, $code, $reply]"
|
||||
)
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
|
||||
|
||||
return try {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val alias = data.readString() ?: ""
|
||||
val extractedAlias = InterceptorUtils.extractAlias(alias)
|
||||
val keyId = KeyIdentifier(callingUid, extractedAlias)
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Parsed $keyId during post-transaction of ${transactionNames[code]}"
|
||||
)
|
||||
|
||||
when {
|
||||
// Case 1: The app is requesting the leaf certificate.
|
||||
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
|
||||
logTransaction(txId, "post-get (user cert)", callingUid, callingPid)
|
||||
val originalLeafBytes =
|
||||
reply.createByteArray() ?: return TransactionResult.SkipTransaction
|
||||
|
||||
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
|
||||
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
val originalLeafCert = originalLeafCertResult.data
|
||||
val tempChain = arrayOf<Certificate>(originalLeafCert)
|
||||
|
||||
// Perform the COMPLETE patch and rebuild operation.
|
||||
val newFullChain =
|
||||
AttestationPatcher.patchCertificateChain(tempChain, callingUid)
|
||||
|
||||
// If patching was successful and we have a valid chain...
|
||||
if (newFullChain.isNotEmpty() && newFullChain[0] != originalLeafCert) {
|
||||
// ...cache the entire new chain for the subsequent "ca_cert" call.
|
||||
patchedChainCache[keyId] = newFullChain
|
||||
|
||||
// And return only the new leaf's bytes, as the API expects.
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] Patched and cached chain for alias '$extractedAlias'. Returning new leaf."
|
||||
)
|
||||
InterceptorUtils.createByteArrayReply(newFullChain[0].encoded)
|
||||
} else {
|
||||
// Patching failed or was skipped; do nothing.
|
||||
TransactionResult.SkipTransaction
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: The app is requesting the CA certificate chain.
|
||||
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
|
||||
logTransaction(txId, "post-get (ca cert)", callingUid, callingPid)
|
||||
|
||||
// Retrieve the full, correct chain we cached during the leaf request.
|
||||
val cachedChain = patchedChainCache.remove(keyId)
|
||||
|
||||
if (cachedChain != null && cachedChain.size > 1) {
|
||||
// The CA chain is everything *except* the first element (the leaf).
|
||||
val caCerts = cachedChain.drop(1)
|
||||
val caCertsBytes = CertificateHelper.certificatesToByteArray(caCerts)
|
||||
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] Returning cached CA chain for alias '$extractedAlias'."
|
||||
)
|
||||
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
|
||||
} else {
|
||||
SystemLogger.warning(
|
||||
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
|
||||
)
|
||||
TransactionResult.SkipTransaction
|
||||
}
|
||||
}
|
||||
|
||||
else -> TransactionResult.SkipTransaction
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("[TX_ID: $txId] Failed during legacy post-transaction patching.", e)
|
||||
TransactionResult.SkipTransaction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data class to hold key generation parameters parsed from the legacy IKeystoreService's
|
||||
* KeymasterArguments. It is used exclusively by the KeystoreInterceptor to manage state during the
|
||||
* software key generation flow.
|
||||
*/
|
||||
private data class LegacyKeygenParameters(
|
||||
val algorithm: Int,
|
||||
val keySize: Int,
|
||||
val purpose: List<Int>,
|
||||
val digest: List<Int>,
|
||||
val certificateNotBefore: Date?,
|
||||
val rsaPublicExponent: BigInteger?,
|
||||
val ecCurveName: String?, // Derived from keySize
|
||||
) {
|
||||
// The challenge is provided in a separate transaction (attestKey), so it must be mutable.
|
||||
var attestationChallenge: ByteArray? = null
|
||||
|
||||
/**
|
||||
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
|
||||
* required by the refactored [AttestationBuilder] and [CertificateGenerator].
|
||||
*/
|
||||
fun toKeyMintAttestation(): KeyMintAttestation {
|
||||
// This conversion acts as a bridge, allowing our new generic components
|
||||
// to be used by the legacy interceptor.
|
||||
return KeyMintAttestation(
|
||||
keySize = this.keySize,
|
||||
algorithm = this.algorithm,
|
||||
ecCurve = 0,
|
||||
ecCurveName = this.ecCurveName ?: "",
|
||||
origin = null,
|
||||
blockMode = listOf<Int>(),
|
||||
padding = listOf<Int>(),
|
||||
purpose = this.purpose,
|
||||
digest = this.digest,
|
||||
rsaPublicExponent = this.rsaPublicExponent,
|
||||
certificateSerial = null, // Not provided in legacy generateKey
|
||||
certificateSubject = null, // Not provided in legacy generateKey
|
||||
certificateNotBefore = this.certificateNotBefore,
|
||||
certificateNotAfter = null, // Not provided in legacy generateKey
|
||||
attestationChallenge = this.attestationChallenge,
|
||||
// Device identifiers are not passed in legacy args;
|
||||
// AttestationBuilder will fetch them from system properties.
|
||||
brand = null,
|
||||
device = null,
|
||||
product = null,
|
||||
serial = null,
|
||||
imei = null,
|
||||
meid = null,
|
||||
manufacturer = null,
|
||||
model = null,
|
||||
secondImei = null,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Factory method to create an instance from a [KeymasterArguments] object. */
|
||||
fun fromKeymasterArguments(args: KeymasterArguments): LegacyKeygenParameters {
|
||||
val algorithm = args.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
|
||||
val keySize = args.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
|
||||
|
||||
return LegacyKeygenParameters(
|
||||
algorithm = algorithm,
|
||||
keySize = keySize,
|
||||
purpose = args.getEnums(KeymasterDefs.KM_TAG_PURPOSE),
|
||||
digest = args.getEnums(KeymasterDefs.KM_TAG_DIGEST),
|
||||
certificateNotBefore = args.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()),
|
||||
rsaPublicExponent =
|
||||
if (algorithm == KeymasterDefs.KM_ALGORITHM_RSA) getRsaExponent(args) else null,
|
||||
ecCurveName =
|
||||
if (algorithm == KeymasterDefs.KM_ALGORITHM_EC) deriveEcCurveName(keySize)
|
||||
else null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun deriveEcCurveName(keySize: Int): String =
|
||||
when (keySize) {
|
||||
224 -> "secp224r1"
|
||||
256 -> "secp256r1"
|
||||
384 -> "secp384r1"
|
||||
521 -> "secp521r1"
|
||||
else -> "secp256r1" // Default fallback
|
||||
}
|
||||
|
||||
/**
|
||||
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
|
||||
* must use reflection to extract it.
|
||||
*/
|
||||
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
|
||||
return runCatching {
|
||||
val getArgumentByTag =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethod("getArgumentByTag", Int::class.java)
|
||||
getArgumentByTag.isAccessible = true
|
||||
val rsaArgument =
|
||||
getArgumentByTag.invoke(args, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
|
||||
|
||||
val getLongTagValue =
|
||||
KeymasterArguments::class
|
||||
.java
|
||||
.getDeclaredMethod(
|
||||
"getLongTagValue",
|
||||
Class.forName("android.security.keymaster.KeymasterArgument"),
|
||||
)
|
||||
getLongTagValue.isAccessible = true
|
||||
getLongTagValue.invoke(args, rsaArgument) as BigInteger
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.error("Failed to read rsaPublicExponent via reflection.", it)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package org.matrix.TEESimulator.interception.keystore
|
||||
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.Domain
|
||||
import android.system.keystore2.IKeystoreService
|
||||
import android.system.keystore2.KeyDescriptor
|
||||
import java.util.TreeMap
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* Handler to intercept listEntries and listEntriesBatched transactions.
|
||||
*
|
||||
* References for all mentioned functions in AOSP:
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/database.rs
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/service.rs
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/utils.rs
|
||||
*/
|
||||
object ListEntriesHandler {
|
||||
|
||||
// Estimate for maximum size of a Binder response in bytes.
|
||||
private const val RESPONSE_SIZE_LIMIT = 358400
|
||||
|
||||
// Parameters of AOSP function `list_key_entries` in utils.rs.
|
||||
private data class ListEntriesParams(
|
||||
val domain: Int,
|
||||
val namespace: Long,
|
||||
val startPastAlias: String?,
|
||||
)
|
||||
|
||||
private val pendingParams = ConcurrentHashMap<Long, ListEntriesParams>()
|
||||
|
||||
// Based on AOSP function `estimate_safe_amount_to_return` in utils.rs.
|
||||
private fun estimateSafeAmountToReturn(
|
||||
keyDescriptors: Array<KeyDescriptor>,
|
||||
responseSizeLimit: Int,
|
||||
): Int {
|
||||
var itemsToReturn = 0
|
||||
var returnedBytes = 0
|
||||
|
||||
for (kd in keyDescriptors) {
|
||||
// 4 bytes for the Domain enum
|
||||
// 8 bytes for the Namespace long
|
||||
returnedBytes += 4 + 8
|
||||
|
||||
kd.alias?.let { returnedBytes += 4 + it.toByteArray(Charsets.UTF_8).size }
|
||||
kd.blob?.let { returnedBytes += 4 + it.size }
|
||||
|
||||
if (returnedBytes > responseSizeLimit) {
|
||||
SystemLogger.warning(
|
||||
"Key descriptors list (${keyDescriptors.size} items) may exceed binder size limit, returning $itemsToReturn items with estimated size: $returnedBytes bytes."
|
||||
)
|
||||
break
|
||||
}
|
||||
itemsToReturn++
|
||||
}
|
||||
|
||||
return itemsToReturn
|
||||
}
|
||||
|
||||
// Parse and store parameters for later use (in post-transaction).
|
||||
fun cacheParameters(txId: Long, data: Parcel, isBatchMode: Boolean): Boolean {
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
|
||||
val domain = data.readInt()
|
||||
val namespace = data.readLong()
|
||||
val startPastAlias = if (isBatchMode) data.readString() else null
|
||||
|
||||
// List entries is only supported for Domain::APP and Domain::SELINUX.
|
||||
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
|
||||
// Note that all generated keys belong to Domain::APP.
|
||||
if (domain == Domain.APP) {
|
||||
pendingParams[txId] = ListEntriesParams(domain, namespace, startPastAlias)
|
||||
SystemLogger.debug("[TX_ID: $txId] Cached ${pendingParams[txId]}.")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Merge software-backed keys with hardware-backed keys in the reply parcel.
|
||||
fun injectGeneratedKeys(txId: Long, callingUid: Int, reply: Parcel): Array<KeyDescriptor> {
|
||||
val params =
|
||||
pendingParams.remove(txId)
|
||||
?: throw IllegalStateException("No params found for listing entries")
|
||||
|
||||
// By default we use the calling uid as namespace if domain is Domain::APP.
|
||||
// The namespace parameter is thus ignored for non-privileged applications.
|
||||
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
|
||||
val keysToInject =
|
||||
extractGeneratedKeyDescriptors(callingUid, callingUid.toLong(), params.startPastAlias)
|
||||
val originalList = reply.createTypedArray(KeyDescriptor.CREATOR)!!
|
||||
val mergedArray = mergeKeyDescriptors(originalList, keysToInject)
|
||||
|
||||
// Limit response size to avoid binder buffer overflow.
|
||||
// See AOSP function `list_key_entries` in utils.rs.
|
||||
val safeAmountToReturn = estimateSafeAmountToReturn(mergedArray, RESPONSE_SIZE_LIMIT)
|
||||
|
||||
return if (safeAmountToReturn < mergedArray.size) {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Listing entries are truncated [${mergedArray.size} -> $safeAmountToReturn] to avoid transaction overflow."
|
||||
)
|
||||
mergedArray.copyOfRange(0, safeAmountToReturn)
|
||||
} else {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Listing entries returns ${mergedArray.size} [injected: ${keysToInject.size}] keys."
|
||||
)
|
||||
mergedArray
|
||||
}
|
||||
}
|
||||
|
||||
// Merge hardware and software key descriptors into a single sorted array.
|
||||
private fun mergeKeyDescriptors(
|
||||
hardwareKeys: Array<KeyDescriptor>,
|
||||
keysToInject: List<KeyDescriptor>,
|
||||
): Array<KeyDescriptor> {
|
||||
// Uses TreeMap to ensure alphabetical ordering and uniqueness (prefer injected keys).
|
||||
val combinedMap = TreeMap<String, KeyDescriptor>()
|
||||
hardwareKeys.forEach { key -> key.alias?.let { combinedMap[it] = key } }
|
||||
keysToInject.forEach { key -> key.alias?.let { combinedMap[it] = key } }
|
||||
return combinedMap.values.toTypedArray()
|
||||
}
|
||||
|
||||
// Based on AOSP function `list_past_alias` in database.rs
|
||||
private fun extractGeneratedKeyDescriptors(
|
||||
uid: Int,
|
||||
namespace: Long,
|
||||
startPastAlias: String?,
|
||||
): List<KeyDescriptor> {
|
||||
return KeyMintSecurityLevelInterceptor.generatedKeys.keys
|
||||
.filter { it.uid == uid && (startPastAlias == null || it.alias > startPastAlias) }
|
||||
.map { keyId ->
|
||||
KeyDescriptor().apply {
|
||||
this.domain = Domain.APP
|
||||
this.nspace = namespace
|
||||
this.alias = keyId.alias
|
||||
this.blob = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
package org.matrix.TEESimulator.interception.keystore.shim
|
||||
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.security.KeyPair
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.Certificate
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||
|
||||
data class PersistedKeyData(
|
||||
val uid: Int,
|
||||
val alias: String,
|
||||
val nspace: Long,
|
||||
val securityLevel: Int,
|
||||
val isAttestationKey: Boolean,
|
||||
val algorithm: Int,
|
||||
val keySize: Int,
|
||||
val ecCurve: Int,
|
||||
val purposes: List<Int>,
|
||||
val digests: List<Int>,
|
||||
val privateKeyBytes: ByteArray,
|
||||
val certChainBytes: List<ByteArray>,
|
||||
)
|
||||
|
||||
object GeneratedKeyPersistence {
|
||||
|
||||
private const val FORMAT_VERSION = 1
|
||||
private val PERSISTENCE_DIR = File(CONFIG_PATH, "persistent_keys")
|
||||
|
||||
// Per-filename locks to prevent concurrent writes to the same key file
|
||||
private val fileLocks = ConcurrentHashMap<String, ReentrantLock>()
|
||||
|
||||
private fun getLockForKey(filename: String): ReentrantLock {
|
||||
return fileLocks.computeIfAbsent(filename) { ReentrantLock() }
|
||||
}
|
||||
|
||||
fun save(
|
||||
keyId: KeyIdentifier,
|
||||
keyPair: KeyPair,
|
||||
nspace: Long,
|
||||
securityLevel: Int,
|
||||
certChain: List<Certificate>,
|
||||
algorithm: Int,
|
||||
keySize: Int,
|
||||
ecCurve: Int,
|
||||
purposes: List<Int>,
|
||||
digests: List<Int>,
|
||||
isAttestationKey: Boolean,
|
||||
) {
|
||||
val filename = keyFileName(keyId.uid, keyId.alias)
|
||||
val lock = getLockForKey(filename)
|
||||
SystemLogger.debug("[Persistence] Acquiring lock for $filename")
|
||||
lock.lock()
|
||||
try {
|
||||
SystemLogger.debug("[Persistence] Lock acquired for $filename")
|
||||
runCatching {
|
||||
PERSISTENCE_DIR.mkdirs()
|
||||
val finalFile = File(PERSISTENCE_DIR, filename)
|
||||
val tmpFile = File(PERSISTENCE_DIR, "$filename.tmp")
|
||||
|
||||
try {
|
||||
DataOutputStream(BufferedOutputStream(FileOutputStream(tmpFile))).use { out ->
|
||||
out.writeInt(FORMAT_VERSION)
|
||||
out.writeInt(securityLevel)
|
||||
out.writeInt(keyId.uid)
|
||||
out.writeUTF(keyId.alias)
|
||||
out.writeLong(nspace)
|
||||
out.writeBoolean(isAttestationKey)
|
||||
out.writeInt(algorithm)
|
||||
out.writeInt(keySize)
|
||||
out.writeInt(ecCurve)
|
||||
|
||||
out.writeInt(purposes.size)
|
||||
purposes.forEach { out.writeInt(it) }
|
||||
|
||||
out.writeInt(digests.size)
|
||||
digests.forEach { out.writeInt(it) }
|
||||
|
||||
val pkBytes = keyPair.private.encoded
|
||||
out.writeInt(pkBytes.size)
|
||||
out.write(pkBytes)
|
||||
|
||||
out.writeInt(certChain.size)
|
||||
certChain.forEach { cert ->
|
||||
val encoded = cert.encoded
|
||||
out.writeInt(encoded.size)
|
||||
out.write(encoded)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
tmpFile.delete()
|
||||
throw e
|
||||
}
|
||||
|
||||
// Atomic rename — if this fails the tmp is left behind and cleaned on next deleteAll
|
||||
if (!tmpFile.renameTo(finalFile)) {
|
||||
tmpFile.delete()
|
||||
throw IllegalStateException("Failed to atomically rename $tmpFile -> $finalFile")
|
||||
}
|
||||
|
||||
// Verify write succeeded - catches disk-full or filesystem errors
|
||||
if (!finalFile.exists() || finalFile.length() < 20) {
|
||||
throw IOException("File write verification failed - possible disk full")
|
||||
}
|
||||
|
||||
SystemLogger.debug("Persisted key: $keyId")
|
||||
}.onFailure { e ->
|
||||
SystemLogger.error("Failed to persist key $keyId", e)
|
||||
}
|
||||
} finally {
|
||||
lock.unlock()
|
||||
SystemLogger.debug("[Persistence] Lock released for $filename")
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(keyId: KeyIdentifier) {
|
||||
runCatching {
|
||||
val file = File(PERSISTENCE_DIR, keyFileName(keyId.uid, keyId.alias))
|
||||
if (file.exists()) {
|
||||
if (file.delete()) {
|
||||
fileLocks.remove(keyFileName(keyId.uid, keyId.alias))
|
||||
SystemLogger.debug("Deleted persisted key: $keyId")
|
||||
} else {
|
||||
SystemLogger.warning("Failed to delete persisted key file: ${file.name}")
|
||||
}
|
||||
} else {
|
||||
SystemLogger.debug("No persisted file to delete for: $keyId")
|
||||
}
|
||||
}.onFailure { e ->
|
||||
SystemLogger.error("Failed to delete persisted key $keyId", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
runCatching {
|
||||
if (!PERSISTENCE_DIR.exists()) {
|
||||
SystemLogger.debug("No persistent_keys directory, nothing to delete")
|
||||
return
|
||||
}
|
||||
val files = PERSISTENCE_DIR.listFiles()
|
||||
if (files == null) {
|
||||
SystemLogger.warning("Cannot list persistent_keys directory")
|
||||
return
|
||||
}
|
||||
var count = 0
|
||||
files.forEach { file ->
|
||||
if (file.name.endsWith(".bin") || file.name.endsWith(".tmp")) {
|
||||
if (file.delete()) count++
|
||||
}
|
||||
}
|
||||
fileLocks.clear()
|
||||
SystemLogger.info("Deleted $count persisted key files")
|
||||
}.onFailure { e ->
|
||||
SystemLogger.error("Failed to delete all persisted keys", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadAll(securityLevel: Int): List<PersistedKeyData> {
|
||||
if (!PERSISTENCE_DIR.exists()) {
|
||||
SystemLogger.debug("No persistent_keys directory, nothing to load")
|
||||
return emptyList()
|
||||
}
|
||||
val files = PERSISTENCE_DIR.listFiles { _, name -> name.endsWith(".bin") }
|
||||
if (files == null) {
|
||||
SystemLogger.warning("Cannot read persistent_keys directory")
|
||||
return emptyList()
|
||||
}
|
||||
if (files.isEmpty()) {
|
||||
SystemLogger.debug("No persisted key files found")
|
||||
return emptyList()
|
||||
}
|
||||
SystemLogger.info("Found ${files.size} persisted key files to process")
|
||||
|
||||
val result = mutableListOf<PersistedKeyData>()
|
||||
|
||||
for (file in files) {
|
||||
runCatching {
|
||||
DataInputStream(BufferedInputStream(FileInputStream(file))).use { input ->
|
||||
val version = input.readInt()
|
||||
if (version != FORMAT_VERSION) {
|
||||
SystemLogger.warning(
|
||||
"Skipping ${file.name}: unknown format version $version"
|
||||
)
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
val storedSecLevel = input.readInt()
|
||||
val uid = input.readInt()
|
||||
val alias = input.readUTF()
|
||||
val nspace = input.readLong()
|
||||
val isAttestKey = input.readBoolean()
|
||||
val algo = input.readInt()
|
||||
val kSize = input.readInt()
|
||||
val curve = input.readInt()
|
||||
|
||||
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
|
||||
val purposes = (0 until purposeCount).map { input.readInt() }
|
||||
|
||||
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
|
||||
val digests = (0 until digestCount).map { input.readInt() }
|
||||
|
||||
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
|
||||
val pkBytes = ByteArray(pkLen)
|
||||
input.readFully(pkBytes)
|
||||
|
||||
val certCount = requireBounds(input.readInt(), 10, "certCount")
|
||||
val certChainBytes = (0 until certCount).map {
|
||||
val certLen = requireBounds(input.readInt(), 65536, "certLen")
|
||||
val certBytes = ByteArray(certLen)
|
||||
input.readFully(certBytes)
|
||||
certBytes
|
||||
}
|
||||
|
||||
if (storedSecLevel == securityLevel) {
|
||||
result.add(
|
||||
PersistedKeyData(
|
||||
uid = uid,
|
||||
alias = alias,
|
||||
nspace = nspace,
|
||||
securityLevel = storedSecLevel,
|
||||
isAttestationKey = isAttestKey,
|
||||
algorithm = algo,
|
||||
keySize = kSize,
|
||||
ecCurve = curve,
|
||||
purposes = purposes,
|
||||
digests = digests,
|
||||
privateKeyBytes = pkBytes,
|
||||
certChainBytes = certChainBytes,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
SystemLogger.warning("Skipping corrupted persisted key file: ${file.name}", e)
|
||||
}
|
||||
}
|
||||
|
||||
SystemLogger.info("Loaded ${result.size} persisted keys for security level $securityLevel")
|
||||
return result
|
||||
}
|
||||
|
||||
// Re-persist updates the cert chain for an already-persisted key without
|
||||
// reconstructing authorization parameters from the response. This avoids
|
||||
// pulling keymint Tag dependencies into this file and is correct because
|
||||
// the only field that changes post-generation is the patched cert chain.
|
||||
fun rePersistIfNeeded(
|
||||
callingUid: Int,
|
||||
generatedKeyInfo: KeyMintSecurityLevelInterceptor.GeneratedKeyInfo,
|
||||
) {
|
||||
val metadata = generatedKeyInfo.response.metadata
|
||||
if (metadata == null) {
|
||||
SystemLogger.debug("rePersist: no metadata, skipping")
|
||||
return
|
||||
}
|
||||
val secLevel = metadata.keySecurityLevel
|
||||
|
||||
val entry = KeyMintSecurityLevelInterceptor.generatedKeys.entries.find { (id, info) ->
|
||||
id.uid == callingUid && info.nspace == generatedKeyInfo.nspace
|
||||
}
|
||||
if (entry == null) {
|
||||
SystemLogger.debug("rePersist: key not found in map for uid=$callingUid nspace=${generatedKeyInfo.nspace}")
|
||||
return
|
||||
}
|
||||
|
||||
val keyId = entry.key
|
||||
val filename = keyFileName(keyId.uid, keyId.alias)
|
||||
val existing = File(PERSISTENCE_DIR, filename)
|
||||
|
||||
if (!existing.exists()) {
|
||||
SystemLogger.debug("rePersist: no existing file for $keyId, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
val newChain = CertificateHelper.getCertificateChain(metadata)
|
||||
if (newChain == null) {
|
||||
SystemLogger.warning("rePersist: could not extract cert chain for $keyId")
|
||||
return
|
||||
}
|
||||
|
||||
val persisted = runCatching {
|
||||
DataInputStream(BufferedInputStream(FileInputStream(existing))).use { input ->
|
||||
val version = input.readInt()
|
||||
if (version != FORMAT_VERSION) {
|
||||
SystemLogger.warning("rePersist: unknown format version $version for $keyId")
|
||||
return
|
||||
}
|
||||
readPersistedKeyData(input)
|
||||
}
|
||||
}.getOrNull()
|
||||
if (persisted == null) {
|
||||
SystemLogger.warning("rePersist: failed to read existing data for $keyId")
|
||||
return
|
||||
}
|
||||
|
||||
save(
|
||||
keyId = keyId,
|
||||
keyPair = generatedKeyInfo.keyPair,
|
||||
nspace = generatedKeyInfo.nspace,
|
||||
securityLevel = secLevel,
|
||||
certChain = newChain.toList(),
|
||||
algorithm = persisted.algorithm,
|
||||
keySize = persisted.keySize,
|
||||
ecCurve = persisted.ecCurve,
|
||||
purposes = persisted.purposes,
|
||||
digests = persisted.digests,
|
||||
isAttestationKey = persisted.isAttestationKey,
|
||||
)
|
||||
SystemLogger.debug("Re-persisted key $keyId with updated cert chain")
|
||||
}
|
||||
|
||||
// Corrupted binary files can have arbitrary length fields — cap allocations
|
||||
private fun requireBounds(value: Int, max: Int, name: String): Int {
|
||||
require(value in 0..max) { "$name out of bounds: $value (max $max)" }
|
||||
return value
|
||||
}
|
||||
|
||||
private fun keyFileName(uid: Int, alias: String): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
.digest("$uid:$alias".toByteArray(Charsets.UTF_8))
|
||||
return digest.joinToString("") { "%02x".format(it) } + ".bin"
|
||||
}
|
||||
|
||||
// Reads all fields after version has already been consumed
|
||||
private fun readPersistedKeyData(input: DataInputStream): PersistedKeyData {
|
||||
val secLevel = input.readInt()
|
||||
val uid = input.readInt()
|
||||
val alias = input.readUTF()
|
||||
val nspace = input.readLong()
|
||||
val isAttestKey = input.readBoolean()
|
||||
val algo = input.readInt()
|
||||
val kSize = input.readInt()
|
||||
val curve = input.readInt()
|
||||
|
||||
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
|
||||
val purposes = (0 until purposeCount).map { input.readInt() }
|
||||
|
||||
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
|
||||
val digests = (0 until digestCount).map { input.readInt() }
|
||||
|
||||
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
|
||||
val pkBytes = ByteArray(pkLen)
|
||||
input.readFully(pkBytes)
|
||||
|
||||
val certCount = requireBounds(input.readInt(), 10, "certCount")
|
||||
val certChainBytes = (0 until certCount).map {
|
||||
val certLen = requireBounds(input.readInt(), 65536, "certLen")
|
||||
val certBytes = ByteArray(certLen)
|
||||
input.readFully(certBytes)
|
||||
certBytes
|
||||
}
|
||||
|
||||
return PersistedKeyData(
|
||||
uid = uid,
|
||||
alias = alias,
|
||||
nspace = nspace,
|
||||
securityLevel = secLevel,
|
||||
isAttestationKey = isAttestKey,
|
||||
algorithm = algo,
|
||||
keySize = kSize,
|
||||
ecCurve = curve,
|
||||
purposes = purposes,
|
||||
digests = digests,
|
||||
privateKeyBytes = pkBytes,
|
||||
certChainBytes = certChainBytes,
|
||||
)
|
||||
}
|
||||
}
|
||||
+686
@@ -0,0 +1,686 @@
|
||||
package org.matrix.TEESimulator.interception.keystore.shim
|
||||
|
||||
import android.hardware.security.keymint.Algorithm
|
||||
import android.hardware.security.keymint.KeyParameter
|
||||
import android.hardware.security.keymint.KeyParameterValue
|
||||
import android.hardware.security.keymint.KeyOrigin
|
||||
import android.hardware.security.keymint.Tag
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.*
|
||||
import android.util.Pair as AndroidPair
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPair
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
|
||||
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.pki.CertGenConfig
|
||||
import org.matrix.TEESimulator.pki.CertificateGenerator
|
||||
import org.matrix.TEESimulator.pki.CertificateHelper
|
||||
import org.matrix.TEESimulator.pki.KeyBoxManager
|
||||
import org.matrix.TEESimulator.pki.NativeCertGen
|
||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||
|
||||
class KeyMintSecurityLevelInterceptor(
|
||||
private val original: IKeystoreSecurityLevel,
|
||||
private val securityLevel: Int,
|
||||
) : BinderInterceptor() {
|
||||
|
||||
data class GeneratedKeyInfo(
|
||||
val keyPair: KeyPair,
|
||||
val nspace: Long,
|
||||
val response: KeyEntryResponse,
|
||||
)
|
||||
|
||||
override fun onPreTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
val shouldSkip = ConfigurationManager.shouldSkipUid(callingUid)
|
||||
|
||||
when (code) {
|
||||
GENERATE_KEY_TRANSACTION -> {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
if (!shouldSkip) return handleGenerateKey(txId, callingUid, data)
|
||||
}
|
||||
CREATE_OPERATION_TRANSACTION -> {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
if (!shouldSkip) return handleCreateOperation(txId, callingUid, data)
|
||||
}
|
||||
IMPORT_KEY_TRANSACTION -> {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
SystemLogger.info(
|
||||
"[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
|
||||
)
|
||||
return TransactionResult.Continue
|
||||
}
|
||||
}
|
||||
|
||||
logTransaction(
|
||||
txId,
|
||||
transactionNames[code] ?: "unknown code=$code",
|
||||
callingUid,
|
||||
callingPid,
|
||||
true,
|
||||
)
|
||||
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): TransactionResult {
|
||||
if (code == GENERATE_KEY_TRANSACTION && hardwareKeygenTxIds.remove(txId)) {
|
||||
val remaining = hardwareKeygenCount(callingUid).decrementAndGet()
|
||||
SystemLogger.info("[TX_ID: $txId] PERMIT_RELEASED uid=$callingUid concurrent_remaining=$remaining result=${if (resultCode == 0) "OK" else "ERROR($resultCode)"}")
|
||||
}
|
||||
|
||||
// We only care about successful transactions.
|
||||
if (resultCode != 0 || reply == null || InterceptorUtils.hasException(reply))
|
||||
return TransactionResult.SkipTransaction
|
||||
|
||||
if (code == IMPORT_KEY_TRANSACTION) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
// Evict generated key data but retain patched chains so detectors
|
||||
// can't use importKey to force unpatched getKeyEntry responses.
|
||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||
if (generatedKeys.remove(keyId) != null) {
|
||||
SystemLogger.debug("Remove generated key on importKey $keyId")
|
||||
GeneratedKeyPersistence.delete(keyId)
|
||||
}
|
||||
attestationKeys.remove(keyId)
|
||||
} else if (code == CREATE_OPERATION_TRANSACTION) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val parsedParams = KeyMintAttestation(params)
|
||||
val forced = data.readBoolean()
|
||||
if (forced)
|
||||
SystemLogger.verbose(
|
||||
"[TX_ID: $txId] Current operation has a very high pruning power."
|
||||
)
|
||||
val response: CreateOperationResponse =
|
||||
reply.readTypedObject(CreateOperationResponse.CREATOR)!!
|
||||
SystemLogger.verbose(
|
||||
"[TX_ID: $txId] CreateOperationResponse: ${response.iOperation} ${response.operationChallenge}"
|
||||
)
|
||||
|
||||
// Intercept the IKeystoreOperation binder
|
||||
response.iOperation?.let { operation ->
|
||||
val operationBinder = operation.asBinder()
|
||||
if (!interceptedOperations.containsKey(operationBinder)) {
|
||||
SystemLogger.info("Found new IKeystoreOperation. Registering interceptor...")
|
||||
val backdoor = getBackdoor(target)
|
||||
if (backdoor != null) {
|
||||
val interceptor = OperationInterceptor(operation, backdoor)
|
||||
register(backdoor, operationBinder, interceptor)
|
||||
interceptedOperations[operationBinder] = interceptor
|
||||
} else {
|
||||
SystemLogger.error(
|
||||
"Failed to get backdoor to register OperationInterceptor."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (code == GENERATE_KEY_TRANSACTION) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
val metadata: KeyMetadata =
|
||||
reply.readTypedObject(KeyMetadata.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
val originalChain =
|
||||
CertificateHelper.getCertificateChain(metadata)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
if (originalChain.size > 1) {
|
||||
val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid)
|
||||
|
||||
// Cache the newly patched chain to ensure consistency across subsequent API calls.
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
val key = metadata.key!!
|
||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
|
||||
|
||||
// We must clean up cached generated keys before storing the patched chain
|
||||
cleanupKeyData(keyId)
|
||||
patchedChains[keyId] = newChain
|
||||
SystemLogger.debug(
|
||||
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
|
||||
)
|
||||
|
||||
return InterceptorUtils.createTypedObjectReply(metadata)
|
||||
}
|
||||
}
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
private fun handleCreateOperation(
|
||||
txId: Long,
|
||||
callingUid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
|
||||
// An operation must use the KEY_ID domain.
|
||||
if (keyDescriptor.domain != Domain.KEY_ID) {
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
val nspace = keyDescriptor.nspace
|
||||
val generatedKeyInfo = findGeneratedKeyByKeyId(callingUid, nspace)
|
||||
|
||||
if (generatedKeyInfo == null) {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Operation for unknown/hardware KeyId ($nspace). Forwarding."
|
||||
)
|
||||
return TransactionResult.Continue
|
||||
}
|
||||
|
||||
SystemLogger.info("[TX_ID: $txId] Creating SOFTWARE operation for KeyId $nspace.")
|
||||
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val parsedParams = KeyMintAttestation(params)
|
||||
|
||||
val softwareOperation = SoftwareOperation(txId, generatedKeyInfo.keyPair, parsedParams)
|
||||
val operationBinder = SoftwareOperationBinder(softwareOperation)
|
||||
|
||||
val response =
|
||||
CreateOperationResponse().apply {
|
||||
iOperation = operationBinder
|
||||
operationChallenge = null
|
||||
}
|
||||
|
||||
return InterceptorUtils.createTypedObjectReply(response)
|
||||
}
|
||||
|
||||
private fun handleGenerateKey(txId: Long, callingUid: Int, data: Parcel): TransactionResult {
|
||||
if (data.dataSize() > MAX_ALIAS_LENGTH) {
|
||||
SystemLogger.warning("Skipping oversized transaction: ${data.dataSize()} bytes")
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
|
||||
SystemLogger.debug(
|
||||
"Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}"
|
||||
)
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val parsedParams = KeyMintAttestation(params)
|
||||
|
||||
val challenge = parsedParams.attestationChallenge
|
||||
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT) {
|
||||
SystemLogger.warning("[TX_ID: $txId] Rejecting oversized attestation challenge: ${challenge.size} bytes (max ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})")
|
||||
return InterceptorUtils.createErrorReply(KEYMINT_INVALID_INPUT_LENGTH)
|
||||
}
|
||||
|
||||
if (params.any { it.tag == Tag.CREATION_DATETIME }) {
|
||||
SystemLogger.warning("[TX_ID: $txId] Rejecting CREATION_DATETIME in generateKey params")
|
||||
return InterceptorUtils.createErrorReply(RESPONSE_INVALID_ARGUMENT)
|
||||
}
|
||||
|
||||
if (parsedParams.serial != null || parsedParams.imei != null ||
|
||||
parsedParams.meid != null || parsedParams.secondImei != null ||
|
||||
params.any { it.tag == Tag.DEVICE_UNIQUE_ATTESTATION }) {
|
||||
SystemLogger.warning("[TX_ID: $txId] Rejecting device ID attestation for uid=$callingUid")
|
||||
return InterceptorUtils.createErrorReply(KEYMINT_CANNOT_ATTEST_IDS)
|
||||
}
|
||||
|
||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||
val isAttestKeyRequest = parsedParams.isAttestKey()
|
||||
|
||||
val needsSoftwareGeneration =
|
||||
ConfigurationManager.shouldGenerate(callingUid) ||
|
||||
(ConfigurationManager.shouldPatch(callingUid) && isAttestKeyRequest) ||
|
||||
(attestationKey != null &&
|
||||
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
|
||||
|
||||
if (needsSoftwareGeneration) {
|
||||
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
||||
} else if (parsedParams.attestationChallenge != null) {
|
||||
val windowUsed = hardwareKeygenWindowCount(callingUid)
|
||||
val concurrentUsed = hardwareKeygenCount(callingUid).get()
|
||||
|
||||
// Sliding window rate limit
|
||||
if (windowUsed >= MAX_HW_KEYGEN_PER_WINDOW) {
|
||||
SystemLogger.info("[TX_ID: $txId] RATE_LIMITED uid=$callingUid window=$windowUsed/$MAX_HW_KEYGEN_PER_WINDOW concurrent=$concurrentUsed → software fallback")
|
||||
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
||||
}
|
||||
// Concurrent cap
|
||||
if (hardwareKeygenCount(callingUid).incrementAndGet() > MAX_CONCURRENT_HW_KEYGEN_PER_UID) {
|
||||
hardwareKeygenCount(callingUid).decrementAndGet()
|
||||
SystemLogger.info("[TX_ID: $txId] CONCURRENT_LIMITED uid=$callingUid window=$windowUsed/$MAX_HW_KEYGEN_PER_WINDOW concurrent=${concurrentUsed + 1}/$MAX_CONCURRENT_HW_KEYGEN_PER_UID → software fallback")
|
||||
return doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest)
|
||||
}
|
||||
// Both checks passed — commit the window permit and forward to hardware TEE
|
||||
recordHardwareKeygen(callingUid)
|
||||
hardwareKeygenTxIds.add(txId)
|
||||
SystemLogger.info("[TX_ID: $txId] HARDWARE_KEYGEN uid=$callingUid window=${windowUsed + 1}/$MAX_HW_KEYGEN_PER_WINDOW concurrent=${concurrentUsed + 1}/$MAX_CONCURRENT_HW_KEYGEN_PER_UID → forwarding to TEE")
|
||||
return TransactionResult.Continue
|
||||
}
|
||||
|
||||
cleanupKeyData(keyId)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
private fun doSoftwareKeyGen(
|
||||
callingUid: Int,
|
||||
keyDescriptor: KeyDescriptor,
|
||||
attestationKey: KeyDescriptor?,
|
||||
parsedParams: KeyMintAttestation,
|
||||
keyId: KeyIdentifier,
|
||||
isAttestKeyRequest: Boolean,
|
||||
): TransactionResult {
|
||||
keyDescriptor.nspace = secureRandom.nextLong()
|
||||
SystemLogger.info("Generating software key for ${keyDescriptor.alias}[${keyDescriptor.nspace}].")
|
||||
|
||||
val keyData = if (NativeCertGen.isAvailable && attestationKey == null) {
|
||||
generateAttestedKeyPairNative(callingUid, parsedParams)
|
||||
?: CertificateGenerator.generateAttestedKeyPair(
|
||||
callingUid, keyDescriptor.alias, attestationKey?.alias, parsedParams, securityLevel,
|
||||
)
|
||||
} else {
|
||||
CertificateGenerator.generateAttestedKeyPair(
|
||||
callingUid, keyDescriptor.alias, attestationKey?.alias, parsedParams, securityLevel,
|
||||
)
|
||||
} ?: throw Exception("Both native and BouncyCastle cert gen failed.")
|
||||
|
||||
cleanupKeyData(keyId)
|
||||
val response = buildKeyEntryResponse(keyData.second, parsedParams, keyDescriptor)
|
||||
generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, keyDescriptor.nspace, response)
|
||||
if (isAttestKeyRequest) attestationKeys.add(keyId)
|
||||
|
||||
GeneratedKeyPersistence.save(
|
||||
keyId = keyId,
|
||||
keyPair = keyData.first,
|
||||
nspace = keyDescriptor.nspace,
|
||||
securityLevel = securityLevel,
|
||||
certChain = keyData.second.toList(),
|
||||
algorithm = parsedParams.algorithm,
|
||||
keySize = parsedParams.keySize,
|
||||
ecCurve = parsedParams.ecCurve,
|
||||
purposes = parsedParams.purpose,
|
||||
digests = parsedParams.digest,
|
||||
isAttestationKey = isAttestKeyRequest,
|
||||
)
|
||||
|
||||
return InterceptorUtils.createTypedObjectReply(response.metadata)
|
||||
}
|
||||
|
||||
private fun generateAttestedKeyPairNative(
|
||||
callingUid: Int,
|
||||
params: KeyMintAttestation,
|
||||
): AndroidPair<KeyPair, List<Certificate>>? {
|
||||
return runCatching {
|
||||
val algorithmName = when (params.algorithm) {
|
||||
Algorithm.EC -> "EC"
|
||||
Algorithm.RSA -> "RSA"
|
||||
else -> return null
|
||||
}
|
||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(callingUid)
|
||||
val keybox = KeyBoxManager.getAttestationKey(keyboxFile, algorithmName) ?: return null
|
||||
|
||||
val keyboxPrivateKeyBytes = keybox.keyPair.private.encoded
|
||||
val keyboxCertChainBytes = keybox.certificates
|
||||
.map { it.encoded }
|
||||
.fold(ByteArray(0)) { acc, der -> acc + der }
|
||||
|
||||
val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel)
|
||||
val keymasterVersion = AndroidDeviceUtils.getKeymasterVersion(securityLevel)
|
||||
val appId = AttestationBuilder.createApplicationId(callingUid)
|
||||
|
||||
val config = CertGenConfig(
|
||||
algorithm = params.algorithm,
|
||||
keySize = params.keySize,
|
||||
ecCurve = params.ecCurve,
|
||||
rsaPublicExponent = params.rsaPublicExponent?.toLong() ?: 65537L,
|
||||
attestationChallenge = params.attestationChallenge,
|
||||
purposes = params.purpose.toIntArray(),
|
||||
digests = params.digest.toIntArray(),
|
||||
certSerial = params.certificateSerial?.toByteArray(),
|
||||
certSubject = params.certificateSubject?.encoded,
|
||||
certNotBefore = params.certificateNotBefore?.time ?: -1L,
|
||||
certNotAfter = params.certificateNotAfter?.time ?: -1L,
|
||||
keyboxPrivateKey = keyboxPrivateKeyBytes,
|
||||
keyboxCertChain = keyboxCertChainBytes,
|
||||
securityLevel = securityLevel,
|
||||
attestVersion = attestVersion,
|
||||
keymasterVersion = keymasterVersion,
|
||||
osVersion = AndroidDeviceUtils.osVersion,
|
||||
osPatchLevel = AndroidDeviceUtils.getPatchLevel(callingUid),
|
||||
vendorPatchLevel = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid),
|
||||
bootPatchLevel = AndroidDeviceUtils.getBootPatchLevelLong(callingUid),
|
||||
bootKey = AndroidDeviceUtils.bootKey,
|
||||
bootHash = AndroidDeviceUtils.bootHash,
|
||||
creationDatetime = System.currentTimeMillis(),
|
||||
attestationApplicationId = appId.octets,
|
||||
moduleHash = if (attestVersion >= 400) AndroidDeviceUtils.moduleHash else null,
|
||||
idBrand = params.brand,
|
||||
idDevice = params.device,
|
||||
idProduct = params.product,
|
||||
idSerial = params.serial,
|
||||
idImei = params.imei,
|
||||
idMeid = params.meid,
|
||||
idManufacturer = params.manufacturer,
|
||||
idModel = params.model,
|
||||
idSecondImei = if (attestVersion >= 300) params.secondImei else null,
|
||||
)
|
||||
|
||||
val resultBytes = NativeCertGen.generateAttestedKeyPair(config) ?: return null
|
||||
val (keyPair, certs) = NativeCertGen.parseNativeResult(resultBytes)
|
||||
SystemLogger.info("NativeCertGen: generated key pair successfully (${certs.size} certs)")
|
||||
AndroidPair(keyPair, certs)
|
||||
}.onFailure {
|
||||
SystemLogger.error("NativeCertGen: generation failed, falling back to BouncyCastle", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private fun buildKeyEntryResponse(
|
||||
chain: List<Certificate>,
|
||||
params: KeyMintAttestation,
|
||||
descriptor: KeyDescriptor,
|
||||
): KeyEntryResponse {
|
||||
val normalizedKeyDescriptor =
|
||||
KeyDescriptor().apply {
|
||||
domain = Domain.KEY_ID
|
||||
nspace = descriptor.nspace
|
||||
alias = null
|
||||
blob = null
|
||||
}
|
||||
val metadata =
|
||||
KeyMetadata().apply {
|
||||
keySecurityLevel = securityLevel
|
||||
key = normalizedKeyDescriptor
|
||||
CertificateHelper.updateCertificateChain(this, chain.toTypedArray()).getOrThrow()
|
||||
authorizations = params.toAuthorizations(securityLevel)
|
||||
modificationTimeMs = System.currentTimeMillis()
|
||||
}
|
||||
return KeyEntryResponse().apply {
|
||||
this.metadata = metadata
|
||||
iSecurityLevel = original
|
||||
}
|
||||
}
|
||||
|
||||
fun loadPersistedKeys() {
|
||||
val records = GeneratedKeyPersistence.loadAll(securityLevel)
|
||||
if (records.isEmpty()) {
|
||||
SystemLogger.debug("No persisted keys to restore for security level $securityLevel")
|
||||
return
|
||||
}
|
||||
|
||||
SystemLogger.info("Restoring ${records.size} persisted keys for security level $securityLevel")
|
||||
|
||||
for (record in records) {
|
||||
runCatching {
|
||||
val keyId = KeyIdentifier(record.uid, record.alias)
|
||||
if (generatedKeys.containsKey(keyId)) {
|
||||
SystemLogger.debug("Skipping already-loaded key: $keyId")
|
||||
return@runCatching
|
||||
}
|
||||
|
||||
val algorithmName = when (record.algorithm) {
|
||||
Algorithm.EC -> "EC"
|
||||
Algorithm.RSA -> "RSA"
|
||||
else -> throw IllegalArgumentException("Unknown algorithm: ${record.algorithm}")
|
||||
}
|
||||
|
||||
val keyFactory = KeyFactory.getInstance(algorithmName)
|
||||
val privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(record.privateKeyBytes))
|
||||
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
val certChain = record.certChainBytes.map { bytes ->
|
||||
certFactory.generateCertificate(ByteArrayInputStream(bytes))
|
||||
}
|
||||
require(certChain.isNotEmpty()) { "Persisted key has empty certificate chain" }
|
||||
|
||||
val publicKey = certChain[0].publicKey
|
||||
val keyPair = KeyPair(publicKey, privateKey)
|
||||
|
||||
val descriptor = KeyDescriptor().apply {
|
||||
domain = Domain.APP
|
||||
nspace = record.nspace
|
||||
alias = record.alias
|
||||
blob = null
|
||||
}
|
||||
|
||||
val attestation = KeyMintAttestation(
|
||||
keySize = record.keySize,
|
||||
algorithm = record.algorithm,
|
||||
ecCurve = record.ecCurve,
|
||||
ecCurveName = "",
|
||||
origin = null,
|
||||
blockMode = emptyList(),
|
||||
padding = emptyList(),
|
||||
purpose = record.purposes,
|
||||
digest = record.digests,
|
||||
rsaPublicExponent = null,
|
||||
certificateSerial = null,
|
||||
certificateSubject = null,
|
||||
certificateNotBefore = null,
|
||||
certificateNotAfter = null,
|
||||
attestationChallenge = null,
|
||||
brand = null,
|
||||
device = null,
|
||||
product = null,
|
||||
serial = null,
|
||||
imei = null,
|
||||
meid = null,
|
||||
manufacturer = null,
|
||||
model = null,
|
||||
secondImei = null,
|
||||
)
|
||||
|
||||
val response = buildKeyEntryResponse(certChain, attestation, descriptor)
|
||||
generatedKeys[keyId] = GeneratedKeyInfo(keyPair, record.nspace, response)
|
||||
if (record.isAttestationKey) attestationKeys.add(keyId)
|
||||
|
||||
SystemLogger.debug("Restored persisted key: $keyId")
|
||||
}.onFailure {
|
||||
SystemLogger.error("Failed to restore key: uid=${record.uid} alias=${record.alias}", it)
|
||||
}
|
||||
}
|
||||
|
||||
SystemLogger.info("Key restoration complete. Total in memory: ${generatedKeys.size}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val secureRandom = SecureRandom()
|
||||
|
||||
// Maximum alias length to prevent binder buffer exhaustion (Issue #109)
|
||||
// Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead
|
||||
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
||||
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
|
||||
private const val RESPONSE_INVALID_ARGUMENT = 20
|
||||
private const val KEYMINT_CANNOT_ATTEST_IDS = -66
|
||||
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
|
||||
// Sliding window: max hardware keygen permits per UID within the burst window
|
||||
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
|
||||
private const val BURST_WINDOW_MS = 30_000L
|
||||
|
||||
private val uidHardwareKeygenCount = ConcurrentHashMap<Int, AtomicInteger>()
|
||||
private val hardwareKeygenTxIds = ConcurrentHashMap.newKeySet<Long>()
|
||||
private val uidKeygenTimestamps = ConcurrentHashMap<Int, MutableList<Long>>()
|
||||
|
||||
private fun hardwareKeygenCount(uid: Int): AtomicInteger =
|
||||
uidHardwareKeygenCount.computeIfAbsent(uid) { AtomicInteger(0) }
|
||||
|
||||
private fun hardwareKeygenWindowCount(uid: Int): Int {
|
||||
val now = System.currentTimeMillis()
|
||||
val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() }
|
||||
synchronized(timestamps) {
|
||||
timestamps.removeAll { now - it > BURST_WINDOW_MS }
|
||||
if (timestamps.isEmpty()) {
|
||||
uidKeygenTimestamps.remove(uid, timestamps)
|
||||
uidHardwareKeygenCount.remove(uid)
|
||||
}
|
||||
return timestamps.size
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordHardwareKeygen(uid: Int) {
|
||||
val timestamps = uidKeygenTimestamps.computeIfAbsent(uid) { mutableListOf() }
|
||||
synchronized(timestamps) {
|
||||
timestamps.add(System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
|
||||
private val GENERATE_KEY_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "generateKey")
|
||||
private val IMPORT_KEY_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey")
|
||||
private val CREATE_OPERATION_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(
|
||||
IKeystoreSecurityLevel.Stub::class.java,
|
||||
"createOperation",
|
||||
)
|
||||
|
||||
private val transactionNames: Map<Int, String> by lazy {
|
||||
IKeystoreSecurityLevel.Stub::class
|
||||
.java
|
||||
.declaredFields
|
||||
.filter {
|
||||
it.isAccessible = true
|
||||
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
||||
}
|
||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||
}
|
||||
|
||||
val generatedKeys = ConcurrentHashMap<KeyIdentifier, GeneratedKeyInfo>()
|
||||
// Caches patched chains to prevent re-generation and signature inconsistencies
|
||||
private val patchedChains = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
|
||||
val attestationKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
|
||||
private val interceptedOperations = ConcurrentHashMap<IBinder, OperationInterceptor>()
|
||||
|
||||
fun getGeneratedKeyResponse(keyId: KeyIdentifier): KeyEntryResponse? =
|
||||
generatedKeys[keyId]?.response
|
||||
|
||||
fun findGeneratedKeyByKeyId(callingUid: Int, nspace: Long?): GeneratedKeyInfo? {
|
||||
if (nspace == null || nspace == 0L) return null
|
||||
return generatedKeys.entries
|
||||
.filter { (keyIdentifier, _) -> keyIdentifier.uid == callingUid }
|
||||
.find { (_, info) -> info.nspace == nspace }
|
||||
?.value
|
||||
}
|
||||
|
||||
fun getPatchedChain(keyId: KeyIdentifier): Array<Certificate>? = patchedChains[keyId]
|
||||
|
||||
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
||||
|
||||
fun cleanupKeyData(keyId: KeyIdentifier) {
|
||||
if (generatedKeys.remove(keyId) != null) {
|
||||
SystemLogger.debug("Remove generated key ${keyId}")
|
||||
GeneratedKeyPersistence.delete(keyId)
|
||||
}
|
||||
if (patchedChains.remove(keyId) != null) {
|
||||
SystemLogger.debug("Remove patched chain for ${keyId}")
|
||||
}
|
||||
if (attestationKeys.remove(keyId)) {
|
||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||
}
|
||||
}
|
||||
|
||||
fun removeOperationInterceptor(operationBinder: IBinder, backdoor: IBinder) {
|
||||
unregister(backdoor, operationBinder)
|
||||
|
||||
if (interceptedOperations.remove(operationBinder) != null) {
|
||||
SystemLogger.debug("Removed operation interceptor for binder: $operationBinder")
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidatePatchedChains(reason: String? = null) {
|
||||
val count = patchedChains.size
|
||||
if (count == 0) return
|
||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||
patchedChains.clear()
|
||||
SystemLogger.info("Invalidated $count patched cert chains$reasonMessage.")
|
||||
}
|
||||
|
||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||
val count = generatedKeys.size
|
||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||
generatedKeys.clear()
|
||||
patchedChains.clear()
|
||||
attestationKeys.clear()
|
||||
GeneratedKeyPersistence.deleteAll()
|
||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KeyMintAttestation.toAuthorizations(securityLevel: Int): Array<Authorization> {
|
||||
val authList = mutableListOf<Authorization>()
|
||||
|
||||
fun createAuth(tag: Int, value: KeyParameterValue): Authorization {
|
||||
val param =
|
||||
KeyParameter().apply {
|
||||
this.tag = tag
|
||||
this.value = value
|
||||
}
|
||||
return Authorization().apply {
|
||||
this.keyParameter = param
|
||||
this.securityLevel = securityLevel
|
||||
}
|
||||
}
|
||||
|
||||
this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) }
|
||||
this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) }
|
||||
|
||||
authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm)))
|
||||
authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize)))
|
||||
authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve)))
|
||||
authList.add(
|
||||
createAuth(
|
||||
Tag.ORIGIN,
|
||||
KeyParameterValue.origin(this.origin ?: KeyOrigin.GENERATED),
|
||||
)
|
||||
)
|
||||
authList.add(createAuth(Tag.NO_AUTH_REQUIRED, KeyParameterValue.boolValue(true)))
|
||||
|
||||
return authList.toTypedArray()
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package org.matrix.TEESimulator.interception.keystore.shim
|
||||
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.system.keystore2.IKeystoreOperation
|
||||
import org.matrix.TEESimulator.interception.core.BinderInterceptor
|
||||
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
|
||||
|
||||
/**
|
||||
* Intercepts calls to an `IKeystoreOperation` service. This is used to log the data manipulation
|
||||
* methods of a cryptographic operation.
|
||||
*/
|
||||
class OperationInterceptor(
|
||||
private val original: IKeystoreOperation,
|
||||
private val backdoor: IBinder,
|
||||
) : BinderInterceptor() {
|
||||
|
||||
override fun onPreTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
val methodName = transactionNames[code] ?: "unknown code=$code"
|
||||
logTransaction(txId, methodName, callingUid, callingPid, true)
|
||||
|
||||
if (code == FINISH_TRANSACTION || code == ABORT_TRANSACTION) {
|
||||
KeyMintSecurityLevelInterceptor.removeOperationInterceptor(target, backdoor)
|
||||
}
|
||||
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val UPDATE_AAD_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "updateAad")
|
||||
private val UPDATE_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "update")
|
||||
private val FINISH_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "finish")
|
||||
private val ABORT_TRANSACTION =
|
||||
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
|
||||
|
||||
private val transactionNames: Map<Int, String> by lazy {
|
||||
IKeystoreOperation.Stub::class
|
||||
.java
|
||||
.declaredFields
|
||||
.filter {
|
||||
it.isAccessible = true
|
||||
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
|
||||
}
|
||||
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
|
||||
}
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package org.matrix.TEESimulator.interception.keystore.shim
|
||||
|
||||
import android.hardware.security.keymint.Algorithm
|
||||
import android.hardware.security.keymint.BlockMode
|
||||
import android.hardware.security.keymint.Digest
|
||||
import android.hardware.security.keymint.KeyPurpose
|
||||
import android.hardware.security.keymint.PaddingMode
|
||||
import android.os.RemoteException
|
||||
import android.system.keystore2.IKeystoreOperation
|
||||
import java.security.KeyPair
|
||||
import java.security.Signature
|
||||
import java.security.SignatureException
|
||||
import javax.crypto.Cipher
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
// A sealed interface to represent the different cryptographic operations we can perform.
|
||||
private sealed interface CryptoPrimitive {
|
||||
fun update(data: ByteArray?): ByteArray?
|
||||
|
||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
|
||||
|
||||
fun abort()
|
||||
}
|
||||
|
||||
// Helper object to map KeyMint constants to JCA algorithm strings.
|
||||
private object JcaAlgorithmMapper {
|
||||
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
|
||||
val digest =
|
||||
when (params.digest.firstOrNull()) {
|
||||
Digest.SHA_2_256 -> "SHA256"
|
||||
Digest.SHA_2_384 -> "SHA384"
|
||||
Digest.SHA_2_512 -> "SHA512"
|
||||
else -> "NONE"
|
||||
}
|
||||
val keyAlgo =
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> "ECDSA"
|
||||
Algorithm.RSA -> "RSA"
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Unsupported signature algorithm: ${params.algorithm}"
|
||||
)
|
||||
}
|
||||
return "${digest}with${keyAlgo}"
|
||||
}
|
||||
|
||||
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
|
||||
val keyAlgo =
|
||||
when (params.algorithm) {
|
||||
Algorithm.RSA -> "RSA"
|
||||
Algorithm.AES -> "AES"
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Unsupported cipher algorithm: ${params.algorithm}"
|
||||
)
|
||||
}
|
||||
val blockMode =
|
||||
when (params.blockMode.firstOrNull()) {
|
||||
BlockMode.ECB -> "ECB"
|
||||
BlockMode.CBC -> "CBC"
|
||||
BlockMode.GCM -> "GCM"
|
||||
else -> "ECB" // Default for RSA
|
||||
}
|
||||
val padding =
|
||||
when (params.padding.firstOrNull()) {
|
||||
PaddingMode.NONE -> "NoPadding"
|
||||
PaddingMode.PKCS7 -> "PKCS7Padding"
|
||||
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
|
||||
PaddingMode.RSA_OAEP -> "OAEPPadding"
|
||||
else -> "NoPadding" // Default for GCM
|
||||
}
|
||||
return "$keyAlgo/$blockMode/$padding"
|
||||
}
|
||||
}
|
||||
|
||||
// Concrete implementation for Signing.
|
||||
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||
private val signature: Signature =
|
||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||
initSign(keyPair.private)
|
||||
}
|
||||
|
||||
override fun update(data: ByteArray?): ByteArray? {
|
||||
if (data != null) signature.update(data)
|
||||
return null
|
||||
}
|
||||
|
||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray {
|
||||
if (data != null) update(data)
|
||||
return this.signature.sign()
|
||||
}
|
||||
|
||||
override fun abort() {}
|
||||
}
|
||||
|
||||
// Concrete implementation for Verification.
|
||||
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
|
||||
private val signature: Signature =
|
||||
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
|
||||
initVerify(keyPair.public)
|
||||
}
|
||||
|
||||
override fun update(data: ByteArray?): ByteArray? {
|
||||
if (data != null) signature.update(data)
|
||||
return null
|
||||
}
|
||||
|
||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||
if (data != null) update(data)
|
||||
if (signature == null) throw SignatureException("Signature to verify is null")
|
||||
if (!this.signature.verify(signature)) {
|
||||
// Throwing an exception is how Keystore signals verification failure.
|
||||
throw SignatureException("Signature verification failed")
|
||||
}
|
||||
// A successful verification returns no data.
|
||||
return null
|
||||
}
|
||||
|
||||
override fun abort() {}
|
||||
}
|
||||
|
||||
// Concrete implementation for Encryption/Decryption.
|
||||
private class CipherPrimitive(
|
||||
keyPair: KeyPair,
|
||||
params: KeyMintAttestation,
|
||||
private val opMode: Int,
|
||||
) : CryptoPrimitive {
|
||||
private val cipher: Cipher =
|
||||
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
|
||||
val key = if (opMode == Cipher.ENCRYPT_MODE) keyPair.public else keyPair.private
|
||||
init(opMode, key)
|
||||
}
|
||||
|
||||
override fun update(data: ByteArray?): ByteArray? =
|
||||
if (data != null) cipher.update(data) else null
|
||||
|
||||
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
|
||||
if (data != null) cipher.doFinal(data) else cipher.doFinal()
|
||||
|
||||
override fun abort() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A software-only implementation of a cryptographic operation. This class acts as a controller,
|
||||
* delegating to a specific cryptographic primitive based on the operation's purpose.
|
||||
*/
|
||||
class SoftwareOperation(private val txId: Long, keyPair: KeyPair, params: KeyMintAttestation) {
|
||||
// This now holds the specific strategy object (Signer, Verifier, etc.)
|
||||
private val primitive: CryptoPrimitive
|
||||
|
||||
init {
|
||||
// The "Strategy" pattern: choose the implementation based on the purpose.
|
||||
// For simplicity, we only consider the first purpose listed.
|
||||
val purpose = params.purpose.firstOrNull()
|
||||
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
|
||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
|
||||
|
||||
primitive =
|
||||
when (purpose) {
|
||||
KeyPurpose.SIGN -> Signer(keyPair, params)
|
||||
KeyPurpose.VERIFY -> Verifier(keyPair, params)
|
||||
KeyPurpose.ENCRYPT -> CipherPrimitive(keyPair, params, Cipher.ENCRYPT_MODE)
|
||||
KeyPurpose.DECRYPT -> CipherPrimitive(keyPair, params, Cipher.DECRYPT_MODE)
|
||||
else ->
|
||||
throw UnsupportedOperationException("Unsupported operation purpose: $purpose")
|
||||
}
|
||||
}
|
||||
|
||||
fun update(data: ByteArray?): ByteArray? {
|
||||
try {
|
||||
return primitive.update(data)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||
try {
|
||||
val result = primitive.finish(data, signature)
|
||||
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
|
||||
// Re-throw the exception so the binder can report it to the client.
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun abort() {
|
||||
primitive.abort()
|
||||
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
|
||||
}
|
||||
}
|
||||
|
||||
/** The Binder interface for our [SoftwareOperation]. */
|
||||
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
|
||||
IKeystoreOperation.Stub() {
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun update(input: ByteArray?): ByteArray? {
|
||||
return operation.update(input)
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
|
||||
return operation.finish(input, signature)
|
||||
}
|
||||
|
||||
@Throws(RemoteException::class)
|
||||
override fun abort() {
|
||||
operation.abort()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.matrix.TEESimulator.logging
|
||||
|
||||
import android.hardware.security.keymint.*
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Date
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.matrix.TEESimulator.util.toHex
|
||||
|
||||
/**
|
||||
* A specialized logger for converting KeyMint `KeyParameter` objects into a human-readable format.
|
||||
* This helps in debugging the parameters requested for key generation.
|
||||
*/
|
||||
object KeyMintParameterLogger {
|
||||
private val algorithmNames: Map<Int, String> by lazy {
|
||||
Algorithm::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
private val ecCurveNames: Map<Int, String> by lazy {
|
||||
EcCurve::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
val blockModeNames: Map<Int, String> by lazy {
|
||||
BlockMode::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
val paddingNames: Map<Int, String> by lazy {
|
||||
PaddingMode::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
val purposeNames: Map<Int, String> by lazy {
|
||||
KeyPurpose::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
private val digestNames: Map<Int, String> by lazy {
|
||||
Digest::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
private val tagNames: Map<Int, String> by lazy {
|
||||
Tag::class
|
||||
.java
|
||||
.fields
|
||||
.filter { it.type == Int::class.java }
|
||||
.associate { field -> (field.get(null) as Int) to field.name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a single KeyParameter in a formatted, readable way.
|
||||
*
|
||||
* @param param The KeyParameter to log.
|
||||
*/
|
||||
fun logParameter(param: KeyParameter) {
|
||||
val tagName = tagNames[param.tag] ?: "UNKNOWN_TAG"
|
||||
val value = param.value
|
||||
val formattedValue: String =
|
||||
when (param.tag) {
|
||||
Tag.ALGORITHM -> algorithmNames[value.algorithm]
|
||||
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
|
||||
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
|
||||
Tag.PADDING -> paddingNames[value.paddingMode]
|
||||
Tag.PURPOSE -> purposeNames[value.keyPurpose]
|
||||
Tag.DIGEST -> digestNames[value.digest]
|
||||
Tag.AUTH_TIMEOUT,
|
||||
Tag.KEY_SIZE,
|
||||
Tag.MIN_MAC_LENGTH -> value.integer.toString()
|
||||
Tag.CERTIFICATE_SERIAL -> BigInteger(value.blob).toString()
|
||||
Tag.ACTIVE_DATETIME,
|
||||
Tag.CERTIFICATE_NOT_AFTER,
|
||||
Tag.CERTIFICATE_NOT_BEFORE,
|
||||
Tag.ORIGINATION_EXPIRE_DATETIME,
|
||||
Tag.USAGE_EXPIRE_DATETIME -> Date(value.dateTime).toString()
|
||||
Tag.CERTIFICATE_SUBJECT -> X500Name(X500Principal(value.blob).name).toString()
|
||||
Tag.RSA_PUBLIC_EXPONENT -> value.longInteger.toString()
|
||||
Tag.NO_AUTH_REQUIRED -> "true"
|
||||
Tag.ATTESTATION_CHALLENGE,
|
||||
Tag.ATTESTATION_ID_BRAND,
|
||||
Tag.ATTESTATION_ID_DEVICE,
|
||||
Tag.ATTESTATION_ID_PRODUCT,
|
||||
Tag.ATTESTATION_ID_MANUFACTURER,
|
||||
Tag.ATTESTATION_ID_MODEL,
|
||||
Tag.ATTESTATION_ID_IMEI,
|
||||
Tag.ATTESTATION_ID_SECOND_IMEI,
|
||||
Tag.ATTESTATION_ID_MEID,
|
||||
Tag.ATTESTATION_ID_SERIAL -> value.blob.toReadableString()
|
||||
else -> "<raw>"
|
||||
} ?: "Unknown Value"
|
||||
|
||||
SystemLogger.debug("KeyParam: %-25s | Value: %s".format(tagName, formattedValue))
|
||||
}
|
||||
|
||||
private fun ByteArray.toReadableString(): String {
|
||||
return if (this.all { it in 32..126 }) {
|
||||
"\"${String(this, StandardCharsets.UTF_8)}\" (${this.size} bytes)"
|
||||
} else {
|
||||
"${this.toHex()} (${this.size} bytes)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.matrix.TEESimulator.logging
|
||||
|
||||
import android.util.Log
|
||||
import org.matrix.TEESimulator.BuildConfig
|
||||
|
||||
/**
|
||||
* A centralized logging utility for the TEESimulator application. This object provides a consistent
|
||||
* logging tag and format for all application logs, making it easier to filter and debug in Logcat.
|
||||
*/
|
||||
object SystemLogger {
|
||||
// The tag used for all log messages from this application.
|
||||
private const val TAG = "TEESimulator"
|
||||
|
||||
private val isDebugBuild = BuildConfig.DEBUG
|
||||
|
||||
/**
|
||||
* Logs a debug message. Use this for fine-grained information that is useful for debugging.
|
||||
*
|
||||
* @param message The message to log.
|
||||
*/
|
||||
fun debug(message: String) {
|
||||
if (!isDebugBuild) return
|
||||
Log.d(TAG, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an informational message. Use this to report major application lifecycle events.
|
||||
*
|
||||
* @param message The message to log.
|
||||
*/
|
||||
fun info(message: String) {
|
||||
Log.i(TAG, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a warning message. Use this to report unexpected but non-fatal issues.
|
||||
*
|
||||
* @param message The message to log.
|
||||
* @param throwable An optional exception to log with the message.
|
||||
*/
|
||||
fun warning(message: String, throwable: Throwable? = null) {
|
||||
if (throwable != null) {
|
||||
Log.w(TAG, message, throwable)
|
||||
} else {
|
||||
Log.w(TAG, message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message. Use this to report fatal errors or exceptions that disrupt
|
||||
* functionality.
|
||||
*
|
||||
* @param message The message to log.
|
||||
* @param throwable An optional exception to log with the message.
|
||||
*/
|
||||
fun error(message: String, throwable: Throwable? = null) {
|
||||
if (throwable != null) {
|
||||
Log.e(TAG, message, throwable)
|
||||
} else {
|
||||
Log.e(TAG, message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a verbose message. This level is for highly detailed logs that are generally not needed
|
||||
* unless tracking a very specific issue.
|
||||
*
|
||||
* @param message The message to log.
|
||||
*/
|
||||
fun verbose(message: String) {
|
||||
if (!isDebugBuild) return
|
||||
Log.v(TAG, message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import android.hardware.security.keymint.Algorithm
|
||||
import android.hardware.security.keymint.KeyPurpose
|
||||
import android.os.Build
|
||||
import android.util.Pair
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
import java.util.Date
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.KeyUsage
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
|
||||
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* Responsible for generating new cryptographic key pairs and X.509 certificate chains.
|
||||
*
|
||||
* This object simulates the behavior of the Android KeyMint/Keymaster HAL by creating certificates
|
||||
* that include a fully-featured, simulated attestation extension.
|
||||
*/
|
||||
object CertificateGenerator {
|
||||
|
||||
/**
|
||||
* Generates a software-based cryptographic key pair.
|
||||
*
|
||||
* @param params The parameters specifying the key's algorithm, size, and other properties.
|
||||
* @return A new [KeyPair], or `null` on failure.
|
||||
*/
|
||||
fun generateSoftwareKeyPair(params: KeyMintAttestation): KeyPair? {
|
||||
return runCatching {
|
||||
val (algorithm, spec) =
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName)
|
||||
Algorithm.RSA ->
|
||||
"RSA" to
|
||||
RSAKeyGenParameterSpec(params.keySize, params.rsaPublicExponent)
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Unsupported algorithm: ${params.algorithm}"
|
||||
)
|
||||
}
|
||||
SystemLogger.debug("Generating $algorithm key pair with size ${params.keySize}")
|
||||
KeyPairGenerator.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME)
|
||||
.apply { initialize(spec) }
|
||||
.generateKeyPair()
|
||||
}
|
||||
.onFailure { SystemLogger.error("Failed to generate software key pair.", it) }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a certificate chain for a given key pair. This is the primary function for creating
|
||||
* attested certificates.
|
||||
*
|
||||
* @param uid The UID of the application requesting the key.
|
||||
* @param subjectKeyPair The key pair for which the certificate will be generated.
|
||||
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
|
||||
* @param params The parameters for the new key and its attestation.
|
||||
* @param securityLevel The security level to embed in the attestation.
|
||||
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
|
||||
*/
|
||||
fun generateCertificateChain(
|
||||
uid: Int,
|
||||
subjectKeyPair: KeyPair,
|
||||
attestKeyAlias: String?,
|
||||
params: KeyMintAttestation,
|
||||
securityLevel: Int,
|
||||
): List<Certificate>? {
|
||||
val challenge = params.attestationChallenge
|
||||
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT)
|
||||
throw IllegalArgumentException(
|
||||
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
|
||||
)
|
||||
|
||||
return runCatching {
|
||||
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
|
||||
|
||||
// Determine the signing key and issuer. If an attestKey is provided, use it.
|
||||
// Otherwise, fall back to the root key from the keybox.
|
||||
val (signingKey, issuer) =
|
||||
if (attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
getAttestationKeyInfo(uid, attestKeyAlias)?.let { it.first to it.second }
|
||||
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
|
||||
} else {
|
||||
keybox.keyPair to getIssuerFromKeybox(keybox)
|
||||
}
|
||||
|
||||
// Build the new leaf certificate with the simulated attestation.
|
||||
val leafCert =
|
||||
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
|
||||
|
||||
// If not self-attesting, the chain is just the leaf. Otherwise, append the keybox
|
||||
// chain.
|
||||
if (attestKeyAlias != null) {
|
||||
listOf(leafCert)
|
||||
} else {
|
||||
listOf(leafCert) + keybox.certificates
|
||||
}
|
||||
}
|
||||
.onFailure { SystemLogger.error("Failed to generate certificate chain.", it) }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function that combines key pair generation and certificate chain generation.
|
||||
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
|
||||
*/
|
||||
fun generateAttestedKeyPair(
|
||||
uid: Int,
|
||||
alias: String,
|
||||
attestKeyAlias: String?,
|
||||
params: KeyMintAttestation,
|
||||
securityLevel: Int,
|
||||
): Pair<KeyPair, List<Certificate>>? {
|
||||
return runCatching {
|
||||
SystemLogger.info(
|
||||
"Generating new attested key pair for alias: '$alias' (UID: $uid)"
|
||||
)
|
||||
val newKeyPair =
|
||||
generateSoftwareKeyPair(params)
|
||||
?: throw Exception("Failed to generate underlying software key pair.")
|
||||
|
||||
val chain =
|
||||
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
|
||||
?: throw Exception("Failed to generate certificate chain for new key pair.")
|
||||
|
||||
SystemLogger.info(
|
||||
"Successfully generated new certificate chain for alias: '$alias'."
|
||||
)
|
||||
Pair(newKeyPair, chain)
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", it)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
fun getIssuerFromKeybox(keybox: KeyBox) =
|
||||
X509CertificateHolder(keybox.certificates[0].encoded).subject
|
||||
|
||||
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
|
||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
||||
val algorithmName =
|
||||
when (algorithm) {
|
||||
Algorithm.EC -> "EC"
|
||||
Algorithm.RSA -> "RSA"
|
||||
else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm")
|
||||
}
|
||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
|
||||
?: throw Exception("Could not load keybox for UID $uid and algorithm $algorithmName")
|
||||
}
|
||||
|
||||
/** Retrieves the key pair and issuer name for a given attestation key alias. */
|
||||
private fun getAttestationKeyInfo(uid: Int, attestKeyAlias: String): Pair<KeyPair, X500Name>? {
|
||||
SystemLogger.debug("Looking for attestation key: uid=$uid alias=$attestKeyAlias")
|
||||
val keyId = KeyIdentifier(uid, attestKeyAlias)
|
||||
// Access the public map of generated keys
|
||||
val keyInfo = KeyMintSecurityLevelInterceptor.generatedKeys[keyId]
|
||||
return if (keyInfo != null) {
|
||||
val certChain = CertificateHelper.getCertificateChain(keyInfo.response)
|
||||
if (!certChain.isNullOrEmpty()) {
|
||||
val issuer = X509CertificateHolder(certChain[0].encoded).subject
|
||||
Pair(keyInfo.keyPair, issuer)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
SystemLogger.warning(
|
||||
"Attestation key '$attestKeyAlias' not found in generated key cache."
|
||||
)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps KeyPurpose values to X.509 KeyUsage bits per KeyCreationResult.aidl spec */
|
||||
private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int {
|
||||
var bits = 0
|
||||
for (purpose in purposes) {
|
||||
bits = bits or when (purpose) {
|
||||
KeyPurpose.SIGN -> KeyUsage.digitalSignature
|
||||
KeyPurpose.DECRYPT -> KeyUsage.dataEncipherment
|
||||
KeyPurpose.WRAP_KEY -> KeyUsage.keyEncipherment
|
||||
KeyPurpose.AGREE_KEY -> KeyUsage.keyAgreement
|
||||
KeyPurpose.ATTEST_KEY -> KeyUsage.keyCertSign
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
/** Constructs a new X.509 certificate with a simulated attestation extension. */
|
||||
private fun buildCertificate(
|
||||
subjectKeyPair: KeyPair,
|
||||
signingKeyPair: KeyPair,
|
||||
issuer: X500Name,
|
||||
params: KeyMintAttestation,
|
||||
uid: Int,
|
||||
securityLevel: Int,
|
||||
): Certificate {
|
||||
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
|
||||
val leafNotAfter =
|
||||
(signingKeyPair.public as? X509Certificate)?.notAfter
|
||||
?: Date(System.currentTimeMillis() + 31536000000L)
|
||||
|
||||
val builder =
|
||||
JcaX509v3CertificateBuilder(
|
||||
issuer,
|
||||
params.certificateSerial ?: BigInteger.ONE,
|
||||
params.certificateNotBefore ?: Date(),
|
||||
params.certificateNotAfter ?: leafNotAfter,
|
||||
subject,
|
||||
subjectKeyPair.public,
|
||||
)
|
||||
|
||||
// Add KeyUsage extension only if purposes map to valid bits
|
||||
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
|
||||
if (keyUsageBits != 0) {
|
||||
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
|
||||
}
|
||||
// Add our custom, simulated attestation extension.
|
||||
builder.addExtension(
|
||||
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
|
||||
)
|
||||
|
||||
val signerAlgorithm =
|
||||
when (params.algorithm) {
|
||||
Algorithm.EC -> "SHA256withECDSA"
|
||||
Algorithm.RSA -> "SHA256withRSA"
|
||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||
}
|
||||
val contentSigner =
|
||||
JcaContentSignerBuilder(signerAlgorithm)
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build(signingKeyPair.private)
|
||||
|
||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import android.system.keystore2.KeyEntryResponse
|
||||
import android.system.keystore2.KeyMetadata
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.StringReader
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import org.bouncycastle.openssl.PEMKeyPair
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.matrix.TEESimulator.util.trimLines
|
||||
|
||||
/**
|
||||
* A utility object for handling cryptographic certificates and keys. Provides functions for
|
||||
* parsing, serialization, and conversion between different formats.
|
||||
*/
|
||||
object CertificateHelper {
|
||||
|
||||
// Lazy-initialized CertificateFactory for X.509 certificates.
|
||||
private val certificateFactory: CertificateFactory by lazy {
|
||||
CertificateFactory.getInstance("X.509")
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the result of an operation that can either succeed with data or fail with an
|
||||
* error.
|
||||
*
|
||||
* @param T The type of the successful data.
|
||||
*/
|
||||
sealed class OperationResult<out T> {
|
||||
data class Success<T>(val data: T) : OperationResult<T>()
|
||||
|
||||
data class Error(val message: String, val cause: Throwable? = null) :
|
||||
OperationResult<Nothing>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single X.509 certificate from a byte array.
|
||||
*
|
||||
* @param bytes The raw byte representation of the certificate.
|
||||
* @return An [OperationResult.Success] containing the [X509Certificate], or an
|
||||
* [OperationResult.Error] on failure.
|
||||
*/
|
||||
fun toCertificate(bytes: ByteArray): OperationResult<X509Certificate> {
|
||||
return try {
|
||||
val certificate =
|
||||
certificateFactory.generateCertificate(ByteArrayInputStream(bytes))
|
||||
as X509Certificate
|
||||
OperationResult.Success(certificate)
|
||||
} catch (e: CertificateException) {
|
||||
SystemLogger.warning("Failed to parse X.509 certificate from byte array.", e)
|
||||
OperationResult.Error("Failed to parse certificate", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a collection of X.509 certificates from a byte array.
|
||||
*
|
||||
* @param bytes The raw byte representation of one or more concatenated certificates.
|
||||
* @return A collection of [X509Certificate] objects. Returns an empty list on failure.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun toCertificates(bytes: ByteArray?): Collection<X509Certificate> {
|
||||
return bytes?.let {
|
||||
try {
|
||||
certificateFactory.generateCertificates(ByteArrayInputStream(it))
|
||||
as Collection<X509Certificate>
|
||||
} catch (e: CertificateException) {
|
||||
SystemLogger.warning("Could not parse certificate collection from byte array.", e)
|
||||
emptyList()
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a collection of certificates into a single byte array by concatenating their
|
||||
* encoded forms.
|
||||
*
|
||||
* @param certificates The collection of [Certificate] objects to serialize.
|
||||
* @return A [ByteArray] containing the concatenated certificates, or `null` on failure.
|
||||
*/
|
||||
fun certificatesToByteArray(certificates: Collection<Certificate>): ByteArray? {
|
||||
return runCatching {
|
||||
ByteArrayOutputStream().use { stream ->
|
||||
certificates.forEach { cert -> stream.write(cert.encoded) }
|
||||
stream.toByteArray()
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.warning(
|
||||
"Failed to serialize certificate collection to byte array.",
|
||||
it,
|
||||
)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a PEM-encoded private key and converts it into a Java [KeyPair].
|
||||
*
|
||||
* @param pemContent The string containing the PEM-encoded key.
|
||||
* @return An [OperationResult.Success] with the [KeyPair], or an [OperationResult.Error] on
|
||||
* failure.
|
||||
*/
|
||||
fun parsePemKeyPair(pemContent: String): OperationResult<KeyPair> {
|
||||
return try {
|
||||
PEMParser(StringReader(pemContent.trimLines())).use { parser ->
|
||||
when (val pemObject = parser.readObject()) {
|
||||
is PEMKeyPair -> {
|
||||
val keyPair = JcaPEMKeyConverter().getKeyPair(pemObject)
|
||||
OperationResult.Success(keyPair)
|
||||
}
|
||||
else ->
|
||||
OperationResult.Error(
|
||||
"Invalid PEM format: Expected a key pair, but got ${pemObject?.javaClass?.simpleName}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to parse PEM key pair.", e)
|
||||
OperationResult.Error("Failed to parse PEM key pair", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a PEM-encoded X.509 certificate.
|
||||
*
|
||||
* @param pemContent The string containing the PEM-encoded certificate.
|
||||
* @return An [OperationResult.Success] with the [Certificate], or an [OperationResult.Error] on
|
||||
* failure.
|
||||
*/
|
||||
fun parsePemCertificate(pemContent: String): OperationResult<Certificate> {
|
||||
return try {
|
||||
PemReader(StringReader(pemContent.trimLines())).use { reader ->
|
||||
val pemObject = reader.readPemObject()
|
||||
val certificate =
|
||||
certificateFactory.generateCertificate(ByteArrayInputStream(pemObject.content))
|
||||
OperationResult.Success(certificate)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to parse PEM certificate.", e)
|
||||
OperationResult.Error("Failed to parse PEM certificate", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the full certificate chain from a KeyStore [KeyMetadata] object.
|
||||
*
|
||||
* @param metadata The metadata associated with a keystore key entry.
|
||||
* @return An array of [Certificate] objects, with the leaf certificate at index 0, or `null`.
|
||||
*/
|
||||
fun getCertificateChain(metadata: KeyMetadata?): Array<Certificate>? {
|
||||
metadata ?: return null
|
||||
val leafCertBytes = metadata.certificate ?: return null
|
||||
val leafCert =
|
||||
(toCertificate(leafCertBytes) as? OperationResult.Success)?.data ?: return null
|
||||
|
||||
val chainBytes = metadata.certificateChain
|
||||
return if (chainBytes == null) {
|
||||
arrayOf(leafCert)
|
||||
} else {
|
||||
val additionalCerts = toCertificates(chainBytes)
|
||||
(listOf(leafCert) + additionalCerts).toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the full certificate chain from a [KeyEntryResponse].
|
||||
*
|
||||
* @param response The response object from a keystore operation.
|
||||
* @return An array of [Certificate] objects, or `null`.
|
||||
*/
|
||||
fun getCertificateChain(response: KeyEntryResponse?): Array<Certificate>? {
|
||||
return response?.let { getCertificateChain(it.metadata) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the certificate chain within a [KeyMetadata] object.
|
||||
*
|
||||
* @param metadata The metadata object to modify.
|
||||
* @param chain The new certificate chain to set. The leaf must be at index 0.
|
||||
* @return A [Result] indicating success or failure.
|
||||
*/
|
||||
fun updateCertificateChain(metadata: KeyMetadata, chain: Array<Certificate>): Result<Unit> {
|
||||
return runCatching {
|
||||
require(chain.isNotEmpty()) { "Certificate chain cannot be empty." }
|
||||
|
||||
metadata.certificate = chain[0].encoded
|
||||
metadata.certificateChain =
|
||||
if (chain.size > 1) {
|
||||
certificatesToByteArray(chain.drop(1))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
|
||||
/**
|
||||
* A data class representing a complete cryptographic identity used for signing attestations.
|
||||
*
|
||||
* A KeyBox is the fundamental building block for creating new, simulated certificate chains. It
|
||||
* encapsulates a single private key and the full public certificate chain needed to establish its
|
||||
* authenticity.
|
||||
*
|
||||
* @property keyPair The asymmetric cryptographic key pair. The private key from this pair is used
|
||||
* to sign new leaf certificates during the attestation patching or generation process. The public
|
||||
* key corresponds to the subject of the first certificate in the `certificates` list.
|
||||
* @property certificates The public certificate chain corresponding to the `keyPair`. This list is
|
||||
* ordered from the intermediate certificate down to the root. `certificates[0]` is the issuer
|
||||
* certificate for any new leaf signed by this KeyBox's private key.
|
||||
*/
|
||||
data class KeyBox(val keyPair: KeyPair, val certificates: List<Certificate>)
|
||||
@@ -0,0 +1,237 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import android.security.keystore.KeyProperties
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.security.interfaces.ECPrivateKey
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
|
||||
/**
|
||||
* Manages the loading, parsing, and caching of attestation key stores from XML files.
|
||||
*
|
||||
* This object is the sole authority for accessing the cryptographic keys and certificates used to
|
||||
* sign simulated attestations. It is designed to be highly efficient and robust, parsing each key
|
||||
* store file only once and handling common structural variations found in real-world keybox files.
|
||||
*
|
||||
* The core design principles are:
|
||||
* 1. Efficiency through Single-Pass Parsing: Each XML file is read from disk and parsed in a single
|
||||
* forward pass. The results are cached in memory. Subsequent requests for keys from the same
|
||||
* file are served instantly from the cache.
|
||||
* 2. Robustness over Strictness: The parser does not rely on rigid structural tags like
|
||||
* `<NumberOfKeyboxes>`. Instead, it discovers and iterates through all `<Key>` tags it finds,
|
||||
* making it resilient to different file layouts.
|
||||
* 3. Clear Naming Convention: To avoid confusion, "Key Store" refers to the entire XML file, while
|
||||
* "KeyBox" refers to the data class containing a single `(KeyPair, CertificateChain)` tuple,
|
||||
* which is the cryptographic entity we care about.
|
||||
*/
|
||||
object KeyBoxManager {
|
||||
|
||||
// The in-memory cache.
|
||||
// Key: The file name of the key store (e.g., "keybox.xml").
|
||||
// Value: A map of all keys found in that file, keyed by their algorithm name (e.g., "EC",
|
||||
// "RSA").
|
||||
private val keyStoreCache = ConcurrentHashMap<String, Map<String, KeyBox>>()
|
||||
|
||||
/**
|
||||
* Retrieves a specific attestation key (KeyPair and Certificate Chain) for a given algorithm
|
||||
* from a specified key store file.
|
||||
*
|
||||
* This is the primary public API. It transparently handles caching, loading, and parsing.
|
||||
*
|
||||
* @param keyStoreFileName The name of the XML file (e.g., "aosp_keybox.xml").
|
||||
* @param algorithm The algorithm name (e.g., "EC" or "RSA").
|
||||
* @return The requested [KeyBox], or `null` if the file doesn't exist or doesn't contain a key
|
||||
* for the specified algorithm.
|
||||
*/
|
||||
fun getAttestationKey(keyStoreFileName: String, algorithm: String): KeyBox? {
|
||||
// Atomically get the parsed key map for the file from the cache.
|
||||
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
||||
val keyMap =
|
||||
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
||||
SystemLogger.verbose(
|
||||
"Fetching attestation key in $keyStoreFileName with $algorithm algorithm."
|
||||
)
|
||||
return keyMap[algorithm]
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cached data for a specific key store file.
|
||||
*
|
||||
* Calling this will force the file to be re-read and re-parsed from disk the next time
|
||||
* [getAttestationKey] is called for this filename.
|
||||
*
|
||||
* @param keyStoreFileName The name of the file to remove from the cache (e.g., "keybox.xml").
|
||||
*/
|
||||
fun invalidateCache(keyStoreFileName: String) {
|
||||
// ConcurrentHashMap.remove returns the value if it existed, or null if it didn't.
|
||||
if (keyStoreCache.remove(keyStoreFileName) != null) {
|
||||
SystemLogger.info("Invalidated cache for key store file: $keyStoreFileName")
|
||||
} else {
|
||||
SystemLogger.debug(
|
||||
"Requested cache invalidation for '$keyStoreFileName', but it was not loaded."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and parses an entire key store XML file, extracting all valid keys. This function is
|
||||
* called only once per file name.
|
||||
*
|
||||
* @param fileName The name of the XML file to parse.
|
||||
* @return A map of all successfully parsed keys from the file, keyed by algorithm.
|
||||
*/
|
||||
private fun parseKeyStoreFile(fileName: String): Map<String, KeyBox> {
|
||||
val filePath = File(CONFIG_PATH, fileName)
|
||||
SystemLogger.info("Parsing new key store file: ${filePath.absolutePath}")
|
||||
|
||||
if (!filePath.exists()) {
|
||||
SystemLogger.error("Key store file not found: ${filePath.absolutePath}")
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
return try {
|
||||
val xmlContent = filePath.readText().trimStart('\uFEFF', '\uFFFE', ' ')
|
||||
parseKeysFromXml(xmlContent)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Fatal error parsing key store file '$fileName'", e)
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The core single-pass XML parser. It iterates through the XML stream once, using a state
|
||||
* machine to collect the data for each `<Key>` entry.
|
||||
*
|
||||
* @param xmlContent The raw XML string.
|
||||
* @return A map of algorithm names to their corresponding [KeyBox] objects.
|
||||
*/
|
||||
private fun parseKeysFromXml(xmlContent: String): Map<String, KeyBox> {
|
||||
val foundKeys = mutableMapOf<String, KeyBox>()
|
||||
val parser =
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(StringReader(xmlContent))
|
||||
}
|
||||
|
||||
// State variables for the current <Key> being parsed.
|
||||
var currentAlgorithm: String? = null
|
||||
var currentPrivateKeyPem: String? = null
|
||||
val currentCertificatePems = mutableListOf<String>()
|
||||
var isInsidePrivateKeyTag = false
|
||||
var isInsideCertificateTag = false
|
||||
|
||||
var eventType = parser.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
when (eventType) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
when (parser.name) {
|
||||
// When we enter a <Key> tag, we read its algorithm and reset state.
|
||||
"Key" -> {
|
||||
currentAlgorithm = parser.getAttributeValue(null, "algorithm")
|
||||
currentPrivateKeyPem = null
|
||||
currentCertificatePems.clear()
|
||||
}
|
||||
"PrivateKey" -> isInsidePrivateKeyTag = true
|
||||
"Certificate" -> isInsideCertificateTag = true
|
||||
}
|
||||
}
|
||||
|
||||
XmlPullParser.TEXT -> {
|
||||
// If we find text content, we check our state to see where it belongs.
|
||||
if (parser.isWhitespace) {
|
||||
eventType = parser.next()
|
||||
continue
|
||||
}
|
||||
when {
|
||||
isInsidePrivateKeyTag -> currentPrivateKeyPem = parser.text
|
||||
isInsideCertificateTag -> currentCertificatePems.add(parser.text)
|
||||
}
|
||||
}
|
||||
|
||||
XmlPullParser.END_TAG -> {
|
||||
when (parser.name) {
|
||||
"PrivateKey" -> isInsidePrivateKeyTag = false
|
||||
"Certificate" -> isInsideCertificateTag = false
|
||||
|
||||
// The </Key> tag is our trigger to finalize and store the KeyBox.
|
||||
"Key" -> {
|
||||
// Use runCatching to ensure one malformed key doesn't stop the whole
|
||||
// process.
|
||||
runCatching {
|
||||
val xmlAlgorithm = currentAlgorithm
|
||||
val keyPem = currentPrivateKeyPem
|
||||
if (
|
||||
xmlAlgorithm != null &&
|
||||
keyPem != null &&
|
||||
currentCertificatePems.isNotEmpty()
|
||||
) {
|
||||
val keyPair =
|
||||
(CertificateHelper.parsePemKeyPair(keyPem)
|
||||
as CertificateHelper.OperationResult.Success)
|
||||
.data
|
||||
val certificates =
|
||||
currentCertificatePems.map {
|
||||
(CertificateHelper.parsePemCertificate(it)
|
||||
as
|
||||
CertificateHelper.OperationResult.Success)
|
||||
.data
|
||||
}
|
||||
|
||||
// Derive the TRUE algorithm from the key object itself.
|
||||
// This is our source of truth.
|
||||
val derivedAlgorithm =
|
||||
when (keyPair.private) {
|
||||
is RSAPrivateKey -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
is ECPrivateKey -> KeyProperties.KEY_ALGORITHM_EC
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Unsupported key type found: ${keyPair.private.javaClass.name}"
|
||||
)
|
||||
}
|
||||
|
||||
// Normalize the algorithm from the XML tag to compare it
|
||||
// fairly with the derived algorithm.
|
||||
val normalizedXmlAlgorithm =
|
||||
when {
|
||||
xmlAlgorithm.contains("RSA", ignoreCase = true) ==
|
||||
true -> KeyProperties.KEY_ALGORITHM_RSA
|
||||
xmlAlgorithm.contains("EC", ignoreCase = true) ==
|
||||
true -> KeyProperties.KEY_ALGORITHM_EC
|
||||
else -> xmlAlgorithm
|
||||
}
|
||||
|
||||
// Warn the user if the XML tag was misleading.
|
||||
if (normalizedXmlAlgorithm != derivedAlgorithm) {
|
||||
SystemLogger.warning(
|
||||
"Key algorithm mismatch in XML file. Tag said '$xmlAlgorithm' but key is actually '$derivedAlgorithm'. Using the correct derived algorithm."
|
||||
)
|
||||
}
|
||||
|
||||
if (foundKeys.containsKey(derivedAlgorithm)) {
|
||||
SystemLogger.warning(
|
||||
"Duplicate key found for algorithm '$derivedAlgorithm'. The later one in the file will be used."
|
||||
)
|
||||
}
|
||||
foundKeys[derivedAlgorithm] = KeyBox(keyPair, certificates)
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
SystemLogger.error(
|
||||
"Failed to parse a <Key> entry for algorithm '$currentAlgorithm'",
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = parser.next()
|
||||
}
|
||||
SystemLogger.info("Finished parsing, found ${foundKeys.size} valid keys.")
|
||||
return foundKeys
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
data class CertGenConfig(
|
||||
val algorithm: Int,
|
||||
val keySize: Int,
|
||||
val ecCurve: Int,
|
||||
val rsaPublicExponent: Long,
|
||||
val attestationChallenge: ByteArray?,
|
||||
val purposes: IntArray,
|
||||
val digests: IntArray,
|
||||
val certSerial: ByteArray?,
|
||||
val certSubject: ByteArray?,
|
||||
val certNotBefore: Long,
|
||||
val certNotAfter: Long,
|
||||
val keyboxPrivateKey: ByteArray,
|
||||
val keyboxCertChain: ByteArray,
|
||||
val securityLevel: Int,
|
||||
val attestVersion: Int,
|
||||
val keymasterVersion: Int,
|
||||
val osVersion: Int,
|
||||
val osPatchLevel: Int,
|
||||
val vendorPatchLevel: Int,
|
||||
val bootPatchLevel: Int,
|
||||
val bootKey: ByteArray,
|
||||
val bootHash: ByteArray,
|
||||
val creationDatetime: Long,
|
||||
val attestationApplicationId: ByteArray,
|
||||
val moduleHash: ByteArray?,
|
||||
val idBrand: ByteArray?,
|
||||
val idDevice: ByteArray?,
|
||||
val idProduct: ByteArray?,
|
||||
val idSerial: ByteArray?,
|
||||
val idImei: ByteArray?,
|
||||
val idMeid: ByteArray?,
|
||||
val idManufacturer: ByteArray?,
|
||||
val idModel: ByteArray?,
|
||||
val idSecondImei: ByteArray?,
|
||||
)
|
||||
|
||||
object NativeCertGen {
|
||||
|
||||
private const val LOG_DIR = "/data/adb/tricky_store/logs"
|
||||
|
||||
@Volatile
|
||||
var isAvailable: Boolean = false
|
||||
private set
|
||||
|
||||
fun initialize(libraryPath: String) {
|
||||
try {
|
||||
System.load(libraryPath)
|
||||
initLogging(false, LOG_DIR)
|
||||
isAvailable = true
|
||||
SystemLogger.info("NativeCertGen: loaded libcertgen.so successfully")
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
SystemLogger.error("NativeCertGen: failed to load libcertgen.so, falling back to BouncyCastle", e)
|
||||
}
|
||||
}
|
||||
|
||||
external fun generateAttestedKeyPair(config: CertGenConfig): ByteArray?
|
||||
|
||||
private external fun initLogging(verbose: Boolean, logDir: String): Boolean
|
||||
|
||||
private external fun dumpLogs(): String?
|
||||
|
||||
fun dump(): String? = if (isAvailable) dumpLogs() else null
|
||||
|
||||
fun parseNativeResult(bytes: ByteArray): Pair<KeyPair, List<Certificate>> {
|
||||
val buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN)
|
||||
|
||||
val pkLen = buf.getInt()
|
||||
if (pkLen < 0 || pkLen > buf.remaining()) {
|
||||
throw IllegalStateException("Invalid private key length: $pkLen")
|
||||
}
|
||||
val pkBytes = ByteArray(pkLen)
|
||||
buf.get(pkBytes)
|
||||
|
||||
val numCerts = buf.getInt()
|
||||
if (numCerts < 0 || numCerts > buf.remaining()) {
|
||||
throw IllegalStateException("Invalid cert count: $numCerts")
|
||||
}
|
||||
val certs = mutableListOf<Certificate>()
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
repeat(numCerts) {
|
||||
val certLen = buf.getInt()
|
||||
if (certLen < 0 || certLen > buf.remaining()) {
|
||||
throw IllegalStateException("Invalid cert length: $certLen")
|
||||
}
|
||||
val certBytes = ByteArray(certLen)
|
||||
buf.get(certBytes)
|
||||
certs.add(certFactory.generateCertificate(ByteArrayInputStream(certBytes)))
|
||||
}
|
||||
|
||||
if (certs.isEmpty()) {
|
||||
throw IllegalStateException("No certificates in native result")
|
||||
}
|
||||
|
||||
val algorithmName = when (certs[0].publicKey.algorithm) {
|
||||
"EC" -> "EC"
|
||||
"RSA" -> "RSA"
|
||||
else -> certs[0].publicKey.algorithm
|
||||
}
|
||||
val keyFactory = KeyFactory.getInstance(algorithmName)
|
||||
val privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(pkBytes))
|
||||
val publicKey = certs[0].publicKey
|
||||
return Pair(KeyPair(publicKey, privateKey), certs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.matrix.TEESimulator.pki
|
||||
|
||||
import java.io.StringReader
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
|
||||
/**
|
||||
* A utility class for parsing XML content using a simplified, dot-notation path.
|
||||
*
|
||||
* This parser allows querying for XML tags and their attributes using a path string like
|
||||
* "Root.Group.Element[1].Value", which makes extracting specific data from a known XML structure
|
||||
* more convenient than manual iteration.
|
||||
*
|
||||
* @param xmlContent The raw XML string to be parsed.
|
||||
*/
|
||||
class XmlParser(xmlContent: String) {
|
||||
|
||||
// Sanitize the XML content by removing BOMs and trimming whitespace.
|
||||
private val sanitizedXml = xmlContent.sanitize()
|
||||
|
||||
/** Represents the result of a parsing operation. */
|
||||
sealed class ParseResult {
|
||||
/** Indicates a successful parse, containing the found attributes and text. */
|
||||
data class Success(val attributes: Map<String, String>) : ParseResult()
|
||||
|
||||
/** Indicates a failure, containing an error message and optional cause. */
|
||||
data class Error(val message: String, val cause: Throwable? = null) : ParseResult()
|
||||
}
|
||||
|
||||
/**
|
||||
* The main public method to find a node by its path and extract its data.
|
||||
*
|
||||
* @param path A dot-separated string representing the path to the desired XML tag. Indexed
|
||||
* access is supported with brackets, e.g., `Key[0]`.
|
||||
* @return A [ParseResult] containing the attributes and text of the found node.
|
||||
*/
|
||||
fun obtainPath(path: String): ParseResult {
|
||||
return try {
|
||||
val parser =
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
setInput(StringReader(sanitizedXml))
|
||||
}
|
||||
val tags = path.split('.').toTypedArray()
|
||||
val result = findNode(parser, tags, 0, mutableMapOf())
|
||||
ParseResult.Success(result)
|
||||
} catch (e: Exception) {
|
||||
ParseResult.Error("Failed to parse XML for path '$path'", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses the XML tree to find the node specified by the path.
|
||||
*
|
||||
* @param parser The active XmlPullParser instance.
|
||||
* @param tags The array of tag names to search for.
|
||||
* @param index The current depth in the `tags` array.
|
||||
* @param tagCounts A map to keep track of indices for tags with the same name (for `Tag[n]`
|
||||
* support).
|
||||
* @return A map of attributes from the found node.
|
||||
*/
|
||||
private fun findNode(
|
||||
parser: XmlPullParser,
|
||||
tags: Array<String>,
|
||||
index: Int,
|
||||
tagCounts: MutableMap<String, Int>,
|
||||
): Map<String, String> {
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||
|
||||
val currentTag = parser.name
|
||||
val (targetTagName, targetIndex) = parseTargetPath(tags[index])
|
||||
|
||||
if (currentTag == targetTagName) {
|
||||
val currentTagCount = tagCounts.getOrPut(currentTag) { 0 }
|
||||
if (currentTagCount == targetIndex) {
|
||||
// We found the correct tag at the correct index.
|
||||
return if (index == tags.size - 1) {
|
||||
// This is the final tag in the path, so read its attributes.
|
||||
readAttributesAndText(parser)
|
||||
} else {
|
||||
// This is an intermediate tag, so recurse deeper.
|
||||
findNode(parser, tags, index + 1, mutableMapOf())
|
||||
}
|
||||
}
|
||||
// This is the right tag name, but not the right index, so increment and continue
|
||||
// searching.
|
||||
tagCounts[currentTag] = currentTagCount + 1
|
||||
skipCurrentElement(parser)
|
||||
} else {
|
||||
// This tag doesn't match, so skip it and its children entirely.
|
||||
skipCurrentElement(parser)
|
||||
}
|
||||
}
|
||||
throw NoSuchElementException("XML path not found: ${tags.joinToString(".")}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of direct child nodes that match the final tag in a given path. For
|
||||
* example, given the path "Root.Group.Element", it will count how many <Element> tags exist
|
||||
* directly under the first <Group> tag.
|
||||
*
|
||||
* @param path A dot-separated string representing the path to the parent node.
|
||||
* @return The number of matching child nodes.
|
||||
*/
|
||||
fun countNodes(path: String): Int {
|
||||
// We find the parent node first.
|
||||
val parentPath = path.substringBeforeLast('.')
|
||||
val childTagName = path.substringAfterLast('.')
|
||||
|
||||
// Re-initialize the parser for a new traversal.
|
||||
val parser =
|
||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
setInput(StringReader(sanitizedXml))
|
||||
}
|
||||
|
||||
try {
|
||||
// Navigate to the parent node. This will leave the parser's cursor
|
||||
// positioned at the start of the parent's content.
|
||||
findNode(parser, parentPath.split('.').toTypedArray(), 0, mutableMapOf())
|
||||
|
||||
var count = 0
|
||||
var depth = 1 // Start inside the parent node.
|
||||
while (depth > 0) {
|
||||
when (parser.next()) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
// If we are at the immediate child level (depth == 1) and the tag name
|
||||
// matches, increment count.
|
||||
if (depth == 1 && parser.name == childTagName) {
|
||||
count++
|
||||
}
|
||||
depth++ // Go deeper into this new tag.
|
||||
}
|
||||
XmlPullParser.END_TAG -> {
|
||||
depth-- // Emerge from a tag.
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
} catch (e: Exception) {
|
||||
// If the path doesn't exist or there's a parsing error, the count is 0.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses a path segment (e.g., "Key[1]") into its base name ("Key") and index (1). */
|
||||
private fun parseTargetPath(targetTag: String): Pair<String, Int> {
|
||||
val parts = targetTag.split('[', limit = 2)
|
||||
val tagName = parts[0]
|
||||
val index =
|
||||
if (parts.size > 1) {
|
||||
parts[1].substringBefore(']').toIntOrNull() ?: 0
|
||||
} else {
|
||||
0 // If no index is specified, we are looking for the first occurrence.
|
||||
}
|
||||
return tagName to index
|
||||
}
|
||||
|
||||
/** Reads all attributes and the text content of the current XML element. */
|
||||
private fun readAttributesAndText(parser: XmlPullParser): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
attributes[parser.getAttributeName(i)] = parser.getAttributeValue(i)
|
||||
}
|
||||
// Check for text content before the next tag.
|
||||
if (parser.next() == XmlPullParser.TEXT && parser.isWhitespace.not()) {
|
||||
attributes["text"] = parser.text
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
/** Advances the parser past the current element and all its children. */
|
||||
private fun skipCurrentElement(parser: XmlPullParser) {
|
||||
var depth = 1
|
||||
while (depth != 0) {
|
||||
when (parser.next()) {
|
||||
XmlPullParser.END_TAG -> depth--
|
||||
XmlPullParser.START_TAG -> depth++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes Byte Order Marks (BOM) and trims whitespace from the XML string. */
|
||||
private fun String.sanitize(): String {
|
||||
return this.trimStart('\uFEFF', '\uFFFE', ' ').trimEnd()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
package org.matrix.TEESimulator.util
|
||||
|
||||
import android.hardware.security.keymint.SecurityLevel
|
||||
import android.os.Build
|
||||
import android.os.SystemProperties
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.MessageDigest
|
||||
import java.time.LocalDate
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
import org.bouncycastle.asn1.ASN1Integer
|
||||
import org.bouncycastle.asn1.DEROctetString
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||
import org.matrix.TEESimulator.logging.SystemLogger
|
||||
|
||||
/**
|
||||
* Provides utility functions for accessing Android system properties and device-specific
|
||||
* information that is critical for generating valid attestations.
|
||||
*/
|
||||
object AndroidDeviceUtils {
|
||||
|
||||
/**
|
||||
* Internal constant to signify that a patch level should not be included in the attestation.
|
||||
*/
|
||||
internal const val DO_NOT_REPORT = -1
|
||||
|
||||
// --- Boot Key and Verified Boot Hash ---
|
||||
|
||||
/**
|
||||
* Lazily initializes and retrieves the verified boot key digest. The value is sourced in the
|
||||
* following order:
|
||||
* 1. From the `ro.boot.vbmeta.public_key_digest` system property.
|
||||
* 2. From a cached TEE attestation record.
|
||||
* 3. As a randomly generated 32-byte value (fallback).
|
||||
*/
|
||||
val bootKey: ByteArray by lazy {
|
||||
initializeBootProperty(
|
||||
propertyName = "ro.boot.vbmeta.public_key_digest",
|
||||
attestationValueProvider = {
|
||||
DeviceAttestationService.CachedAttestationData?.verifiedBootKey
|
||||
},
|
||||
expectedSize = 32,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily initializes and retrieves the verified boot hash (vbmeta digest). The value is sourced
|
||||
* in the following order:
|
||||
* 1. From the `ro.boot.vbmeta.digest` system property.
|
||||
* 2. From a cached TEE attestation record.
|
||||
* 3. As a randomly generated 32-byte value (fallback).
|
||||
*/
|
||||
val bootHash: ByteArray by lazy {
|
||||
initializeBootProperty(
|
||||
propertyName = "ro.boot.vbmeta.digest",
|
||||
attestationValueProvider = {
|
||||
DeviceAttestationService.CachedAttestationData?.verifiedBootHash
|
||||
},
|
||||
expectedSize = 32,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Public function to explicitly trigger the initialization of the boot key and hash. Accessing
|
||||
* these properties here ensures they are set up before they might be needed elsewhere.
|
||||
*/
|
||||
fun setupBootKeyAndHash() {
|
||||
SystemLogger.debug("Triggering initialization of boot key and hash...")
|
||||
// Accessing the properties will trigger their `lazy` initialization logic.
|
||||
bootKey
|
||||
bootHash
|
||||
SystemLogger.debug("Boot key and hash initialization complete.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic initializer for boot properties like the key and hash. It attempts to read from a
|
||||
* system property first, then from a TEE attestation, and finally falls back to a random value
|
||||
* if neither is available.
|
||||
*
|
||||
* @param propertyName The name of the system property (e.g., "ro.boot.vbmeta.digest").
|
||||
* @param attestationValueProvider A function that supplies the value from a cached attestation.
|
||||
* @param expectedSize The expected length of the byte array (e.g., 32 for a SHA-256 digest).
|
||||
* @return The resulting byte array for the property.
|
||||
*/
|
||||
private fun initializeBootProperty(
|
||||
propertyName: String,
|
||||
attestationValueProvider: () -> ByteArray?,
|
||||
expectedSize: Int,
|
||||
): ByteArray {
|
||||
getProperty(propertyName, expectedSize)?.let {
|
||||
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
|
||||
persistToFile(propertyName, it)
|
||||
return it
|
||||
}
|
||||
|
||||
try {
|
||||
attestationValueProvider()?.let {
|
||||
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
||||
setProperty(propertyName, it)
|
||||
persistToFile(propertyName, it)
|
||||
return it
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to get $propertyName from attestation.", e)
|
||||
}
|
||||
|
||||
readFromFile(propertyName, expectedSize)?.let {
|
||||
SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}")
|
||||
setProperty(propertyName, it)
|
||||
return it
|
||||
}
|
||||
|
||||
return generateRandomBytes(expectedSize).also {
|
||||
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
||||
setProperty(propertyName, it)
|
||||
persistToFile(propertyName, it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a system property and validates its format.
|
||||
*
|
||||
* @param name The name of the system property.
|
||||
* @param expectedSize The expected byte length of the property (e.g., 32 for a 64-char hex
|
||||
* string).
|
||||
* @return The property value as a ByteArray, or null if not found or invalid.
|
||||
*/
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun getProperty(name: String, expectedSize: Int): ByteArray? {
|
||||
val value = SystemProperties.get(name, null)
|
||||
if (value.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
// A valid digest is (2 * size) hex characters.
|
||||
return if (value.length == expectedSize * 2) value.hexToByteArray() else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a system property using the `resetprop` command.
|
||||
*
|
||||
* @param name The name of the property to set.
|
||||
* @param bytes The value to set, which will be converted to a hex string.
|
||||
*/
|
||||
private fun setProperty(name: String, bytes: ByteArray) {
|
||||
val hex = bytes.toHex()
|
||||
try {
|
||||
SystemLogger.debug("Setting system property '$name' to: $hex")
|
||||
val command = arrayOf("resetprop", name, hex)
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val exitCode = process.waitFor()
|
||||
|
||||
if (exitCode != 0) {
|
||||
val errorOutput = process.errorStream.bufferedReader().readText()
|
||||
SystemLogger.error(
|
||||
"resetprop for '$name' failed with exit code $exitCode: $errorOutput"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to set '$name' property via resetprop.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRandomBytes(size: Int): ByteArray =
|
||||
ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) }
|
||||
|
||||
private val PERSIST_DIR = File("/data/adb/tricky_store")
|
||||
|
||||
private fun fileForProperty(propertyName: String): File = when (propertyName) {
|
||||
"ro.boot.vbmeta.digest" -> File(PERSIST_DIR, "boot_hash.bin")
|
||||
"ro.boot.vbmeta.public_key_digest" -> File(PERSIST_DIR, "boot_key.bin")
|
||||
else -> File(PERSIST_DIR, "${propertyName.replace('.', '_')}.bin")
|
||||
}
|
||||
|
||||
private fun persistToFile(propertyName: String, bytes: ByteArray) {
|
||||
try {
|
||||
fileForProperty(propertyName).writeBytes(bytes)
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to persist $propertyName to file.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFromFile(propertyName: String, expectedSize: Int): ByteArray? {
|
||||
return try {
|
||||
val file = fileForProperty(propertyName)
|
||||
if (!file.exists()) return null
|
||||
val bytes = file.readBytes()
|
||||
if (bytes.size == expectedSize) bytes else null
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to read $propertyName from file.", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// --- Patch Level Properties ---
|
||||
|
||||
fun getPatchLevel(uid: Int): Int {
|
||||
val custom = getCustomPatchLevelFor(uid, "system", isLong = false)
|
||||
return custom ?: getRealDevicePatchLevelInt("system", isLong = false)
|
||||
}
|
||||
|
||||
fun getVendorPatchLevelLong(uid: Int): Int {
|
||||
val custom = getCustomPatchLevelFor(uid, "vendor", isLong = true)
|
||||
return custom ?: getRealDevicePatchLevelInt("vendor", isLong = true)
|
||||
}
|
||||
|
||||
fun getBootPatchLevelLong(uid: Int): Int {
|
||||
val custom = getCustomPatchLevelFor(uid, "boot", isLong = true)
|
||||
return custom ?: getRealDevicePatchLevelInt("boot", isLong = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the definitive device patch level integer for a given component. This function
|
||||
* encapsulates the entire fallback chain and guarantees a non-null return.
|
||||
*
|
||||
* Fallback Priority:
|
||||
* 1. Cached TEE attestation data.
|
||||
* 2. Specific system property (e.g., ro.vendor.build.security_patch).
|
||||
* 3. Default system patch level from Build.VERSION.SECURITY_PATCH.
|
||||
*
|
||||
* @param component The component ("system", "vendor", "boot").
|
||||
* @param isLong Whether the final integer should be in YYYYMMDD format.
|
||||
* @return The patch level as a guaranteed non-null Integer.
|
||||
*/
|
||||
private fun getRealDevicePatchLevelInt(component: String, isLong: Boolean): Int {
|
||||
// Get value from cached TEE attestation data
|
||||
DeviceAttestationService.CachedAttestationData?.let { data ->
|
||||
val value =
|
||||
when (component) {
|
||||
"system" -> data.osPatchLevel
|
||||
"vendor" -> data.vendorPatchLevel
|
||||
"boot" -> data.bootPatchLevel
|
||||
else -> null
|
||||
}
|
||||
if (value != null) return value
|
||||
}
|
||||
|
||||
// We only check the specific vendor property, as the boot one is non-existent.
|
||||
if (component == "vendor") {
|
||||
val propValue = SystemProperties.get("ro.vendor.build.security_patch", "")
|
||||
if (!propValue.isNullOrBlank()) {
|
||||
parsePatchLevelValue(propValue, isLong)?.let { parsedValue ->
|
||||
return parsedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a custom patch level from the configuration if available for a specific UID.
|
||||
*
|
||||
* @param uid The UID of the calling application.
|
||||
* @param component The component to get the patch level for ("system", "vendor", "boot").
|
||||
* @param isLong Whether to return the patch level in `YYYYMMDD` or `YYYYMM` format.
|
||||
* @return The custom patch level, or null if not configured.
|
||||
*/
|
||||
private fun getCustomPatchLevelFor(uid: Int, component: String, isLong: Boolean): Int? {
|
||||
val config = ConfigurationManager.getPatchLevelForUid(uid) ?: return null
|
||||
val value =
|
||||
when (component) {
|
||||
"system" -> config.system ?: config.all
|
||||
"vendor" -> config.vendor ?: config.all
|
||||
"boot" -> config.boot ?: config.all
|
||||
else -> config.all
|
||||
} ?: return null
|
||||
|
||||
// First, resolve dynamic keywords and templates into a concrete date string.
|
||||
val resolvedValue = resolveDateKeywords(value)
|
||||
|
||||
return when {
|
||||
resolvedValue.equals("device_default", ignoreCase = true) -> null
|
||||
// Resolve from live system prop — matches what detectors see via getprop,
|
||||
// even when PIF has spoofed ro.build.version.security_patch via resetprop
|
||||
resolvedValue.equals("prop", ignoreCase = true) ->
|
||||
parsePatchLevelValue(SystemProperties.get("ro.build.version.security_patch", ""), isLong)
|
||||
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
|
||||
else -> parsePatchLevelValue(resolvedValue, isLong)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves special date keywords and templates into a concrete "YYYY-MM-DD" date string.
|
||||
*
|
||||
* @param value The configuration value string (e.g., "today", "YYYY-MM-01").
|
||||
* @return A concrete date string, or the original value if it's not a dynamic date keyword.
|
||||
*/
|
||||
private fun resolveDateKeywords(value: String): String {
|
||||
// Handle the "today" keyword.
|
||||
if (value.equals("today", ignoreCase = true)) {
|
||||
return LocalDate.now().toString() // Returns "YYYY-MM-DD" format
|
||||
}
|
||||
|
||||
// Handle date templates like "YYYY-MM-01" or "2025-MM-DD".
|
||||
if (
|
||||
value.contains("YYYY", ignoreCase = true) ||
|
||||
value.contains("MM", ignoreCase = true) ||
|
||||
value.contains("DD", ignoreCase = true)
|
||||
) {
|
||||
|
||||
val now = LocalDate.now()
|
||||
// Chain replacements for YYYY, MM, and DD placeholders.
|
||||
return value
|
||||
.replace("YYYY", now.year.toString(), ignoreCase = true)
|
||||
.replace("MM", String.format("%02d", now.monthValue), ignoreCase = true)
|
||||
.replace("DD", String.format("%02d", now.dayOfMonth), ignoreCase = true)
|
||||
}
|
||||
|
||||
// If it's not a dynamic keyword or template, return the original value.
|
||||
return value
|
||||
}
|
||||
|
||||
/** Parses a patch level string (e.g., "2025-11-01") into an integer format. */
|
||||
private fun parsePatchLevelValue(value: String, isLong: Boolean): Int? {
|
||||
val normalized = value.replace("-", "")
|
||||
return try {
|
||||
when (normalized.length) {
|
||||
8 -> { // YYYYMMDD
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
val day = normalized.substring(6, 8).toInt()
|
||||
if (isLong) year * 10000 + month * 100 + day else year * 100 + month
|
||||
}
|
||||
6 -> { // YYYYMM
|
||||
val year = normalized.substring(0, 4).toInt()
|
||||
val month = normalized.substring(4, 6).toInt()
|
||||
if (isLong) year * 10000 + month * 100 + 1 else year * 100 + month
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
SystemLogger.warning("Could not parse patch level value: $value", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts a security patch string (e.g., "2025-11-01") to an integer representation. */
|
||||
private fun String.toPatchLevelInt(isLong: Boolean): Int {
|
||||
return parsePatchLevelValue(this, isLong) ?: 20240401 // Fallback
|
||||
}
|
||||
|
||||
// --- OS and Attestation Version Properties ---
|
||||
|
||||
private val osVersionMap =
|
||||
mapOf(
|
||||
Build.VERSION_CODES.BAKLAVA to 160000,
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 150000,
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 140000,
|
||||
Build.VERSION_CODES.TIRAMISU to 130000,
|
||||
Build.VERSION_CODES.S_V2 to 120100,
|
||||
Build.VERSION_CODES.S to 120000,
|
||||
Build.VERSION_CODES.R to 110000,
|
||||
Build.VERSION_CODES.Q to 100000,
|
||||
)
|
||||
|
||||
val osVersion: Int
|
||||
get() =
|
||||
DeviceAttestationService.CachedAttestationData?.osVersion
|
||||
?: osVersionMap[Build.VERSION.SDK_INT]
|
||||
?: 160000 // Default to a recent version
|
||||
|
||||
private val attestVersionMap =
|
||||
mapOf(
|
||||
Build.VERSION_CODES.Q to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.R to 4, // Keymaster 4.1
|
||||
Build.VERSION_CODES.S to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.S_V2 to 100, // KeyMint 1.0
|
||||
Build.VERSION_CODES.TIRAMISU to 200, // KeyMint 2.0
|
||||
Build.VERSION_CODES.UPSIDE_DOWN_CAKE to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.VANILLA_ICE_CREAM to 300, // KeyMint 3.0
|
||||
Build.VERSION_CODES.BAKLAVA to 400, // KeyMint 4.0
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves the attestation version based on security level and OS version. StrongBox (level 2)
|
||||
* requires version 300.
|
||||
*
|
||||
* @param securityLevel The security level of the attestation (1 for TEE, 2 for StrongBox).
|
||||
* @return The appropriate attestation version number.
|
||||
*/
|
||||
fun getAttestVersion(securityLevel: Int): Int {
|
||||
// StrongBox security level requires an attestation version of at least 300.
|
||||
if (securityLevel == SecurityLevel.STRONGBOX) {
|
||||
return 300
|
||||
}
|
||||
return DeviceAttestationService.CachedAttestationData?.attestVersion
|
||||
?: attestVersionMap[Build.VERSION.SDK_INT]
|
||||
?: 400 // Default to a recent version
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Keymaster/KeyMint version based on the attestation version.
|
||||
*
|
||||
* @param securityLevel The security level, used to determine the correct attestation version.
|
||||
* @return The appropriate Keymaster or KeyMint version number.
|
||||
*/
|
||||
fun getKeymasterVersion(securityLevel: Int): Int {
|
||||
val attestVersion = getAttestVersion(securityLevel)
|
||||
return if (attestVersion >= 100) attestVersion else 41 // Keymaster 4.1 for older versions
|
||||
}
|
||||
|
||||
// --- APEX and Module Hash Properties ---
|
||||
|
||||
// Minimal protobuf parser for apex_manifest.pb (field 1: name, field 2: version)
|
||||
private class MinimalApexManifestParser(private val data: ByteArray) {
|
||||
var pos = 0
|
||||
|
||||
fun parse(): Pair<String, Long>? {
|
||||
var name: String? = null
|
||||
var version: Long? = null
|
||||
|
||||
while (pos < data.size) {
|
||||
val tag = readVarint()
|
||||
val fieldNum = tag ushr 3
|
||||
val wireType = (tag and 0x07).toInt()
|
||||
|
||||
when (fieldNum) {
|
||||
1L -> {
|
||||
val length = readVarint().toInt()
|
||||
if (pos + length > data.size) return null
|
||||
name = String(data, pos, length, Charsets.UTF_8)
|
||||
pos += length
|
||||
}
|
||||
2L -> {
|
||||
version = readVarint()
|
||||
}
|
||||
else -> skipField(wireType)
|
||||
}
|
||||
}
|
||||
|
||||
return if (name != null && version != null) {
|
||||
name to version
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readVarint(): Long {
|
||||
var value = 0L
|
||||
var shift = 0
|
||||
while (pos < data.size) {
|
||||
val b = data[pos++].toInt()
|
||||
value = value or ((b and 0x7F).toLong() shl shift)
|
||||
if ((b and 0x80) == 0) return value
|
||||
shift += 7
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private fun skipField(wireType: Int) {
|
||||
when (wireType) {
|
||||
0 -> readVarint()
|
||||
1 -> pos += 8
|
||||
2 -> {
|
||||
val len = readVarint().toInt()
|
||||
pos += len
|
||||
}
|
||||
5 -> pos += 4
|
||||
else -> throw IllegalStateException("Unknown wire type $wireType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val apexInfos: List<Pair<String, Long>> by lazy {
|
||||
val results = mutableListOf<Pair<String, Long>>()
|
||||
val apexRoot = File("/apex")
|
||||
|
||||
if (!apexRoot.exists() || !apexRoot.isDirectory) {
|
||||
return@lazy emptyList()
|
||||
}
|
||||
|
||||
apexRoot.listFiles()?.forEach { file ->
|
||||
if (!file.isDirectory) return@forEach
|
||||
val name = file.name
|
||||
|
||||
if (name.startsWith(".")) return@forEach
|
||||
if (name.contains("@")) return@forEach
|
||||
if (name == "sharedlibs") return@forEach
|
||||
|
||||
val manifestFile = File(file, "apex_manifest.pb")
|
||||
if (manifestFile.exists()) {
|
||||
runCatching {
|
||||
val bytes = FileInputStream(manifestFile).use { it.readBytes() }
|
||||
val parser = MinimalApexManifestParser(bytes)
|
||||
parser.parse()?.let { (pkgName, version) -> results.add(pkgName to version) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.distinctBy { it.first }
|
||||
}
|
||||
|
||||
val moduleHash: ByteArray by lazy {
|
||||
DeviceAttestationService.CachedAttestationData?.moduleHash
|
||||
?: runCatching {
|
||||
data class ModuleEntry(
|
||||
val nameEncoded: ByteArray,
|
||||
val fullEncoded: ByteArray,
|
||||
)
|
||||
|
||||
val modules =
|
||||
apexInfos.map { (packageName, versionCode) ->
|
||||
val nameOctet = DEROctetString(packageName.toByteArray(Charsets.UTF_8))
|
||||
val versionInt = ASN1Integer(versionCode)
|
||||
|
||||
val vec = ASN1EncodableVector()
|
||||
vec.add(nameOctet)
|
||||
vec.add(versionInt)
|
||||
val sequence = DERSequence(vec)
|
||||
|
||||
// AOSP sorts by encoded name only, not full sequence
|
||||
ModuleEntry(
|
||||
nameEncoded = nameOctet.encoded,
|
||||
fullEncoded = sequence.encoded,
|
||||
)
|
||||
}
|
||||
|
||||
val sortedModules =
|
||||
modules.sortedWith { m1, m2 ->
|
||||
compareByteArrays(m1.nameEncoded, m2.nameEncoded)
|
||||
}
|
||||
|
||||
val payloadStream = ByteArrayOutputStream()
|
||||
sortedModules.forEach { payloadStream.write(it.fullEncoded) }
|
||||
val payload = payloadStream.toByteArray()
|
||||
|
||||
// Wrap in DER SET tag manually — DERSet() re-sorts by full encoding
|
||||
val finalDerSet = encodeAsDerSet(payload)
|
||||
|
||||
MessageDigest.getInstance("SHA-256").digest(finalDerSet)
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("Failed to compute module hash.", it)
|
||||
ByteArray(32)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareByteArrays(a: ByteArray, b: ByteArray): Int {
|
||||
val length = minOf(a.size, b.size)
|
||||
for (i in 0 until length) {
|
||||
val byteA = a[i].toInt() and 0xFF
|
||||
val byteB = b[i].toInt() and 0xFF
|
||||
if (byteA != byteB) {
|
||||
return byteA - byteB
|
||||
}
|
||||
}
|
||||
return a.size - b.size
|
||||
}
|
||||
|
||||
private fun encodeAsDerSet(payload: ByteArray): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
out.write(0x31)
|
||||
writeDerLength(out, payload.size)
|
||||
out.write(payload)
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
private fun writeDerLength(out: ByteArrayOutputStream, length: Int) {
|
||||
if (length < 128) {
|
||||
out.write(length)
|
||||
} else {
|
||||
var size = length
|
||||
val bytes = ArrayList<Byte>()
|
||||
while (size > 0) {
|
||||
bytes.add((size and 0xFF).toByte())
|
||||
size = size ushr 8
|
||||
}
|
||||
out.write(0x80 or bytes.size)
|
||||
for (i in bytes.indices.reversed()) {
|
||||
out.write(bytes[i].toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user