Chromium: Incorrect size calculation when deserializing Mojo "Event" messages leading to OOB access

2018-03-15T00:00:00
ID SSV:97178
Type seebug
Reporter Root
Modified 2018-03-15T00:00:00

Description

VULNERABILITY DETAILS

Mojo IPC allows endpoints to communicate with one another, potentially across process boundaries. Each endpoint initially receives a handle to the broker host node, using which it can request subsequent "child" channels to be created (https://cs.chromium.org/chromium/src/mojo/edk/system/broker_messages.h?l=16). Once a child node is created, the node controller can register with the broker node, create subsequent ports, and send messages to its peers. Messages transferred over Mojo IPC conform to the message (https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio) and wire (https://docs.google.com/document/d/1jNcsxOdO3Al52s6lIrMOOgY7KXB7TJ8wGGWstAHiTd8) formats.

Among the supported message types, "EVENT_MESSAGE" is one of the most "complex", and can be used to encapsulate one of several types of events, including "user events", "port accepted", "port merge" and others. When such a message is received by a node controller, it deserializes the received bytes in order to extract the encoded event. This is done by calling the "NodeController::DeserializeEventMessage" method (note that this method is also called when broadcasts of type BROADCAST_EVENT are received). The above method calls into several others, before arriving at "Event::Deserialize" (https://cs.chromium.org/chromium/src/mojo/edk/system/ports/event.cc?l=87). Here's a snippet from that function: 1. ScopedEvent Event::Deserialize(const void* buffer, size_t num_bytes) { 2. if (num_bytes < sizeof(SerializedHeader)) 3. return nullptr; 4. 5. const auto* header = static_cast<const SerializedHeader*>(buffer); 6. const PortName& port_name = header->port_name; 7. const size_t data_size = num_bytes - sizeof(header); 8. switch (header->type) { 9. case Type::kUserMessage: 10. return UserMessageEvent::Deserialize(port_name, header + 1, data_size); 11. case Type::kPortAccepted: 12. return PortAcceptedEvent::Deserialize(port_name, header + 1, data_size); 13. ... 14. } 15. }

As we can see above, the function first checks that the received message's size is large enough to contain the "SerializedHeader" structure. Then, it extracts the message type and sends the message to additional parsing in each of the specialised parsing methods. However, the message size passed into the parsing functions ("data_size") is incorrectly calculated at line 7 -- instead of using "sizeof(*header)" (24 bytes), it uses "sizeof(header)" (4 or 8 bytes).

Consequently, each of the specialised parsing methods can access data OOB if the encoded data size was small enough to begin with. In most cases, this will lead to OOB reads, which may leak information to remote nodes if the OOB-read data is subsequently written back into a Mojo message. However, I think this can also be used in order to trigger memory corruption is some cases. For example, consider "UserMessageEvent::Deserialize": 1. ScopedEvent UserMessageEvent::Deserialize(const PortName& port_name, 2. const void* buffer, 3. size_t num_bytes) { 4. if (num_bytes < sizeof(UserMessageEventData)) 5. return nullptr; 6. 7. const auto* data = static_cast<const UserMessageEventData*>(buffer); 8. ... 9. auto event = 10. base::WrapUnique(new UserMessageEvent(port_name, data->sequence_num)); 11. event->ReservePorts(data->num_ports); 12. const auto* in_descriptors = 13. reinterpret_cast<const PortDescriptor*>(data + 1); 14. std::copy(in_descriptors, in_descriptors + data->num_ports, 15. event->port_descriptors()); 16 ... 17. } Under regular circumstances, the contents of the "data" buffer does not change during the function's execution. However, if "data->num_ports" is accessed OOB, it may have one value when reserving the port array (line 11) and another, larger value, when copying the ports into the event (lines 14-15), thereby triggering a buffer overflow.

Since sending Mojo IPC messages doesn't require any capabilities, any process (including the renderer) can encode such a message and send it to any peer (including the browser), thereby triggering the above OOB accesses.

VERSION

  • Chromium 64.0.3282.0 64-bit
  • Revision dd12859a9c856c6919cedf6c35d13b8b22af94e1-refs/heads/master@{#520743}
  • OS Linux 4.4.0-97-generic

REPRODUCTION CASE

I'm attaching a patch that adds code to the renderer process which modifies a serialized event message into a malformed "observe proxy" message with a small size encoded in the serialized header. Applying the patch and running Chrome will trigger an OOB access. Here's the corresponding crash output from an ASAN build: ``` ================================================================= ==236812==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60800017b600 at pc 0x55738286be69 bp 0x7f58714290b0 sp 0x7f5871428860 READ of size 16 at 0x60800017b600 thread T24 (Chrome_IOThread) #0 0x55738286be68 in __asan_memcpy asan_rtl:3 #1 0x557383b50fdc in ObserveProxyEvent <PATH>/src/out/asan/../../mojo/edk/system/ports/event.cc:297:7 #2 0x557383b50fdc in make_unique<mojo::edk::ports::ObserveProxyEvent, const mojo::edk::ports::PortName &, const mojo::edk::ports::NodeName &, const mojo::edk::ports::PortName &, const mojo::edk::ports::NodeName &, const mojo::edk::ports::PortName &> <PATH>/src/out/asan/../../buildtools/third_party/libc++/trunk/include/memory:3026:0 #3 0x557383b50fdc in Deserialize <PATH>/src/out/asan/../../mojo/edk/system/ports/event.cc:315:0 #4 0x557383b50fdc in mojo::edk::ports::Event::Deserialize(void const, unsigned long) <PATH>/src/out/asan/../../mojo/edk/system/ports/event.cc:146:0 #5 0x55738cf9bd59 in mojo::edk::(anonymous namespace)::DeserializeEventMessage(mojo::edk::ports::NodeName const&, std::__1::unique_ptr<mojo::edk::Channel::Message, std::__1::default_delete<mojo::edk::Channel::Message> >) <PATH>/src/out/asan/../../mojo/edk/system/node_controller.cc:134:16 #6 0x55738cf9b2de in mojo::edk::NodeController::OnEventMessage(mojo::edk::ports::NodeName const&, std::__1::unique_ptr<mojo::edk::Channel::Message, std::__1::default_delete<mojo::edk::Channel::Message> >) <PATH>/src/out/asan/../../mojo/edk/system/node_controller.cc:1035:16 #7 0x55738cfb3f77 in mojo::edk::NodeChannel::OnChannelMessage(void const, unsigned long, std::__1::vector<mojo::edk::ScopedPlatformHandle, std::__1::allocator<mojo::edk::ScopedPlatformHandle> >) <PATH>/src/out/asan/../../mojo/edk/system/node_channel.cc:710:18 #8 0x55738cfae12f in mojo::edk::Channel::OnReadComplete(unsigned long, unsigned long) <PATH>/src/out/asan/../../mojo/edk/system/channel.cc:729:18 #9 0x55738cfd4405 in mojo::edk::(anonymous namespace)::ChannelPosix::OnFileCanReadWithoutBlocking(int) <PATH>/src/out/asan/../../mojo/edk/system/channel_posix.cc:312:14 #10 0x557388c55431 in base::MessagePumpLibevent::OnLibeventNotification(int, short, void) <PATH>/src/out/asan/../../base/message_loop/message_pump_libevent.cc:0:13 #11 0x557388e08a08 in event_process_active <PATH>/src/out/asan/../../base/third_party/libevent/event.c:381:4 #12 0x557388e08a08 in event_base_loop <PATH>/src/out/asan/../../base/third_party/libevent/event.c:521:0 #13 0x557388c55ec7 in base::MessagePumpLibevent::Run(base::MessagePump::Delegate) <PATH>/src/out/asan/../../base/message_loop/message_pump_libevent.cc:257:9 #14 0x557388cc8321 in base::RunLoop::Run() <PATH>/src/out/asan/../../base/run_loop.cc:114:14 #15 0x557384460e2f in content::BrowserThreadImpl::IOThreadRun(base::RunLoop) <PATH>/src/out/asan/../../content/browser/browser_thread_impl.cc:248:11 #16 0x55738446148c in content::BrowserThreadImpl::Run(base::RunLoop) <PATH>/src/out/asan/../../content/browser/browser_thread_impl.cc:283:14 #17 0x557388d5a3e9 in base::Thread::ThreadMain() <PATH>/src/out/asan/../../base/threading/thread.cc:338:3 #18 0x557388d487f4 in base::(anonymous namespace)::ThreadFunc(void) <PATH>/src/out/asan/../../base/threading/platform_thread_posix.cc:75:13 #19 0x7f589e4fc183 in start_thread /build/eglibc-SvCtMH/eglibc-2.19/nptl/pthread_create.c:312:0

0x60800017b600 is located 0 bytes to the right of 96-byte region [0x60800017b5a0,0x60800017b600) allocated by thread T24 (Chrome_IOThread) here: #0 0x55738286d9fe in __interceptor_posix_memalign asan_rtl:3 #1 0x557388c3c34f in base::AlignedAlloc(unsigned long, unsigned long) <PATH>/src/out/asan/../../base/memory/aligned_memory.cc:31:7 #2 0x55738cfab204 in Message <PATH>/src/out/asan/../../mojo/edk/system/channel.cc:121:7 #3 0x55738cfab204 in Message <PATH>/src/out/asan/../../mojo/edk/system/channel.cc:78:0 #4 0x55738cfab204 in mojo::edk::Channel::Message::Message(unsigned long, unsigned long) <PATH>/src/out/asan/../../mojo/edk/system/channel.cc:64:0 #5 0x55738cfb3d96 in mojo::edk::NodeChannel::OnChannelMessage(void const, unsigned long, std::__1::vector<mojo::edk::ScopedPlatformHandle, std::__1::allocator<mojo::edk::ScopedPlatformHandle> >) <PATH>/src/out/asan/../../mojo/edk/system/node_channel.cc:707:15 #6 0x55738cfae12f in mojo::edk::Channel::OnReadComplete(unsigned long, unsigned long) <PATH>/src/out/asan/../../mojo/edk/system/channel.cc:729:18 #7 0x55738cfd4405 in mojo::edk::(anonymous namespace)::ChannelPosix::OnFileCanReadWithoutBlocking(int) <PATH>/src/out/asan/../../mojo/edk/system/channel_posix.cc:312:14 #8 0x557388c55431 in base::MessagePumpLibevent::OnLibeventNotification(int, short, void) <PATH>/src/out/asan/../../base/message_loop/message_pump_libevent.cc:0:13 #9 0x557388e08a08 in event_process_active <PATH>/src/out/asan/../../base/third_party/libevent/event.c:381:4 #10 0x557388e08a08 in event_base_loop <PATH>/src/out/asan/../../base/third_party/libevent/event.c:521:0 #11 0x557388c55ec7 in base::MessagePumpLibevent::Run(base::MessagePump::Delegate) <PATH>/src/out/asan/../../base/message_loop/message_pump_libevent.cc:257:9 #12 0x557388cc8321 in base::RunLoop::Run() <PATH>/src/out/asan/../../base/run_loop.cc:114:14 #13 0x557384460e2f in content::BrowserThreadImpl::IOThreadRun(base::RunLoop) <PATH>/src/out/asan/../../content/browser/browser_thread_impl.cc:248:11 #14 0x55738446148c in content::BrowserThreadImpl::Run(base::RunLoop) <PATH>/src/out/asan/../../content/browser/browser_thread_impl.cc:283:14 #15 0x557388d5a3e9 in base::Thread::ThreadMain() <PATH>/src/out/asan/../../base/threading/thread.cc:338:3 #16 0x557388d487f4 in base::(anonymous namespace)::ThreadFunc(void*) <PATH>/src/out/asan/../../base/threading/platform_thread_posix.cc:75:13 #17 0x7f589e4fc183 in start_thread /build/eglibc-SvCtMH/eglibc-2.19/nptl/pthread_create.c:312:0

Thread T24 (Chrome_IOThread) created by T0 (chrome) here: #0 0x557382855fbd in __interceptor_pthread_create asan_rtl:3 #1 0x557388d47b0a in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThread::Delegate, base::PlatformThreadHandle, base::ThreadPriority) <PATH>/src/out/asan/../../base/threading/platform_thread_posix.cc:114:13 #2 0x557388d596b4 in base::Thread::StartWithOptions(base::Thread::Options const&) <PATH>/src/out/asan/../../base/threading/thread.cc:112:15 #3 0x557384461dad in content::BrowserThreadImpl::StartWithOptions(base::Thread::Options const&) <PATH>/src/out/asan/../../content/browser/browser_thread_impl.cc:374:25 #4 0x5573844372c6 in content::BrowserMainLoop::CreateThreads() <PATH>/src/out/asan/../../content/browser/browser_main_loop.cc:1137:49 #5 0x55738509e448 in Run <PATH>/src/out/asan/../../base/callback.h:94:12 #6 0x55738509e448 in content::StartupTaskRunner::RunAllTasksNow() <PATH>/src/out/asan/../../content/browser/startup_task_runner.cc:45:0 #7 0x557384436075 in content::BrowserMainLoop::CreateStartupTasks() <PATH>/src/out/asan/../../content/browser/browser_main_loop.cc:963:25 #8 0x557384444ef1 in content::BrowserMainRunnerImpl::Initialize(content::MainFunctionParams const&) <PATH>/src/out/asan/../../content/browser/browser_main_runner.cc:119:17 #9 0x55738442f344 in content::BrowserMain(content::MainFunctionParams const&) <PATH>/src/out/asan/../../content/browser/browser_main.cc:42:32 #10 0x5573881fe727 in content::ContentMainRunnerImpl::Run() <PATH>/src/out/asan/../../content/app/content_main_runner.cc:728:12 #11 0x557388222261 in service_manager::Main(service_manager::MainParams const&) <PATH>/src/out/asan/../../services/service_manager/embedder/main.cc:456:29 #12 0x5573881fad60 in content::ContentMain(content::ContentMainParams const&) <PATH>/src/out/asan/../../content/app/content_main.cc:19:10 #13 0x557382899cbf in ChromeMain <PATH>/src/out/asan/../../chrome/app/chrome_main.cc:130:12 #14 0x7f58975bbf44 in __libc_start_main /build/eglibc-SvCtMH/eglibc-2.19/csu/libc-start.c:287:0

SUMMARY: AddressSanitizer: heap-buffer-overflow (<PATH>/src/out/asan/chrome+0x7161e68) Shadow bytes around the buggy address: 0x0c1080027670: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c1080027680: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c1080027690: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c10800276a0: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c10800276b0: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c10800276c0:[fa]fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c10800276d0: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c10800276e0: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd 0x0c10800276f0: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00 0x0c1080027700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c1080027710: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==236812==ABORTING ```