rust_binder: correctly handle FDA objects of length zero

Fix a bug where an empty FDA (fd array) object with 0 fds would cause an
out-of-bounds error. The previous implementation used `skip == 0` to
mean "this is a pointer fixup", but 0 is also the correct skip length
for an empty FDA. If the FDA is at the end of the buffer, then this
results in an attempt to write 8-bytes out of bounds. This is caught and
results in an EINVAL error being returned to userspace.

The pattern of using `skip == 0` as a special value originates from the
C-implementation of Binder. As part of fixing this bug, this pattern is
replaced with a Rust enum.

I considered the alternate option of not pushing a fixup when the length
is zero, but I think it's cleaner to just get rid of the zero-is-special
stuff.

The root cause of this bug was diagnosed by Gemini CLI on first try. I
used the following prompt:

> There appears to be a bug in @drivers/android/binder/thread.rs where
> the Fixups oob bug is triggered with 316 304 316 324. This implies
> that we somehow ended up with a fixup where buffer A has a pointer to
> buffer B, but the pointer is located at an index in buffer A that is
> out of bounds. Please investigate the code to find the bug. You may
> compare with @drivers/android/binder.c that implements this correctly.

Cc: stable@vger.kernel.org
Reported-by: DeepChirp <DeepChirp@outlook.com>
Closes: https://github.com/waydroid/waydroid/issues/2157
Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver")
Tested-by: DeepChirp <DeepChirp@outlook.com>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
Acked-by: Carlos Llamas <cmllamas@google.com>
Link: https://patch.msgid.link/20251229-fda-zero-v1-1-58a41cb0e7ec@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Alice Ryhl and committed by
Greg Kroah-Hartman
8f589c9c 63804fed

+34 -25
+34 -25
drivers/android/binder/thread.rs
··· 69 69 } 70 70 71 71 /// This entry specifies that a fixup should happen at `target_offset` of the 72 - /// buffer. If `skip` is nonzero, then the fixup is a `binder_fd_array_object` 73 - /// and is applied later. Otherwise if `skip` is zero, then the size of the 74 - /// fixup is `sizeof::<u64>()` and `pointer_value` is written to the buffer. 75 - struct PointerFixupEntry { 76 - /// The number of bytes to skip, or zero for a `binder_buffer_object` fixup. 77 - skip: usize, 78 - /// The translated pointer to write when `skip` is zero. 79 - pointer_value: u64, 80 - /// The offset at which the value should be written. The offset is relative 81 - /// to the original buffer. 82 - target_offset: usize, 72 + /// buffer. 73 + enum PointerFixupEntry { 74 + /// A fixup for a `binder_buffer_object`. 75 + Fixup { 76 + /// The translated pointer to write. 77 + pointer_value: u64, 78 + /// The offset at which the value should be written. The offset is relative 79 + /// to the original buffer. 80 + target_offset: usize, 81 + }, 82 + /// A skip for a `binder_fd_array_object`. 83 + Skip { 84 + /// The number of bytes to skip. 85 + skip: usize, 86 + /// The offset at which the skip should happen. The offset is relative 87 + /// to the original buffer. 88 + target_offset: usize, 89 + }, 83 90 } 84 91 85 92 /// Return type of `apply_and_validate_fixup_in_parent`. ··· 769 762 770 763 parent_entry.fixup_min_offset = info.new_min_offset; 771 764 parent_entry.pointer_fixups.push( 772 - PointerFixupEntry { 773 - skip: 0, 765 + PointerFixupEntry::Fixup { 774 766 pointer_value: buffer_ptr_in_user_space, 775 767 target_offset: info.target_offset, 776 768 }, ··· 813 807 parent_entry 814 808 .pointer_fixups 815 809 .push( 816 - PointerFixupEntry { 810 + PointerFixupEntry::Skip { 817 811 skip: fds_len, 818 - pointer_value: 0, 819 812 target_offset: info.target_offset, 820 813 }, 821 814 GFP_KERNEL, ··· 876 871 let mut reader = 877 872 UserSlice::new(UserPtr::from_addr(sg_entry.sender_uaddr), sg_entry.length).reader(); 878 873 for fixup in &mut sg_entry.pointer_fixups { 879 - let fixup_len = if fixup.skip == 0 { 880 - size_of::<u64>() 881 - } else { 882 - fixup.skip 874 + let (fixup_len, fixup_offset) = match fixup { 875 + PointerFixupEntry::Fixup { target_offset, .. } => { 876 + (size_of::<u64>(), *target_offset) 877 + } 878 + PointerFixupEntry::Skip { 879 + skip, 880 + target_offset, 881 + } => (*skip, *target_offset), 883 882 }; 884 883 885 - let target_offset_end = fixup.target_offset.checked_add(fixup_len).ok_or(EINVAL)?; 886 - if fixup.target_offset < end_of_previous_fixup || offset_end < target_offset_end { 884 + let target_offset_end = fixup_offset.checked_add(fixup_len).ok_or(EINVAL)?; 885 + if fixup_offset < end_of_previous_fixup || offset_end < target_offset_end { 887 886 pr_warn!( 888 887 "Fixups oob {} {} {} {}", 889 - fixup.target_offset, 888 + fixup_offset, 890 889 end_of_previous_fixup, 891 890 offset_end, 892 891 target_offset_end ··· 899 890 } 900 891 901 892 let copy_off = end_of_previous_fixup; 902 - let copy_len = fixup.target_offset - end_of_previous_fixup; 893 + let copy_len = fixup_offset - end_of_previous_fixup; 903 894 if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { 904 895 pr_warn!("Failed copying into alloc: {:?}", err); 905 896 return Err(err.into()); 906 897 } 907 - if fixup.skip == 0 { 908 - let res = alloc.write::<u64>(fixup.target_offset, &fixup.pointer_value); 898 + if let PointerFixupEntry::Fixup { pointer_value, .. } = fixup { 899 + let res = alloc.write::<u64>(fixup_offset, pointer_value); 909 900 if let Err(err) = res { 910 901 pr_warn!("Failed copying ptr into alloc: {:?}", err); 911 902 return Err(err.into());