mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 00:35:44 +00:00
Bug 1401939 - Align XDR content to avoid undefined behaviours. r=kmag,tcampbell
This commit is contained in:
parent
f200bd41c5
commit
dbd24194ed
@ -712,27 +712,38 @@ template<XDRMode mode>
|
||||
bool
|
||||
js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
|
||||
{
|
||||
bool latin1 = false;
|
||||
uint32_t length = 0;
|
||||
uint32_t lengthAndEncoding = 0;
|
||||
if (mode == XDR_ENCODE) {
|
||||
static_assert(JSString::MAX_LENGTH <= INT32_MAX, "String length must fit in 31 bits");
|
||||
uint32_t length = atomp->length();
|
||||
uint32_t lengthAndEncoding = (length << 1) | uint32_t(atomp->hasLatin1Chars());
|
||||
if (!xdr->codeUint32(&lengthAndEncoding))
|
||||
return false;
|
||||
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
return atomp->hasLatin1Chars()
|
||||
? xdr->codeChars(atomp->latin1Chars(nogc), length)
|
||||
: xdr->codeChars(const_cast<char16_t*>(atomp->twoByteChars(nogc)), length);
|
||||
latin1 = atomp->hasLatin1Chars();
|
||||
length = atomp->length();
|
||||
lengthAndEncoding = (length << 1) | uint32_t(latin1);
|
||||
}
|
||||
|
||||
/* Avoid JSString allocation for already existing atoms. See bug 321985. */
|
||||
uint32_t lengthAndEncoding;
|
||||
if (!xdr->codeUint32(&lengthAndEncoding))
|
||||
return false;
|
||||
|
||||
uint32_t length = lengthAndEncoding >> 1;
|
||||
bool latin1 = lengthAndEncoding & 0x1;
|
||||
if (mode == XDR_DECODE) {
|
||||
length = lengthAndEncoding >> 1;
|
||||
latin1 = lengthAndEncoding & 0x1;
|
||||
}
|
||||
|
||||
// We need to align the string in the XDR buffer such that we can avoid
|
||||
// non-align loads of 16bits characters.
|
||||
if (!latin1 && !xdr->codeAlign(sizeof(char16_t)))
|
||||
return false;
|
||||
|
||||
if (mode == XDR_ENCODE) {
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
if (latin1)
|
||||
return xdr->codeChars(atomp->latin1Chars(nogc), length);
|
||||
return xdr->codeChars(const_cast<char16_t*>(atomp->twoByteChars(nogc)), length);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mode == XDR_DECODE);
|
||||
/* Avoid JSString allocation for already existing atoms. See bug 321985. */
|
||||
JSContext* cx = xdr->cx();
|
||||
JSAtom* atom;
|
||||
if (latin1) {
|
||||
@ -754,6 +765,8 @@ js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
|
||||
size_t nbyte = length * sizeof(char16_t);
|
||||
if (!xdr->peekData(&ptr, nbyte))
|
||||
return false;
|
||||
MOZ_ASSERT(reinterpret_cast<uintptr_t>(ptr) % sizeof(char16_t) == 0,
|
||||
"non-aligned buffer during JSAtom decoding");
|
||||
chars = reinterpret_cast<const char16_t*>(ptr);
|
||||
}
|
||||
atom = AtomizeChars(cx, chars, length);
|
||||
|
@ -622,6 +622,8 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
|
||||
|
||||
// Everything added below can substituted by the non-lazy-script version of
|
||||
// this function later.
|
||||
if (!xdr->codeAlign(sizeof(js::XDRAlignment)))
|
||||
return false;
|
||||
js::AutoXDRTree funTree(xdr, xdr->getTreeKey(fun));
|
||||
|
||||
if (!xdr->codeUint32(&firstword))
|
||||
@ -680,6 +682,11 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
|
||||
if (!xdr->codeMarker(0x9E35CA1F))
|
||||
return false;
|
||||
|
||||
// Required by AutoXDRTree to copy & paste snipet of sub-trees while keeping
|
||||
// the alignment.
|
||||
if (!xdr->codeAlign(sizeof(js::XDRAlignment)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/EnvironmentObject.h"
|
||||
@ -170,6 +171,12 @@ XDRState<mode>::codeScript(MutableHandleScript scriptp)
|
||||
mode == XDR_DECODE ? TraceLogger_DecodeScript : TraceLogger_EncodeScript;
|
||||
AutoTraceLog tl(logger, event);
|
||||
|
||||
// This should be a no-op when encoding, but when decoding it would eat any
|
||||
// miss-aligned bytes if when encoding the XDR buffer is appended at the end
|
||||
// of an existing buffer, such as in XDRIncrementalEncoder::linearize
|
||||
// function.
|
||||
if (!codeAlign(sizeof(js::XDRAlignment)))
|
||||
return false;
|
||||
AutoXDRTree scriptTree(this, getTopLevelTreeKey());
|
||||
|
||||
if (mode == XDR_DECODE)
|
||||
@ -188,6 +195,9 @@ XDRState<mode>::codeScript(MutableHandleScript scriptp)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!codeAlign(sizeof(js::XDRAlignment)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -206,12 +216,16 @@ AutoXDRTree::AutoXDRTree(XDRCoderBase* xdr, AutoXDRTree::Key key)
|
||||
parent_(this),
|
||||
xdr_(xdr)
|
||||
{
|
||||
// Expect sub-tree to start with the maximum alignment required.
|
||||
MOZ_ASSERT(xdr->isAligned(sizeof(js::XDRAlignment)));
|
||||
if (key_ != AutoXDRTree::noKey)
|
||||
xdr->createOrReplaceSubTree(this);
|
||||
}
|
||||
|
||||
AutoXDRTree::~AutoXDRTree()
|
||||
{
|
||||
// Expect sub-tree to end with the maximum alignment required.
|
||||
MOZ_ASSERT(xdr_->isAligned(sizeof(js::XDRAlignment)));
|
||||
if (key_ != AutoXDRTree::noKey)
|
||||
xdr_->endSubTree();
|
||||
}
|
||||
@ -340,6 +354,18 @@ XDRIncrementalEncoder::linearize(JS::TranscodeBuffer& buffer)
|
||||
// Do not linearize while we are currently adding bytes.
|
||||
MOZ_ASSERT(scope_ == nullptr);
|
||||
|
||||
// Ensure the content of the buffer is properly aligned within the buffer.
|
||||
// This alignment difference should be consumed by the codeAlign function
|
||||
// call of codeScript when decoded.
|
||||
size_t alignLen = sizeof(js::XDRAlignment);
|
||||
if (buffer.length() % alignLen) {
|
||||
alignLen = ComputeByteAlignment(buffer.length(), alignLen);
|
||||
if (!buffer.appendN(0, alignLen)) {
|
||||
ReportOutOfMemory(cx());
|
||||
return fail(JS::TranscodeResult_Throw);
|
||||
}
|
||||
}
|
||||
|
||||
// Visit the tree parts in a depth first order, to linearize the bits.
|
||||
Vector<SlicesNode::ConstRange> depthFirst(cx());
|
||||
|
||||
@ -364,6 +390,8 @@ XDRIncrementalEncoder::linearize(JS::TranscodeBuffer& buffer)
|
||||
// buffer which would be serialized.
|
||||
MOZ_ASSERT(slice.sliceBegin <= slices_.length());
|
||||
MOZ_ASSERT(slice.sliceBegin + slice.sliceLength <= slices_.length());
|
||||
MOZ_ASSERT(buffer.length() % sizeof(XDRAlignment) == 0);
|
||||
MOZ_ASSERT(slice.sliceLength % sizeof(XDRAlignment) == 0);
|
||||
if (!buffer.append(slices_.begin() + slice.sliceBegin, slice.sliceLength)) {
|
||||
ReportOutOfMemory(cx());
|
||||
return fail(JS::TranscodeResult_Throw);
|
||||
|
@ -24,7 +24,13 @@ class XDRBufferBase
|
||||
{
|
||||
public:
|
||||
explicit XDRBufferBase(JSContext* cx, size_t cursor = 0)
|
||||
: context_(cx), cursor_(cursor) { }
|
||||
: context_(cx), cursor_(cursor)
|
||||
#ifdef DEBUG
|
||||
// Note, when decoding the buffer can be set to a range, which does not
|
||||
// have any alignment requirement as opposed to allocations.
|
||||
, aligned_(false)
|
||||
#endif
|
||||
{ }
|
||||
|
||||
JSContext* cx() const {
|
||||
return context_;
|
||||
@ -34,9 +40,24 @@ class XDRBufferBase
|
||||
return cursor_;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// This function records if the cursor got changed by codeAlign or by any
|
||||
// other read/write of data. This is used for AutoXDRTree assertions, as a
|
||||
// way to ensure that the last thing done is properly setting the alignment
|
||||
// with codeAlign function.
|
||||
void setAligned(bool aligned) { aligned_ = aligned; }
|
||||
bool isAligned() const { return aligned_; }
|
||||
#else
|
||||
void setAligned(bool) const {}
|
||||
bool isAligned() const { return true; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
JSContext* const context_;
|
||||
size_t cursor_;
|
||||
#ifdef DEBUG
|
||||
bool aligned_;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <XDRMode mode>
|
||||
@ -52,6 +73,7 @@ class XDRBuffer<XDR_ENCODE> : public XDRBufferBase
|
||||
|
||||
uint8_t* write(size_t n) {
|
||||
MOZ_ASSERT(n != 0);
|
||||
setAligned(false);
|
||||
if (!buffer_.growByUninitialized(n)) {
|
||||
ReportOutOfMemory(cx());
|
||||
return nullptr;
|
||||
@ -66,6 +88,11 @@ class XDRBuffer<XDR_ENCODE> : public XDRBufferBase
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uintptr_t uptr() const {
|
||||
// Note: Avoid bounds check assertion if the buffer is not yet allocated.
|
||||
return reinterpret_cast<uintptr_t>(buffer_.begin() + cursor_);
|
||||
}
|
||||
|
||||
private:
|
||||
JS::TranscodeBuffer& buffer_;
|
||||
};
|
||||
@ -84,6 +111,7 @@ class XDRBuffer<XDR_DECODE> : public XDRBufferBase
|
||||
|
||||
const uint8_t* read(size_t n) {
|
||||
MOZ_ASSERT(cursor_ < buffer_.length());
|
||||
setAligned(false);
|
||||
uint8_t* ptr = &buffer_[cursor_];
|
||||
cursor_ += n;
|
||||
|
||||
@ -99,12 +127,19 @@ class XDRBuffer<XDR_DECODE> : public XDRBufferBase
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uintptr_t uptr() const {
|
||||
// Note: Avoid bounds check assertion at the end of the buffer.
|
||||
return reinterpret_cast<uintptr_t>(buffer_.begin().get() + cursor_);
|
||||
}
|
||||
|
||||
private:
|
||||
const JS::TranscodeRange buffer_;
|
||||
};
|
||||
|
||||
class XDRCoderBase;
|
||||
class XDRIncrementalEncoder;
|
||||
using XDRAlignment = char16_t;
|
||||
static const uint8_t AlignPadding[sizeof(XDRAlignment)] = { 0, 0 };
|
||||
|
||||
// An AutoXDRTree is used to identify section encoded by an XDRIncrementalEncoder.
|
||||
//
|
||||
@ -157,6 +192,7 @@ class XDRCoderBase
|
||||
virtual AutoXDRTree::Key getTreeKey(JSFunction* fun) const { return AutoXDRTree::noKey; }
|
||||
virtual void createOrReplaceSubTree(AutoXDRTree* child) {};
|
||||
virtual void endSubTree() {};
|
||||
virtual bool isAligned(size_t n) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -219,6 +255,45 @@ class XDRState : public XDRCoderBase
|
||||
return true;
|
||||
}
|
||||
|
||||
// Alignment is required when doing memcpy of data which contains element
|
||||
// largers than 1 byte.
|
||||
bool isAligned(size_t n) override {
|
||||
MOZ_ASSERT(mozilla::IsPowerOfTwo(n));
|
||||
// If there is a failure, always assume that we are aligned.
|
||||
if (resultCode() != JS::TranscodeResult_Ok)
|
||||
return true;
|
||||
size_t mask = n - 1;
|
||||
size_t offset = buf.uptr() & mask;
|
||||
// In debug build, we not only check if the cursor is aligned, but also
|
||||
// if the last cursor manipulation was made by the codeAlign function.
|
||||
return offset == 0 && buf.isAligned();
|
||||
}
|
||||
bool codeAlign(size_t n) {
|
||||
MOZ_ASSERT(mozilla::IsPowerOfTwo(n));
|
||||
size_t mask = n - 1;
|
||||
MOZ_ASSERT_IF(mode == XDR_ENCODE, (buf.uptr() & mask) == (buf.cursor() & mask));
|
||||
size_t offset = buf.uptr() & mask;
|
||||
if (offset) {
|
||||
size_t padding = n - offset;
|
||||
MOZ_ASSERT(padding < sizeof(AlignPadding));
|
||||
if (mode == XDR_ENCODE) {
|
||||
uint8_t* ptr = buf.write(padding);
|
||||
if (!ptr)
|
||||
return fail(JS::TranscodeResult_Throw);
|
||||
memcpy(ptr, AlignPadding, padding);
|
||||
} else {
|
||||
const uint8_t* ptr = buf.read(padding);
|
||||
if (!ptr)
|
||||
return fail(JS::TranscodeResult_Failure_BadDecode);
|
||||
if (memcmp(ptr, AlignPadding, padding) != 0)
|
||||
return fail(JS::TranscodeResult_Failure_BadDecode);
|
||||
}
|
||||
}
|
||||
buf.setAligned(true);
|
||||
MOZ_ASSERT(isAligned(n));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool codeUint8(uint8_t* n) {
|
||||
if (mode == XDR_ENCODE) {
|
||||
uint8_t* ptr = buf.write(sizeof(*n));
|
||||
|
@ -52,6 +52,16 @@ using namespace mozilla::loader;
|
||||
|
||||
ProcessType ScriptPreloader::sProcessType;
|
||||
|
||||
// This type correspond to js::vm::XDRAlignment type, which is used as a size
|
||||
// reference for alignment of XDR buffers.
|
||||
using XDRAlign = uint16_t;
|
||||
static const uint8_t sAlignPadding[sizeof(XDRAlign)] = { 0, 0 };
|
||||
|
||||
static inline size_t
|
||||
ComputeByteAlignment(size_t bytes, size_t align)
|
||||
{
|
||||
return (align - (bytes % align)) % align;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
|
||||
@ -460,6 +470,8 @@ ScriptPreloader::InitCacheInternal(JS::HandleObject scope)
|
||||
}
|
||||
|
||||
auto data = mCacheData.get<uint8_t>();
|
||||
uint8_t* start = data.get();
|
||||
MOZ_ASSERT(reinterpret_cast<uintptr_t>(start) % sizeof(XDRAlign) == 0);
|
||||
auto end = data + size;
|
||||
|
||||
if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) {
|
||||
@ -486,6 +498,10 @@ ScriptPreloader::InitCacheInternal(JS::HandleObject scope)
|
||||
|
||||
InputBuffer buf(header);
|
||||
|
||||
size_t len = data.get() - start;
|
||||
size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
|
||||
data += alignLen;
|
||||
|
||||
size_t offset = 0;
|
||||
while (!buf.finished()) {
|
||||
auto script = MakeUnique<CachedScript>(*this, buf);
|
||||
@ -503,6 +519,7 @@ ScriptPreloader::InitCacheInternal(JS::HandleObject scope)
|
||||
}
|
||||
offset += script->mSize;
|
||||
|
||||
MOZ_ASSERT(reinterpret_cast<uintptr_t>(scriptData.get()) % sizeof(XDRAlign) == 0);
|
||||
script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
|
||||
|
||||
// Don't pre-decode the script unless it was used in this process type during the
|
||||
@ -657,6 +674,7 @@ ScriptPreloader::WriteCache()
|
||||
OutputBuffer buf;
|
||||
size_t offset = 0;
|
||||
for (auto script : scripts) {
|
||||
MOZ_ASSERT(offset % sizeof(XDRAlign) == 0);
|
||||
script->mOffset = offset;
|
||||
script->Code(buf);
|
||||
|
||||
@ -666,11 +684,22 @@ ScriptPreloader::WriteCache()
|
||||
uint8_t headerSize[4];
|
||||
LittleEndian::writeUint32(headerSize, buf.cursor());
|
||||
|
||||
size_t len = 0;
|
||||
MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
|
||||
len += sizeof(MAGIC);
|
||||
MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
|
||||
len += sizeof(headerSize);
|
||||
MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
|
||||
len += buf.cursor();
|
||||
size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
|
||||
if (alignLen) {
|
||||
MOZ_TRY(Write(fd, sAlignPadding, alignLen));
|
||||
len += alignLen;
|
||||
}
|
||||
for (auto script : scripts) {
|
||||
MOZ_ASSERT(script->mSize % sizeof(XDRAlign) == 0);
|
||||
MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
|
||||
len += script->mSize;
|
||||
|
||||
if (script->mScript) {
|
||||
script->FreeData();
|
||||
|
Loading…
Reference in New Issue
Block a user