google-security-research
vasiab / google-security-research Goto Github PK
View Code? Open in Web Editor NEWAutomatically exported from code.google.com/p/google-security-research
Automatically exported from code.google.com/p/google-security-research
google-security-research
When setting a new keyboard mapping the following code will be reached:
IOHIKeyboardMapper * IOHIKeyboardMapper::keyboardMapper(
IOHIKeyboard * delegate,
const UInt8 * mapping,
UInt32 mappingLength,
bool mappingShouldBeFreed )
{
IOHIKeyboardMapper * me = new IOHIKeyboardMapper;
if (me && !me->init(delegate, mapping, mappingLength, mappingShouldBeFreed))
{
me->free();
If the init method returns false, IOHIKeyboardMapper::free will be called.
bool IOHIKeyboardMapper::init( IOHIKeyboard *delegate,
const UInt8 *map,
UInt32 mappingLen,
bool mappingShouldBeFreed )
{
if (!super::init()) return false;
_delegate = delegate;
if (!parseKeyMapping(map, mappingLen, &_parsedMapping)) return false;
...
_reserved = IONew(ExpansionData, 1);
If the parseKeyMapping method returns false (by supplying a malformed key
mapping),
the init function will return early, and won't initialize the _reserved member.
The IOHIKeyboardMapper::free method will call stickyKeysfree() (both
_parsedMapping.mapping and _parsedMapping.mappingLen
are non-zero) :
void IOHIKeyboardMapper::free()
{
if (!_parsedMapping.mapping || !_parsedMapping.mappingLen)
return;
stickyKeysfree();
stickyKeysfree attempts to release all member objects which have been
initialized:
void IOHIKeyboardMapper::stickyKeysfree (void)
{
// release shift toggle struct
if (_stickyKeys_ShiftToggle)
stickyKeysFreeToggleInfo(_stickyKeys_ShiftToggle);
// release option toggle struct
if (_stickyKeys_OptionToggle)
stickyKeysFreeToggleInfo(_stickyKeys_OptionToggle);
// release on param dict
if (_onParamDict)
_onParamDict->release();
// release off param dict
if (_offParamDict)
_offParamDict->release();
// release off fn param dict
if (_offFnParamDict) <-- (a)
_offFnParamDict->release(); <-- (b)
However, at (a) _offFnParamDict isn't actually a member but the following macro:
#define _offFnParamDict _reserved->offFnParamDict
Since we returned early from IOHIKeyboardMapper::init before _reserved was
allocated it's null.
By mapping the null page we can control the value of the offFnParamDict pointer
and therefore
control the virtual function call at (b)
Original issue reported on code.google.com by [email protected]
on 30 Jun 2014 at 3:25
Attachments:
A SWF to reproduce is attached, along with source. To reproduce, host
JPEGLeak2.swf on the same web server / directory as twocomps.jpg. A screenshot
of the PoC in action is also attached.
twocomps.jpg is a weird JPEG file that has all sorts of problems (truncated,
etc.,) but the main problem is that no software really knows how to handle
2-component JPEGs, as these do not exist in the wild. It looks like Flash's
response to not knowing how to handle it is to leave the image canvas
uninitialized. This can be a significant security issue.
The PoC goes most of the way to pulling a pointer value (ASLR defeat) out of
the uninitialized canvas -- for the 64-bit Linux platform. But you can get the
point just by refreshing the PoC a lot and seeing the rendered content change.
Since it's very easy to use this vulnerability to read uninitialized memory
content, a 90-day disclosure deadline applies.
Original issue reported on code.google.com by [email protected]
on 9 Jul 2014 at 11:22
Attachments:
The class IODataQueue is used in various places in the kernel. There are a
couple of exploitable integer overflow issues in the ::enqueue method:
Boolean IODataQueue::enqueue(void * data, UInt32 dataSize)
{
const UInt32 head = dataQueue->head; // volatile
const UInt32 tail = dataQueue->tail;
const UInt32 entrySize = dataSize + DATA_QUEUE_ENTRY_HEADER_SIZE; <-- (a)
IODataQueueEntry * entry;
if ( tail >= head )
{
// Is there enough room at the end for the entry?
if ( (tail + entrySize) <= dataQueue->queueSize ) <-- (b)
{
entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
entry->size = dataSize;
memcpy(&entry->data, data, dataSize); <-- (c)
The additions at (a) and (b) should be checked for overflow. In both cases, by
supplying a large value for dataSize an attacker can reach the memcpy call at
(c) with a length argument which is larger than the remaining space in the
queue buffer.
The majority of this PoC involves setting up the conditions to actually be able
to reach a call to ::enqueue with a controlled dataSize argument, the bug
itself it quite simple.
This PoC creates an IOHIDLibUserClient (IOHIDPointingDevice) and calls the
create_queue externalMethod to create an IOHIDEventQueue (which inherits from
IODataQueue.) This is the queue which will have the ::enqueue method invoked
with the large dataSize argument.
The PoC then calls IOConnectMapMemory with a memoryType argument of 0 which
maps an array of IOHIDElementValues into userspace:
typedef struct _IOHIDElementValue
{
IOHIDElementCookie cookie;
UInt32 totalSize;
AbsoluteTime timestamp;
UInt32 generation;
UInt32 value[1];
}IOHIDElementValue;
The first dword of the mapped memory is a cookie value and the second is a size.
When the IOHIDElementPrivate::processReport method is invoked (in response to
an HID event) if there are any listening queues then the IOHIDElementValue will
be enqueued - and the size is in shared memory :-)
The PoC calls the startQueue selector to start the listening queue then calls
addElementToQueue passing the cookie for the first IOHIDElementValue and the ID
of the listening queue.
A loop then overwrites the totalSize field of the IOHIDElementValue in shared
memory with 0xfffffffe. When the processReport method is called this will call
IODataQueue::enqueue and overflow the calculation of entry size such that it
will attempt to memcpy 0xfffffffe bytes. Note that the size of the queue buffer
is also attacked controlled, and the kernel is 64-bit, so a 4gb memcpy is
almost certainly exploitable.
Note that lldb seems to get confused by the crash - the memcpy implementation
uses rep movsq and lldb doesn't seem to understand the 0xf3 (rep) prefix - IDA
disassembles the function fine though. Also the symbols for memcpy and
real_mode_bootstrap_end seem to have the same address so the lldb backtrace
looks weird, but it is actually memcpy.
Original issue reported on code.google.com by [email protected]
on 26 Jun 2014 at 1:33
Attachments:
notifyd is an OS X service running as root. Chrome and Safari sandboxed
renderers have a mach port to talk to notifyd.
The prev_slot argument of the _notify_server_regenerate MIG IPC method isn't
bounds checked:
notify_ipc.defs:
routine _notify_server_regenerate
(
server : mach_port_t;
name : notify_name;
token : int;
reg_type : uint32_t;
port : mach_port_make_send_t;
sig: int;
prev_slot: int;
prev_state : uint64_t;
prev_time : uint64_t;
path : notify_name;
path_flags: int;
out new_slot : int;
out new_name_id : uint64_t;
out status : int;
ServerAuditToken audit : audit_token_t
);
server implementation in notify_proc.c:
case NOTIFY_TYPE_MEMORY:
{
kstatus = __notify_server_register_check_2(server, name, nameCnt, token, &size, new_slot, new_nid, status, audit);
if (*status == NOTIFY_STATUS_OK)
{
if ((*new_slot != UINT32_MAX) && (prev_slot != UINT32_MAX) && (global.last_shm_base != NULL)) <-- (a)
{
global.shared_memory_base[*new_slot] = global.shared_memory_base[*new_slot] + global.last_shm_base[prev_slot] - 1; <-- (b)
global.last_shm_base[prev_slot] = 0; <-- (c)
}
}
break;
}
If global.last_shm_base is not NULL (a) then prev_slot is used as in index to
read (b) and write (c). prev_slot is only checked to
not be UINT32_MAX; it isn't validated to fall within the bounds of
global.last_shm_base.
global.shared_memory_base will only not be NULL if the notifyd service has
restarted - I don't know under what circumstances this would
occur, therefore severity is lower.
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 7:16
IOThunderboltFamilyUserClient::xDomainRequestAction doesn't verify that a
pointer is non-NULL before calling a virtual function, giving trivial kernel
RIP control if the user process maps the NULL page, as this PoC demonstrates.
IOThunderboltFamilyUserClient::xDomainRequestAction is called by
IOThunderboltFamilyUserClient::xDomainRequest which is selector 13 of
IOThunderboltController
Original issue reported on code.google.com by [email protected]
on 22 May 2014 at 8:03
Attachments:
A SWF to reproduce is attached, along with source. To reproduce, host the
additional resource SWF "imglossless1bpp.swf" on the same web server /
directory as Lossless1bppLeak.swf
This bug is a strange one. I think the 1bpp image is reasonably well-formed and
valid: it has a 2-color color table (black and white), and enough image data to
fill the entire 64x64 1bpp canvas. Despite this, a multi-color image is
rendered, which clearly contains some uninitialized data.
Maybe 1bpp image support is broken? I'm not really sure what's going on other
than the definite observation of uninitialized memory content leaking to script.
A screenshot is attached for convenience.
Original issue reported on code.google.com by [email protected]
on 14 Jul 2014 at 7:02
Attachments:
launchd is the OS X equivalent of init. It runs as root and is the parent of
all other processes. On OS X it also serves as the bootstrap server, allowing
any process with a send right to the launchd bootstrap mach port (by default
all processes, including chrome/safari renderers) to request and register
services provided via mach ports.
The MIG methods legacy_ipc_request and swap_complex pass a pointer to an
out-of-line buffer containing arguments in a serialized in a custom format,
which is deserialized by the function launch_data_unpack:
liblaunch.c:
launch_data_t
launch_data_unpack(void *data, size_t data_size, int *fds, size_t fd_cnt,
size_t *data_offset, size_t *fdoffset)
{
launch_data_t r = data + *data_offset;
size_t i, tmpcnt;
if ((data_size - *data_offset) < sizeof(struct _launch_data))
return NULL;
*data_offset += sizeof(struct _launch_data);
...
data points to a user-controlled buffer. launch_data_t is typedef'd as a
pointer to a struct _launch_data:
liblaunch.c:
struct _launch_data {
uint64_t type;
union {
struct {
union {
launch_data_t *_array;
char *string;
void *opaque;
int64_t __junk;
};
union {
uint64_t _array_cnt;
uint64_t string_len;
uint64_t opaque_size;
};
};
int64_t fd;
uint64_t mp;
uint64_t err;
int64_t number;
uint64_t boolean;
double float_num;
};
};
launch_data_unpack unpacks each of the _launch_data structures in the input
buffer (data.) If the type is one of: LAUNCH_DATA_DICTIONARY,
LAUNCH_DATA_ARRAY, LAUNCH_DATA_STRING or LAUNCH_DATA_OPAQUE then the struct
_launch_data is followed by the payload. launch_data_unpack fixes up the
pointer (_array, string or opaque) to point to the payload.
The following code unpack arrays and dictionaries:
liblaunch.c:
...
case LAUNCH_DATA_DICTIONARY:
case LAUNCH_DATA_ARRAY:
tmpcnt = big2wire(r->_array_cnt);
if ((data_size - *data_offset) < (tmpcnt * sizeof(uint64_t))) { <-- (a)
errno = EAGAIN;
return NULL;
}
r->_array = data + *data_offset;
*data_offset += tmpcnt * sizeof(uint64_t);
for (i = 0; i < tmpcnt; i++) {
r->_array[i] = launch_data_unpack(data, data_size, fds, fd_cnt, data_offset, fdoffset); <-- (b)
if (r->_array[i] == NULL) <-- (c)
return NULL;
}
r->_array_cnt = tmpcnt;
break;
...
The first bug is the lack of an integer overflow check on (tmpcnt *
sizeof(uint64_t)). Although this result isn't used for an allocation size, we
can still
leverage it for heap corruption:
By setting tmpcnt to (UINT64_MAX + 1) / 8 we can force the multiplication to
overflow to 0. If we also ensure that (data_size - *data_offset) is 0 (that is,
there is no actual payload data) then r->_array will point to the first byte
after the data buffer. At (b) a NULL pointer will then be written to
r->_array[0],
setting the 8 bytes following the data buffer to 0. By crafting a buffer
containing an array of strings and arrays its possible to control the size of
the data
buffer exactly.
Exploitation would involve finding something interesting to corrupt, the
canonical example would be finding something which was reference counted and
overwriting the reference count.
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 6:51
Calling IOConnectMapMemory with type=0 of userclient 0x100 of IOService
"IntelAccelerator" hits the following exploitable kernel NULL pointer
dereference:
mov rdi, [r12+1D8h] ; rdi := NULL
mov rax, [rdi] ; read vtable pointer from NULL
call qword ptr [rax+20h] ; controlled call
See attached PoC which maps the NULL page and kernel panics calling a virtual
function near 0x4141414141414141.
This userclient is reachable from the chrome GPU process sandbox and the safari
renderer sandbox.
Original issue reported on code.google.com by [email protected]
on 12 Jun 2014 at 4:37
Attachments:
A SWF to reproduce is attached, along with source. To reproduce, host the
additional resource SWF "imglossless8bpp.swf" on the same web server /
directory as Lossless8bppLeak.swf
I'm fairly sure this is a very different bug to the "Lossless1bppLeak.swf" bug.
To manifest this bug, we pull a fun little trick: we terminate the image data
zlib stream early, before emitting any pixel data for the image. This leaves
uninitialized data in the canvas which we can read out to script. The demo SWF
file grabs a pointer value and displays it (64-bit Linux) to illustrate the
point.
A screenshot is attached for convenience.
Since it's very easy to use this vulnerability to read uninitialized memory
content, a 90-day disclosure deadline applies.
Original issue reported on code.google.com by [email protected]
on 14 Jul 2014 at 6:45
Attachments:
A SWF to reproduce is attached, along with source. To reproduce, host the
additional resource SWF "jpgswfalpha.swf" on the same web server / directory as
JPEGLeakAlpha.swf
For JPEG images in Flash, there's an optional zlib-compressed alpha channel
component after the JPEG data. If we supply a zlib stream that terminates
early, uninitialized alpha channel values are used and these can be leaked to
script.
The demo SWF file grabs a pointer value and displays it (64-bit Linux) to
illustrate the point.
A screenshot is attached for convenience.
Since it's very easy to use this vulnerability to read uninitialized memory
content, a 90-day disclosure deadline applies.
Original issue reported on code.google.com by [email protected]
on 14 Jul 2014 at 9:22
Attachments:
IOSharedDataQueue is used by OS X kernel drivers to implement a user/kernel
queue in shared memory.
The memory which is mapped into userspace is represented by the variable-sized
struct IODataQueueMemory:
typedef struct _IODataQueueMemory {
UInt32 queueSize;
volatile UInt32 head;
volatile UInt32 tail;
IODataQueueEntry queue[1];
} IODataQueueMemory;
This is allocated on the kernel heap with IOMallocAligned (the size is rounded
up to the nearest page_size multiple.) This size is stored in the queueSize
field.
Kernel code can call IOSharedDataQueue::getMemoryDescriptor to wrap these pages
in an IOMemoryDescriptor which can then be mapped into the userspace task (via
IOConnectMapMemory.)
When the IOSharedDataQueue is destructed its ::free method passes the queueSize
to kmem_free, which simply removes the corresponding number of pages from the
kernel_map. If userspace increased the value of the queueSize field this will
remove more pages than were allocated - potentially removing other live
allocations from the map.
This could be leveraged for code execution by, for example, forcing these free
pages to be reallocated with controlled data before they are accessed.
[[ Note that due to the nature of this bug this PoC will crash in weird ways -
break at IODataQueue::free to see the bad size]]
Original issue reported on code.google.com by [email protected]
on 18 Jun 2014 at 4:11
Attachments:
I took another look at the mach-o loading code yesterday and noticed that OS X
only raises the vm_map min_addr for 64-bit programs (this can be trivially
avoided, but *should* provide some protection for sandboxed 64-bit
processes...) Chrome is still 32-bit, which means you can literally just
vm_deallocate the --- page at address 0 and vm_allocate a rw- one there :-(
Relevant code from the loader:
**************
mach_loader.c
load_segment(
boolean_t prohibit_pagezero_mapping = FALSE;
...
/* XXX (4596982) this interferes with Rosetta, so limit to 64-bit tasks */
if (scp->cmd == LC_SEGMENT_64) {
prohibit_pagezero_mapping = TRUE;
}
if (prohibit_pagezero_mapping) {
...
ret = vm_map_raise_min_offset(map, seg_size); //only place this is called
*************
This leads to some quite nice bugs actually - I'm reporting four to Apple now
which are all reachable from the chrome GPU process sandbox.
The first two are in the Intel graphics driver again - it's possible to call an
external method before the pointer to the methodDescs has been initialized
(which is done in contextStart.) If you set things up correctly you can craft a
fake IOExternalMethod near NULL which will get passed to
shim_io_connect_method_scalarI_scalarO where you can reach this code:
err = (object->*func)( ARG32(input[0]), ARG32(input[1]), ARG32(input[2]),
ARG32(input[3]), ARG32(input[4]), ARG32(input[5]) );
where func and input are user controlled (they're read from the
IOExternalMethod.) This is pretty cool as you get control of a c++ pointer to
member method :-) I didn't actually know how those were implemented until I
looked at the disassembly of this - if the least significant bit of func is set
then it's treated as an offset into the vtable of object (after subtracting 1.)
If the least significant bit is clear then it's just a regular function
pointer. You could easily get a UaF from this by calling the destructor but
there's almost certainly loads you could do with this.
I've attached two PoCs (both different bugs) which demonstrate both
pointer-to-virtual-member-method and function pointer crashes.
I've also attached two more PoCs which show similar issues in the nVidia
graphics driver (used in the MacBookPro.) These also give trivial instruction
pointer control (they actually crash trying to take a lock, but the address of
that lock is controlled. Right after that the call virtual functions from a
controlled vtable address.)
Original issue reported on code.google.com by [email protected]
on 6 May 2014 at 7:41
Attachments:
[deleted issue]
IOAccel2DContext2::blit is implemented in IOAcceleratorFamily2.kext - the
vulnerable code can be reached from the chrome GPU process sandbox and the
safari renderer sandbox.
The kernel code fails to validate an index passed from userspace which is used
to index an array of pointers to Surfaces.
Provided a suitable structure can be constructed in kernel memory this bug can
be leveraged for code execution since the code will call an attacker controller
function pointer.
See attached blit.c for a crashing poc.
Original issue reported on code.google.com by [email protected]
on 2 May 2014 at 3:01
Attachments:
[deleted issue]
The handler for the WebPageProxy::DidReceiveEvent IPC message fails to check
that the WTF::Deque m_currentlyProcessedWheelEvents is not empty before calling
takeFirst() when processing an event of type WebEvent::Wheel:
void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
...
case WebEvent::Wheel: {
ASSERT(!m_currentlyProcessedWheelEvents.isEmpty());
OwnPtr<Vector<NativeWebWheelEvent>> oldestCoalescedEvent = m_currentlyProcessedWheelEvents.takeFirst();
...
That debug ASSERT should be a runtime CHECK.
takeFirst() simply returns the head of the queue and bumps the pointer (mod the
buffer size,) there is no check that the queue isn't empty.
When this scope is left that OwnPtr will be freed. Exploitability depends on
the contents of the buffer backing the queue, a crash stack is show below where
the faulting address is clearly a poison value, however the memory pointed to
by that poisoned pointer doesn't seem to be reserved, and could probably be
easily heap-sprayed too. Further investigation of the operation of WTF::Deque
would be required to understand if this could be exploited reliably.
Crash stack:
(lldb) attach 9724
Process 9724 stopped
Executable module set to
"/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment".
Architecture set to: x86_64-apple-macosx.
(lldb) c
Process 9724 resuming
Process 9724 stopped
* thread #1: tid = 0x9ef7, 0x0000000101bd1513
WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul,
WTF::CrashOnOverflow>::~Vector(this=0x00000001badbeef5) + 9 at Vector.h:595,
queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1,
address=0x1badbef01)
frame #0: 0x0000000101bd1513 WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector(this=0x00000001badbeef5) + 9 at Vector.h:595
592
593 ~Vector()
594 {
-> 595 if (m_size)
596 shrink(0);
597 }
598
WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul,
WTF::CrashOnOverflow>::~Vector() + 9 at Vector.h:595:
-> 0x101bd1513: cmp DWORD PTR [RBX + 12], 0
0x101bd1517: je 0x101bd1523 ; WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector() + 25 [inlined] WTF::VectorBufferBase<WebKit::NativeWebWheelEvent>::buffer() at Vector.h:373
WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector()
+ 25 [inlined] WTF::VectorBuffer<WebKit::NativeWebWheelEvent,
0ul>::~VectorBuffer() at Vector.h:597
WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector()
+ 25 at Vector.h:597
0x101bd1519: mov RDI, RBX
(lldb) bt
* thread #1: tid = 0x9ef7, 0x0000000101bd1513
WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul,
WTF::CrashOnOverflow>::~Vector(this=0x00000001badbeef5) + 9 at Vector.h:595,
queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1,
address=0x1badbef01)
frame #0: 0x0000000101bd1513 WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector(this=0x00000001badbeef5) + 9 at Vector.h:595
frame #1: 0x0000000101bd14f4 WebKit2`WTF::OwnPtr<WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow> >::~OwnPtr() [inlined] WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector(this=0x00000001badbeef5, p=0x00000001badbeef5) + 8 at Vector.h:594
frame #2: 0x0000000101bd14ec WebKit2`WTF::OwnPtr<WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow> >::~OwnPtr() [inlined] void WTF::deleteOwnedPtr<WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow> >(ptr=0x00000001badbeef5) at OwnPtrCommon.h:37
frame #3: 0x0000000101bd14ec WebKit2`WTF::OwnPtr<WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow> >::~OwnPtr(this=<unavailable>) + 14 at OwnPtr.h:47
frame #4: 0x0000000101bbd198 WebKit2`WebKit::WebPageProxy::didReceiveEvent(unsigned int, bool) [inlined] WTF::OwnPtr<WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow> >::~OwnPtr(this=0x00000001badbeef5) + 776 at OwnPtr.h:47
frame #5: 0x0000000101bbd193 WebKit2`WebKit::WebPageProxy::didReceiveEvent(this=<unavailable>, opaqueType=<unavailable>, handled=<unavailable>) + 771 at WebPageProxy.cpp:3699
frame #6: 0x0000000101bdb648 WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (WebKit::WebPageProxy::*)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) [inlined] void IPC::callMemberFunctionImpl<WebKit::WebPageProxy, void (args=0x0000000100000003, __t=0x0000000100000003, object=<unavailable>)(unsigned int, bool), std::__1::tuple<unsigned int, bool>, 0ul, 1ul>(WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool), std::__1::tuple<unsigned int, bool>&&, std::index_sequence<0ul, 1ul>) + 73 at HandleMessage.h:16
frame #7: 0x0000000101bdb62a WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (WebKit::WebPageProxy::*)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) [inlined] void IPC::callMemberFunction<WebKit::WebPageProxy, void (args=0x0000000100000003, __t=0x0000000100000003)(unsigned int, bool), std::__1::tuple<unsigned int, bool>, std::make_index_sequence<2ul> >(std::__1::tuple<unsigned int, bool>&&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) at HandleMessage.h:22
frame #8: 0x0000000101bdb62a WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (decoder=<unavailable>, object=<unavailable>, function=<unavailable>)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) + 43 at HandleMessage.h:117
frame #9: 0x0000000101bd8c8e WebKit2`WebKit::WebPageProxy::didReceiveMessage(this=0x00007fc8eb80ea18, connection=<unavailable>, decoder=0x0000000107206820) + 916 at WebPageProxyMessageReceiver.cpp:214
frame #10: 0x0000000101abf7d5 WebKit2`IPC::MessageReceiverMap::dispatchMessage(this=<unavailable>, connection=0x000000010720d780, decoder=0x0000000107206820) + 125 at MessageReceiverMap.cpp:87
frame #11: 0x0000000101a6c947 WebKit2`WebKit::ChildProcessProxy::dispatchMessage(this=<unavailable>, connection=<unavailable>, decoder=<unavailable>) + 13 at ChildProcessProxy.cpp:118
frame #12: 0x0000000101c05328 WebKit2`WebKit::WebProcessProxy::didReceiveMessage(this=0x000000010591ea80, connection=0x000000010720d780, decoder=0x0000000107206820) + 24 at WebProcessProxy.cpp:364
frame #13: 0x0000000101a6dafc WebKit2`IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::MessageDecoder, std::__1::default_delete<IPC::MessageDecoder> >) [inlined] IPC::Connection::dispatchMessage(decoder=<unavailable>, this=<unavailable>) + 94 at Connection.cpp:770
frame #14: 0x0000000101a6daef WebKit2`IPC::Connection::dispatchMessage(this=0x000000010720d780, message=0x00007fff5eb85020) + 81 at Connection.cpp:791
frame #15: 0x0000000101a6fb70 WebKit2`IPC::Connection::dispatchOneMessage(this=0x000000010720d780) + 106 at Connection.cpp:817
frame #16: 0x0000000101463a45 JavaScriptCore`WTF::RunLoop::performWork() [inlined] std::__1::function<void (this=0x00007fff5eb850e0)>::operator()() const + 421 at functional:1435
frame #17: 0x0000000101463a3b JavaScriptCore`WTF::RunLoop::performWork(this=0x000000010590df30) + 411 at RunLoop.cpp:104
frame #18: 0x0000000101464122 JavaScriptCore`WTF::RunLoop::performWork(context=<unavailable>) + 34 at RunLoopCF.cpp:38
frame #19: 0x00007fff871bb731 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #20: 0x00007fff871acea2 CoreFoundation`__CFRunLoopDoSources0 + 242
frame #21: 0x00007fff871ac62f CoreFoundation`__CFRunLoopRun + 831
frame #22: 0x00007fff871ac0b5 CoreFoundation`CFRunLoopRunSpecific + 309
frame #23: 0x00007fff90fb1a0d HIToolbox`RunCurrentEventLoopInMode + 226
frame #24: 0x00007fff90fb17b7 HIToolbox`ReceiveNextEventCommon + 479
frame #25: 0x00007fff90fb15bc HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 65
frame #26: 0x00007fff8db9e3de AppKit`_DPSNextEvent + 1434
frame #27: 0x00007fff8db9da2b AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 122
frame #28: 0x00007fff8a241290 Safari`-[BrowserApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 161
frame #29: 0x00007fff8db91b2c AppKit`-[NSApplication run] + 553
frame #30: 0x00007fff8db7c913 AppKit`NSApplicationMain + 940
frame #31: 0x00007fff8a41306d Safari`SafariMain + 267
frame #32: 0x00007fff83a405fd libdyld.dylib`start + 1
frame #33: 0x00007fff83a405fd libdyld.dylib`start + 1
(lldb) register read
General Purpose Registers:
rax = 0x0000000000000000
rbx = 0x00000001badbeef5
rcx = 0x000000000000000f
rdx = 0x0000000000000000
rdi = 0x00000001badbeef5
rsi = 0x0000000000000003
rbp = 0x00007fff5eb84d50
rsp = 0x00007fff5eb84d40
r8 = 0x0000000000000074
r9 = 0x0000000000000000
r10 = 0x00007fc8eb4caf9c
r11 = 0x00007fff73da4db8 (void *)0x00007fff73da4d90: NSAutoreleasePool
r12 = 0x00007fc8eb80ea18
r13 = 0x00007fff5eb85200
r14 = 0x0000000000000001
r15 = 0x00000001badbeef5
rip = 0x0000000101bd1513 WebKit2`WTF::Vector<WebKit::NativeWebWheelEvent, 0ul, WTF::CrashOnOverflow>::~Vector() + 9 at Vector.h:595
rflags = 0x0000000000010206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
This issue can be easily reproduced by applying following patch to the renderer
code then scrolling using the mouse on a page:
Source/WebKit2/WebProcess/WebPage/EventDispatcher.cpp:
ScrollingTree::EventResult result = scrollingTree->tryToHandleWheelEvent(platformWheelEvent);
if (result == ScrollingTree::DidHandleEvent || result == ScrollingTree::DidNotHandleEvent) {
- sendDidReceiveEvent(pageID, wheelEvent, result ==
ScrollingTree::DidHandleEvent);
+ for (int i = 0; i < 128; i++){
+ sendDidReceiveEvent(pageID, wheelEvent, result ==
ScrollingTree::DidHandleEvent);
+ }
return;
}
}
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 5:57
IGAccelVideoContextMain is the userclient used for GPU accelerated video
encoding on the Intel HD integrated GPUs. It's userclient 0x100 of the
IntelAccelerator IOService. IOConnectMapMemory type=0 of this userclient is a
shared token buffer. Token 0x8a is ColorSpaceConversion, implemented in
IGAccelVideoContextMain::process_token_ColorSpaceConversion
The dword at offset 0x14 of this token is used to compute the offset for a
write without checking the bounds, allowing a controlled kernel memory write.
Triggering this is a bit annoying, sorry, haven't had time to make a
self-contained repro for this bug yet:
Compile this dylib:
$ clang -Wall -dynamiclib -o ig_video_main_ColorSpaceConversion.dylib ig_video_main_ColorSpaceConversion.c -framework IOKit -arch i386 -arch x86_64
Load it into Quicktime:
$ DYLD_INSERT_LIBRARIES=./ig_video_main_ColorSpaceConversion.dylib /Applications/QuickTime\ Player.app/Contents/MacOS/QuickTime\ Player
Start a screen recording:
File -> New Screen Recording -> Click the red circle -> start the recording
This interpose library will look for the ColorSpaceConversion token in the
shared memory and trigger the bug.
Impact:
This userclient can be instantiated from the Chrome GPU process sandbox and the
Safari renderer sandbox
Original issue reported on code.google.com by [email protected]
on 13 Jun 2014 at 12:47
Attachments:
MIG ipc routine:
routine
init_session(
j : job_t;
session : name_t;
asport : mach_port_t
);
Implementation:
core.c:
kern_return_t
job_mig_init_session(job_t j, name_t session_type, mach_port_t asport)
{
...
if (j->mgr->session_initialized) {
job_log(j, LOG_ERR, "Tried to initialize an already setup session!");
kr = BOOTSTRAP_NOT_PRIVILEGED; <-- even if the session is already initialized there's no early return here, it will still hit the strcpy
}
...
jobmgr_log(j->mgr, LOG_DEBUG, "Initializing as %s", session_type);
strcpy(j->mgr->name_init, session_type); <-- unchecked strcpy
The buffer which is being copied into is allocated here:
jobmgr_t
jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port,
bool sflag, const char *name, bool skip_init, mach_port_t asport)
{
job_t bootstrapper = NULL;
jobmgr_t jmr;
...
jmr = calloc(1, sizeof(struct jobmgr_s) + (name ? (strlen(name) + 1) : NAME_MAX + 1)); <-- here
jobmgr_t is typedef'd as a pointer to a struct jobmgr_s, which has a zero-sized
array at the end:
struct jobmgr_s {
...
union {
const char name[0];
char name_init[0];
};
};
It's possible to force the allocation of a new jobmgr_t with a partially
controlled name field using the subset MIG ipc - this will allocate only enough
space for this string in the jobmgr_s struct:
kern_return_t
job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp)
{
int bsdepth = 0;
jobmgr_t jmr;
...
jmr = j->mgr;
...
char name[NAME_MAX];
snprintf(name, sizeof(name), "%s[%i].subset.%i", j->anonymous ? j->prog : j->label, j->p, MACH_PORT_INDEX(requestorport));
if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, name, true, j->asport)) != NULL)) {
This subset bootstrap port can then be passed to init_session with a longer
session_type.
Original issue reported on code.google.com by [email protected]
on 4 Apr 2014 at 2:45
[deleted issue]
The Intel OpenCL IOKit userclient has pretty much exactly the same bug as the
OpenGL one - they trust a user-supplied pointer and call a virtual function off
of it.
Specifically the function IGAccelCLContext::unmap_user_memory is reachable as
selector 0x101.
Attached poc hello.c (uses the apple OpenCL hello world example to initialize
OpenCL and get the correct userclient) will kernel panic dereferencing
0x4141414141414141. Compile with -framework OpenCL -framework IOKit
This should be reachable from the chrome gpu process sandbox and the safari
renderer sandbox.
Original issue reported on code.google.com by [email protected]
on 2 May 2014 at 11:00
Attachments:
[deleted issue]
The log_forward MIG method is used for per-user launchd processes to send log
messages to the root launchd (pid 1 running as root.)
job.defs:
routine
log_forward(
j : job_t;
inval : pointer_t
);
The log_forward implementation checks that the sender was a per-user launchd
process before calling launchd_log_forward:
core.c:
kern_return_t
job_mig_log_forward(job_t j, vm_offset_t inval, mach_msg_type_number_t invalCnt)
{
...
if (!job_assumes(j, j->per_user)) {
return BOOTSTRAP_NOT_PRIVILEGED;
}
return launchd_log_forward(ldc->euid, ldc->egid, inval, invalCnt);
...
This means that this vulnerability can only be exploited by a per-user launchd
process. This still represents a privilege escalation
however as per-user launchds processes run with the user's uid. Exploitation is
therefore predicated on being able to inject code into a process
running as the same user.
The vulnerability is in the code which deserializes the array of log messages
(inval points to the controlled buffer:)
log.c:
launchd_log_forward(uid_t forward_uid, gid_t forward_gid, vm_offset_t inval,
mach_msg_type_number_t invalCnt)
{
struct logmsg_s *lm, *lm_walk;
mach_msg_type_number_t data_left = invalCnt;
...
for (lm_walk = (struct logmsg_s *)inval; (data_left > 0) && (lm_walk->obj_sz <= data_left); lm_walk = ((void *)lm_walk + lm_walk->obj_sz)) {
...
if (lm_walk->obj_sz == 0) {
...
break;
}
if (!(lm = malloc(lm_walk->obj_sz))) { <-- (a)
...
break;
}
memcpy(lm, lm_walk, lm_walk->obj_sz);
lm->sender_uid = forward_uid; <-- (b)
lm->sender_gid = forward_gid;
lm->from_name += (size_t)lm;
lm->about_name += (size_t)lm;
lm->msg += (size_t)lm;
lm->session_name += (size_t)lm;
...
There is no check on the obj_sz field at (a). By passing in a crafted data
structure with an obj_sz field smaller than sizeof(struct logmsg_s)
lm will point to an undersized allocation and the assignments at (b) will point
outside the allocated memory.
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 7:05
IOBluetoothFamily implements its own queuing primitive: IOBluetoothDataQueue
(doesn't appear to inherit from IODataQueue, but I could be wrong about that?)
IOBluetoothHCIPacketLogUserClient is userclient type 1 of
IOBluetoothHCIController.
The IOBluetoothDataQueue free method uses the queue size field which was mapped
into userspace
when freeing the queue - a userspace client can modify this field forcing a bad
kmem_free.
Original issue reported on code.google.com by [email protected]
on 23 Jun 2014 at 11:21
Attachments:
[deleted issue]
The Intel GPU driver uses shared memory for drawing commands. The userspace
client of the driver calls IOConnectMapMemory to map a shared page which it
will use,
calling selector 2 (submit_data_buffers) to signal to the driver that it should
consume the commands (tokens) written there by the client.
The function IGAccelGLContext::processSidebandToken checks the token ID and
length then
jumps to the function responsible for actually parsing the token:
; IGAccelGLContext::processSidebandToken(IOAccelCommandStreamInfo &)
push rbp
mov rbp, rsp
mov ax, [rsi+18h] ; this is the token id
test ax, ax
jns short not_us ; jump if not sign - token must be >= 0x8000
cmp ax, 0A1FFh
ja short err ; jump if token > 0xa1ff
movzx ecx, ax ; otherwise, take the upper 8 bits, subtract 0x80 and use as an index into s_cTokenInfo array of token function descriptors
mov r8, [rdi+1090h] ;
shr ecx, 8
add ecx, 0FFFFFF80h
lea rcx, [rcx+rcx*2]
mov edx, [r8+rcx*8+10h] ; get pointer to descriptor
s_cTokenInfo points to an array of 0x21 descriptors (each 0x18 bytes) therefore
the maximum index allowed should be 0x20. Supplying a token
with a token id field of 0xa100 will read a descriptor off the end of the
s_cTokenInfo array. The bytes following the array happen to be zero, which means
that the code will reach a jmp rax where rax is zero.
Exploitability depends on two things: being able to map the zero page and being
able to execute it - mapping the zero page is possible (see previous
bug reports) but SMEP (Supervisor Mode Execution Prevention) will stop
exploitation of this for Ivy Bridge and newer cpus. On Sandy Bridge and older
hardware
this bug will be exploitable (eg MacBookPro <= 8,3 which so far as I understand
is still completely supported hardware.)
Original issue reported on code.google.com by [email protected]
on 10 Jun 2014 at 12:06
Attachments:
sgdt is an unprivileged instructions, a process at any privilege level
can execute sgdt to get the address of that processor's Global Descriptor Table
pmap.h has the following comment:
/*
* For KASLR, we alias the master processor's IDT and GDT at fixed
* virtual addresses to defeat SIDT/SGDT address leakage.
*/
#define MASTER_IDT_ALIAS (VM_MIN_KERNEL_ADDRESS + 0x0000)
#define MASTER_GDT_ALIAS (VM_MIN_KERNEL_ADDRESS + 0x1000)
executing sgdt on cpu0 returns this fixed address
the GDTs for all other cpus are allocated very early on the heap
in cpu_data_alloc()
Since the heap begins at a fixed offset from the kernel text segment
vm_init.c contains the following comment:
/*
* Eat a random amount of kernel_map to fuzz subsequent heap, zone and
* stack addresses. (With a 4K page and 9 bits of randomness, this
* eats at most 2M of VA from the map.)
*/
The code then uses early_random() to make a randomly-sized allocation.
However, since the granularity of kASLR is 2MB, if a suitably early
kernel heap allocation address can be leaked then it's enough to guess
the kASLR offset reasonably reliably (in testing at least 50% correct)
The GDT allocations are suitably early and the state of the heap sufficiently
deterministic that by executing sgdt on all cpus and filtering out the result
for cpu0 you can with at least 50% reliability determine the kASLR slide
PoC attached.
Original issue reported on code.google.com by [email protected]
on 21 May 2014 at 6:27
Attachments:
[deleted issue]
The AGPM (AppleGraphicsPowerManagement) user client is reachable from the
chrome gpu process sandbox and the safari renderer sandbox.
The getPStatesOccupancy method fails to bounds check the index it's passed. The
oob value which is read is then returned to the userspace caller allowing a
sandboxed program to programmatically dump large amounts of kernel memory.
Attached PoC leak_kmem.c will try to dump 256 kB of kernel memory to the file
dump.bin.
This is of course a nice kASLR defeat since you can almost certainly find all
the pointers you need.
On OS X another interesting attack scenario with a bug like this would be to
try to read a sandbox extension - since these are just HMAC'ed strings if you
could force another process to request an extension and then read it from
kernel memory you could just consume it since extensions aren't tied to a
particular process. I don't know how feasible it would be to read the HMAC key,
if I have time I'll experiment a bit with this. (Chrome doesn't use sandbox
extensions, safari does.)
Original issue reported on code.google.com by [email protected]
on 2 May 2014 at 3:23
Attachments:
A SWF to reproduce is attached, along with source.
This is probably due to an integer overflow.
Note that this bug is almost certainly 64-bit only. The PoC relies on creating
a ByteArray that is almost 4GB in size, and obviously such an allocation is
never going to succeed in a 32-bit address space.
Also, the bug does not work in Chrome 64-bit Linux, because Chrome 64-bit Linux
has a defense that limits total allocations to 4GB.
In order to repro, use 64-bit Flash in 64-bit IE, or run Chrome 64-bit Linux
with the --no-sandbox flag (which disabled the 4GB limit).
Original issue reported on code.google.com by [email protected]
on 14 Jul 2014 at 7:36
Attachments:
[deleted issue]
[deleted issue]
The handler for the WebPageProxy::DidReceiveEvent IPC message fails to check
that the WTF::Deque m_keyEventQueue is not empty before calling first() when
processing an event of type WebEvent::Char:
void WebPageProxy::didReceiveEvent(uint32_t opaqueType, bool handled)
...
case WebEvent::Char: {
LOG(KeyHandling, "WebPageProxy::didReceiveEvent: %s", webKeyboardEventTypeString(type));
NativeWebKeyboardEvent event = m_keyEventQueue.first();
MESSAGE_CHECK(type == event.type());
m_keyEventQueue.removeFirst();
if (!m_keyEventQueue.isEmpty())
m_process->send(Messages::WebPage::KeyEvent(m_keyEventQueue.first()), m_pageID);
m_pageClient.doneWithKeyEvent(event, handled);
if (handled)
break;
if (m_uiClient->implementsDidNotHandleKeyEvent())
m_uiClient->didNotHandleKeyEvent(this, event);
break;
...
Not sure about the security implications. I haven't looked into this one very
much, but the NativeWebKeyboardEvent does contain some interesting pointers on
some platforms.
Repro patch, apply and scroll a webpage:
Source/WebKit2/WebProcess/WebPage/EventDispatcher.cpp:
ScrollingTree::EventResult result = scrollingTree->tryToHandleWheelEvent(platformWheelEvent);
if (result == ScrollingTree::DidHandleEvent || result == ScrollingTree::DidNotHandleEvent) {
- sendDidReceiveEvent(pageID, wheelEvent, result ==
ScrollingTree::DidHandleEvent);
+ for (int i = 0; i < 128; i++){
+
WebProcess::shared().parentProcessConnection()->send(Messages::WebPageProxy::Did
ReceiveEvent(static_cast<uint32_t>(7), false), pageID);
+ }
return;
}
}
Crash Stack (null pointer dereference)
(lldb) bt
* thread #1: tid = 0x15bcc, 0x000000010b0b4f00
WebKit2`WebKit::WebPageProxy::didReceiveEvent(unsigned int, bool) [inlined]
WebKit::WebKeyboardEvent::WebKeyboardEvent(WebKit::WebKeyboardEvent const&) at
WebEvent.h:214, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS
(code=1, address=0x0)
frame #0: 0x000000010b0b4f00 WebKit2`WebKit::WebPageProxy::didReceiveEvent(unsigned int, bool) [inlined] WebKit::WebKeyboardEvent::WebKeyboardEvent(WebKit::WebKeyboardEvent const&) at WebEvent.h:214
frame #1: 0x000000010b0b4f00 WebKit2`WebKit::WebPageProxy::didReceiveEvent(unsigned int, bool) [inlined] WebKit::NativeWebKeyboardEvent::NativeWebKeyboardEvent(this=0x00007fff55691e3c) at NativeWebKeyboardEvent.h:60
frame #2: 0x000000010b0b4f00 WebKit2`WebKit::WebPageProxy::didReceiveEvent(unsigned int, bool) [inlined] WTF::Deque<WebKit::NativeWebKeyboardEvent>::first(this=0x00007fff55691e3c) + 20 at NativeWebKeyboardEvent.h:60
frame #3: 0x000000010b0b4eec WebKit2`WebKit::WebPageProxy::didReceiveEvent(this=0x00007fa3a1824818, opaqueType=7, handled=false) + 92 at WebPageProxy.cpp:3707
frame #4: 0x000000010b0d3648 WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (WebKit::WebPageProxy::*)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) [inlined] void IPC::callMemberFunctionImpl<WebKit::WebPageProxy, void (args=0x0000000000000007, __t=0x0000000000000007, object=<unavailable>)(unsigned int, bool), std::__1::tuple<unsigned int, bool>, 0ul, 1ul>(WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool), std::__1::tuple<unsigned int, bool>&&, std::index_sequence<0ul, 1ul>) + 73 at HandleMessage.h:16
frame #5: 0x000000010b0d362a WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (WebKit::WebPageProxy::*)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) [inlined] void IPC::callMemberFunction<WebKit::WebPageProxy, void (args=0x0000000000000007, __t=0x0000000000000007)(unsigned int, bool), std::__1::tuple<unsigned int, bool>, std::make_index_sequence<2ul> >(std::__1::tuple<unsigned int, bool>&&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) at HandleMessage.h:22
frame #6: 0x000000010b0d362a WebKit2`void IPC::handleMessage<Messages::WebPageProxy::DidReceiveEvent, WebKit::WebPageProxy, void (decoder=<unavailable>, object=<unavailable>, function=<unavailable>)(unsigned int, bool)>(IPC::MessageDecoder&, WebKit::WebPageProxy*, void (WebKit::WebPageProxy::*)(unsigned int, bool)) + 43 at HandleMessage.h:117
frame #7: 0x000000010b0d0c8e WebKit2`WebKit::WebPageProxy::didReceiveMessage(this=0x00007fa3a1824818, connection=<unavailable>, decoder=0x00000001139b4958) + 916 at WebPageProxyMessageReceiver.cpp:214
frame #8: 0x000000010afb77d5 WebKit2`IPC::MessageReceiverMap::dispatchMessage(this=<unavailable>, connection=0x0000000110707780, decoder=0x00000001139b4958) + 125 at MessageReceiverMap.cpp:87
frame #9: 0x000000010af64947 WebKit2`WebKit::ChildProcessProxy::dispatchMessage(this=<unavailable>, connection=<unavailable>, decoder=<unavailable>) + 13 at ChildProcessProxy.cpp:118
frame #10: 0x000000010b0fd328 WebKit2`WebKit::WebProcessProxy::didReceiveMessage(this=0x000000010ee18a80, connection=0x0000000110707780, decoder=0x00000001139b4958) + 24 at WebProcessProxy.cpp:364
frame #11: 0x000000010af65afc WebKit2`IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::MessageDecoder, std::__1::default_delete<IPC::MessageDecoder> >) [inlined] IPC::Connection::dispatchMessage(decoder=<unavailable>, this=<unavailable>) + 94 at Connection.cpp:770
frame #12: 0x000000010af65aef WebKit2`IPC::Connection::dispatchMessage(this=0x0000000110707780, message=0x00007fff55692020) + 81 at Connection.cpp:791
frame #13: 0x000000010af67b70 WebKit2`IPC::Connection::dispatchOneMessage(this=0x0000000110707780) + 106 at Connection.cpp:817
frame #14: 0x000000010a95aa45 JavaScriptCore`WTF::RunLoop::performWork() [inlined] std::__1::function<void (this=0x00007fff556920e0)>::operator()() const + 421 at functional:1435
frame #15: 0x000000010a95aa3b JavaScriptCore`WTF::RunLoop::performWork(this=0x000000010ee07f30) + 411 at RunLoop.cpp:104
frame #16: 0x000000010a95b122 JavaScriptCore`WTF::RunLoop::performWork(context=<unavailable>) + 34 at RunLoopCF.cpp:38
frame #17: 0x00007fff871bb731 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #18: 0x00007fff871acea2 CoreFoundation`__CFRunLoopDoSources0 + 242
frame #19: 0x00007fff871ac62f CoreFoundation`__CFRunLoopRun + 831
frame #20: 0x00007fff871ac0b5 CoreFoundation`CFRunLoopRunSpecific + 309
frame #21: 0x00007fff90fb1a0d HIToolbox`RunCurrentEventLoopInMode + 226
frame #22: 0x00007fff90fb17b7 HIToolbox`ReceiveNextEventCommon + 479
frame #23: 0x00007fff90fb15bc HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 65
frame #24: 0x00007fff8db9e3de AppKit`_DPSNextEvent + 1434
frame #25: 0x00007fff8db9da2b AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 122
frame #26: 0x00007fff8a241290 Safari`-[BrowserApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 161
frame #27: 0x00007fff8db91b2c AppKit`-[NSApplication run] + 553
frame #28: 0x00007fff8db7c913 AppKit`NSApplicationMain + 940
frame #29: 0x00007fff8a41306d Safari`SafariMain + 267
frame #30: 0x00007fff83a405fd libdyld.dylib`start + 1
frame #31: 0x00007fff83a405fd libdyld.dylib`start + 1
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 6:13
[deleted issue]
(see issue 12 for background on launchd and launch_data_unpack)
liblaunch.c:
launch_data_unpack:
...
case LAUNCH_DATA_STRING:
tmpcnt = big2wire(r->string_len);
if ((data_size - *data_offset) < (tmpcnt + 1)) {
errno = EAGAIN;
return NULL;
}
r->string = data + *data_offset;
r->string_len = tmpcnt;
*data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt + 1);
break;
case LAUNCH_DATA_OPAQUE:
tmpcnt = big2wire(r->opaque_size);
if ((data_size - *data_offset) < tmpcnt) {
errno = EAGAIN;
return NULL;
}
r->opaque = data + *data_offset;
r->opaque_size = tmpcnt;
*data_offset += ROUND_TO_64BIT_WORD_SIZE(tmpcnt);
break;
...
Both these cases check that there is enough space remaining in the buffer for
the given payload size. However, they both then round up the given size to the
nearest 8 bytes.
This rounding can cause data_offset to become larger than data_size if the
data_size was not a multiple of 8 bytes. This causes (data_size - *data_offset)
to underflow, meaning
that the code will continue to read and deserialize _launch_data structures off
the end of the data buffer.
Original issue reported on code.google.com by [email protected]
on 3 Apr 2014 at 6:58
The clientMemoryForType method of AppleUSBMultitouchUserClient creates an
AppleMultitouchIODataQueue
and maps it into kernel/user shared memory. AppleMultitouchIODataQueue inherits
from IODataQueue.
The memory which is mapped into userspace is represented by the variable-sized
struct IODataQueueMemory:
typedef struct _IODataQueueMemory {
UInt32 queueSize;
volatile UInt32 head;
volatile UInt32 tail;
IODataQueueEntry queue[1];
} IODataQueueMemory;
These queueSize, head and tail values are used to ensure that the enqueued
items stay within the bounds of the queue. Userspace can modify the queueSize,
head and tail values such that the kernel will try to enqueue a value to the
queue outside of the allocated memory.
Original issue reported on code.google.com by [email protected]
on 20 Jun 2014 at 3:58
Attachments:
This one requires local root so isn't maybe so interesting on OS X since root
is still equivalent to kernel code execution anyway. It's a different story on
iOS, but I don't have any iOS devices to test on
(http://theiphonewiki.com/wiki/Kernel has the output of kextstat on iOS 6 and
the same driver (IOUSBFamily) is listed.) I'll let Apple figure out if this is
something to worry about on iOS.
The IOUSBController userclient external method 8 is
IOUSBControllerUserClient::ReadRegister. This method fails to bounds check its
first argument which is used directly as an offset into kernel memory:
mov ecx, r15d <-- r15d controlled
mov eax, [rax+rcx]
mov [r14], eax <-- will get returned to userspace
Severity Low because of the root requirement.
Original issue reported on code.google.com by [email protected]
on 12 May 2014 at 11:50
Attachments:
IGAccelGLContext::getTargetAndMethodForIndex doesn't correctly bounds check the
selector:
__text:000000000000873A ;
IGAccelGLContext::getTargetAndMethodForIndex(IOService **, unsigned int)
__text:000000000000873A push rbp
__text:000000000000873B mov rbp, rsp
__text:000000000000873E mov [rsi], rdi
__text:0000000000008741 lea eax, [rdx-106h] ; rdx is
controlled selector index
__text:0000000000008747 cmp eax, 100h
__text:000000000000874C jnb short loc_8765 ; pass
selectors 0..0x105 && 0x207+ to superclass
__text:000000000000874E add edx, 0FFFFFE00h ; selectors
in the range 0x106..0x206 pass the test,
; this then subtracts 0x200, underflowing for any selector
; in the range 0x106..0x1ff
__text:0000000000008754 lea rax, [rdx+rdx*2]
__text:0000000000008758 shl rax, 4
__text:000000000000875C add rax, [rdi+1098h] ;
underflowed value used as index into array of IOExternalMethods
__text:0000000000008763 pop rbp
__text:0000000000008764 retn
the correct range of selectors is 0x200..0x206, however the selector is only
checked to be in the range 0x106..0x206 :-)
Exploitation of this issue depends on the value stored at this+0x1098 (which
should point to the static methodDescs array.) I've already reported another bug
where this field can be NULL. Combined with this new bug you get a nice way to
exploit that NULL pointer deref without having to be able to map the NULL page:
Since the index will underflow a 32-bit value then be extended to 64-bits and
multiplied by 48 you end up with a pointer something like this:
0x0000002fffffdf18
which is a mappable address for a 64-bit process, even one which is sandboxed
and has a PAGEZERO segment since it's above 4GB. By crafting a valid
IOExternalMethod
struct there you can trivially get kernel RIP control.
Once the NULL methodDescs bug is fixed, exploitability of this bug will depend
on what data happens to be in the oob region and whether it could be interpreted
as a valid IOExternalMethod structure. (I haven't tried to exploit this
approach yet, but the methodDescs array is near various vtables and other
IOExternalMethod arrays so I wouldn't rule it out.)
The OpenCL userclient (different code path) suffers from a similar bad bounds
checking issue, I've attached a PoC for that too.
Original issue reported on code.google.com by [email protected]
on 20 May 2014 at 4:23
Attachments:
wheee
Original issue reported on code.google.com by [email protected]
on 27 Feb 2014 at 12:04
The description we have so far is in the form of some raw notes that describe
the logic error:
Goal: get UniversalFileReadSandboxExtension
This IPC will get it for us, providing we pass a few checks:
WebPageProxy::
BackForwardGoToItem(uint64_t itemID) -> (WebKit::SandboxExtension::Handle sandboxExtensionHandle)
void WebPageProxy::backForwardGoToItem(uint64_t itemID,
SandboxExtension::Handle& sandboxExtensionHandle)
{
WebBackForwardListItem* item = m_process->webBackForwardItem(itemID); <-- //check 1: we need to get a file url as an itemID
if (!item)
return;
bool createdExtension = maybeInitializeSandboxExtensionHandle(URL(URL(), item->url()), sandboxExtensionHandle); <-- //*** [1]
if (createdExtension)
m_process->willAcquireUniversalFileReadSandboxExtension();
m_backForwardList->goToItem(item);
}
//[1]
//getting the sandbox extension looks easy; just call this function with the
file:// URL (and if inspector is enabled, get isInspectorPage(this) to return
false
bool WebPageProxy::maybeInitializeSandboxExtensionHandle(const URL& url,
SandboxExtension::Handle& sandboxExtensionHandle)
{
if (!url.isLocalFile())
return false;
#if ENABLE(INSPECTOR)
// Don't give the inspector full access to the file system.
if (WebInspectorProxy::isInspectorPage(this)) <-- //this will be important later
return false;
#endif
SandboxExtension::createHandle("/", SandboxExtension::ReadOnly, sandboxExtensionHandle); <-- //very importantly, this is actually going to grant us read access to the WHOLE FS, not just
//the file:// url we are trying to get an extension for :-)
return true;
}
//So check 1: get a file:// url as a webBackForwardItem:
WebBackForwardListItem* WebProcessProxy::webBackForwardItem(uint64_t itemID)
const
{
return m_backForwardListItemMap.get(itemID);
}
//just get it inserted in that map;
// easy with this IPC
WebProcessProxy::
AddBackForwardItem(uint64_t itemID, WTF::String originalURL, WTF::String url, WTF::String title, CoreIPC::DataReference backForwardData)
void WebProcessProxy::addBackForwardItem(uint64_t itemID, const String&
originalURL, const String& url, const String& title, const
CoreIPC::DataReference& backForwardData)
{
MESSAGE_CHECK_URL(originalURL); <-- //EXCEPT, what are these checks
MESSAGE_CHECK_URL(url); <-- //.... ? [2]
WebBackForwardListItemMap::AddResult result = m_backForwardListItemMap.add(itemID, nullptr);
if (result.isNewEntry) {
result.iterator->value = WebBackForwardListItem::create(originalURL, url, title, backForwardData.data(), backForwardData.size(), itemID);
return;
}
// Update existing item.
result.iterator->value->setOriginalURL(originalURL);
result.iterator->value->setURL(url);
result.iterator->value->setTitle(title);
result.iterator->value->setBackForwardData(backForwardData.data(), backForwardData.size());
}
[2]
#define MESSAGE_CHECK_URL(url)
MESSAGE_CHECK_BASE(m_process->checkURLReceivedFromWebProcess(url),
m_process->connection())
//it's a runtime check
#define MESSAGE_CHECK_BASE(assertion, connection) do \
if (!(assertion)) { \
ASSERT(assertion); \
(connection)->markCurrentlyDispatchedMessageAsInvalid(); \
return; \
} \
while (0)
//what is it actually checking? we have to be able to return true from this
function to proceed:
bool WebProcessProxy::checkURLReceivedFromWebProcess(const URL& url)
{
// FIXME: Consider checking that the URL is valid. Currently, WebProcess sends invalid URLs in many cases, but it probably doesn't have good reasons to do that.
// Any other non-file URL is OK.
if (!url.isLocalFile()) <-- //it's okay if this isn't a file:// url, well, we want a file:// url so this doesn help
return true;
// Any file URL is also OK if we've loaded a file URL through API before, granting universal read access.
if (m_mayHaveUniversalFileReadSandboxExtension) <-- //we don't have this yet so this is no help...
return true;
// If we loaded a string with a file base URL before, loading resources from that subdirectory is fine.
// There are no ".." components, because all URLs received from WebProcess are parsed with URL, which removes those.
String path = url.fileSystemPath();
for (HashSet<String>::const_iterator iter = m_localPathsWithAssumedReadAccess.begin(); iter != m_localPathsWithAssumedReadAccess.end(); ++iter) {
if (path.startsWith(*iter))
return true; //THIS looks more promising :-) if our file://XXX url is in a subdirectory in m_localPathsWithAssumedReadAccess then we can load it
// this doesn't sound so exciting, except that remember if we can get ANY file:// url through here then we're actually going to get
// a sandbox extension for '/'.... so how can we get something in this list?
}
...
return false;
}
ENTER INSPECTOR
//that m_localPathsWithAssumedReadAccess is only modified here:
void WebProcessProxy::assumeReadAccessToBaseURL(const String& urlString)
{
URL url(URL(), urlString);
if (!url.isLocalFile())
return;
// There's a chance that urlString does not point to a directory.
// Get url's base URL to add to m_localPathsWithAssumedReadAccess.
URL baseURL(URL(), url.baseAsString());
// Client loads an alternate string. This doesn't grant universal file read, but the web process is assumed
// to have read access to this directory already.
m_localPathsWithAssumedReadAccess.add(baseURL.fileSystemPath());
}
//which in turn is only called here in code reachable via IPC:
IPC::
CreateInspectorPage() -> (uint64_t inspectorPageID, WebKit::WebPageCreationParameters inspectorPageParameters)
void WebInspectorProxy::createInspectorPage(uint64_t& inspectorPageID,
WebPageCreationParameters& inspectorPageParameters)
{
... //a lot of important stuff happens here; come back to that later
m_page->process()->assumeReadAccessToBaseURL(inspectorBaseURL()); //importantly, this is getting set for the process, not for the page
//the path which is getting passed to assumeReadAccessToBaseURL
//it doesn't really matter what that path is, so long as it's actually a
file:// url (and fixed or guessable)
String WebInspectorProxy::inspectorBaseURL() const
{
// Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
WebInspectorUILibrary();
NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] resourcePath];
ASSERT([path length]);
return [[NSURL fileURLWithPath:path] absoluteString];
}
//need to debug to check but it's almost certainly under here:
/System/Library/PrivateFrameworks/WebInspectorUI.framework/Resources
//see Main.html, Main.js etc
//need to check exactly how we route messages to a particular
WebPageProxy/WebProcessProxy
//at this point only really have to make sure we pass the
//WebInspectorProxy::isInspectorPage(this) check
//note that this here corrisponds to a WebPageProxy - not a WebProcessProxy:
bool WebPageProxy::maybeInitializeSandboxExtensionHandle(const URL& url,
SandboxExtension::Handle& sandboxExtensionHandle)
{
if (!url.isLocalFile())
return false;
#if ENABLE(INSPECTOR)
// Don't give the inspector full access to the file system.
if (WebInspectorProxy::isInspectorPage(this)) //we have created an Inspector page through createInspectorPage, so we'll have to be careful getting around this
return false;
bool WebInspectorProxy::isInspectorPage(WebPageProxy* page)
{
return WebInspectorPageGroups::shared().isInspectorPageGroup(page->pageGroup());
}
//this WebInspectorPageGroups thing seems to be there to allow nesting of
inspectors (since the inspector itself is just another Page)
in WebInspectorProxy.cpp
class WebInspectorPageGroups {
public:
static WebInspectorPageGroups& shared()
{
//so this is just a singleton
DEFINE_STATIC_LOCAL(WebInspectorPageGroups, instance, ());
return instance;
}
//will be called with page->pageGroup() as group when called by isInspectorPage
bool isInspectorPageGroup(WebPageGroup* group)
{
return m_pageGroupLevel.contains(group);
}
typedef HashMap<WebPageGroup*, unsigned> PageGroupLevelMap;
PageGroupLevelMap m_pageGroupLevel;
//so a page is considered an inspector if it's pageGroup pointer is a key in
m_pageGroupLevels
//this is the only place keys are added to m_pageGroupLevel
WebPageGroup* inspectorPageGroupForLevel(unsigned level)
{
// The level is the key of the HashMap, so it cannot be 0.
ASSERT(level);
auto iterator = m_pageGroupByLevel.find(level);
if (iterator != m_pageGroupByLevel.end())
return iterator->value.get();
RefPtr<WebPageGroup> group = createInspectorPageGroup(level); //note that it's always adding a new PageGroup
m_pageGroupByLevel.set(level, group.get());
m_pageGroupLevel.set(group.get(), level);
return group.get();
}
WebPageProxy::WebPageProxy(PageClient* pageClient, PassRefPtr<WebProcessProxy>
process, WebPageGroup* pageGroup, uint64_t pageID){
...
#if ENABLE(INSPECTOR)
m_inspector = WebInspectorProxy::create(this);
#endif
...
}
//the constructor of the WebInspectorProxy (invoked from the WebPageProxy
constructor)
WebInspectorProxy::WebInspectorProxy(WebPageProxy* page){
...
//does this add m_page->pageGroup to the m_pageGroupLevel map?
m_level = WebInspectorPageGroups::shared().inspectorLevel(m_page->pageGroup()); // <-- m_level will be 1
//adds itself as a messageReceived for this process+pageID combo
m_page->process()->addMessageReceiver(Messages::WebInspectorProxy::messageReceiverName(), m_page->pageID(), this);
}
unsigned inspectorLevel(WebPageGroup* inspectedPageGroup)
{
//the first time around m_pageGroupLevel will be empty so isInspectorPageGroup must be empty -> this returns 1 (and nothing gets added to m_pageGroupLevel)
return isInspectorPageGroup(inspectedPageGroup) ? inspectorPageGroupLevel(inspectedPageGroup) + 1 : 1;
}
//going back to createInspectorPage we can see when that map is maipulated to
add entries:
void WebInspectorProxy::createInspectorPage(uint64_t& inspectorPageID,
WebPageCreationParameters& inspectorPageParameters)
{
inspectorPageID = 0;
if (!m_page)
return;
m_isAttached = shouldOpenAttached();
m_attachmentSide = static_cast<AttachmentSide>(inspectorPageGroup() <--
...
WebPageGroup* WebInspectorProxy::inspectorPageGroup() const
{
return WebInspectorPageGroups::shared().inspectorPageGroupForLevel(m_level);
}
//this will call createInspectorPageGroup
static PassRefPtr<WebPageGroup> createInspectorPageGroup(unsigned level)
{
RefPtr<WebPageGroup> pageGroup = WebPageGroup::create(String::format("__WebInspectorPageGroupLevel%u__", level), false, false);
...
}
//which creates a new WebPageGroup corrisponding to the correct WebInspector
level
//so what was the point of all that:
class WebProcessProxy {
...
typedef HashMap<uint64_t, WebPageProxy*> WebPageProxyMap;
WebPageProxyMap m_pageMap; <-- // WebProcessProxies have multiple WebPageProxies - identified by pageIDs (uint64_t's)
HashSet<String> m_localPathsWithAssumedReadAccess; <-- //this is also defined on a per process basis
WebBackForwardListItemMap m_backForwardListItemMap; // each WebProcessProxy has a single backforwardlistitemmap
...
}
//the crux of the issue is that only the inspector pageID is prevented from
getting '/' read access, but the inspected page isn't
Original issue reported on code.google.com by [email protected]
on 11 Mar 2014 at 6:35
The function IOHIKeyboardMapper::parseKeyMapping(const UInt8 * map, UInt32
mappingLen, NXParsedKeyMapping * parsedMapping)
can be reached by a regular user by setting the HIDKeyMapping property of
IOHIDKeyboard in the IOKit registry.
This function is responsible for parsing a binary keymapping file.
The function NextNum reads either 1 or two bytes from map depending on whether
the first two bytes in the file are non-zero.
IOHIKeyboardMapper::parseKeyMapping(const UInt8 * map, UInt32 mappingLen, NXParsedKeyMapping * parsedMapping)
...
parsedMapping->numSeqs = NextNum(&nmd); <-- (a)
if (parsedMapping->numSeqs <= maxSeqNum) <-- (b)
return false;
for(i = 0; i < parsedMapping->numSeqs; i++)
{
parsedMapping->seqDefs[i] = (unsigned char *)nmd.bp; <-- (c)
for(j=0, l=NextNum(&nmd); j<l; j++)
{
NextNum(&nmd);
NextNum(&nmd);
}
}
At (a) this function reads the number of sequence definitions follow, at (b)
this number is checked against a lower bound however
there is no upper-bound check. By specifying a large value for numSeqs (can be
up to 0xffff) we can overflow the fixed-size seqDefs buffer at (c).
seqDefs is defined here:
typedef struct _NXParsedKeyMapping_ {
short shorts;
char keyBits[NX_NUMKEYCODES];
int maxMod;
unsigned char *modDefs[NX_NUMMODIFIERS];
int numDefs;
unsigned char *keyDefs[NX_NUMKEYCODES];
int numSeqs;
unsigned char *seqDefs[NX_NUMSEQUENCES]; <-- seqDefs ( #define NX_NUMSEQUENCES 128 )
int numSpecialKeys;
unsigned short specialKeys[NX_NUMSPECIALKEYS];
const unsigned char *mapping;
int mappingLen;
} NXParsedKeyMapping;
It's actually very easy to get code execution with this buffer overflow:
This NXParsedKeyMapping is a member var of IOHIKeyboardMapper:
class IOHIKeyboardMapper : public OSObject
{
private:
IOHIKeyboard * _delegate;
bool _mappingShouldBeFreed;
NXParsedKeyMapping _parsedMapping; <-- _parsedMapping.seqDefs will be overflowed
IOHIDSystem * _hidSystem;
...
ExpansionData * _reserved;
...
UInt32 _stickyKeys_State;
int _stickyKeys_NumModifiersDown;
UInt8 _stickyKeys_Modifiers[kMAX_MODIFIERS];
StickyKeys_ToggleInfo * _stickyKeys_ShiftToggle;
StickyKeys_ToggleInfo * _stickyKeys_OptionToggle;
bool _stateDirty;
OSDictionary * _onParamDict; <-- pointer to an object with virtual method
We can easily cause a virtual method to be called on _onParamDict by ensuring
that parseKeyMapping returns false indicating
an error parsing the keyfile. This will cause a call to:
IOHIKeyboardMapper::free which will call stickyKeysFree:
void IOHIKeyboardMapper::stickyKeysfree (void)
{
...
if (_onParamDict) <-- we overwrite this pointer
_onParamDict->release(); <-- virtual method ( offset 0x28 in the vtable)
Some offsets:
_onParamDict is at offset 0xe30 in the IOHIDKeyboardMapper
seqDefs is at offset 0x998 in NXParsedKeyMapping, and NXParsedKeyMapping is at
offset 0x20 in IOHIKeyboardMapper:
+0x000: IOHIKeyboardMapper
...
+0x020: NXParsedKeyMapping _parsedMapping
...
+0x9b8: unsigned char* seqDefs[] -+
... | 0x478 bytes -> 0x8f pointers
+0xe30: OSDictionary* _onParamDict -+
So the 0x90th seqDef entry will overlap with the _onParamDict pointer.
Looking again at the parseKeyMapping function snippet where the overflow takes
place:
for(i = 0; i < parsedMapping->numSeqs; i++)
{
parsedMapping->seqDefs[i] = (unsigned char *)nmd.bp;
for(j=0, l=NextNum(&nmd); j<l; j++)
{
NextNum(&nmd);
NextNum(&nmd);
}
(nmb.bp points to the next number to parse.)
This loop is parsing a length followed by twice that number of Nums.
We can supply 0x8f entries of: 0x00 0x00 (len=0)
followed by one entry of: 0x00 0x02 (len = 0x2)
0x00 0x78 0x56 0x00
0x00 0x00 0x00 0x00
which will overwrite the _onParamDict pointer with a pointer to: 0x5678000000
which will be intepreted as a pointer to
a vtable :-) We can then map that page in userspace and write a fake vtable at
0x5678000200.
We can then force the parseKeyMapping function to return false here by
supplying a value larger than NX_NUMSPECIALKEYS:
numMods = NextNum(&nmd);
parsedMapping->numSpecialKeys = numMods;
if ( numMods > NX_NUMSPECIALKEYS )
return false;
PoC attached.
Original issue reported on code.google.com by [email protected]
on 30 Jun 2014 at 2:47
Attachments:
[deleted issue]
[deleted issue]
By setting the HIDKeyboardModifierMappingPairs property of IOHIDKeyboard we can
reach the following code in
IOHIKeyboardMapper.cpp:
if ((array = OSDynamicCast(OSArray, dict->getObject(kIOHIDKeyboardModifierMappingPairsKey))) && (_parsedMapping.maxMod != -1))
{
UInt32 count = array->getCount();
if ( count )
{
for ( unsigned i=0; i<count; i++)
{
OSDictionary * pair = OSDynamicCast(OSDictionary, array->getObject(i));
SInt32 src = 0;
SInt32 dst = 0;
if ( !pair ) continue;
number = OSDynamicCast(OSNumber, pair->getObject(kIOHIDKeyboardModifierMappingSrcKey));
if ( !number ) continue;
src = number->unsigned32BitValue();
number = OSDynamicCast(OSNumber, pair->getObject(kIOHIDKeyboardModifierMappingDstKey));
if ( !number ) continue;
dst = number->unsigned32BitValue();
...
if ((src >= NX_MODIFIERKEY_ALPHALOCK) && (src <= NX_MODIFIERKEY_RCOMMAND))
_modifierSwap_Modifiers[src] = dst; <-- (a)
This code expects an array of dictionaries, each containing two keys
(IOHIDKeyboardModifierMappingSrc and
IOHIDKeyboardModifierMappingDst) which have unsigned integer values. The
purpose of this code is to allow
swapping of modifier keys. The value dst at (a) is controlled and there is no
bounds check to ensure that
it corrisponds to a valid modifier key index.
_modifierSwap_Modifiers is defined as:
#define _modifierSwap_Modifiers _reserved->modifierSwap_Modifiers
where _reserved->modifierSwap_Modifiers is:
SInt32 modifierSwap_Modifiers[NX_NUMMODIFIERS];
These values are read here when a modifier key is pressed:
bool IOHIKeyboardMapper::modifierSwapFilterKey(UInt8 * key)
{
unsigned char thisBits = _parsedMapping.keyBits[*key];
SInt16 modBit = (thisBits & NX_WHICHMODMASK);
SInt16 swapBit;
unsigned char *map;
...
swapBit = _modifierSwap_Modifiers[modBit]; <-- (b)
...
if (((map = _parsedMapping.modDefs[swapBit]) != 0 ) && <-- (c)
( NEXTNUM(&map, _parsedMapping.shorts) ))
*key = NEXTNUM(&map, _parsedMapping.shorts); <-- (d)
swapBit (read at (b)) is completely controlled and there's no bounds checking
at (c) to ensure that it falls
within the bounds of _parsedMapping.modDefs.
modDefs is defined as:
unsigned char *modDefs[NX_NUMMODIFIERS];
and NEXTNUM as:
static inline int NEXTNUM(unsigned char ** mapping, short shorts)
{
int returnValue;
if (shorts)
{
returnValue = OSSwapBigToHostInt16(*((unsigned short *)*mapping));
*mapping += sizeof(unsigned short);
}
else
{
returnValue = **((unsigned char **)mapping);
*mapping += sizeof(unsigned char);
}
return returnValue;
}
Here we can see that if we can control a value at a fixed offset from modDefs
it will be treated as a char* from
which two values will be read (depening on value of _parsedMapping.shorts
either bytes or shorts.) If the first
value is non-zero then the second will be interpreted as a keycode. If it's a
valid keycode then this value can
be read by a userspace application, for example like this:
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask
handler:
^NSEvent *(NSEvent * event) {
printf("%02x\n", [event keyCode]);
return event;
} ];
We can quite easily fake a pointer to read from at a fixed offset from modDefs
by setting our own keymap
and encoding the read target in the specialKeys, since looking at the
definition of NXParsedKeyMapping
this array of shorts is at a fixed offset from modDefs:
typedef struct _NXParsedKeyMapping_ {
short shorts;
char keyBits[NX_NUMKEYCODES];
int maxMod;
unsigned char *modDefs[NX_NUMMODIFIERS];
int numDefs;
unsigned char *keyDefs[NX_NUMKEYCODES];
int numSeqs;
unsigned char *seqDefs[NX_NUMSEQUENCES];
int numSpecialKeys;
unsigned short specialKeys[NX_NUMSPECIALKEYS];
const unsigned char *mapping;
int mappingLen;
} NXParsedKeyMapping;
This PoC creates and applies such a keymap, see the code for details.
By reading modified keycodes (using for example snippet above in a cocoa app)
you can leak individual bytes of kernel memory.
Original issue reported on code.google.com by [email protected]
on 7 Jul 2014 at 6:51
Attachments:
IOAccelDisplayPipe2::transaction_set_plane_gamma_table fails to verify the
second dword of IOAccelDisplayPipeGammaTableArgs which can be controlled by
calling the external method with selector 5 of IOAccelDisplayPipeUserClient2.
This unchecked dword is passed to
IOAccelDisplayPipeTransaction2::set_plane_gamma_table where it is used as an
index to read a pointer to a c++ object from an array. By specifying a large
index this will read a c++ object pointer out-of-bounds. The code then calls a
virtual function on this object.
Impact:
This userclient can be instantiated in the chrome GPU process sandbox and the
safari renderer sandbox.
Original issue reported on code.google.com by [email protected]
on 16 Jun 2014 at 1:55
Attachments:
The Intel GPU driver uses shared memory for drawing commands. The userspace
client of the driver calls IOConnectMapMemory to map a shared page which it
will use,
calling selector 2 of the IOAccelerator userclient (submit_data_buffers) to
signal to the driver that it should
consume the commands (tokens) written there by the client.
The first 0x10 bytes of the shared memory are some kind of header, the rest is
filled with
tokens of the form:
+0x00 2-byte token ID
+0x02 length of token (in 4 byte words, including this header)
+0x04 4 byte output offset??
+0x08 body of token
..
I'm still not completely sure what the 4 byte output offset field is actually
for,
but after processing all the tokens the driver calls
IGAccelFIFOChannel::submitBuffer,
and writes two words (maybe end of buffer delimiters?) using a value derived
from those offset fields
as an index and there's no bounds checking, so by specifying a large output
offset for a token
you can get this function to write the two words: 0x05000000 0x00000000 at a
controlled offset.
tested on: MacBookAir5,2 w/ 10.9.3/13d64
(it appears to crash the GeForce driver too with what looks at first glance
like a similar issue. I haven't had a chance to look at it yet but running this
repro on a MacBookPro10,1 crashes in nvFermiGLContext::UpdateDrawableOffsets
with an OOB write)
Original issue reported on code.google.com by [email protected]
on 6 Jun 2014 at 7:03
Attachments:
This is probably another instance of CVE-2013-6629, reference:
http://seclists.org/fulldisclosure/2013/Nov/83
A SWF to reproduce is attached, along with source. To reproduce, host
JPEGLeak.swf on the same web server / directory as 55.jpg.
Since this is uninitialized data, you can reload the SWF and see the rendered
JPEG change either slightly or sometimes dramatically. It seems to work best
with a Flash process that is not fresh. A couple of screenshots are attached to
illustrate that the rendered images can sometimes differ significantly. The SWF
file also demonstrates that the uninitialized data can be leaked to script,
which makes the issue interesting / more serious.
If this is indeed the same underlying issue as CVE-2013-6629, then this patch
may be useful:
http://src.chromium.org/viewvc/chrome/trunk/src/third_party/libjpeg/jdmarker.c?r
1=228354&r2=228353&pathrev=228354
Original issue reported on code.google.com by [email protected]
on 8 Jul 2014 at 7:35
Attachments:
The functions IGAccelGLContext::process_token_BindConstantBuffers,
IGAccelGLContext::process_token_BindDrawFBOColor and
GAccelGLContext::process_token_BindTextures fail to bounds-check the dword at
offset 0x10 of the token they're parsing - this value is read from user/kernel
shared memory and is thus completely attacker controlled. The value is used as
the index for a kernel memory write.
(See previous token parsing bugs for more details of the IOAccelerator token
structures.)
These PoCs find the tokens in shared memory and set the offset to a large value
to cause a kernel panic.
IMPACT:
This userclient can be instantiated from the chrome gpu sandbox and the safari
renderer sandbox
Original issue reported on code.google.com by [email protected]
on 17 Jun 2014 at 4:35
Attachments:
IGAccelVideoContextMedia is the userclient responsible for gpu accelerated video decoding - it's userclient type 0x101 of the IntelAccelerator IOService.
Clients of IGAccelVideoContextMedia call IOConnectMapMemory with type=0 to map
a shared buffer which is used to pass tokens to the kernel.
The IGAccelVideoContextMedia::process_token_* methods parse these tokens
(offset +0x10 of the IOAccelCommandStreamInfo& which is passed to the
process_token_* methods is a pointer into the shared buffer.)
There are multiple cases of insufficient bounds checking allowing an attacker
to get controlled writes to kernel memory - please see each of the attached
PoCs for more details of each bug.
This userclient can be reached from the chrome GPU process sandbox and the
safari renderer sandbox.
These PoCs are interpose libraries which have been tested by loading them into
Quicktime, the bugs are still reachable from chrome and safari, but Quicktime
hits more of the kernel video code (so required less effort to write the PoCs
for.)
The attached Makefile will build all the libraries, load them like this:
DYLD_INSERT_LIBRARIES=<path/to/this/lib.dylib> /Applications/QuickTime\
Player.app/Contents/MacOS/QuickTime\ Player
and open an mp4 file
Original issue reported on code.google.com by [email protected]
on 12 Jun 2014 at 4:29
Attachments:
IOBluetoothFamily implements its own queuing primitive: IOBluetoothDataQueue
IOBluetoothHCIPacketLogUserClient is userclient type 1 of
IOBluetoothHCIController. Its clientMemoryForType
method uses the type argument as a length and calls
IOBluetoothDataQueue::withCapacity, which in turn calls
IOBluetoothDataQueue::initWithCapacity which uses the following code to
calculate the buffer size to allocate:
(r14d is controlled size)
lea edi, [r14+100Bh] ; overflow
and edi, 0FFFFF000h
mov esi, 1000h
call _IOMallocAligned
Calling selector 0 will cause the kernel to enqueue data to the undersized
queue. This selector is restricted to
root, so this doesn't actually get you an EoP on OS X hence Severity-None.
Original issue reported on code.google.com by [email protected]
on 23 Jun 2014 at 11:25
Attachments:
[deleted issue]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.