Releases: Enginex0/resetprop-rs
Release list
v0.6.0
Seal: new capabilities
- Repeatable
--seal.resetprop --seal A v1 --seal B v2seals several properties in one run, building a single multi-entry lock list inside init instead of spawning one process per prop. --seal NAME --check: dry-run the Tier B install. Resolves__system_property_updatein init and validates the splice site without ptrace-writing anything. Single NAME only.--observe-init [--duration SECS]: ptrace init (PID 1) and print its/dev/kmsgwrites for a window (default 5s, aarch64 only). Use it to confirm which init thread services a property write before you seal it.
Hook page now backed by memfd
- The Tier B hook body is mapped into init from an anonymous
memfd, not a file under/data/adb/resetprop-rs/. Nothing is written to or unlinked from disk. In/proc/1/mapsthe page shows as/memfd:resetprop-hook (deleted), mappedPROT_R|Xso init'sprocess:execmemSELinux class is never exercised.
Long values: create support
set()now creates properties of 92 bytes or more, not just reads and overwrites them. A created long prop is byte-identical to one init writes:kLongFlag, the bionic legacy error string invalue[], the self-relative offset to the full value, and a serial length byte equal to the error-message length.- Fix: a long prop's serial length byte stores the error-message length, not the value length. Bionic copies
(serial>>24)+1bytes ofvalue[]into aPROP_VALUE_MAXbuffer before it checkskLongFlag, so a length byte at or abovePROP_VALUE_MAXoverflows that buffer and trips a FORTIFY abort.
Seal hardening
- Stop init's entire thread group before poking, and resume any partially seized threads when
PTRACE_SEIZEfails midway. No more half-stopped init. - Verify init's identity before the first poke, and reject a re-seal over an init that already carries our trampoline.
- Read the trampoline back after writing it to confirm the bytes landed.
- Split the ptrace register layout per architecture (aarch64, arm, x86_64, x86) instead of sharing one struct.
- Free the remote bootstrap page when an arena step fails, tolerate benign signal-stops while waiting on the tracee, and throttle hook-install retries.
- Encoder fix:
.advancenow emitsldrbwith a#1post-index.
Persist
- Reject a persistent-property field whose declared length overflows the record instead of trusting the on-disk length.
Build and CI
- Pin the Rust toolchain to 1.96.0.
- Run the full fmt, clippy, and test wall on every PR.
Testing
- 165 library unit tests (up from 133 at v0.5.0).
- Per-arch ptrace smoke fixtures plus an independent A64 assembler oracle that re-derives the hook body from canonical opcodes.
- Long-value create roundtrip test that asserts the serial length byte stays below
PROP_VALUE_MAX.
v0.5.0
Seal (Two-Tier Property Locking)
--seal NAME VALUE/-sl: Tier B per-prop hook installed in init via ptrace plus remotemmap. Only the named property freezes; neighbour properties keep updating normally.--seal-arena NAME VALUE/-sla: Tier A arena-levelMAP_PRIVATE|MAP_FIXEDremap in init. Broader blast radius, used as a manual fallback when Tier B refuses (HookInstallFailed,ElfParse,SymbolNotFound).--unseal NAMEand--unseal-arena NAMErevert a specific seal.--sealslists active seal records (name, tier, arena path).- AArch64 only at runtime. Builds for other ABIs return
Error::Unsupportedrather than corrupting init's libc.text. - File-backed hook page at
/data/adb/resetprop-rs/hook-<pid>-<nanos>.bin, unlinked post-mmapsoprocess:execmemSELinux class is never exercised. - In-session only: seals do not persist across reboots,
SystemProperties::Reload, or init restart. Re-apply on every boot. - Library API:
PropSystem::seal,PropSystem::unseal,PropSystem::seal_arena,PropSystem::unseal_arena,PropSystem::seals.
Conditional Property Primitives
--if-diffwith positional NAME VALUE: write only when the property exists and the current value differs from VALUE.--if-match NEEDLEwith positional NAME VALUE: write only when the current value equals NEEDLE and differs from VALUE.--delete-if-exist NAME: delete only when the property is present, exit 0 on absent.- All three skip absent properties and short-circuit equal-value writes so the per-prop serial never bumps spuriously.
- Library API:
PropSystem::set_if_diff,PropSystem::set_if_match.
Wait For Value
--wait NAME [VALUE]with optional--timeout SECSblocks until the property either exists (no VALUE) or equals VALUE.- Library API:
PropSystem::wait(name, expected, timeout)returnsSome(value)on match,Noneon timeout.
Bionic-Correct Serial Discipline
normalize_serialAPI canonicalises the dirty bit and length field on an existing entry without bumping the serial counter. Mirrors thefix_serialsstep real init runs at boot.- Seal write routes
ro.*properties throughset_stealth(no listeners, so a wake would be the detection signal) and everything else throughset_init(real listeners present, so a missing wake would be the signal). - Hybrid wake policy:
futex_wakeonly fires when listeners exist on the prop. Tracer-busy errors now surface the holding pid for diagnosis.
Build And CI Hardening
- New
Error::Unsupported(String)variant for arch-gated features. - Release matrix runs with
fail-fast: false. One failed ABI no longer cancels the other three in flight. - Release-artifact build scoped to
cargo build -p resetprop-cli. Toolchain quirks inpropdetect-bionicno longer block the shipping binary. - Tagged releases auto-publish:
release.ymlextracts the matching## vX.Y.Zsection fromCHANGELOG.mdvia awk and feeds it tosoftprops/action-gh-releaseas the release body. - Workspace lints clean under Rust 1.95.0 (
clippy::sort_by_key,clippy::collapsible_match).
Testing
- 133 library unit tests (up from 50 at v0.4.0). Five integration test binaries cover ptrace primitives, Tier A and Tier B child isolation, ELF fixture parsing, plus the existing doc tests.
- New seal coverage: A64 encoder vectors versus canonical opcodes, hook body splice equivalence, lock-list capacity envelope, PT_DYNAMIC duplicate-tolerant parsing.
- Device-side stress: Tests 21 and 22 exercise seal under SELinux denials and ksu_props bypass probes.
v0.4.0
feat(core): add context, appcompat, bionic, and wait Property area lookup now parses the property_info binary trie for O(depth) resolution instead of linear scanning all areas. Falls back to text property_contexts on Android 8-9, then to linear scan if neither is available. Android 14+ appcompat_override areas are detected and dual-written automatically, preserving the write mode (stealth, init, standard) on the mirror area. Bionic __system_property_* functions are loaded via dlsym as a read fallback when direct mmap access is unavailable. Entire bionic module is cfg-gated to Android targets only. New --wait flag blocks until a property exists or matches a value, with optional --timeout. Uses bionic __system_property_wait when available, falls back to futex_wait on the property serial or the global serial for properties that don't exist yet. New modules: context.rs (643 LOC), bionic.rs (202 LOC), appcompat.rs (52 LOC), wait.rs (115 LOC). All 60 tests pass.
v0.3.1
build: add x86_64 and x86 Android targets Extends from 2 ABIs to all 4 Android ABIs so emulator users and x86 devices get prebuilt binaries too.
v0.3.0
v0.3.0
- Persistent property support via
-pand-Pflags -pwrites to both prop_area (memory) and/data/property/persistent_properties(disk)-Preads directly from the persist file without requiring prop_area access- Hand-rolled proto2 encode/decode matching AOSP's
PersistentPropertiesschema - Atomic write with SELinux xattr preservation, matching AOSP's temp+rename+fsync pattern
- Legacy format read support for pre-Android 9 devices (one file per property)
PersistStorepublic API: load, get, set, delete, listPropSystem::set_persist()andPropSystem::delete_persist()for dual memory+disk writes
v0.2.1
v0.2.1: add set_init() and --init CLI for init-style serial writes
v0.2.0
resetprop-rs v0.2.0 — Stealth Harvest Overhaul
Major hexpatch-delete rework. Replacement names now come from the device's own property vocabulary instead of a static dictionary.
What changed
Runtime segment harvesting — hexpatch scans all properties on the device and builds a per-length word pool. Replacements are drawn from segments that already exist on that specific device, making them indistinguishable from real properties.
Randomized selection — pool picks use OS-seeded entropy instead of deterministic first-match. Same device, different mangled names each run.
Dot-split compound generation — when harvest pool and static dict are exhausted, the fallback now joins short stems with dots (e.g. cfg.hw.svc) instead of cyclic underscore patterns. Produces more natural-looking segment names for long replacements.
Plausible value — mangled properties now show value 0 instead of empty string. Serial field encodes length=1 with counter bits preserved. Indistinguishable from a boot-time property via the public API.
Name consistency fix — trie segments and prop_info name are now written from the same source vector. Previously they could diverge, creating a detectable mismatch between __system_property_find and __system_property_foreach.
Shared segment protection — trie nodes that have children with their own properties are no longer renamed, preventing corruption of sibling properties.
3-tier replacement strategy
- Harvest pool (device vocabulary, randomized)
- Static dictionary (~95 words, lengths 1-16)
- Dot-split compound (stem combinations with dots)
Testing
- 29 unit tests (mock property areas)
- 20 on-device tests (OnePlus, Android 14, KSU Next) — all pass
- Verified against Magisk resetprop: property counts match within 3
Binaries
| ABI | File |
|---|---|
| arm64-v8a | resetprop-arm64-v8a |
| armeabi-v7a | resetprop-armeabi-v7a |
Static, stripped, zero runtime dependencies beyond libc.
v0.1.0
resetprop-rs v0.1.0
Pure Rust Android property manipulation. No Magisk dependency.
Features
- Get/Set/Delete — direct mmap writes, bypasses property_service
- Hexpatch Delete — trie-aware name destruction with dictionary words, serial preserved
- Batch Load —
-fflag loads name=value pairs from file - Library crate —
PropAreaandPropSystemwith typed errors
Hexpatch Stealth
Unlike magiskboot's blind byte substitution, resetprop-rs walks the actual property prefix trie:
- Segments renamed with same-length dictionary words (thermal, display, sensor, codec)
- Trie structure intact — no enumeration gaps
- Serial counters preserved — no NativeTest detection
- Shared prefixes (ro, persist) automatically skipped
Binaries
| ABI | Size |
|---|---|
| arm64-v8a | 322 KB |
| armeabi-v7a | 237 KB |
Static, stripped, zero runtime dependencies beyond libc.
Testing
- 16 off-device unit tests (mock property areas)
- 17 on-device tests (Redmi, Android 14, KSU Next) — all pass
- Verified against Magisk resetprop: property count and values match