Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

binder: frozen notification

Frozen processes present a significant challenge in binder transactions.
When a process is frozen, it cannot, by design, accept and/or respond to
binder transactions. As a result, the sender needs to adjust its
behavior, such as postponing transactions until the peer process
unfreezes. However, there is currently no way to subscribe to these
state change events, making it impossible to implement frozen-aware
behaviors efficiently.

Introduce a binder API for subscribing to frozen state change events.
This allows programs to react to changes in peer process state,
mitigating issues related to binder transactions sent to frozen
processes.

Implementation details:
For a given binder_ref, the state of frozen notification can be one of
the followings:
1. Userspace doesn't want a notification. binder_ref->freeze is null.
2. Userspace wants a notification but none is in flight.
list_empty(&binder_ref->freeze->work.entry) = true
3. A notification is in flight and waiting to be read by userspace.
binder_ref_freeze.sent is false.
4. A notification was read by userspace and kernel is waiting for an ack.
binder_ref_freeze.sent is true.

When a notification is in flight, new state change events are coalesced into
the existing binder_ref_freeze struct. If userspace hasn't picked up the
notification yet, the driver simply rewrites the state. Otherwise, the
notification is flagged as requiring a resend, which will be performed
once userspace acks the original notification that's inflight.

See https://r.android.com/3070045 for how userspace is going to use this
feature.

Signed-off-by: Yu-Ting Tseng <yutingtseng@google.com>
Acked-by: Carlos Llamas <cmllamas@google.com>
Link: https://lore.kernel.org/r/20240709070047.4055369-4-yutingtseng@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Yu-Ting Tseng and committed by
Greg Kroah-Hartman
d579b04a 104831a1

