Advertisement






Android - IMemory Native Interface is Insecure for IPC Use

CVE Category Price Severity
CVE-2021-39627 CWE-119 $15,000 High
Author Risk Exploitation Type Date
Unknown High Local 2016-04-12
CPE
cpe:cpe:/a:android:imemory
CVSS EPSS EPSSP
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2016040083

Below is a copy:

Android - IMemory Native Interface is Insecure for IPC UseSource: https://bugs.chromium.org/p/project-zero/issues/detail?id=706
 
Android: IMemory Native Interface is insecure for IPC use
Platform: Tested on Android 6.0.1 January patches
Class: Elevation of Privilege
 
Summary:
The IMemory interface in frameworks/native/libs/binder/IMemory.cpp, used primarily by the media services can be tricked to return arbitrary memory locations leading to information disclosure or memory corruption.
 
Description:
The IMemory interface allows the passing of shared memory across the Binder IPC channel on Android. The interface supports a single remote call, GET_MEMORY which requests a separate IMemoryHeap interface along with an offset value and size for the shared memory buffer. The IMemoryHeap interface in turn supports a HEAP_ID call which marshals across a FileDescriptor, size, flags and an offset. This is passed to mmap to map the shared memory into the current process.
 
The underlying vulnerability is the sizes in IMemory and IMemoryHeap are not checked relative to one another, and nor is the offset in IMemory checked against the size of IMemoryHeap. This allows a local process to craft fake IMemory and IMemoryHeap objects such that they lie about their values and either cause information disclosure or memory corruption.
 
To understand this lets look at how the pointer to the shared buffer is extracted from IMemory::pointer:
 
void* IMemory::pointer() const {
    ssize_t offset;
    sp<IMemoryHeap> heap = getMemory(&offset);
    void* const base = heap!=0 ? heap->base() : MAP_FAILED;
    if (base == MAP_FAILED)
        return 0;
    return static_cast<char*>(base) + offset; <- No check on IMemoryHeap size
}
 
Maybe we check sizes in getMemory() ?
 
sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const
{
    if (mHeap == 0) {
        Parcel data, reply;
        data.writeInterfaceToken(IMemory::getInterfaceDescriptor());
        if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {
            sp<IBinder> heap = reply.readStrongBinder();
            ssize_t o = reply.readInt32();
            size_t s = reply.readInt32(); <- No check.
            if (heap != 0) {
                mHeap = interface_cast<IMemoryHeap>(heap);
                if (mHeap != 0) {
                    mOffset = o;
                    mSize = s;
                }
            }
        }
    }
    if (offset) *offset = mOffset;
    if (size) *size = mSize;
    return mHeap;
}
 
Nope, as we can see, no check is made of IMemoryHeaps size, so you could specify a mapped file smaller than offset and create a pointer out of bounds. Of course if IMemoryHeap is invalid then the mmap process will return MAP_FAILED which will end up as NULL after the call to pointer(). 
 
So how can this be abused? Any IPC service which calls pointer() can be tricked into accessing an arbitrary location, either a relative offset to the file mapped or NULL. For example look at ICrypto::onTransact with the DECRYPT operation. It checks that the offset is within the total size (this has been exploited before) with:
 
} else if (totalSize > sharedBuffer->size()) {
  result = -EINVAL;
} else if ((size_t)offset > sharedBuffer->size() - totalSize) {
  result = -EINVAL;
 
The size is the value returned through IMemory, and not the actual mapped size from IMemoryHeap so in this case offset can be arbitrary. With the right plugin (such as the clearkey plugin) we can get this to read arbitrary memory. Even more so as theres no NULL checking in pointer() we can cause IMemoryHeap to fail which causes pointer() to return NULL. Setting size to 0xFFFFFFFF means we can read any memory location from 0 to 0xFFFFFFFF. 
 
This can be turned into an arbitrary write as long as you can pass an arbitrary IMemory to another service. For example the BnCameraRecordingProxy::onTransact in frameworks/av/camera/ICameraRecordingProxy.cpp does the following for onReleaseRecordingFrame
 
 
case RELEASE_RECORDING_FRAME: {
            ALOGV("RELEASE_RECORDING_FRAME");
            CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
            sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder());
 
            if (CameraUtils::isNativeHandleMetadata(mem)) {
                VideoNativeHandleMetadata *metadata =
                        (VideoNativeHandleMetadata*)(mem->pointer());
                metadata->pHandle = data.readNativeHandle();
 
                // releaseRecordingFrame will be responsble to close the native handle.
            }
            releaseRecordingFrame(mem);
 
            return NO_ERROR;
        } break;
 
As you can coerce the pointer value, as long as the first 4 bytes make the integer 3 the next 4 bytes will be overwritten by the native handle value which can be controlled. 
 
Proof of Concept:
Ive provided a PoC which exploits the issue in ICrypto::decrypt. I will just SIG_SEGV on reading an arbitrary location (in this case 1GiB relative to the mapped memory). If it succeeds then thats good as well as it shouldn't succeed. You should be able to create default Android Studio project and replace the MainActivity with the provided Java file. When run it should cause media server to crash.
 


Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum