Conversation
write_kstorage() looked up the existing node under rcu_read_lock() but replaced it under the group spinlock. Two writers racing on the same did could both find the same old node; the second hlist_replace_rcu() on an already-replaced node wrote through its poisoned ->pprev (LIST_POISON2), causing a kernel Oops (observed in apd during APatch re-authorization after a system_server soft restart). Move the lookup inside the spinlock so find+replace/add are atomic.
AYwlilwYA
force-pushed
the
fix-kstorage-race
branch
from
September 18, 2026 04:47
bf264c6 to
27782df
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #293 (which was closed). Same one-line-of-thought patch, but this time with the answers
I should have given when asked "kstorage 在什么情况下会导致这样的情况" — plus field evidence and
the reason I believe the spinlock concern doesn't hold.
Problem
write_kstorage()finds an existing entry outside the group spinlock and replaces it inside:Two writers racing on the same
didboth capture the sameold. The firsthlist_replace_rcu()sets
old->pprev = LIST_POISON2; the second one then executesrcu_assign_pointer(*(struct hlist_node **)old->pprev, new)— a write through the poisoned pointer.Under what circumstances
su_add_allow_uid()is only awrite_kstorage()on keyuid(sucompat.c:72-83), so writing the sameuid twice sequentially is harmless. The bug needs two writers inside
write_kstorage()for thesame
didat the same time. That is reachable:load_ap_package_config()(userd.c:1133) hasno locking of any kind in its body, and it is reachable from four independent entry points:
report_user_event("post-fs-data")user_event.c:20SUPERCALL_AP_LOAD_PACKAGE_CONFIG(0x100d)supercall.c:290-291reload-cfgsupercmd.c:466sucompat_init()sucompat.c:410-4112000and0plus a third writer
SUPERCALL_SU_GRANT_UID→call_grant_uid()(supercall.c:301-302), also apd-driven.The kernel also has a cross-context entry: the LSM hooks
after_security_path_rename/after_security_inode_rename→refresh_packages_list_tmp_rename()(userd.c:1620+) fire when/system/packages.list.tmpis renamed, i.e. in the context of whatever task performed the rename(normally system_server writing
/data/system/packages.list). So "install / uninstall / update apackage" and "apd (re-)authorizes" can genuinely overlap, and both walk the same list calling
su_add_allow_uid()for the same uid — uid2000appears in every one of those paths.This is not exotic on the affected device: at boot the same batch of 5 uids is added twice within
2.6 s (
[4.624261]…[4.624269], then[7.256972]…[7.256985]).Field evidence
dead000000000122is LIST_POISON2 (on arm64 the0xdead…prefix comes fromCONFIG_ILLEGAL_POINTER_VALUE) — a dereference of a poisoned list pointer.ESR = 0x96000044→ WnR = 1: a write fault, matchingrcu_assign_pointer(*(struct hlist_node **)old->pprev, new)insidehlist_replace_rcu().KP Region, the caller frame insideKP Hook— consistent withhlist_replace_rcu()beingstatic inlineand folded intowrite_kstorage().[+] KP D su_add_allow_uid: uid: 2000, to_uid: 0, sctx: u:r:magisk:s0, rc: 0.Disassembly extracted from the device's own boot partition confirms the lookup is outside the lock
(
link base 0xd000, running 0.13.3 build):Fix
Move the lookup inside the group spinlock so find + replace/add are atomic under the same lock.
vmalloc()/memcpy()still happen before the lock (they may sleep).On the spinlock / contention concern
KSTORAGE_NBUCKETS = 256(
kstorage.c:21-22); on the affected device the su list holds 6 entries, so buckets are almostalways empty and the walk is 0–2 nodes.
vmalloc(),memcpy()) are already outside the lock.remove_kstorage()already does exactly this — samekstorage_glocks[gid], bucket walkedinside the lock (
kstorage.c:243-249). "Lookup + mutate under the group spinlock" is already theestablished pattern in this file.
Writes to this group only happen on config change / authorization — not a hot path.
On ordering
The missing property is mutual exclusion, not ordering.
hlist_replace_rcu()/hlist_add_head_rcu()publish withrcu_assign_pointer(compiled tostlr) and readers usercu_dereference, so the ordering is already correct and no manual barrier is needed. The defect isthat the find is not covered by the same lock as the replace.
Happy to rework the shape (per-bucket locks, etc.) if you prefer — I'd just like the correctness fix in.