+337 -4
+282 -2
drivers/android/binder.c
··· 1355 1355 if (ref->node) 1356 1356 binder_free_node(ref->node); 1357 1357 kfree(ref->death); 1358 + kfree(ref->freeze); 1358 1359 kfree(ref); 1359 1360 } 1360 1361 ··· 3845 3844 } 3846 3845 } 3847 3846 3847 + static int 3848 + binder_request_freeze_notification(struct binder_proc *proc, 3849 + struct binder_thread *thread, 3850 + struct binder_handle_cookie *handle_cookie) 3851 + { 3852 + struct binder_ref_freeze *freeze; 3853 + struct binder_ref *ref; 3854 + bool is_frozen; 3855 + 3856 + freeze = kzalloc(sizeof(*freeze), GFP_KERNEL); 3857 + if (!freeze) 3858 + return -ENOMEM; 3859 + binder_proc_lock(proc); 3860 + ref = binder_get_ref_olocked(proc, handle_cookie->handle, false); 3861 + if (!ref) { 3862 + binder_user_error("%d:%d BC_REQUEST_FREEZE_NOTIFICATION invalid ref %d\n", 3863 + proc->pid, thread->pid, handle_cookie->handle); 3864 + binder_proc_unlock(proc); 3865 + kfree(freeze); 3866 + return -EINVAL; 3867 + } 3868 + 3869 + binder_node_lock(ref->node); 3870 + 3871 + if (ref->freeze || !ref->node->proc) { 3872 + binder_user_error("%d:%d invalid BC_REQUEST_FREEZE_NOTIFICATION %s\n", 3873 + proc->pid, thread->pid, 3874 + ref->freeze ? "already set" : "dead node"); 3875 + binder_node_unlock(ref->node); 3876 + binder_proc_unlock(proc); 3877 + kfree(freeze); 3878 + return -EINVAL; 3879 + } 3880 + binder_inner_proc_lock(ref->node->proc); 3881 + is_frozen = ref->node->proc->is_frozen; 3882 + binder_inner_proc_unlock(ref->node->proc); 3883 + 3884 + binder_stats_created(BINDER_STAT_FREEZE); 3885 + INIT_LIST_HEAD(&freeze->work.entry); 3886 + freeze->cookie = handle_cookie->cookie; 3887 + freeze->work.type = BINDER_WORK_FROZEN_BINDER; 3888 + freeze->is_frozen = is_frozen; 3889 + 3890 + ref->freeze = freeze; 3891 + 3892 + binder_inner_proc_lock(proc); 3893 + binder_enqueue_work_ilocked(&ref->freeze->work, &proc->todo); 3894 + binder_wakeup_proc_ilocked(proc); 3895 + binder_inner_proc_unlock(proc); 3896 + 3897 + binder_node_unlock(ref->node); 3898 + binder_proc_unlock(proc); 3899 + return 0; 3900 + } 3901 + 3902 + static int 3903 + binder_clear_freeze_notification(struct binder_proc *proc, 3904 + struct binder_thread *thread, 3905 + struct binder_handle_cookie *handle_cookie) 3906 + { 3907 + struct binder_ref_freeze *freeze; 3908 + struct binder_ref *ref; 3909 + 3910 + binder_proc_lock(proc); 3911 + ref = binder_get_ref_olocked(proc, handle_cookie->handle, false); 3912 + if (!ref) { 3913 + binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION invalid ref %d\n", 3914 + proc->pid, thread->pid, handle_cookie->handle); 3915 + binder_proc_unlock(proc); 3916 + return -EINVAL; 3917 + } 3918 + 3919 + binder_node_lock(ref->node); 3920 + 3921 + if (!ref->freeze) { 3922 + binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n", 3923 + proc->pid, thread->pid); 3924 + binder_node_unlock(ref->node); 3925 + binder_proc_unlock(proc); 3926 + return -EINVAL; 3927 + } 3928 + freeze = ref->freeze; 3929 + binder_inner_proc_lock(proc); 3930 + if (freeze->cookie != handle_cookie->cookie) { 3931 + binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch %016llx != %016llx\n", 3932 + proc->pid, thread->pid, (u64)freeze->cookie, 3933 + (u64)handle_cookie->cookie); 3934 + binder_inner_proc_unlock(proc); 3935 + binder_node_unlock(ref->node); 3936 + binder_proc_unlock(proc); 3937 + return -EINVAL; 3938 + } 3939 + ref->freeze = NULL; 3940 + /* 3941 + * Take the existing freeze object and overwrite its work type. There are three cases here: 3942 + * 1. No pending notification. In this case just add the work to the queue. 3943 + * 2. A notification was sent and is pending an ack from userspace. Once an ack arrives, we 3944 + * should resend with the new work type. 3945 + * 3. A notification is pending to be sent. Since the work is already in the queue, nothing 3946 + * needs to be done here. 3947 + */ 3948 + freeze->work.type = BINDER_WORK_CLEAR_FREEZE_NOTIFICATION; 3949 + if (list_empty(&freeze->work.entry)) { 3950 + binder_enqueue_work_ilocked(&freeze->work, &proc->todo); 3951 + binder_wakeup_proc_ilocked(proc); 3952 + } else if (freeze->sent) { 3953 + freeze->resend = true; 3954 + } 3955 + binder_inner_proc_unlock(proc); 3956 + binder_node_unlock(ref->node); 3957 + binder_proc_unlock(proc); 3958 + return 0; 3959 + } 3960 + 3961 + static int 3962 + binder_freeze_notification_done(struct binder_proc *proc, 3963 + struct binder_thread *thread, 3964 + binder_uintptr_t cookie) 3965 + { 3966 + struct binder_ref_freeze *freeze = NULL; 3967 + struct binder_work *w; 3968 + 3969 + binder_inner_proc_lock(proc); 3970 + list_for_each_entry(w, &proc->delivered_freeze, entry) { 3971 + struct binder_ref_freeze *tmp_freeze = 3972 + container_of(w, struct binder_ref_freeze, work); 3973 + 3974 + if (tmp_freeze->cookie == cookie) { 3975 + freeze = tmp_freeze; 3976 + break; 3977 + } 3978 + } 3979 + if (!freeze) { 3980 + binder_user_error("%d:%d BC_FREEZE_NOTIFICATION_DONE %016llx not found\n", 3981 + proc->pid, thread->pid, (u64)cookie); 3982 + binder_inner_proc_unlock(proc); 3983 + return -EINVAL; 3984 + } 3985 + binder_dequeue_work_ilocked(&freeze->work); 3986 + freeze->sent = false; 3987 + if (freeze->resend) { 3988 + freeze->resend = false; 3989 + binder_enqueue_work_ilocked(&freeze->work, &proc->todo); 3990 + binder_wakeup_proc_ilocked(proc); 3991 + } 3992 + binder_inner_proc_unlock(proc); 3993 + return 0; 3994 + } 3995 + 3848 3996 /** 3849 3997 * binder_free_buf() - free the specified buffer 3850 3998 * @proc: binder proc that owns buffer ··· 4477 4327 binder_inner_proc_unlock(proc); 4478 4328 } break; 4479 4329 4330 + case BC_REQUEST_FREEZE_NOTIFICATION: { 4331 + struct binder_handle_cookie handle_cookie; 4332 + int error; 4333 + 4334 + if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie))) 4335 + return -EFAULT; 4336 + ptr += sizeof(handle_cookie); 4337 + error = binder_request_freeze_notification(proc, thread, 4338 + &handle_cookie); 4339 + if (error) 4340 + return error; 4341 + } break; 4342 + 4343 + case BC_CLEAR_FREEZE_NOTIFICATION: { 4344 + struct binder_handle_cookie handle_cookie; 4345 + int error; 4346 + 4347 + if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie))) 4348 + return -EFAULT; 4349 + ptr += sizeof(handle_cookie); 4350 + error = binder_clear_freeze_notification(proc, thread, &handle_cookie); 4351 + if (error) 4352 + return error; 4353 + } break; 4354 + 4355 + case BC_FREEZE_NOTIFICATION_DONE: { 4356 + binder_uintptr_t cookie; 4357 + int error; 4358 + 4359 + if (get_user(cookie, (binder_uintptr_t __user *)ptr)) 4360 + return -EFAULT; 4361 + 4362 + ptr += sizeof(cookie); 4363 + error = binder_freeze_notification_done(proc, thread, cookie); 4364 + if (error) 4365 + return error; 4366 + } break; 4367 + 4480 4368 default: 4481 4369 pr_err("%d:%d unknown command %u\n", 4482 4370 proc->pid, thread->pid, cmd); ··· 4904 4716 if (cmd == BR_DEAD_BINDER) 4905 4717 goto done; /* DEAD_BINDER notifications can cause transactions */ 4906 4718 } break; 4719 + 4720 + case BINDER_WORK_FROZEN_BINDER: { 4721 + struct binder_ref_freeze *freeze; 4722 + struct binder_frozen_state_info info; 4723 + 4724 + memset(&info, 0, sizeof(info)); 4725 + freeze = container_of(w, struct binder_ref_freeze, work); 4726 + info.is_frozen = freeze->is_frozen; 4727 + info.cookie = freeze->cookie; 4728 + freeze->sent = true; 4729 + binder_enqueue_work_ilocked(w, &proc->delivered_freeze); 4730 + binder_inner_proc_unlock(proc); 4731 + 4732 + if (put_user(BR_FROZEN_BINDER, (uint32_t __user *)ptr)) 4733 + return -EFAULT; 4734 + ptr += sizeof(uint32_t); 4735 + if (copy_to_user(ptr, &info, sizeof(info))) 4736 + return -EFAULT; 4737 + ptr += sizeof(info); 4738 + binder_stat_br(proc, thread, BR_FROZEN_BINDER); 4739 + goto done; /* BR_FROZEN_BINDER notifications can cause transactions */ 4740 + } break; 4741 + 4742 + case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: { 4743 + struct binder_ref_freeze *freeze = 4744 + container_of(w, struct binder_ref_freeze, work); 4745 + binder_uintptr_t cookie = freeze->cookie; 4746 + 4747 + binder_inner_proc_unlock(proc); 4748 + kfree(freeze); 4749 + binder_stats_deleted(BINDER_STAT_FREEZE); 4750 + if (put_user(BR_CLEAR_FREEZE_NOTIFICATION_DONE, (uint32_t __user *)ptr)) 4751 + return -EFAULT; 4752 + ptr += sizeof(uint32_t); 4753 + if (put_user(cookie, (binder_uintptr_t __user *)ptr)) 4754 + return -EFAULT; 4755 + ptr += sizeof(binder_uintptr_t); 4756 + binder_stat_br(proc, thread, BR_CLEAR_FREEZE_NOTIFICATION_DONE); 4757 + } break; 4758 + 4907 4759 default: 4908 4760 binder_inner_proc_unlock(proc); 4909 4761 pr_err("%d:%d: bad work type %d\n", ··· 5552 5324 return false; 5553 5325 } 5554 5326 5327 + static void binder_add_freeze_work(struct binder_proc *proc, bool is_frozen) 5328 + { 5329 + struct rb_node *n; 5330 + struct binder_ref *ref; 5331 + 5332 + binder_inner_proc_lock(proc); 5333 + for (n = rb_first(&proc->nodes); n; n = rb_next(n)) { 5334 + struct binder_node *node; 5335 + 5336 + node = rb_entry(n, struct binder_node, rb_node); 5337 + binder_inner_proc_unlock(proc); 5338 + binder_node_lock(node); 5339 + hlist_for_each_entry(ref, &node->refs, node_entry) { 5340 + /* 5341 + * Need the node lock to synchronize 5342 + * with new notification requests and the 5343 + * inner lock to synchronize with queued 5344 + * freeze notifications. 5345 + */ 5346 + binder_inner_proc_lock(ref->proc); 5347 + if (!ref->freeze) { 5348 + binder_inner_proc_unlock(ref->proc); 5349 + continue; 5350 + } 5351 + ref->freeze->work.type = BINDER_WORK_FROZEN_BINDER; 5352 + if (list_empty(&ref->freeze->work.entry)) { 5353 + ref->freeze->is_frozen = is_frozen; 5354 + binder_enqueue_work_ilocked(&ref->freeze->work, &ref->proc->todo); 5355 + binder_wakeup_proc_ilocked(ref->proc); 5356 + } else { 5357 + if (ref->freeze->sent && ref->freeze->is_frozen != is_frozen) 5358 + ref->freeze->resend = true; 5359 + ref->freeze->is_frozen = is_frozen; 5360 + } 5361 + binder_inner_proc_unlock(ref->proc); 5362 + } 5363 + binder_node_unlock(node); 5364 + binder_inner_proc_lock(proc); 5365 + } 5366 + binder_inner_proc_unlock(proc); 5367 + } 5368 + 5555 5369 static int binder_ioctl_freeze(struct binder_freeze_info *info, 5556 5370 struct binder_proc *target_proc) 5557 5371 { ··· 5605 5335 target_proc->async_recv = false; 5606 5336 target_proc->is_frozen = false; 5607 5337 binder_inner_proc_unlock(target_proc); 5338 + binder_add_freeze_work(target_proc, false); 5608 5339 return 0; 5609 5340 } 5610 5341 ··· 5638 5367 binder_inner_proc_lock(target_proc); 5639 5368 target_proc->is_frozen = false; 5640 5369 binder_inner_proc_unlock(target_proc); 5370 + } else { 5371 + binder_add_freeze_work(target_proc, true); 5641 5372 } 5642 5373 5643 5374 return ret; ··· 6015 5742 binder_stats_created(BINDER_STAT_PROC); 6016 5743 proc->pid = current->group_leader->pid; 6017 5744 INIT_LIST_HEAD(&proc->delivered_death); 5745 + INIT_LIST_HEAD(&proc->delivered_freeze); 6018 5746 INIT_LIST_HEAD(&proc->waiting_threads); 6019 5747 filp->private_data = proc; 6020 5748 ··· 6567 6293 "BR_FAILED_REPLY", 6568 6294 "BR_FROZEN_REPLY", 6569 6295 "BR_ONEWAY_SPAM_SUSPECT", 6570 - "BR_TRANSACTION_PENDING_FROZEN" 6296 + "BR_TRANSACTION_PENDING_FROZEN", 6297 + "BR_FROZEN_BINDER", 6298 + "BR_CLEAR_FREEZE_NOTIFICATION_DONE", 6571 6299 }; 6572 6300 6573 6301 static const char * const binder_command_strings[] = { ··· 6592 6316 "BC_DEAD_BINDER_DONE", 6593 6317 "BC_TRANSACTION_SG", 6594 6318 "BC_REPLY_SG", 6319 + "BC_REQUEST_FREEZE_NOTIFICATION", 6320 + "BC_CLEAR_FREEZE_NOTIFICATION", 6321 + "BC_FREEZE_NOTIFICATION_DONE", 6595 6322 }; 6596 6323 6597 6324 static const char * const binder_objstat_strings[] = { ··· 6604 6325 "ref", 6605 6326 "death", 6606 6327 "transaction", 6607 - "transaction_complete" 6328 + "transaction_complete", 6329 + "freeze", 6608 6330 }; 6609 6331 6610 6332 static void print_binder_stats(struct seq_file *m, const char *prefix,
+19 -2
drivers/android/binder_internal.h
··· 130 130 BINDER_STAT_DEATH, 131 131 BINDER_STAT_TRANSACTION, 132 132 BINDER_STAT_TRANSACTION_COMPLETE, 133 + BINDER_STAT_FREEZE, 133 134 BINDER_STAT_COUNT 134 135 }; 135 136 136 137 struct binder_stats { 137 - atomic_t br[_IOC_NR(BR_TRANSACTION_PENDING_FROZEN) + 1]; 138 - atomic_t bc[_IOC_NR(BC_REPLY_SG) + 1]; 138 + atomic_t br[_IOC_NR(BR_CLEAR_FREEZE_NOTIFICATION_DONE) + 1]; 139 + atomic_t bc[_IOC_NR(BC_FREEZE_NOTIFICATION_DONE) + 1]; 139 140 atomic_t obj_created[BINDER_STAT_COUNT]; 140 141 atomic_t obj_deleted[BINDER_STAT_COUNT]; 141 142 }; ··· 161 160 BINDER_WORK_DEAD_BINDER, 162 161 BINDER_WORK_DEAD_BINDER_AND_CLEAR, 163 162 BINDER_WORK_CLEAR_DEATH_NOTIFICATION, 163 + BINDER_WORK_FROZEN_BINDER, 164 + BINDER_WORK_CLEAR_FREEZE_NOTIFICATION, 164 165 } type; 165 166 }; 166 167 ··· 279 276 binder_uintptr_t cookie; 280 277 }; 281 278 279 + struct binder_ref_freeze { 280 + struct binder_work work; 281 + binder_uintptr_t cookie; 282 + bool is_frozen:1; 283 + bool sent:1; 284 + bool resend:1; 285 + }; 286 + 282 287 /** 283 288 * struct binder_ref_data - binder_ref counts and id 284 289 * @debug_id: unique ID for the ref ··· 319 308 * @node indicates the node must be freed 320 309 * @death: pointer to death notification (ref_death) if requested 321 310 * (protected by @node->lock) 311 + * @freeze: pointer to freeze notification (ref_freeze) if requested 312 + * (protected by @node->lock) 322 313 * 323 314 * Structure to track references from procA to target node (on procB). This 324 315 * structure is unsafe to access without holding @proc->outer_lock. ··· 337 324 struct binder_proc *proc; 338 325 struct binder_node *node; 339 326 struct binder_ref_death *death; 327 + struct binder_ref_freeze *freeze; 340 328 }; 341 329 342 330 /** ··· 391 377 * (atomics, no lock needed) 392 378 * @delivered_death: list of delivered death notification 393 379 * (protected by @inner_lock) 380 + * @delivered_freeze: list of delivered freeze notification 381 + * (protected by @inner_lock) 394 382 * @max_threads: cap on number of binder threads 395 383 * (protected by @inner_lock) 396 384 * @requested_threads: number of binder threads requested but not ··· 440 424 struct list_head todo; 441 425 struct binder_stats stats; 442 426 struct list_head delivered_death; 427 + struct list_head delivered_freeze; 443 428 u32 max_threads; 444 429 int requested_threads; 445 430 int requested_threads_started;
+36
include/uapi/linux/android/binder.h
··· 236 236 __u32 async_recv; 237 237 }; 238 238 239 + struct binder_frozen_state_info { 240 + binder_uintptr_t cookie; 241 + __u32 is_frozen; 242 + __u32 reserved; 243 + }; 244 + 239 245 /* struct binder_extened_error - extended error information 240 246 * @id: identifier for the failed operation 241 247 * @command: command as defined by binder_driver_return_protocol ··· 473 467 /* 474 468 * The target of the last async transaction is frozen. No parameters. 475 469 */ 470 + 471 + BR_FROZEN_BINDER = _IOR('r', 21, struct binder_frozen_state_info), 472 + /* 473 + * The cookie and a boolean (is_frozen) that indicates whether the process 474 + * transitioned into a frozen or an unfrozen state. 475 + */ 476 + 477 + BR_CLEAR_FREEZE_NOTIFICATION_DONE = _IOR('r', 22, binder_uintptr_t), 478 + /* 479 + * void *: cookie 480 + */ 476 481 }; 477 482 478 483 enum binder_driver_command_protocol { ··· 566 549 BC_REPLY_SG = _IOW('c', 18, struct binder_transaction_data_sg), 567 550 /* 568 551 * binder_transaction_data_sg: the sent command. 552 + */ 553 + 554 + BC_REQUEST_FREEZE_NOTIFICATION = 555 + _IOW('c', 19, struct binder_handle_cookie), 556 + /* 557 + * int: handle 558 + * void *: cookie 559 + */ 560 + 561 + BC_CLEAR_FREEZE_NOTIFICATION = _IOW('c', 20, 562 + struct binder_handle_cookie), 563 + /* 564 + * int: handle 565 + * void *: cookie 566 + */ 567 + 568 + BC_FREEZE_NOTIFICATION_DONE = _IOW('c', 21, binder_uintptr_t), 569 + /* 570 + * void *: cookie 569 571 */ 570 572 }; 571 573