зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1312001 - Scramble hash codes securely, to avoid leaking bits of object and symbol addresses.
MozReview-Commit-ID: yR1cIjrlPP --HG-- extra : rebase_source : 871821e53eee5502cd255d52f02665f6845e3f09
This commit is contained in:
Родитель
2aced9c475
Коммит
5d60d5ca87
|
@ -46,6 +46,10 @@ class MOZ_STACK_CLASS SourceBufferHolder;
|
|||
class HandleValueArray;
|
||||
|
||||
class ObjectOpResult;
|
||||
|
||||
class Symbol;
|
||||
enum class SymbolCode: uint32_t;
|
||||
|
||||
} // namespace JS
|
||||
|
||||
// Do the importing.
|
||||
|
@ -154,6 +158,9 @@ using JS::ObjectOpResult;
|
|||
|
||||
using JS::Zone;
|
||||
|
||||
using JS::Symbol;
|
||||
using JS::SymbolCode;
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* NamespaceImports_h */
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
|
@ -65,20 +66,38 @@ HashableValue::setValue(JSContext* cx, HandleValue v)
|
|||
}
|
||||
|
||||
static HashNumber
|
||||
HashValue(const Value& v)
|
||||
HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs)
|
||||
{
|
||||
// HashableValue::setValue normalizes values so that the SameValue relation
|
||||
// on HashableValues is the same as the == relationship on
|
||||
// value.data.asBits.
|
||||
// value.asRawBits(). So why not just return that? Security.
|
||||
//
|
||||
// To avoid revealing GC of atoms, string-based hash codes are computed
|
||||
// from the string contents rather than any pointer; to avoid revealing
|
||||
// addresses, pointer-based hash codes are computed using the
|
||||
// HashCodeScrambler.
|
||||
|
||||
if (v.isString())
|
||||
return v.toString()->asAtom().hash();
|
||||
if (v.isSymbol()) {
|
||||
Symbol* sym = v.toSymbol();
|
||||
if (sym->isWellKnownSymbol())
|
||||
return HashNumber(sym->code());
|
||||
if (sym->code() == SymbolCode::InSymbolRegistry)
|
||||
return sym->description()->hash();
|
||||
return hcs.scramble(v.asRawBits());
|
||||
}
|
||||
if (v.isObject())
|
||||
return hcs.scramble(v.asRawBits());
|
||||
|
||||
MOZ_ASSERT(!v.isGCThing(), "do not reveal pointers via hash codes");
|
||||
return v.asRawBits();
|
||||
}
|
||||
|
||||
HashNumber
|
||||
HashableValue::hash() const
|
||||
HashableValue::hash(const mozilla::HashCodeScrambler& hcs) const
|
||||
{
|
||||
return HashValue(value);
|
||||
return HashValue(value, hcs);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -365,7 +384,9 @@ MapObject::trace(JSTracer* trc, JSObject* obj)
|
|||
|
||||
struct js::UnbarrieredHashPolicy {
|
||||
typedef Value Lookup;
|
||||
static HashNumber hash(const Lookup& v) { return HashValue(v); }
|
||||
static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) {
|
||||
return HashValue(v, hcs);
|
||||
}
|
||||
static bool match(const Value& k, const Lookup& l) { return k == l; }
|
||||
static bool isEmpty(const Value& v) { return v.isMagic(JS_HASH_KEY_EMPTY); }
|
||||
static void makeEmpty(Value* vp) { vp->setMagic(JS_HASH_KEY_EMPTY); }
|
||||
|
@ -415,17 +436,18 @@ class js::OrderedHashTableRef : public gc::BufferableRef
|
|||
explicit OrderedHashTableRef(ObjectT* obj) : object(obj) {}
|
||||
|
||||
void trace(JSTracer* trc) override {
|
||||
auto table = reinterpret_cast<typename ObjectT::UnbarrieredTable*>(object->getData());
|
||||
auto realTable = object->getData();
|
||||
auto unbarrieredTable = reinterpret_cast<typename ObjectT::UnbarrieredTable*>(realTable);
|
||||
NurseryKeysVector* keys = GetNurseryKeys(object);
|
||||
MOZ_ASSERT(keys);
|
||||
for (JSObject* obj : *keys) {
|
||||
MOZ_ASSERT(obj);
|
||||
Value key = ObjectValue(*obj);
|
||||
Value prior = key;
|
||||
MOZ_ASSERT(UnbarrieredHashPolicy::hash(key) ==
|
||||
HashableValue::Hasher::hash(*reinterpret_cast<HashableValue*>(&key)));
|
||||
MOZ_ASSERT(unbarrieredTable->hash(key) ==
|
||||
realTable->hash(*reinterpret_cast<HashableValue*>(&key)));
|
||||
TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key");
|
||||
table->rekeyOneEntry(prior, key);
|
||||
unbarrieredTable->rekeyOneEntry(prior, key);
|
||||
}
|
||||
DeleteNurseryKeys(object);
|
||||
}
|
||||
|
@ -513,7 +535,8 @@ MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v)
|
|||
MapObject*
|
||||
MapObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
|
||||
{
|
||||
auto map = cx->make_unique<ValueMap>(cx->runtime());
|
||||
auto map = cx->make_unique<ValueMap>(cx->runtime(),
|
||||
cx->compartment()->randomHashCodeScrambler());
|
||||
if (!map || !map->init()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
|
@ -579,7 +602,7 @@ MapObject::is(HandleObject o)
|
|||
}
|
||||
|
||||
#define ARG0_KEY(cx, args, key) \
|
||||
Rooted<HashableValue> key(cx); \
|
||||
Rooted<HashableValue> key(cx); \
|
||||
if (args.length() > 0 && !key.setValue(cx, args[0])) \
|
||||
return false
|
||||
|
||||
|
@ -1094,7 +1117,8 @@ SetObject::add(JSContext* cx, HandleObject obj, HandleValue k)
|
|||
SetObject*
|
||||
SetObject::create(JSContext* cx, HandleObject proto /* = nullptr */)
|
||||
{
|
||||
auto set = cx->make_unique<ValueSet>(cx->runtime());
|
||||
auto set = cx->make_unique<ValueSet>(cx->runtime(),
|
||||
cx->compartment()->randomHashCodeScrambler());
|
||||
if (!set || !set->init()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
|
|
|
@ -32,7 +32,9 @@ class HashableValue
|
|||
public:
|
||||
struct Hasher {
|
||||
typedef HashableValue Lookup;
|
||||
static HashNumber hash(const Lookup& v) { return v.hash(); }
|
||||
static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler& hcs) {
|
||||
return v.hash(hcs);
|
||||
}
|
||||
static bool match(const HashableValue& k, const Lookup& l) { return k == l; }
|
||||
static bool isEmpty(const HashableValue& v) { return v.value.isMagic(JS_HASH_KEY_EMPTY); }
|
||||
static void makeEmpty(HashableValue* vp) { vp->value = MagicValue(JS_HASH_KEY_EMPTY); }
|
||||
|
@ -41,7 +43,7 @@ class HashableValue
|
|||
HashableValue() : value(UndefinedValue()) {}
|
||||
|
||||
MOZ_MUST_USE bool setValue(JSContext* cx, HandleValue v);
|
||||
HashNumber hash() const;
|
||||
HashNumber hash(const mozilla::HashCodeScrambler& hcs) const;
|
||||
bool operator==(const HashableValue& other) const;
|
||||
HashableValue trace(JSTracer* trc) const;
|
||||
Value get() const { return value.get(); }
|
||||
|
|
|
@ -29,12 +29,15 @@
|
|||
*
|
||||
* See the comment about "Hash policy" in HashTable.h for general features that
|
||||
* hash policy classes must provide. Hash policies for OrderedHashMaps and Sets
|
||||
* must additionally provide a distinguished "empty" key value and the
|
||||
* differ in that the hash() method takes an extra argument:
|
||||
* static js::HashNumber hash(Lookup, const HashCodeScrambler&);
|
||||
* They must additionally provide a distinguished "empty" key value and the
|
||||
* following static member functions:
|
||||
* bool isEmpty(const Key&);
|
||||
* void makeEmpty(Key*);
|
||||
*/
|
||||
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
using mozilla::Forward;
|
||||
|
@ -78,10 +81,11 @@ class OrderedHashTable
|
|||
uint32_t hashShift; // multiplicative hash shift
|
||||
Range* ranges; // list of all live Ranges on this table
|
||||
AllocPolicy alloc;
|
||||
mozilla::HashCodeScrambler hcs; // don't reveal pointer hash codes
|
||||
|
||||
public:
|
||||
explicit OrderedHashTable(AllocPolicy& ap)
|
||||
: hashTable(nullptr), data(nullptr), dataLength(0), ranges(nullptr), alloc(ap) {}
|
||||
OrderedHashTable(AllocPolicy& ap, mozilla::HashCodeScrambler hcs)
|
||||
: hashTable(nullptr), data(nullptr), dataLength(0), ranges(nullptr), alloc(ap), hcs(hcs) {}
|
||||
|
||||
MOZ_MUST_USE bool init() {
|
||||
MOZ_ASSERT(!hashTable, "init must be called at most once");
|
||||
|
@ -432,8 +436,8 @@ class OrderedHashTable
|
|||
void rekeyFront(const Key& k) {
|
||||
MOZ_ASSERT(valid());
|
||||
Data& entry = ht->data[i];
|
||||
HashNumber oldHash = prepareHash(Ops::getKey(entry.element)) >> ht->hashShift;
|
||||
HashNumber newHash = prepareHash(k) >> ht->hashShift;
|
||||
HashNumber oldHash = ht->prepareHash(Ops::getKey(entry.element)) >> ht->hashShift;
|
||||
HashNumber newHash = ht->prepareHash(k) >> ht->hashShift;
|
||||
Ops::setKey(entry.element, k);
|
||||
if (newHash != oldHash) {
|
||||
// Remove this entry from its old hash chain. (If this crashes
|
||||
|
@ -558,10 +562,12 @@ class OrderedHashTable
|
|||
*/
|
||||
static double minDataFill() { return 0.25; }
|
||||
|
||||
static HashNumber prepareHash(const Lookup& l) {
|
||||
return ScrambleHashCode(Ops::hash(l));
|
||||
public:
|
||||
HashNumber prepareHash(const Lookup& l) const {
|
||||
return ScrambleHashCode(Ops::hash(l, hcs));
|
||||
}
|
||||
|
||||
private:
|
||||
/* The size of hashTable, in elements. Always a power of two. */
|
||||
uint32_t hashBuckets() const {
|
||||
return 1 << (HashNumberSizeBits - hashShift);
|
||||
|
@ -740,7 +746,7 @@ class OrderedHashMap
|
|||
public:
|
||||
typedef typename Impl::Range Range;
|
||||
|
||||
explicit OrderedHashMap(AllocPolicy ap = AllocPolicy()) : impl(ap) {}
|
||||
OrderedHashMap(AllocPolicy ap, mozilla::HashCodeScrambler hcs) : impl(ap, hcs) {}
|
||||
MOZ_MUST_USE bool init() { return impl.init(); }
|
||||
uint32_t count() const { return impl.count(); }
|
||||
bool has(const Key& key) const { return impl.has(key); }
|
||||
|
@ -755,6 +761,8 @@ class OrderedHashMap
|
|||
return impl.put(Entry(key, Forward<V>(value)));
|
||||
}
|
||||
|
||||
HashNumber hash(const Key& key) const { return impl.prepareHash(key); }
|
||||
|
||||
void rekeyOneEntry(const Key& current, const Key& newKey) {
|
||||
const Entry* e = get(current);
|
||||
if (!e)
|
||||
|
@ -796,7 +804,7 @@ class OrderedHashSet
|
|||
public:
|
||||
typedef typename Impl::Range Range;
|
||||
|
||||
explicit OrderedHashSet(AllocPolicy ap = AllocPolicy()) : impl(ap) {}
|
||||
explicit OrderedHashSet(AllocPolicy ap, mozilla::HashCodeScrambler hcs) : impl(ap, hcs) {}
|
||||
MOZ_MUST_USE bool init() { return impl.init(); }
|
||||
uint32_t count() const { return impl.count(); }
|
||||
bool has(const T& value) const { return impl.has(value); }
|
||||
|
@ -805,6 +813,8 @@ class OrderedHashSet
|
|||
bool remove(const T& value, bool* foundp) { return impl.remove(value, foundp); }
|
||||
MOZ_MUST_USE bool clear() { return impl.clear(); }
|
||||
|
||||
HashNumber hash(const T& value) const { return impl.prepareHash(value); }
|
||||
|
||||
void rekeyOneEntry(const T& current, const T& newKey) {
|
||||
return impl.rekeyOneEntry(current, newKey, newKey);
|
||||
}
|
||||
|
|
|
@ -141,7 +141,9 @@ namespace gc {
|
|||
|
||||
struct WeakKeyTableHashPolicy {
|
||||
typedef JS::GCCellPtr Lookup;
|
||||
static HashNumber hash(const Lookup& v) { return mozilla::HashGeneric(v.asCell()); }
|
||||
static HashNumber hash(const Lookup& v, const mozilla::HashCodeScrambler&) {
|
||||
return mozilla::HashGeneric(v.asCell());
|
||||
}
|
||||
static bool match(const JS::GCCellPtr& k, const Lookup& l) { return k == l; }
|
||||
static bool isEmpty(const JS::GCCellPtr& v) { return !v; }
|
||||
static void makeEmpty(JS::GCCellPtr* vp) { *vp = nullptr; }
|
||||
|
|
|
@ -31,6 +31,7 @@ JS::Zone::Zone(JSRuntime* rt)
|
|||
types(this),
|
||||
compartments(),
|
||||
gcGrayRoots(),
|
||||
gcWeakKeys(SystemAllocPolicy(), rt->randomHashCodeScrambler()),
|
||||
typeDescrObjects(this, SystemAllocPolicy()),
|
||||
gcMallocBytes(0),
|
||||
gcMallocGCTriggered(false),
|
||||
|
|
|
@ -77,6 +77,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
|
|||
nonSyntacticLexicalEnvironments_(nullptr),
|
||||
gcIncomingGrayPointers(nullptr),
|
||||
debugModeBits(0),
|
||||
randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
|
||||
watchpointMap(nullptr),
|
||||
scriptCountsMap(nullptr),
|
||||
debugScriptMap(nullptr),
|
||||
|
@ -1277,6 +1278,13 @@ JSCompartment::addTelemetry(const char* filename, DeprecatedLanguageExtension e)
|
|||
sawDeprecatedLanguageExtension[e] = true;
|
||||
}
|
||||
|
||||
mozilla::HashCodeScrambler
|
||||
JSCompartment::randomHashCodeScrambler()
|
||||
{
|
||||
return mozilla::HashCodeScrambler(randomKeyGenerator_.next(),
|
||||
randomKeyGenerator_.next());
|
||||
}
|
||||
|
||||
AutoSetNewObjectMetadata::AutoSetNewObjectMetadata(ExclusiveContext* ecx
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
|
||||
: CustomAutoRooter(ecx)
|
||||
|
|
|
@ -701,6 +701,12 @@ struct JSCompartment
|
|||
// Initialize randomNumberGenerator if needed.
|
||||
void ensureRandomNumberGenerator();
|
||||
|
||||
private:
|
||||
mozilla::non_crypto::XorShift128PlusRNG randomKeyGenerator_;
|
||||
|
||||
public:
|
||||
mozilla::HashCodeScrambler randomHashCodeScrambler();
|
||||
|
||||
static size_t offsetOfRegExps() {
|
||||
return offsetof(JSCompartment, regExps);
|
||||
}
|
||||
|
|
|
@ -246,6 +246,7 @@ using namespace js::gc;
|
|||
|
||||
using mozilla::ArrayLength;
|
||||
using mozilla::Get;
|
||||
using mozilla::HashCodeScrambler;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Swap;
|
||||
|
||||
|
@ -4163,7 +4164,7 @@ js::gc::MarkingValidator::nonIncrementalMark(AutoLockForExclusiveAccess& lock)
|
|||
* For saving, smush all of the keys into one big table and split them back
|
||||
* up into per-zone tables when restoring.
|
||||
*/
|
||||
gc::WeakKeyTable savedWeakKeys;
|
||||
gc::WeakKeyTable savedWeakKeys(SystemAllocPolicy(), runtime->randomHashCodeScrambler());
|
||||
if (!savedWeakKeys.init())
|
||||
return;
|
||||
|
||||
|
|
|
@ -776,6 +776,32 @@ JSRuntime::removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promis
|
|||
PromiseRejectionHandlingState::Handled, data);
|
||||
}
|
||||
|
||||
mozilla::non_crypto::XorShift128PlusRNG&
|
||||
JSRuntime::randomKeyGenerator()
|
||||
{
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(this));
|
||||
if (randomKeyGenerator_.isNothing()) {
|
||||
mozilla::Array<uint64_t, 2> seed;
|
||||
GenerateXorShift128PlusSeed(seed);
|
||||
randomKeyGenerator_.emplace(seed[0], seed[1]);
|
||||
}
|
||||
return randomKeyGenerator_.ref();
|
||||
}
|
||||
|
||||
mozilla::HashCodeScrambler
|
||||
JSRuntime::randomHashCodeScrambler()
|
||||
{
|
||||
auto& rng = randomKeyGenerator();
|
||||
return mozilla::HashCodeScrambler(rng.next(), rng.next());
|
||||
}
|
||||
|
||||
mozilla::non_crypto::XorShift128PlusRNG
|
||||
JSRuntime::forkRandomKeyGenerator()
|
||||
{
|
||||
auto& rng = randomKeyGenerator();
|
||||
return mozilla::non_crypto::XorShift128PlusRNG(rng.next(), rng.next());
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::updateMallocCounter(size_t nbytes)
|
||||
{
|
||||
|
|
|
@ -769,6 +769,15 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
void addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);
|
||||
void removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);
|
||||
|
||||
private:
|
||||
// Used to generate random keys for hash tables.
|
||||
mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> randomKeyGenerator_;
|
||||
mozilla::non_crypto::XorShift128PlusRNG& randomKeyGenerator();
|
||||
|
||||
public:
|
||||
mozilla::HashCodeScrambler randomHashCodeScrambler();
|
||||
mozilla::non_crypto::XorShift128PlusRNG forkRandomKeyGenerator();
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Self-hosting support
|
||||
//-------------------------------------------------------------------------
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Char16.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
@ -298,6 +299,90 @@ HashString(const wchar_t* aStr, size_t aLength)
|
|||
MOZ_MUST_USE extern MFBT_API uint32_t
|
||||
HashBytes(const void* bytes, size_t aLength);
|
||||
|
||||
/**
|
||||
* A pseudorandom function mapping 32-bit integers to 32-bit integers.
|
||||
*
|
||||
* This is for when you're feeding private data (like pointer values or credit
|
||||
* card numbers) to a non-crypto hash function (like HashBytes) and then using
|
||||
* the hash code for something that untrusted parties could observe (like a JS
|
||||
* Map). Plug in a HashCodeScrambler before that last step to avoid leaking the
|
||||
* private data.
|
||||
*
|
||||
* By itself, this does not prevent hash-flooding DoS attacks, because an
|
||||
* attacker can still generate many values with exactly equal hash codes by
|
||||
* attacking the non-crypto hash function alone. Equal hash codes will, of
|
||||
* course, still be equal however much you scramble them.
|
||||
*
|
||||
* The algorithm is SipHash-1-3. See <https://131002.net/siphash/>.
|
||||
*/
|
||||
class HashCodeScrambler
|
||||
{
|
||||
struct SipHasher;
|
||||
|
||||
uint64_t mK0, mK1;
|
||||
|
||||
public:
|
||||
/** Creates a new scrambler with the given 128-bit key. */
|
||||
constexpr HashCodeScrambler(uint64_t aK0, uint64_t aK1) : mK0(aK0), mK1(aK1) {}
|
||||
|
||||
/**
|
||||
* Scramble a hash code. Always produces the same result for the same
|
||||
* combination of key and hash code.
|
||||
*/
|
||||
uint32_t scramble(uint32_t aHashCode) const
|
||||
{
|
||||
SipHasher hasher(mK0, mK1);
|
||||
return uint32_t(hasher.sipHash(aHashCode));
|
||||
}
|
||||
|
||||
private:
|
||||
struct SipHasher
|
||||
{
|
||||
SipHasher(uint64_t aK0, uint64_t aK1)
|
||||
{
|
||||
// 1. Initialization.
|
||||
mV0 = aK0 ^ UINT64_C(0x736f6d6570736575);
|
||||
mV1 = aK1 ^ UINT64_C(0x646f72616e646f6d);
|
||||
mV2 = aK0 ^ UINT64_C(0x6c7967656e657261);
|
||||
mV3 = aK1 ^ UINT64_C(0x7465646279746573);
|
||||
}
|
||||
|
||||
uint64_t sipHash(uint64_t aM)
|
||||
{
|
||||
// 2. Compression.
|
||||
mV3 ^= aM;
|
||||
sipRound();
|
||||
mV0 ^= aM;
|
||||
|
||||
// 3. Finalization.
|
||||
mV2 ^= 0xff;
|
||||
for (int i = 0; i < 3; i++)
|
||||
sipRound();
|
||||
return mV0 ^ mV1 ^ mV2 ^ mV3;
|
||||
}
|
||||
|
||||
void sipRound()
|
||||
{
|
||||
mV0 += mV1;
|
||||
mV1 = RotateLeft(mV1, 13);
|
||||
mV1 ^= mV0;
|
||||
mV0 = RotateLeft(mV0, 32);
|
||||
mV2 += mV3;
|
||||
mV3 = RotateLeft(mV3, 16);
|
||||
mV3 ^= mV2;
|
||||
mV0 += mV3;
|
||||
mV3 = RotateLeft(mV3, 21);
|
||||
mV3 ^= mV0;
|
||||
mV2 += mV1;
|
||||
mV1 = RotateLeft(mV1, 17);
|
||||
mV1 ^= mV2;
|
||||
mV2 = RotateLeft(mV2, 32);
|
||||
}
|
||||
|
||||
uint64_t mV0, mV1, mV2, mV3;
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace mozilla */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче