CVE-2024-49746 AI-generated analysis
platform/frameworks/native, libs/binder (Parcel)
Parcel::continueWrite in libbinder no longer closes every file descriptor of a parcel with an mOwner buffer when the parcel is resized. It closes only FDs in the truncated region, drops objects that straddle the new size, and marks retained kernel FDs as owned. The fix also adds bounds checks to the RPC object truncation paths.
Root cause
In libs/binder/Parcel.cpp, Parcel::continueWrite handles the case where the parcel data is owned by a release callback (mOwner). The new tests exercise this with replies received through the kernel binder driver. When the parcel is resized (appending data or truncating with setDataSize), the code copies the data and object table into a new buffer. It then called closeFileDescriptors(), which closes every FD in the parcel, including FDs in the retained part of the data. The code carried a TODO(b/239222407) noting this was wrong. Also, the object-table truncation compared only the object start offset against the new size (mObjects[i] < desired). An object partially cut by the truncation was therefore kept and not properly freed. The RPC-parcel loop did the same with mObjectPositions. Parcel::truncateRpcObjects read the object type and fd index from mData at positions taken from mObjectPositions without checking them against mDataSize. The commit message says this could have resulted in an OOB access.
How the fix works
- libs/binder/Parcel.cpp: adds Parcel::truncateFileDescriptors(size_t newObjectsSize). It walks the kernel object table from the end and closes FD objects only at indices >= newObjectsSize. closeFileDescriptors() now delegates to truncateFileDescriptors(0).
- libs/binder/Parcel.cpp: Parcel::continueWrite, kernel path (now inside BINDER_WITH_KERNEL_IPC): calls validateReadData(mDataSize), which is commented as a hack to sort the objects. It then keeps an object only if mObjects[i] + sizeof(flat_binder_object) <= desired, so objects that straddle the new size are dropped.
- libs/binder/Parcel.cpp: Parcel::continueWrite, RPC path: computes each object's size from its RpcFields::ObjectType. TYPE_BINDER_NULL is the header only, TYPE_BINDER adds uint64_t and TYPE_NATIVE_FILE_DESCRIPTOR adds int32_t. It checks pos + sizeof(ObjectType) against mDataSize with overflow detection, returns BAD_VALUE on failure, and keeps an object only if pos + size <= desired.
- libs/binder/Parcel.cpp: Parcel::continueWrite, when releasing the mOwner buffer: after the memcpy of the retained object table, it sets flat->cookie = 1 on every retained BINDER_TYPE_FD object, so those FDs are explicitly marked as owned. It also replaces closeFileDescriptors() with truncateFileDescriptors(objectsSize) before mOwner is called.
- libs/binder/Parcel.cpp: Parcel::truncateRpcObjects: before reading the object type, checks pos + sizeof(ObjectType) <= mDataSize with overflow detection. For TYPE_NATIVE_FILE_DESCRIPTOR it also checks that the int32_t fd index lies within mDataSize. It returns BAD_VALUE if either check fails. mObjectPositions.pop_back() moves to after the object has been processed.
- libs/binder/include/binder/Parcel.h: declares the private, hidden-visibility truncateFileDescriptors(size_t). The Android 14 cherry-pick keeps the public closeFileDescriptors() to avoid API change errors.
- libs/binder/tests/binderLibTest.cpp: adds FdLeakDetector and the RecvOwned/UnownedFileDescriptors tests (plain, AndWriteInt, AndTruncate), using the new BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED/UNOWNED_TRANSACTION server codes. Two owned-FD cases are skipped with GTEST_SKIP because of b/370824489.
Completeness
- On the kernel path, FDs in the retained region are no longer closed on resize, straddling objects are dropped, and retained FDs are marked owned (cookie = 1). The RPC object loop in continueWrite also drops straddling objects.
- truncateRpcObjects gains bounds checks for both the type field and the fd index field. The RPC loop in continueWrite gains the same type-field check.
- The RPC size computation in continueWrite has no default case. An unrecognized ObjectType is treated as having only the type header size and is not rejected there.
- The kernel-path object loop relies on validateReadData(mDataSize) to sort the object table, which the code comments as a hack.
- Per the commit message, two owned-FD tests stay disabled because of a separate bug in the Parcel fdsan code (b/370824489) that this change does not address.
- The non-mOwner branches of continueWrite are not shown in the diff. Whether they needed changes is not visible in the material.
Behavior changes
- Resizing a parcel with an mOwner buffer no longer closes FDs that remain in the retained data. Those retained FD objects now have cookie set to 1 (marked as owned).
- An object that starts before the new size but extends beyond it is now removed from the object table. In the unowned truncate test, reading it afterwards returns BAD_TYPE from readUniqueFileDescriptor.
- continueWrite can now return BAD_VALUE for RPC parcels whose object positions fall outside mDataSize.
- truncateRpcObjects can now return BAD_VALUE instead of reading past mDataSize.
- On Android 14, public closeFileDescriptors() keeps its behavior by delegating to truncateFileDescriptors(0). On Android 15 it becomes a private closeFileDescriptors(size_t), and the public LIBBINDER_EXPORTED declaration is removed.
Per major
- Android 14: Adapted fix: commit 8d76a855a1ba in android-security-14.0.0_r16. It adds a private hidden truncateFileDescriptors(size_t) and keeps the public closeFileDescriptors() to avoid API change errors. The test was tweaked for older C++/android-base. 8/8 lines of the patched region are present at android-security-14.0.0_r29.
- Android 15: Adapted fix: commit b3cdb06ab913 in android-security-15.0.0_r4. It changes closeFileDescriptors to a private closeFileDescriptors(size_t newObjectsSize), removes the public LIBBINDER_EXPORTED declaration and updates freeDataNoInit to pass 0. The continueWrite and truncateRpcObjects logic is the same as on 14. 7/8 lines of the patched region are present at android-security-15.0.0_r17.
- Android 16: Not affected (not listed by the bulletin). 7/8 lines of the patched region are present at android-security-16.0.0_r8.
- Android 17: Not affected (not listed by the bulletin). 7/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- The material does not show the full continueWrite function or validateReadData. The non-mOwner branches and the sort hack cannot be verified here.
- The material does not show the downstream effect of the prematurely closed FDs beyond the tests being 'used to trigger fdsan error'.
- The material does not identify which system services or callers resize mOwner parcels that contain FDs in practice.
- The material does not say which line of the patched region differs on Android 15, 16 and 17 (7/8 lines present).
- The bulletin lists Android 12, 12L and 13 as affected, but the material includes no fix commit for those branches.