Bug 982974 - Be paranoid about neutering ArrayBuffer objects. r=sfink, r=jorendorff

This commit is contained in:
Jeff Walden 2014-03-14 16:55:48 -07:00
parent 05b48cb8ca
commit 1190a86bb6
2 changed files with 84 additions and 27 deletions

View File

@ -473,23 +473,28 @@ ArrayBufferObject::changeContents(JSContext *cx, ObjectElements *newHeader)
}
void
ArrayBufferObject::neuter(JSContext *cx)
ArrayBufferObject::neuter(ObjectElements *newHeader, JSContext *cx)
{
JS_ASSERT(!isSharedArrayBuffer());
MOZ_ASSERT(!isSharedArrayBuffer());
if (hasStealableContents()) {
MOZ_ASSERT(newHeader);
JS_ASSERT(cx);
if (hasDynamicElements() && !isAsmJSArrayBuffer()) {
ObjectElements *oldHeader = getElementsHeader();
changeContents(cx, ObjectElements::fromElements(fixedElements()));
MOZ_ASSERT(newHeader != oldHeader);
changeContents(cx, newHeader);
FreeOp fop(cx->runtime(), false);
fop.free_(oldHeader);
} else {
elements = newHeader->elements();
}
uint32_t byteLen = 0;
updateElementsHeader(getElementsHeader(), byteLen);
updateElementsHeader(newHeader, byteLen);
getElementsHeader()->setIsNeuteredBuffer();
newHeader->setIsNeuteredBuffer();
}
/* static */ bool
@ -756,19 +761,20 @@ ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp
ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer, void **contents,
uint8_t **data)
{
// If the ArrayBuffer's elements are dynamically allocated and nothing else
// prevents us from stealing them, transfer ownership directly. Otherwise,
// the elements are small and allocated inside the ArrayBuffer object's GC
// header so we must make a copy.
ObjectElements *transferableHeader;
bool stolen;
if (buffer->hasDynamicElements() && !buffer->isAsmJSArrayBuffer()) {
stolen = true;
transferableHeader = buffer->getElementsHeader();
} else {
stolen = false;
uint32_t byteLen = buffer->byteLength();
uint32_t byteLen = buffer->byteLength();
// If the ArrayBuffer's elements are transferrable, transfer ownership
// directly. Otherwise we have to copy the data into new elements.
ObjectElements *transferableHeader;
ObjectElements *newHeader;
bool stolen = buffer->hasStealableContents();
if (stolen) {
transferableHeader = buffer->getElementsHeader();
newHeader = AllocateArrayBufferContents(cx, byteLen);
if (!newHeader)
return false;
} else {
transferableHeader = AllocateArrayBufferContents(cx, byteLen);
if (!transferableHeader)
return false;
@ -776,6 +782,9 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffe
initElementsHeader(transferableHeader, byteLen);
void *headerDataPointer = reinterpret_cast<void*>(transferableHeader->elements());
memcpy(headerDataPointer, buffer->dataPointer(), byteLen);
// Keep using the current elements.
newHeader = buffer->getElementsHeader();
}
JS_ASSERT(!IsInsideNursery(cx->runtime(), transferableHeader));
@ -787,13 +796,13 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffe
if (!ArrayBufferObject::neuterViews(cx, buffer))
return false;
// If the elements were taken from the neutered buffer, revert it back to
// using inline storage so it doesn't attempt to free the stolen elements
// when finalized.
// If the elements were transferrable, revert the buffer back to using
// inline storage so it doesn't attempt to free the stolen elements when
// finalized.
if (stolen)
buffer->changeContents(cx, ObjectElements::fromElements(buffer->fixedElements()));
buffer->neuter(cx);
buffer->neuter(newHeader, cx);
return true;
}
@ -1270,9 +1279,33 @@ JS_NeuterArrayBuffer(JSContext *cx, HandleObject obj)
}
Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
if (!ArrayBufferObject::neuterViews(cx, buffer))
ObjectElements *newHeader;
if (buffer->hasStealableContents()) {
// If we're "disposing" with the buffer contents, allocate zeroed
// memory of equal size and swap that in as contents. This ensures
// that stale indexes that assume the original length, won't index out
// of bounds. This is a temporary hack: when we're confident we've
// eradicated all stale accesses, we'll stop doing this.
newHeader = AllocateArrayBufferContents(cx, buffer->byteLength());
if (!newHeader)
return false;
} else {
// This case neuters out the existing elements in-place, so use the
// old header as new.
newHeader = buffer->getElementsHeader();
}
// Mark all views of the ArrayBuffer as neutered.
if (!ArrayBufferObject::neuterViews(cx, buffer)) {
if (buffer->hasStealableContents()) {
FreeOp fop(cx->runtime(), false);
fop.free_(newHeader);
}
return false;
buffer->neuter(cx);
}
buffer->neuter(newHeader, cx);
return true;
}

View File

@ -132,6 +132,24 @@ class ArrayBufferObject : public JSObject
static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector);
static void restoreArrayBufferLists(ArrayBufferVector &vector);
bool hasStealableContents() const {
// Inline elements strictly adhere to the corresponding buffer.
if (!hasDynamicElements())
return false;
// asm.js buffer contents are transferred by copying, just like inline
// elements.
if (isAsmJSArrayBuffer())
return false;
// Neutered contents aren't transferrable because we want a neutered
// array's contents to be backed by zeroed memory equal in length to
// the original buffer contents. Transferring these contents would
// allocate new ones based on the current byteLength, which is 0 for a
// neutered array -- not the original byteLength.
return !isNeutered();
}
static bool stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer, void **contents,
uint8_t **data);
@ -176,10 +194,16 @@ class ArrayBufferObject : public JSObject
uint8_t * dataPointer() const;
/*
* Discard the ArrayBuffer contents. For asm.js buffers, at least, should
* Discard the ArrayBuffer contents, and use |newHeader| for the buffer's
* new contents. (These new contents are zeroed, of identical size in
* memory as the current contents, but appear to be neutered and of zero
* length. This is purely precautionary against stale indexes that were
* in-bounds with respect to the initial length but would not be after
* neutering. This precaution will be removed once we're sure such stale
* indexing no longer happens.) For asm.js buffers, at least, should
* be called after neuterViews().
*/
void neuter(JSContext *cx);
void neuter(ObjectElements *newHeader, JSContext *cx);
/*
* Check if the arrayBuffer contains any data. This will return false for