Bug 1065811 - Clean up ObjectId handling with static type checking. r=billm

While adding the CPOW flag for xray waivers, I discovered a bunch of
inconsistency and sloppiness with respect to our handling of object ids,
and a general lack of clarity about when the id included flags or not. Given
the fact that I'm removing static callability for CPOWs, we _could_ just get
rid of the flags, and store the xray waiver state on the answer-side only. But
I eventually decided that these kinds of flags (which are accessible to both
the Answer _and_ the Owner) had enough potential utility that they were worth
cleaning up.

It's worth noting that that utility comes with the large caveat that the flags
can't be trusted for security-sensitive decisions (at least in the parent->child
case), since they could be forged by a compromised child.
This commit is contained in:
Bobby Holley 2014-09-25 13:13:29 +02:00
parent b4b92fa4cc
commit ecd6e25975
6 changed files with 140 additions and 105 deletions

View File

@ -35,181 +35,181 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
/*** IPC handlers ***/
bool AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
return Answer::AnswerPreventExtensions(objId, rs);
bool AnswerPreventExtensions(const uint64_t &objId, ReturnStatus *rs) {
return Answer::AnswerPreventExtensions(ObjectId::deserialize(objId), rs);
}
bool AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
bool AnswerGetPropertyDescriptor(const uint64_t &objId, const nsString &id,
ReturnStatus *rs,
PPropertyDescriptor *out) {
return Answer::AnswerGetPropertyDescriptor(objId, id, rs, out);
return Answer::AnswerGetPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
}
bool AnswerGetOwnPropertyDescriptor(const ObjectId &objId,
bool AnswerGetOwnPropertyDescriptor(const uint64_t &objId,
const nsString &id,
ReturnStatus *rs,
PPropertyDescriptor *out) {
return Answer::AnswerGetOwnPropertyDescriptor(objId, id, rs, out);
return Answer::AnswerGetOwnPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
}
bool AnswerDefineProperty(const ObjectId &objId, const nsString &id,
bool AnswerDefineProperty(const uint64_t &objId, const nsString &id,
const PPropertyDescriptor &flags,
ReturnStatus *rs) {
return Answer::AnswerDefineProperty(objId, id, flags, rs);
return Answer::AnswerDefineProperty(ObjectId::deserialize(objId), id, flags, rs);
}
bool AnswerDelete(const ObjectId &objId, const nsString &id,
bool AnswerDelete(const uint64_t &objId, const nsString &id,
ReturnStatus *rs, bool *success) {
return Answer::AnswerDelete(objId, id, rs, success);
return Answer::AnswerDelete(ObjectId::deserialize(objId), id, rs, success);
}
bool AnswerHas(const ObjectId &objId, const nsString &id,
bool AnswerHas(const uint64_t &objId, const nsString &id,
ReturnStatus *rs, bool *bp) {
return Answer::AnswerHas(objId, id, rs, bp);
return Answer::AnswerHas(ObjectId::deserialize(objId), id, rs, bp);
}
bool AnswerHasOwn(const ObjectId &objId, const nsString &id,
bool AnswerHasOwn(const uint64_t &objId, const nsString &id,
ReturnStatus *rs, bool *bp) {
return Answer::AnswerHasOwn(objId, id, rs, bp);
return Answer::AnswerHasOwn(ObjectId::deserialize(objId), id, rs, bp);
}
bool AnswerGet(const ObjectId &objId, const ObjectVariant &receiverVar,
bool AnswerGet(const uint64_t &objId, const ObjectVariant &receiverVar,
const nsString &id,
ReturnStatus *rs, JSVariant *result) {
return Answer::AnswerGet(objId, receiverVar, id, rs, result);
return Answer::AnswerGet(ObjectId::deserialize(objId), receiverVar, id, rs, result);
}
bool AnswerSet(const ObjectId &objId, const ObjectVariant &receiverVar,
bool AnswerSet(const uint64_t &objId, const ObjectVariant &receiverVar,
const nsString &id, const bool &strict,
const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
return Answer::AnswerSet(objId, receiverVar, id, strict, value, rs, result);
return Answer::AnswerSet(ObjectId::deserialize(objId), receiverVar, id, strict, value, rs, result);
}
bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
bool AnswerIsExtensible(const uint64_t &objId, ReturnStatus *rs,
bool *result) {
return Answer::AnswerIsExtensible(objId, rs, result);
return Answer::AnswerIsExtensible(ObjectId::deserialize(objId), rs, result);
}
bool AnswerCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
bool AnswerCallOrConstruct(const uint64_t &objId, const nsTArray<JSParam> &argv,
const bool &construct, ReturnStatus *rs, JSVariant *result,
nsTArray<JSParam> *outparams) {
return Answer::AnswerCallOrConstruct(objId, argv, construct, rs, result, outparams);
return Answer::AnswerCallOrConstruct(ObjectId::deserialize(objId), argv, construct, rs, result, outparams);
}
bool AnswerHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
return Answer::AnswerHasInstance(objId, v, rs, bp);
bool AnswerHasInstance(const uint64_t &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
return Answer::AnswerHasInstance(ObjectId::deserialize(objId), v, rs, bp);
}
bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
bool AnswerObjectClassIs(const uint64_t &objId, const uint32_t &classValue,
bool *result) {
return Answer::AnswerObjectClassIs(objId, classValue, result);
return Answer::AnswerObjectClassIs(ObjectId::deserialize(objId), classValue, result);
}
bool AnswerClassName(const ObjectId &objId, nsString *result) {
return Answer::AnswerClassName(objId, result);
bool AnswerClassName(const uint64_t &objId, nsString *result) {
return Answer::AnswerClassName(ObjectId::deserialize(objId), result);
}
bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
bool AnswerGetPropertyNames(const uint64_t &objId, const uint32_t &flags,
ReturnStatus *rs, nsTArray<nsString> *names) {
return Answer::AnswerGetPropertyNames(objId, flags, rs, names);
return Answer::AnswerGetPropertyNames(ObjectId::deserialize(objId), flags, rs, names);
}
bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
bool AnswerInstanceOf(const uint64_t &objId, const JSIID &iid,
ReturnStatus *rs, bool *instanceof) {
return Answer::AnswerInstanceOf(objId, iid, rs, instanceof);
return Answer::AnswerInstanceOf(ObjectId::deserialize(objId), iid, rs, instanceof);
}
bool AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
bool AnswerDOMInstanceOf(const uint64_t &objId, const int &prototypeID, const int &depth,
ReturnStatus *rs, bool *instanceof) {
return Answer::AnswerDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
return Answer::AnswerDOMInstanceOf(ObjectId::deserialize(objId), prototypeID, depth, rs, instanceof);
}
bool AnswerIsCallable(const ObjectId &objId, bool *result) {
return Answer::AnswerIsCallable(objId, result);
bool AnswerIsCallable(const uint64_t &objId, bool *result) {
return Answer::AnswerIsCallable(ObjectId::deserialize(objId), result);
}
bool AnswerIsConstructor(const ObjectId &objId, bool *result) {
return Answer::AnswerIsConstructor(objId, result);
bool AnswerIsConstructor(const uint64_t &objId, bool *result) {
return Answer::AnswerIsConstructor(ObjectId::deserialize(objId), result);
}
bool RecvDropObject(const ObjectId &objId) {
return Answer::RecvDropObject(objId);
bool RecvDropObject(const uint64_t &objId) {
return Answer::RecvDropObject(ObjectId::deserialize(objId));
}
/*** Dummy call handlers ***/
bool SendDropObject(const ObjectId &objId) {
return Base::SendDropObject(objId);
return Base::SendDropObject(objId.serialize());
}
bool CallPreventExtensions(const ObjectId &objId, ReturnStatus *rs) {
return Base::CallPreventExtensions(objId, rs);
return Base::CallPreventExtensions(objId.serialize(), rs);
}
bool CallGetPropertyDescriptor(const ObjectId &objId, const nsString &id,
ReturnStatus *rs,
PPropertyDescriptor *out) {
return Base::CallGetPropertyDescriptor(objId, id, rs, out);
return Base::CallGetPropertyDescriptor(objId.serialize(), id, rs, out);
}
bool CallGetOwnPropertyDescriptor(const ObjectId &objId,
const nsString &id,
ReturnStatus *rs,
PPropertyDescriptor *out) {
return Base::CallGetOwnPropertyDescriptor(objId, id, rs, out);
return Base::CallGetOwnPropertyDescriptor(objId.serialize(), id, rs, out);
}
bool CallDefineProperty(const ObjectId &objId, const nsString &id,
const PPropertyDescriptor &flags,
ReturnStatus *rs) {
return Base::CallDefineProperty(objId, id, flags, rs);
return Base::CallDefineProperty(objId.serialize(), id, flags, rs);
}
bool CallDelete(const ObjectId &objId, const nsString &id,
ReturnStatus *rs, bool *success) {
return Base::CallDelete(objId, id, rs, success);
return Base::CallDelete(objId.serialize(), id, rs, success);
}
bool CallHas(const ObjectId &objId, const nsString &id,
ReturnStatus *rs, bool *bp) {
return Base::CallHas(objId, id, rs, bp);
return Base::CallHas(objId.serialize(), id, rs, bp);
}
bool CallHasOwn(const ObjectId &objId, const nsString &id,
ReturnStatus *rs, bool *bp) {
return Base::CallHasOwn(objId, id, rs, bp);
return Base::CallHasOwn(objId.serialize(), id, rs, bp);
}
bool CallGet(const ObjectId &objId, const ObjectVariant &receiverVar,
const nsString &id,
ReturnStatus *rs, JSVariant *result) {
return Base::CallGet(objId, receiverVar, id, rs, result);
return Base::CallGet(objId.serialize(), receiverVar, id, rs, result);
}
bool CallSet(const ObjectId &objId, const ObjectVariant &receiverVar,
const nsString &id, const bool &strict,
const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
return Base::CallSet(objId, receiverVar, id, strict, value, rs, result);
return Base::CallSet(objId.serialize(), receiverVar, id, strict, value, rs, result);
}
bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
bool *result) {
return Base::CallIsExtensible(objId, rs, result);
return Base::CallIsExtensible(objId.serialize(), rs, result);
}
bool CallCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
const bool &construct, ReturnStatus *rs, JSVariant *result,
nsTArray<JSParam> *outparams) {
return Base::CallCallOrConstruct(objId, argv, construct, rs, result, outparams);
return Base::CallCallOrConstruct(objId.serialize(), argv, construct, rs, result, outparams);
}
bool CallHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) {
return Base::CallHasInstance(objId, v, rs, bp);
return Base::CallHasInstance(objId.serialize(), v, rs, bp);
}
bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
bool *result) {
return Base::CallObjectClassIs(objId, classValue, result);
return Base::CallObjectClassIs(objId.serialize(), classValue, result);
}
bool CallClassName(const ObjectId &objId, nsString *result) {
return Base::CallClassName(objId, result);
return Base::CallClassName(objId.serialize(), result);
}
bool CallGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
ReturnStatus *rs, nsTArray<nsString> *names) {
return Base::CallGetPropertyNames(objId, flags, rs, names);
return Base::CallGetPropertyNames(objId.serialize(), flags, rs, names);
}
bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
ReturnStatus *rs, bool *instanceof) {
return Base::CallInstanceOf(objId, iid, rs, instanceof);
return Base::CallInstanceOf(objId.serialize(), iid, rs, instanceof);
}
bool CallDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth,
ReturnStatus *rs, bool *instanceof) {
return Base::CallDOMInstanceOf(objId, prototypeID, depth, rs, instanceof);
return Base::CallDOMInstanceOf(objId.serialize(), prototypeID, depth, rs, instanceof);
}
bool CallIsCallable(const ObjectId &objId, bool *result) {
return Base::CallIsCallable(objId, result);
return Base::CallIsCallable(objId.serialize(), result);
}
bool CallIsConstructor(const ObjectId &objId, bool *result) {
return Base::CallIsConstructor(objId, result);
return Base::CallIsConstructor(objId.serialize(), result);
}
/* The following code is needed to suppress a bogus MSVC warning (C4250). */

View File

@ -160,9 +160,9 @@ class Logging
case JSVariant::TObjectVariant: {
const ObjectVariant &ovar = value.get_ObjectVariant();
if (ovar.type() == ObjectVariant::TLocalObject)
formatObject(incoming, true, ovar.get_LocalObject().id(), out);
formatObject(incoming, true, ObjectId::deserialize(ovar.get_LocalObject().serializedId()), out);
else
formatObject(incoming, false, ovar.get_RemoteObject().id(), out);
formatObject(incoming, false, ObjectId::deserialize(ovar.get_RemoteObject().serializedId()), out);
break;
}
case JSVariant::Tdouble: {

View File

@ -123,7 +123,7 @@ ObjectToIdMap::find(JSObject *obj)
{
Table::Ptr p = table_->lookup(obj);
if (!p)
return 0;
return ObjectId::nullId();
return p->value();
}
@ -162,7 +162,7 @@ bool JavaScriptShared::sStackLoggingEnabled;
JavaScriptShared::JavaScriptShared(JSRuntime *rt)
: rt_(rt),
refcount_(1),
lastId_(0)
nextSerialNumber_(1)
{
if (!sLoggingInitialized) {
sLoggingInitialized = true;
@ -381,7 +381,7 @@ JavaScriptShared::ConvertID(const JSIID &from, nsID *to)
}
JSObject *
JavaScriptShared::findObjectById(JSContext *cx, uint32_t objId)
JavaScriptShared::findObjectById(JSContext *cx, const ObjectId &objId)
{
RootedObject obj(cx, findObjectById(objId));
if (!obj) {

View File

@ -16,7 +16,47 @@
namespace mozilla {
namespace jsipc {
typedef uint64_t ObjectId;
class ObjectId {
public:
// Use 47 bits at most, to be safe, since jsval privates are encoded as
// doubles. See bug 1065811 comment 12 for an explanation.
static const size_t SERIAL_NUMBER_BITS = 47;
static const size_t FLAG_BITS = 1;
static const uint64_t SERIAL_NUMBER_MAX = (uint64_t(1) << SERIAL_NUMBER_BITS) - 1;
explicit ObjectId(uint64_t serialNumber, bool isCallable)
: serialNumber_(serialNumber), isCallable_(isCallable)
{
if (MOZ_UNLIKELY(serialNumber == 0 || serialNumber > SERIAL_NUMBER_MAX))
MOZ_CRASH("Bad CPOW Id");
}
bool operator==(const ObjectId &other) const {
bool equal = serialNumber() == other.serialNumber();
MOZ_ASSERT_IF(equal, isCallable() == other.isCallable());
return equal;
}
bool isNull() { return !serialNumber_; }
uint64_t serialNumber() const { return serialNumber_; }
bool isCallable() const { return isCallable_; }
uint64_t serialize() const {
MOZ_ASSERT(serialNumber(), "Don't send a null ObjectId over IPC");
return uint64_t((serialNumber() << FLAG_BITS) | ((isCallable() ? 1 : 0) << 0));
}
static ObjectId nullId() { return ObjectId(); }
static ObjectId deserialize(uint64_t data) {
return ObjectId(data >> FLAG_BITS, data & 1);
}
private:
ObjectId() : serialNumber_(0), isCallable_(false) {}
uint64_t serialNumber_ : SERIAL_NUMBER_BITS;
bool isCallable_ : 1;
};
class JavaScriptShared;
@ -36,12 +76,27 @@ class CpowIdHolder : public CpowHolder
const InfallibleTArray<CpowEntry> &cpows_;
};
// DefaultHasher<T> requires that T coerce to an integral type. We could make
// ObjectId do that, but doing so would weaken our type invariants, so we just
// reimplement it manually.
struct ObjectIdHasher
{
typedef ObjectId Lookup;
static js::HashNumber hash(const Lookup &l) {
return l.serialize();
}
static bool match(const ObjectId &k, const ObjectId &l) {
return k == l;
}
static void rekey(ObjectId &k, const ObjectId& newKey) {
k = newKey;
}
};
// Map ids -> JSObjects
class IdToObjectMap
{
typedef js::DefaultHasher<ObjectId> TableKeyHasher;
typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, TableKeyHasher, js::SystemAllocPolicy> Table;
typedef js::HashMap<ObjectId, JS::Heap<JSObject *>, ObjectIdHasher, js::SystemAllocPolicy> Table;
public:
IdToObjectMap();
@ -95,9 +150,6 @@ class JavaScriptShared
void decref();
void incref();
static const uint32_t OBJECT_EXTRA_BITS = 1;
static const uint32_t OBJECT_IS_CALLABLE = (1 << 0);
bool Unwrap(JSContext *cx, const InfallibleTArray<CpowEntry> &aCpows, JS::MutableHandleObject objp);
bool Wrap(JSContext *cx, JS::HandleObject aObj, InfallibleTArray<CpowEntry> *outCpows);
@ -119,13 +171,13 @@ class JavaScriptShared
static void ConvertID(const nsID &from, JSIID *to);
static void ConvertID(const JSIID &from, nsID *to);
JSObject *findCPOWById(uint32_t objId) {
JSObject *findCPOWById(const ObjectId &objId) {
return cpows_.find(objId);
}
JSObject *findObjectById(uint32_t objId) {
JSObject *findObjectById(const ObjectId &objId) {
return objects_.find(objId);
}
JSObject *findObjectById(JSContext *cx, uint32_t objId);
JSObject *findObjectById(JSContext *cx, const ObjectId &objId);
static bool LoggingEnabled() { return sLoggingEnabled; }
static bool StackLoggingEnabled() { return sStackLoggingEnabled; }
@ -143,7 +195,7 @@ class JavaScriptShared
IdToObjectMap objects_;
IdToObjectMap cpows_;
ObjectId lastId_;
uint64_t nextSerialNumber_;
ObjectToIdMap objectIds_;
static bool sLoggingInitialized;
@ -151,9 +203,6 @@ class JavaScriptShared
static bool sStackLoggingEnabled;
};
// Use 47 at most, to be safe, since jsval privates are encoded as doubles.
static const uint64_t MAX_CPOW_IDS = (uint64_t(1) << 47) - 1;
} // namespace jsipc
} // namespace mozilla

View File

@ -29,12 +29,12 @@ struct JSIID
struct LocalObject
{
uint64_t id;
uint64_t serializedId;
};
struct RemoteObject
{
uint64_t id;
uint64_t serializedId;
};
union ObjectVariant

View File

@ -38,8 +38,8 @@ WrapperOwner::idOfUnchecked(JSObject *obj)
Value v = GetProxyExtra(obj, 1);
MOZ_ASSERT(v.isDouble());
ObjectId objId = BitwiseCast<uint64_t>(v.toDouble());
MOZ_ASSERT(objId);
ObjectId objId = ObjectId::deserialize(BitwiseCast<uint64_t>(v.toDouble()));
MOZ_ASSERT(!objId.isNull());
return objId;
}
@ -872,13 +872,13 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob
// in findObjectById.
obj = js::UncheckedUnwrap(obj, false);
if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
*objVarp = LocalObject(idOf(obj));
*objVarp = LocalObject(idOf(obj).serialize());
return true;
}
ObjectId id = objectIds_.find(obj);
if (id) {
*objVarp = RemoteObject(id);
if (!id.isNull()) {
*objVarp = RemoteObject(id.serialize());
return true;
}
@ -887,22 +887,13 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob
if (mozilla::dom::IsDOMObject(obj))
mozilla::dom::TryPreserveWrapper(obj);
id = ++lastId_;
if (id > MAX_CPOW_IDS) {
JS_ReportError(cx, "CPOW id limit reached");
return false;
}
id <<= OBJECT_EXTRA_BITS;
if (JS::IsCallable(obj))
id |= OBJECT_IS_CALLABLE;
id = ObjectId(nextSerialNumber_++, JS::IsCallable(obj));
if (!objects_.add(id, obj))
return false;
if (!objectIds_.add(cx, obj, id))
return false;
*objVarp = RemoteObject(id);
*objVarp = RemoteObject(id.serialize());
return true;
}
@ -919,14 +910,9 @@ WrapperOwner::fromObjectVariant(JSContext *cx, ObjectVariant objVar)
JSObject *
WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
{
ObjectId objId = objVar.id();
ObjectId objId = ObjectId::deserialize(objVar.serializedId());
RootedObject obj(cx, findCPOWById(objId));
if (!obj) {
// If we didn't find an existing CPOW, we need to create one.
if (objId > MAX_CPOW_IDS) {
JS_ReportError(cx, "unusable CPOW id");
return nullptr;
}
// All CPOWs live in the privileged junk scope.
RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
@ -947,7 +933,7 @@ WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
incref();
SetProxyExtra(obj, 0, PrivateValue(this));
SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId)));
SetProxyExtra(obj, 1, DoubleValue(BitwiseCast<double>(objId.serialize())));
}
if (!JS_WrapObject(cx, &obj))
@ -958,7 +944,7 @@ WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar)
JSObject *
WrapperOwner::fromLocalObjectVariant(JSContext *cx, LocalObject objVar)
{
ObjectId id = objVar.id();
ObjectId id = ObjectId::deserialize(objVar.serializedId());
Rooted<JSObject*> obj(cx, findObjectById(cx, id));
if (!obj)
return nullptr;