Re: [RFC PATCH v2 2/2] binder: add install_mutex to serialize page install and shrinker zap

From: Bo Zhang

Date: Thu Sep 03 2026 - 08:09:10 EST


Thanks for the detailed review. Replying to the three points below.

1) ABBA deadlock between install_mutex and mmap_lock

Sashiko says

"Can this locking order cause an ABBA deadlock with the shrinker path?
Here in binder_install_single_page(), alloc->install_mutex is acquired
first, followed by memory locks (per-VMA read lock or mmap_read_lock)
inside binder_page_insert(). However, in the shrinker callback
binder_alloc_free_page(), the locks are acquired in the reverse order.
If a concurrent writer is waiting on mmap_write_lock (which blocks new
readers), could this inversion result in a deadlock?"

This is a real issue and I'll fix it in v3. The install side holds
install_mutex and then falls back to a blocking mmap_read_lock(), while
the shrinker holds mmap_lock and then takes install_mutex. With a writer
pending on mmap_write_lock, the rwsem's writer-priority can turn this
inversion into a deadlock.

The fix is to never block on mmap_lock while holding install_mutex. The
mmap_lock fallback in binder_page_insert() becomes mmap_read_trylock(),
and on contention it returns -EAGAIN. The caller then drops install_mutex
and retries after waiting for mmap_lock to become available (without
holding any binder lock), so the install side never holds install_mutex
while blocked on mmap_lock. The per-VMA lock path is unaffected.

2) Potential infinite loop on -EBUSY

Sashiko says

"If vm_insert_page() persistently returns -EBUSY ... will this result in
an infinite loop? Because the -EAGAIN return from
binder_install_single_page() unconditionally jumps back to the retry
label without sleeping or limiting the number of attempts, could this
lead to a CPU lockup and local denial of service?"

Good catch. Under install_mutex the PTE should never be already
populated, so -EBUSY is not expected. In v3 I'll stop treating -EBUSY as
retryable: it returns an error instead of -EAGAIN, so it can never loop.
The -EAGAIN retry path is then only reached for genuine mmap_lock
contention, where the retry waits on mmap_lock rather than busy-looping.

3) Use-after-free of the preallocated next buffer on retry

Sashiko says

"Does retrying on -EAGAIN here result in a use-after-free of the next
buffer? ... the error path in binder_alloc_new_buf_locked() calls
kfree(new_buffer) ... If we jump back to the retry label and pass the
exact same freed pointer back into binder_alloc_new_buf_locked() ...
Additionally, if the buffer was split prior to the -EAGAIN failure, the
split is not rolled back."

You're right on both counts, thanks for catching this. In v3:

- binder_alloc_new_buf_locked() rolls back the split on the -EAGAIN
path (rb_erase() + list_del() of the newly inserted free buffer),
restoring the original free block.

- The preallocated 'next' buffer is (re)allocated inside the retry loop
in binder_alloc_new_buf(), so a freed pointer is never reused.

I'll send v3 with all three fixes.

Bo