From f092f72653947413483183f5141facfff3da1542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 24 May 2016 15:03:12 +0200 Subject: [PATCH 01/70] Implemented very basic number tainting functionality --- js/src/jsnum.cpp | 111 ++++++++++++++++++++++++++++++++--- js/src/jsstr.cpp | 10 +++- js/src/jstaint.cpp | 11 ++++ js/src/jstaint.h | 28 +++++++++ js/src/vm/Interpreter.cpp | 15 ++++- js/src/vm/NumberObject-inl.h | 11 ++++ js/src/vm/NumberObject.h | 39 ++++++++++++ 7 files changed, 216 insertions(+), 9 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 6031a4cc87035..ba131028caf7c 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -466,7 +466,16 @@ static const JSFunctionSpec number_functions[] = { const Class NumberObject::class_ = { js_Number_str, - JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number) + // TaintFox: The number object now needs a private slot to store the taint information + JSCLASS_HAS_RESERVED_SLOTS(NumberObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number) | JSCLASS_HAS_PRIVATE, + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + Finalize }; static bool @@ -518,13 +527,20 @@ MOZ_ALWAYS_INLINE bool num_toSource_impl(JSContext* cx, const CallArgs& args) { double d = Extract(args.thisv()); - StringBuffer sb(cx); - if (!sb.append("(new Number(") || - !NumberValueToStringBuffer(cx, NumberValue(d), sb) || - !sb.append("))")) - { - return false; + + // TaintFox: Hide the fact that tainted numbers are NumberObjects. + if (isTaintedNumber(args.thisv())) { + if (!NumberValueToStringBuffer(cx, NumberValue(d), sb)) { + return false; + } + } else { + if (!sb.append("(new Number(") || + !NumberValueToStringBuffer(cx, NumberValue(d), sb) || + !sb.append("))")) + { + return false; + } } JSString* str = sb.finishString(); @@ -1011,6 +1027,66 @@ static const JSFunctionSpec number_methods[] = { JS_FS_END }; +static bool +num_taint_getter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNull(); + + // This will be the case for unboxed integers. In that case just return null. + if (!args.thisv().isObject()) + return true; + + RootedObject number(cx, &args.thisv().toObject()); + if (!number->is()) + return true; + + const TaintFlow& taint = number->as().taint(); + + // TODO(samuel) refactor into separate function + AutoValueVector taint_flow(cx); + for (TaintNode& taint_node : taint) { + RootedObject node(cx, JS_NewObject(cx, nullptr)); + if (!node) + return false; + + RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); + if (!operation) + return false; + + if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + // Wrap the arguments. + AutoValueVector taint_arguments(cx); + for (auto& taint_argument : taint_node.operation().arguments()) { + RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); + if (!argument) + return false; + + if (!taint_arguments.append(StringValue(argument))) + return false; + } + + RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); + if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + taint_flow.append(ObjectValue(*node)); + } + + args.rval().setObject(*NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); + + return true; +} + +/* TaintFox: Add |taint| property. */ +static const +JSPropertySpec number_taint_properties[] = { + JS_PSG("taint", num_taint_getter, JSPROP_PERMANENT), + JS_PS_END +}; + // ES6 draft ES6 15.7.3.12 static bool Number_isInteger(JSContext* cx, unsigned argc, Value* vp) @@ -1027,12 +1103,29 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp) return true; } +// TaintFox: taint numbers manually using this method. +static bool +Number_tainted(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + double d; + if (!ToNumber(cx, args.get(0), &d)) { + return false; + } + + JSObject* number = NumberObject::createTainted(cx, d, TaintFlow(TaintSource("manual taint source", { taintarg(cx, d) }))); + args.rval().setObject(*number); + + return true; +} static const JSFunctionSpec number_static_methods[] = { JS_SELF_HOSTED_FN("isFinite", "Number_isFinite", 1,0), JS_FN("isInteger", Number_isInteger, 1, 0), JS_SELF_HOSTED_FN("isNaN", "Number_isNaN", 1,0), JS_SELF_HOSTED_FN("isSafeInteger", "Number_isSafeInteger", 1,0), + JS_FN("tainted", Number_tainted, 1,0), JS_FS_END }; @@ -1180,6 +1273,10 @@ js::InitNumberClass(JSContext* cx, HandleObject obj) if (!JS_DefineFunctions(cx, global, number_functions)) return nullptr; + /* TaintFox: Add taint related properties to all number instances. */ + if (!DefinePropertiesAndFunctions(cx, numberProto, number_taint_properties, nullptr)) + return nullptr; + /* Number.parseInt should be the same function object as global parseInt. */ RootedId parseIntId(cx, NameToId(cx->names().parseInt)); JSFunction* parseInt = DefineFunction(cx, global, parseIntId, num_parseInt, 2, diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 60794d88e9c10..7d7ecfc96c046 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1309,7 +1309,15 @@ js::str_charCodeAt_impl(JSContext* cx, HandleString string, HandleValue index, M char16_t c; if (!string->getChar(cx, i , &c)) return false; - res.setInt32(c); + + // TaintFox: Propagate taint into the char codes. + const TaintFlow* taint; + if (string->isTainted() && (taint = string->taint().at(i))) { + res.setObject(*NumberObject::createTainted(cx, c, *taint)); + } else { + res.setInt32(c); + } + return true; out_of_range: diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 66cba269eb67d..5c278b537396d 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -1,6 +1,8 @@ #include "jsapi.h" #include "jstaint.h" +#include "vm/NumberObject.h" + #include #include @@ -71,3 +73,12 @@ void js::MarkTaintedFunctionArguments(JSContext* cx, const JSFunction* function, } */ } + +bool js::isTaintedNumber(const Value& val) +{ + if (val.isObject() && val.toObject().is()) { + NumberObject& number = val.toObject().as(); + return number.isTainted(); + } + return false; +} diff --git a/js/src/jstaint.h b/js/src/jstaint.h index 345eee669a242..d8c54ec678768 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -32,6 +32,34 @@ std::u16string taintarg(JSContext* cx, int32_t num); // This is mainly useful for tracing tainted arguments through the code. void MarkTaintedFunctionArguments(JSContext* cx, const JSFunction* function, const CallArgs& args); +// Check if the argument value is a tainted number object. +bool isTaintedNumber(const JS::Value& val); + +} + +#define HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, OP) \ +{ \ + decltype(lhs) __lhs = (lhs); \ + decltype(rhs) __rhs = (rhs); \ + if (isTaintedNumber(__lhs) || isTaintedNumber(__rhs)) { \ + double lhsValue, rhsValue; \ + TaintFlow taint; \ + if (isTaintedNumber(__lhs)) { \ + taint = __lhs.toObject().as().taint(); \ + } else { \ + taint = __rhs.toObject().as().taint(); \ + } \ + \ + ToNumber(cx, __lhs, &lhsValue); \ + ToNumber(cx, __rhs, &rhsValue); \ + \ + if (taint) { \ + TaintFlow newTaint = taint.extend(TaintOperation(#OP, \ + {taintarg(cx, lhsValue), taintarg(cx, rhsValue)})); \ + res.setObject(*NumberObject::createTainted(cx, lhsValue OP rhsValue, newTaint)); \ + return true; \ + } \ + } \ } #endif diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b76c7dcd12654..6d6ef88e8f06a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -908,7 +908,8 @@ js::TypeOfObject(JSObject* obj) JSType js::TypeOfValue(const Value& v) { - if (v.isNumber()) + // TaintFox: Hide the fact that tainted numbers are number objects. + if (v.isNumber() || isTaintedNumber(v)) return JSTYPE_NUMBER; if (v.isString()) return JSTYPE_STRING; @@ -1311,6 +1312,9 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta } } + // TaintFox: Taint propagation when adding tainted numbers. + HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, +); + if (!ToPrimitive(cx, lhs)) return false; if (!ToPrimitive(cx, rhs)) @@ -1359,6 +1363,9 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { + // TaintFox: Taint propagation when subtracting tainted numbers. + HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, -); + double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; @@ -1369,6 +1376,9 @@ SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { + // TaintFox: Taint propagation when multiplying tainted numbers. + HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, *); + double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; @@ -1379,6 +1389,9 @@ MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { + // TaintFox: Taint propagation when dividing tainted numbers. + HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, /); + double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; diff --git a/js/src/vm/NumberObject-inl.h b/js/src/vm/NumberObject-inl.h index 7e0237b1c864d..200eec2469989 100644 --- a/js/src/vm/NumberObject-inl.h +++ b/js/src/vm/NumberObject-inl.h @@ -23,6 +23,17 @@ NumberObject::create(JSContext* cx, double d, HandleObject proto /* = nullptr */ return obj; } +inline NumberObject* +NumberObject::createTainted(JSContext* cx, double d, const TaintFlow& taint, HandleObject proto /* = nullptr */) +{ + NumberObject* obj = NewObjectWithClassProto(cx, proto); + if (!obj) + return nullptr; + obj->setPrimitiveValue(d); + obj->setTaint(taint); + return obj; +} + } // namespace js #endif /* vm_NumberObject_inl_h */ diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index dd808309c782b..30de7ad0be44a 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -7,10 +7,13 @@ #ifndef vm_NumberObject_h #define vm_NumberObject_h +#include "Taint.h" + #include "jsnum.h" namespace js { +// TaintFox: Number objects can be tainted. class NumberObject : public NativeObject { /* Stores this Number object's [[PrimitiveValue]]. */ @@ -28,10 +31,46 @@ class NumberObject : public NativeObject static inline NumberObject* create(JSContext* cx, double d, HandleObject proto = nullptr); + static inline NumberObject* createTainted(JSContext* cx, double d, + const TaintFlow& taint, + HandleObject proto = nullptr); + + // TaintFox: A finalizer is required for correct memory handling. + static void Finalize(FreeOp* fop, JSObject* obj) { + NumberObject& number = obj->as(); + TaintNode* head = (TaintNode*)number.getPrivate(); + if (head) + head->release(); + } + double unbox() const { return getFixedSlot(PRIMITIVE_VALUE_SLOT).toNumber(); } + + TaintFlow taint() const { + TaintNode* head = (TaintNode*)getPrivate(); + if (head) + head->addref(); + return TaintFlow(head); + } + + void setTaint(const TaintFlow& taint) { + TaintNode* current = (TaintNode*)getPrivate(); + if (current) + current->release(); + + TaintNode* head = taint.head(); + if (head) + head->addref(); + + setPrivate(head); + } + + bool isTainted() const { + return !!getPrivate(); + } + private: inline void setPrimitiveValue(double d) { setFixedSlot(PRIMITIVE_VALUE_SLOT, NumberValue(d)); From d3fea975fab5f6ce8f674ff01bd2ee13f0e618be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Sun, 29 May 2016 18:54:17 +0200 Subject: [PATCH 02/70] Tainted Number <-> String conversion --- js/src/jsstr.cpp | 17 +++++++++++++++- js/src/jstaint.cpp | 9 +++++++++ js/src/jstaint.h | 3 +++ js/src/tests/basic_number_operations.js | 8 ++++++++ .../tests/taint/number_string_conversions.js | 20 +++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 js/src/tests/basic_number_operations.js create mode 100644 js/src/tests/taint/number_string_conversions.js diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 7d7ecfc96c046..22fc9da8dbdf8 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -4654,15 +4654,21 @@ str_fromCharCode_few_args(JSContext* cx, const CallArgs& args) MOZ_ASSERT(args.length() <= JSFatInlineString::MAX_LENGTH_TWO_BYTE); char16_t chars[JSFatInlineString::MAX_LENGTH_TWO_BYTE]; + StringTaint taint; for (unsigned i = 0; i < args.length(); i++) { uint16_t code; if (!ToUint16(cx, args[i], &code)) return false; chars[i] = char16_t(code); + + // TaintFox: propagate taint into newly constructed string. + if (isTaintedNumber(args[i])) + taint.set(i, getNumberTaint(args[i])); } JSString* str = NewStringCopyN(cx, chars, args.length()); if (!str) return false; + str->setTaint(taint); args.rval().setString(str); return true; } @@ -4687,6 +4693,7 @@ js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) return str_fromCharCode_few_args(cx, args); char16_t* chars = cx->pod_malloc(args.length() + 1); + StringTaint taint; if (!chars) return false; for (unsigned i = 0; i < args.length(); i++) { @@ -4696,6 +4703,9 @@ js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) return false; } chars[i] = char16_t(code); + // TaintFox: propagate taint into newly constructed string. + if (isTaintedNumber(args[i])) + taint.set(i, getNumberTaint(args[i])); } chars[args.length()] = 0; JSString* str = NewString(cx, chars, args.length()); @@ -4704,6 +4714,7 @@ js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) return false; } + str->setTaint(taint); args.rval().setString(str); return true; } @@ -4716,7 +4727,7 @@ js::str_fromCharCode_one_arg(JSContext* cx, HandleValue code, MutableHandleValue if (!ToUint16(cx, code, &ucode)) return false; - if (StaticStrings::hasUnit(ucode)) { + if (StaticStrings::hasUnit(ucode) && !isTaintedNumber(code)) { rval.setString(cx->staticStrings().getUnit(ucode)); return true; } @@ -4726,6 +4737,10 @@ js::str_fromCharCode_one_arg(JSContext* cx, HandleValue code, MutableHandleValue if (!str) return false; + // TaintFox: propagate taint into newly constructed string. + if (isTaintedNumber(code)) + str->setTaint(StringTaint(getNumberTaint(code), 1)); + rval.setString(str); return true; } diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 5c278b537396d..99fbb202682b9 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -82,3 +82,12 @@ bool js::isTaintedNumber(const Value& val) } return false; } + +TaintFlow js::getNumberTaint(const Value& val) +{ + if (val.isObject() && val.toObject().is()) { + NumberObject& number = val.toObject().as(); + return number.taint(); + } + return TaintFlow(nullptr); +} diff --git a/js/src/jstaint.h b/js/src/jstaint.h index d8c54ec678768..042290a8c9213 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -35,6 +35,9 @@ void MarkTaintedFunctionArguments(JSContext* cx, const JSFunction* function, con // Check if the argument value is a tainted number object. bool isTaintedNumber(const JS::Value& val); +// Extract the taint information from a number. +TaintFlow getNumberTaint(const JS::Value& val); + } #define HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, OP) \ diff --git a/js/src/tests/basic_number_operations.js b/js/src/tests/basic_number_operations.js new file mode 100644 index 0000000000000..803f39e477ca6 --- /dev/null +++ b/js/src/tests/basic_number_operations.js @@ -0,0 +1,8 @@ +function basicNumberOperationTest() { + var n = taint(42); +} + +runTaintTest(basicNumberOperationTest); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js new file mode 100644 index 0000000000000..f7df19419632b --- /dev/null +++ b/js/src/tests/taint/number_string_conversions.js @@ -0,0 +1,20 @@ +function numberStringConversionTests() { + // Test single character taint propagation + var taintedStr = randomTaintedString(); + assertTainted(String.fromCharCode(taintedStr.charCodeAt(0))); + + // Test multi character taint propagation + taintedStr = randomMultiTaintedString(); + var chars = []; + for (var i = 0; i < taintedStr.length; i++) { + chars.push(taintedStr.charCodeAt(i)); + } + + var copiedString = String.fromCharCode.apply(null, chars); + assertEqualTaint(copiedString, taintedStr); +} + +runTaintTest(numberStringConversionTests); + +if (typeof reportCompare === 'function') + reportCompare(true, true); From 427cb4f489f7d11c83903a52d3980c5ea503e686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Sun, 29 May 2016 19:19:31 +0200 Subject: [PATCH 03/70] Basic number tainting test --- js/src/jsnum.cpp | 4 ++-- js/src/jsnum.h | 3 +++ js/src/shell/js.cpp | 12 +++++++++++- js/src/tests/basic_number_operations.js | 8 -------- js/src/tests/taint/number_tainting.js | 22 ++++++++++++++++++++++ js/src/tests/taint/shell.js | 9 +++++++++ 6 files changed, 47 insertions(+), 11 deletions(-) delete mode 100644 js/src/tests/basic_number_operations.js create mode 100644 js/src/tests/taint/number_tainting.js diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index ba131028caf7c..d9daf92f83b06 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1104,8 +1104,8 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp) } // TaintFox: taint numbers manually using this method. -static bool -Number_tainted(JSContext* cx, unsigned argc, Value* vp) +bool +js::Number_tainted(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); diff --git a/js/src/jsnum.h b/js/src/jsnum.h index f94bf085b3a78..21d2fe68ad220 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -32,6 +32,9 @@ namespace js { +// TaintFox: Exported for the js shell: taint(number). +bool Number_tainted(JSContext* cx, unsigned argc, Value* vp); + class StringBuffer; extern bool diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 7e471bd8348f5..0cd0f7665bacc 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4881,7 +4881,17 @@ EntryPoints(JSContext* cx, unsigned argc, Value* vp) static bool Taint(JSContext* cx, unsigned argc, Value* vp) { - return str_tainted(cx, argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 1) { + JS_ReportError(cx, "Wrong number of arguments"); + return false; + } + + if (args[0].isNumber()) + return Number_tainted(cx, argc, vp); + else + return str_tainted(cx, argc, vp); } static bool diff --git a/js/src/tests/basic_number_operations.js b/js/src/tests/basic_number_operations.js deleted file mode 100644 index 803f39e477ca6..0000000000000 --- a/js/src/tests/basic_number_operations.js +++ /dev/null @@ -1,8 +0,0 @@ -function basicNumberOperationTest() { - var n = taint(42); -} - -runTaintTest(basicNumberOperationTest); - -if (typeof reportCompare === 'function') - reportCompare(true, true); diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js new file mode 100644 index 0000000000000..32dca958835a7 --- /dev/null +++ b/js/src/tests/taint/number_tainting.js @@ -0,0 +1,22 @@ +function numberTaintingTest() { + var a = taint(42); + var b = taint(13.37); + assertNumberTainted(a); + assertNumberTainted(b); + + // Basic arithmetic tests + assertNumberTainted(a + 13.37); + assertNumberTainted(a - 13.37); + assertNumberTainted(a * 13.37); + assertNumberTainted(a / 13.37); + + assertNumberTainted(a + b); + assertNumberTainted(a - b); + assertNumberTainted(a * b); + assertNumberTainted(a / b); +} + +runTaintTest(numberTaintingTest); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/tests/taint/shell.js b/js/src/tests/taint/shell.js index 275669f11c359..de038f28f9397 100644 --- a/js/src/tests/taint/shell.js +++ b/js/src/tests/taint/shell.js @@ -365,3 +365,12 @@ if (typeof runTaintTest === 'undefined') { runJITTest(doTest); } } + +if (typeof assertNumberTainted === 'undefined') { + // Assert that the given number is tainted. + var assertNumberTainted = function(num) { + if (num.taint.length == 0) { + throw Error("Number ('" + num + "') is not tainted"); + } + } +} From f45272b66b5b0e9d38a5269bfc83d1b5d187e319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Sun, 29 May 2016 20:20:47 +0200 Subject: [PATCH 04/70] Bitwise number operations are now taint aware --- js/src/jit/CodeGenerator.cpp | 6 ++- js/src/jit/Recover.cpp | 36 ++++++++-------- js/src/jit/SharedIC.cpp | 24 +++-------- js/src/tests/taint/number_tainting.js | 8 ++++ js/src/vm/Interpreter-inl.h | 62 +++++++++++++++++++++------ js/src/vm/Interpreter.cpp | 26 ++++++++--- 6 files changed, 106 insertions(+), 56 deletions(-) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 4900c325e31f7..e5fe232f94926 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -9044,7 +9044,8 @@ CodeGenerator::visitThrow(LThrow* lir) callVM(ThrowInfoCodeGen, lir); } -typedef bool (*BitNotFn)(JSContext*, HandleValue, int* p); +// TaintFox: Signature changed to return a Javascript value. Required to support tainted numbers. +typedef bool (*BitNotFn)(JSContext*, HandleValue, MutableHandleValue p); static const VMFunction BitNotInfo = FunctionInfo(BitNot); void @@ -9054,7 +9055,8 @@ CodeGenerator::visitBitNotV(LBitNotV* lir) callVM(BitNotInfo, lir); } -typedef bool (*BitopFn)(JSContext*, HandleValue, HandleValue, int* p); +// TaintFox: Signature changed to return a Javascript value. Required to support tainted numbers. +typedef bool (*BitopFn)(JSContext*, HandleValue, HandleValue, MutableHandleValue p); static const VMFunction BitAndInfo = FunctionInfo(BitAnd); static const VMFunction BitOrInfo = FunctionInfo(BitOr); static const VMFunction BitXorInfo = FunctionInfo(BitXor); diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 18fef3aef73cb..17fd8b96f17c6 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -163,11 +163,11 @@ RBitNot::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue operand(cx, iter.read()); - int32_t result; - if (!js::BitNot(cx, operand, &result)) + // TaintFox: modified due to changed signature of BitNot. + RootedValue rootedResult(cx); + if (!js::BitNot(cx, operand, &rootedResult)) return false; - RootedValue rootedResult(cx, js::Int32Value(result)); iter.storeInstructionResult(rootedResult); return true; } @@ -188,13 +188,13 @@ RBitAnd::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitAnd(cx, lhs, rhs, &result)) + // TaintFox: modified due to changed signature of BitAnd. + RootedValue rootedResult(cx); + if (!js::BitAnd(cx, lhs, rhs, &rootedResult)) return false; - RootedValue rootedResult(cx, js::Int32Value(result)); iter.storeInstructionResult(rootedResult); return true; } @@ -215,13 +215,13 @@ RBitOr::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitOr(cx, lhs, rhs, &result)) + // TaintFox: modified due to changed signature of BitOr. + RootedValue asValue(cx); + if (!js::BitOr(cx, lhs, rhs, &asValue)) return false; - RootedValue asValue(cx, js::Int32Value(result)); iter.storeInstructionResult(asValue); return true; } @@ -243,11 +243,11 @@ RBitXor::recover(JSContext* cx, SnapshotIterator& iter) const RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; - if (!js::BitXor(cx, lhs, rhs, &result)) + // TaintFox: modified due to changed signature of BitXor. + RootedValue rootedResult(cx); + if (!js::BitXor(cx, lhs, rhs, &rootedResult)) return false; - RootedValue rootedResult(cx, js::Int32Value(result)); iter.storeInstructionResult(rootedResult); return true; } @@ -268,13 +268,13 @@ RLsh::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitLsh(cx, lhs, rhs, &result)) + // TaintFox: modified due to changed signature of BitLsh. + RootedValue asValue(cx); + if (!js::BitLsh(cx, lhs, rhs, &asValue)) return false; - RootedValue asValue(cx, js::Int32Value(result)); iter.storeInstructionResult(asValue); return true; } @@ -297,11 +297,11 @@ RRsh::recover(JSContext* cx, SnapshotIterator& iter) const RootedValue rhs(cx, iter.read()); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - int32_t result; - if (!js::BitRsh(cx, lhs, rhs, &result)) + // TaintFox: modified due to changed signature of BitRsh. + RootedValue rootedResult(cx); + if (!js::BitRsh(cx, lhs, rhs, &rootedResult)) return false; - RootedValue rootedResult(cx, js::Int32Value(result)); iter.storeInstructionResult(rootedResult); return true; } diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index adc405f1f8bae..1b95dae89b407 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -986,38 +986,28 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub return false; break; case JSOP_BITOR: { - int32_t result; - if (!BitOr(cx, lhs, rhs, &result)) + if (!BitOr(cx, lhs, rhs, ret)) return false; - ret.setInt32(result); break; } case JSOP_BITXOR: { - int32_t result; - if (!BitXor(cx, lhs, rhs, &result)) + if (!BitXor(cx, lhs, rhs, ret)) return false; - ret.setInt32(result); break; } case JSOP_BITAND: { - int32_t result; - if (!BitAnd(cx, lhs, rhs, &result)) + if (!BitAnd(cx, lhs, rhs, ret)) return false; - ret.setInt32(result); break; } case JSOP_LSH: { - int32_t result; - if (!BitLsh(cx, lhs, rhs, &result)) + if (!BitLsh(cx, lhs, rhs, ret)) return false; - ret.setInt32(result); break; } case JSOP_RSH: { - int32_t result; - if (!BitRsh(cx, lhs, rhs, &result)) + if (!BitRsh(cx, lhs, rhs, ret)) return false; - ret.setInt32(result); break; } case JSOP_URSH: { @@ -1512,10 +1502,8 @@ DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, switch (op) { case JSOP_BITNOT: { - int32_t result; - if (!BitNot(cx, val, &result)) + if (!BitNot(cx, val, res)) return false; - res.setInt32(result); break; } case JSOP_NEG: diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index 32dca958835a7..db3e69e836cc0 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -14,6 +14,14 @@ function numberTaintingTest() { assertNumberTainted(a - b); assertNumberTainted(a * b); assertNumberTainted(a / b); + + // Bitwise operations + assertNumberTainted(a << 1); + assertNumberTainted(a >> 1); + assertNumberTainted(a & 1); + assertNumberTainted(a | 1); + assertNumberTainted(a ^ 1); + assertNumberTainted(~a); } runTaintTest(numberTaintingTest); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index cbf30c2a8d6be..6d498575723c7 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -381,6 +381,14 @@ NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val * INT32_FITS_IN_JSVAL(-i) unless i is 0 or INT32_MIN when the * results, -0.0 or INT32_MAX + 1, are double values. */ + // TaintFox: handle tainted numbers + if (isTaintedNumber(val)) { + double d; + if (!ToNumber(cx, val, &d)) + return false; + res.setObject(*NumberObject::createTainted(cx, d, getNumberTaint(val))); + } + int32_t i; if (val.isInt32() && (i = val.toInt32()) != 0 && i != INT32_MIN) { res.setInt32(-i); @@ -688,62 +696,87 @@ GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandle } static MOZ_ALWAYS_INLINE bool -BitNot(JSContext* cx, HandleValue in, int* out) +BitNot(JSContext* cx, HandleValue in, MutableHandleValue out) { int i; if (!ToInt32(cx, in, &i)) return false; - *out = ~i; + int res = ~i; + if (isTaintedNumber(in)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(in))); + else + out.setInt32(res); return true; } static MOZ_ALWAYS_INLINE bool -BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) { int left, right; if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; - *out = left ^ right; + int res = left ^ right; + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(lhs))); + else + out.setInt32(res); return true; } static MOZ_ALWAYS_INLINE bool -BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) { int left, right; if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; - *out = left | right; + int res = left | right; + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(lhs))); + else + out.setInt32(res); return true; } +// TaintFox: handle tainted arguments in all bitwise operations static MOZ_ALWAYS_INLINE bool -BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) { int left, right; if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; - *out = left & right; + int res = left & right; + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(lhs))); + else + out.setInt32(res); return true; } static MOZ_ALWAYS_INLINE bool -BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) { int32_t left, right; if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; - *out = uint32_t(left) << (right & 31); + int32_t res = uint32_t(left) << (right & 31); + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(lhs))); + else + out.setInt32(res); return true; } static MOZ_ALWAYS_INLINE bool -BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) { int32_t left, right; if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; - *out = left >> (right & 31); + int32_t res = left >> (right & 31); + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, res, getNumberTaint(lhs))); + else + out.setInt32(res); return true; } @@ -755,7 +788,10 @@ UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValu if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) return false; left >>= right & 31; - out.setNumber(uint32_t(left)); + if (isTaintedNumber(lhs)) + out.setObject(*NumberObject::createTainted(cx, left, getNumberTaint(lhs))); + else + out.setNumber(uint32_t(left)); return true; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 6d6ef88e8f06a..eac69f2c46527 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -2136,16 +2136,24 @@ CASE(JSOP_BINDVAR) } END_CASE(JSOP_BINDVAR) +// TaintFox: handle taint propagation here +// TODO do we care about tainted right hand side values? #define BITWISE_OP(OP) \ JS_BEGIN_MACRO \ int32_t i, j; \ + HandleValue val = REGS.stackHandleAt(-2); \ if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ goto error; \ if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ goto error; \ i = i OP j; \ REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ + if (isTaintedNumber(val)) { \ + REGS.sp[-1].setObject(*NumberObject::createTainted(cx, i, \ + getNumberTaint(val))); \ + } else { \ + REGS.sp[-1].setInt32(i); \ + } \ JS_END_MACRO CASE(JSOP_BITOR) @@ -2264,16 +2272,24 @@ CASE(JSOP_GE) } END_CASE(JSOP_GE) +// TaintFox: handle taint propagation here +// TODO do we care about tainted shift amount values? #define SIGNED_SHIFT_OP(OP) \ JS_BEGIN_MACRO \ int32_t i, j; \ + HandleValue val = REGS.stackHandleAt(-2); \ if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ goto error; \ if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ goto error; \ i = i OP (j & 31); \ REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ + if (isTaintedNumber(val)) { \ + REGS.sp[-1].setObject(*NumberObject::createTainted(cx, i, \ + getNumberTaint(val))); \ + } else { \ + REGS.sp[-1].setInt32(i); \ + } \ JS_END_MACRO CASE(JSOP_LSH) @@ -2373,11 +2389,11 @@ END_CASE(JSOP_NOT) CASE(JSOP_BITNOT) { - int32_t i; + // TaintFox: modified due to changed signature of BitNot. HandleValue value = REGS.stackHandleAt(-1); - if (!BitNot(cx, value, &i)) + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!BitNot(cx, value, res)) goto error; - REGS.sp[-1].setInt32(i); } END_CASE(JSOP_BITNOT) From 671c15c5198871029b2eca2762d006f126b14086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Sun, 29 May 2016 21:24:15 +0200 Subject: [PATCH 05/70] The ++ and -- operators now work with tainted numbers --- js/src/jsnum.h | 3 ++- js/src/tests/taint/number_tainting.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 21d2fe68ad220..ffb4a88b2d0c3 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -13,6 +13,7 @@ #include "NamespaceImports.h" #include "js/Conversions.h" +#include "jstaint.h" // This macro is should be `one' if current compiler supports builtin functions @@ -173,7 +174,7 @@ StringToNumber(ExclusiveContext* cx, JSString* str, double* result); MOZ_ALWAYS_INLINE bool ToNumber(JSContext* cx, JS::MutableHandleValue vp) { - if (vp.isNumber()) + if (vp.isNumber() || isTaintedNumber(vp)) return true; double d; extern JS_PUBLIC_API(bool) ToNumberSlow(JSContext* cx, Value v, double* dp); diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index db3e69e836cc0..c42f286850fd8 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -15,6 +15,15 @@ function numberTaintingTest() { assertNumberTainted(a * b); assertNumberTainted(a / b); + assertNumberTainted(a++); + assertNumberTainted(a); + assertNumberTainted(a--); + assertNumberTainted(a); + assertNumberTainted(++a); + assertNumberTainted(a); + assertNumberTainted(--a); + assertNumberTainted(a); + // Bitwise operations assertNumberTainted(a << 1); assertNumberTainted(a >> 1); From b5dbd1ca4abe26e167cb745bd72bb9ad9781139c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 31 May 2016 11:16:56 +0200 Subject: [PATCH 06/70] Implemented tainted element access --- js/src/tests/taint/base64.js | 89 +++++++++++++++++++++++++++ js/src/tests/taint/number_tainting.js | 4 ++ js/src/vm/Interpreter-inl.h | 23 +++++++ 3 files changed, 116 insertions(+) create mode 100644 js/src/tests/taint/base64.js diff --git a/js/src/tests/taint/base64.js b/js/src/tests/taint/base64.js new file mode 100644 index 0000000000000..dc7daf3c6270a --- /dev/null +++ b/js/src/tests/taint/base64.js @@ -0,0 +1,89 @@ +// Base64 code taken from https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64 + +var base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(""); +var base64inv = {}; +for (var i = 0; i < base64chars.length; i++) { + base64inv[base64chars[i]] = i; +} + +function base64_encode(s) +{ + // the result/encoded string, the padding string, and the pad count + var r = ""; + var p = ""; + var c = s.length % 3; + + // add a right zero pad to make this string a multiple of 3 characters + if (c > 0) { + for (; c < 3; c++) { + p += '='; + s += "\0"; + } + } + + // increment over the length of the string, three characters at a time + for (c = 0; c < s.length; c += 3) { + + // we add newlines after every 76 output characters, according to the MIME specs + if (c > 0 && (c / 3 * 4) % 76 == 0) { + r += "\r\n"; + } + + // these three 8-bit (ASCII) characters become one 24-bit number + var n = (s.charCodeAt(c) << 16) + (s.charCodeAt(c+1) << 8) + s.charCodeAt(c+2); + + // this 24-bit number gets separated into four 6-bit numbers + n = [(n >>> 18) & 63, (n >>> 12) & 63, (n >>> 6) & 63, n & 63]; + + // those four 6-bit numbers are used as indices into the base64 character list + r += base64chars[n[0]] + base64chars[n[1]] + base64chars[n[2]] + base64chars[n[3]]; + } + // add the actual padding string, after removing the zero pad + return r.substring(0, r.length - p.length) + p; +} + +function base64_decode(s) +{ + // remove/ignore any characters not in the base64 characters list + // or the pad character -- particularly newlines + s = s.replace(new RegExp('[^'+base64chars.join("")+'=]', 'g'), ""); + + // replace any incoming padding with a zero pad (the 'A' character is zero) + var p = (s.charAt(s.length-1) == '=' ? + (s.charAt(s.length-2) == '=' ? 'AA' : 'A') : ""); + var r = ""; + s = s.substr(0, s.length - p.length) + p; + + // increment over the length of this encoded string, four characters at a time + for (var c = 0; c < s.length; c += 4) { + + // each of these four characters represents a 6-bit index in the base64 characters list + // which, when concatenated, will give the 24-bit number for the original 3 characters + var n = (base64inv[s.charAt(c)] << 18) + (base64inv[s.charAt(c+1)] << 12) + + (base64inv[s.charAt(c+2)] << 6) + base64inv[s.charAt(c+3)]; + + // split the 24-bit number into the original three 8-bit (ASCII) characters + r += String.fromCharCode((n >>> 16) & 255, (n >>> 8) & 255, n & 255); + } + // remove any zero pad that was added to make this a multiple of 24 bits + return r.substring(0, r.length - p.length); +} + +function base64TaintTest() { + //var s = randomMultiTaintedString(); + var s = randomTaintedString(); + + assertTainted(base64_encode(s)); + print(s); + print(stringifyTaint(s.taint)); + print(base64_encode(s)); + print(base64_encode(s).length); + print(stringifyTaint(base64_encode(s).taint)); + + assertEqualTaint(s, base64_decode(base64_encode(s))); +} + +runTaintTest(base64TaintTest); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index c42f286850fd8..2d09a99bad693 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -31,6 +31,10 @@ function numberTaintingTest() { assertNumberTainted(a | 1); assertNumberTainted(a ^ 1); assertNumberTainted(~a); + + // Element access + var table = [0,1,2,3,4,5,6,7]; + assertNumberTainted(table[a & 7]); } runTaintTest(numberTaintingTest); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 6d498575723c7..8e26d2265e2f6 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -426,6 +426,10 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM || op == JSOP_GETELEM_SUPER); MOZ_ASSERT_IF(op == JSOP_GETELEM || op == JSOP_CALLELEM, obj == receiver); + // TaintFox: tainted numbers or strings might be used for element access. In that case, also + // try to taint the resulting value. + TaintFlow taint; + do { uint32_t index; if (IsDefinitelyIndex(key, &index)) { @@ -439,6 +443,11 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand if (key.isString()) { JSString* str = key.toString(); + + // TaintFox: if tainted, just pick the first taintflow. + if (str->isTainted()) + taint = str->taint().begin()->flow(); + JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str); if (!name) return false; @@ -451,6 +460,9 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand } } + if (isTaintedNumber(key)) + taint = getNumberTaint(key); + RootedId id(cx); if (!ToPropertyKey(cx, key, &id)) return false; @@ -458,6 +470,17 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand return false; } while (false); + // TaintFox: add taint information to looked up element. + if (taint) { + // TODO(samuel) need a taint() method? + if (res.isString()) { + JSString* str = res.toString(); + str->setTaint(StringTaint(taint, str->length())); + } else if (res.isNumber()) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), taint)); + } + } + assertSameCompartmentDebugOnly(cx, res); return true; } From 6525afb8cd0e5a6fab88dd52ba19c50d13145d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 31 May 2016 15:10:35 +0200 Subject: [PATCH 07/70] Improved the number tainting test --- js/src/jstaint.cpp | 13 +++++++ js/src/jstaint.h | 31 ++++------------ js/src/tests/taint/number_tainting.js | 46 +++++++++++++++++++++--- js/src/vm/Interpreter.cpp | 52 ++++++++++++++++++++------- 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 99fbb202682b9..526d003da92a4 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -91,3 +91,16 @@ TaintFlow js::getNumberTaint(const Value& val) } return TaintFlow(nullptr); } + +bool js::isAnyTaintedNumber(const Value& val1, const Value& val2) +{ + return isTaintedNumber(val1) || isTaintedNumber(val2); +} + +TaintFlow js::getAnyNumberTaint(const Value& val1, const Value& val2) +{ + if (isTaintedNumber(val1)) + return getNumberTaint(val1); + else + return getNumberTaint(val2); +} diff --git a/js/src/jstaint.h b/js/src/jstaint.h index 042290a8c9213..6603b5e1f9edd 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -38,31 +38,14 @@ bool isTaintedNumber(const JS::Value& val); // Extract the taint information from a number. TaintFlow getNumberTaint(const JS::Value& val); -} +// Check if any of the argument values is a tainted number object. +// TODO make this accept a variable amount of arguments using variadic templates +bool isAnyTaintedNumber(const JS::Value& val1, const JS::Value& val2); + +// Extract the taint information from the first tainted number argument. +// TODO make this accept a variable amount of arguments using variadic templates +TaintFlow getAnyNumberTaint(const JS::Value& val1, const JS::Value& val2); -#define HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, OP) \ -{ \ - decltype(lhs) __lhs = (lhs); \ - decltype(rhs) __rhs = (rhs); \ - if (isTaintedNumber(__lhs) || isTaintedNumber(__rhs)) { \ - double lhsValue, rhsValue; \ - TaintFlow taint; \ - if (isTaintedNumber(__lhs)) { \ - taint = __lhs.toObject().as().taint(); \ - } else { \ - taint = __rhs.toObject().as().taint(); \ - } \ - \ - ToNumber(cx, __lhs, &lhsValue); \ - ToNumber(cx, __rhs, &rhsValue); \ - \ - if (taint) { \ - TaintFlow newTaint = taint.extend(TaintOperation(#OP, \ - {taintarg(cx, lhsValue), taintarg(cx, rhsValue)})); \ - res.setObject(*NumberObject::createTainted(cx, lhsValue OP rhsValue, newTaint)); \ - return true; \ - } \ - } \ } #endif diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index 2d09a99bad693..cdaa51718021c 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -4,16 +4,36 @@ function numberTaintingTest() { assertNumberTainted(a); assertNumberTainted(b); + // // Basic arithmetic tests assertNumberTainted(a + 13.37); - assertNumberTainted(a - 13.37); - assertNumberTainted(a * 13.37); - assertNumberTainted(a / 13.37); - + assertNumberTainted(13.37 + a); assertNumberTainted(a + b); + assertEq(a + b, 42 + 13.37); // assertEq uses === for comparing values + assertEq(a + b == 42 + 13.37, true); + + assertNumberTainted(a - 13.37); + assertNumberTainted(13.37 - a); assertNumberTainted(a - b); + assertEq(a - b, 42 - 13.37); + assertEq(a - b == 42 - 13.37, true); + + assertNumberTainted(a * 13.37); + assertNumberTainted(13.37 * a); assertNumberTainted(a * b); + assertEq(a * b, 42 * 13.37); + assertEq(a * b == 42 * 13.37, true); + + assertNumberTainted(a / 13.37); + assertNumberTainted(13.37 / a); assertNumberTainted(a / b); + assertEq(a / b, 42 / 13.37); + assertEq(a / b == 42 / 13.37, true); + + assertNumberTainted(a % 13.37); + assertNumberTainted(a % b); + assertEq(a % b, 42 % 13.37); + assertEq(a % b == 42 % 13.37, true); assertNumberTainted(a++); assertNumberTainted(a); @@ -24,13 +44,31 @@ function numberTaintingTest() { assertNumberTainted(--a); assertNumberTainted(a); + // // Bitwise operations + b = taint(3); assertNumberTainted(a << 1); + assertNumberTainted(a << b); + assertEq(a << b, 42 << 3); + assertNumberTainted(a >> 1); + assertNumberTainted(a >> b); + assertEq(a >> b, 42 >> 3); + assertNumberTainted(a & 1); + assertNumberTainted(a & b); + assertEq(a & b, 42 & 3); + assertNumberTainted(a | 1); + assertNumberTainted(a | b); + assertEq(a | b, 42 | 3); + assertNumberTainted(a ^ 1); + assertNumberTainted(a ^ b); + assertEq(a ^ b, 42 ^ 3); + assertNumberTainted(~a); + assertEq(~a, ~42); // Element access var table = [0,1,2,3,4,5,6,7]; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index eac69f2c46527..95fecf0b70cd9 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -861,6 +861,18 @@ js::StrictlyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal return true; } + // TaintFox: special case to handle strict equality of tainted numbers. + if (isAnyTaintedNumber(lval, rval) && + (lval.isNumber() || isTaintedNumber(lval)) && + (rval.isNumber() || isTaintedNumber(rval))) { + double l, r; + if (!ToNumber(cx, lval, &l) || !ToNumber(cx, rval, &r)) + return false; + + *equal = (l == r); + return true; + } + *equal = false; return true; } @@ -1303,6 +1315,10 @@ ComputeImplicitThis(JSObject* obj) static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (lhs.isInt32() && rhs.isInt32()) { int32_t l = lhs.toInt32(), r = rhs.toInt32(); int32_t t; @@ -1312,9 +1328,6 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta } } - // TaintFox: Taint propagation when adding tainted numbers. - HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, +); - if (!ToPrimitive(cx, lhs)) return false; if (!ToPrimitive(cx, rhs)) @@ -1355,6 +1368,10 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) return false; res.setNumber(l + r); + + // TaintFox: Taint propagation when adding tainted numbers. + if (isAnyTaintedNumber(origLhs, origRhs)) + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; @@ -1363,39 +1380,45 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { - // TaintFox: Taint propagation when subtracting tainted numbers. - HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, -); - double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; res.setNumber(d1 - d2); + + // TaintFox: Taint propagation when subtracting tainted numbers. + if (isAnyTaintedNumber(lhs, rhs)) + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + return true; } static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { - // TaintFox: Taint propagation when multiplying tainted numbers. - HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, *); - double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; res.setNumber(d1 * d2); + + // TaintFox: Taint propagation when multiplying tainted numbers. + if (isAnyTaintedNumber(lhs, rhs)) + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + return true; } static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { - // TaintFox: Taint propagation when dividing tainted numbers. - HANDLE_NUMBER_TAINT_BINARY_OP(lhs, rhs, /); - double d1, d2; if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) return false; res.setNumber(NumberDiv(d1, d2)); + + // TaintFox: Taint propagation when dividing tainted numbers. + if (isAnyTaintedNumber(lhs, rhs)) + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + return true; } @@ -1415,6 +1438,11 @@ ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue return false; res.setNumber(NumberMod(d1, d2)); + + // TaintFox: Taint propagation when performing modulo operations on tainted numbers. + if (isTaintedNumber(lhs)) + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getNumberTaint(lhs))); + return true; } From ab9d40592c6dae9ab2f82204616261e1e9fb5296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Wed, 1 Jun 2016 11:14:39 +0200 Subject: [PATCH 08/70] Fix build under Linux --- js/src/vm/Interpreter-inl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 8e26d2265e2f6..bb3272bf87a0d 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -23,6 +23,7 @@ #include "vm/Stack-inl.h" #include "vm/String-inl.h" #include "vm/UnboxedObject-inl.h" +#include "vm/NumberObject-inl.h" namespace js { From e4c41befe45d7bb3ab9841db41f2bde956ea9003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 14 Jun 2016 10:55:10 +0200 Subject: [PATCH 09/70] WIP --- .../tests/taint/number_string_conversions.js | 2 ++ js/src/vm/Interpreter-inl.h | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index f7df19419632b..e4fa30512e33a 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -12,6 +12,8 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); + + // TODO do we care about number.toString()? } runTaintTest(numberStringConversionTests); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 24cf33247e1f8..0621100073c6a 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -420,6 +420,7 @@ ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue id return true; } +#include static MOZ_ALWAYS_INLINE bool GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleObject receiver, HandleValue key, MutableHandleValue res) @@ -473,10 +474,31 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand // TaintFox: add taint information to looked up element. if (taint) { - // TODO(samuel) need a taint() method? if (res.isString()) { - JSString* str = res.toString(); - str->setTaint(StringTaint(taint, str->length())); + // Simple heuristic. In essence we want to apply taint in case of a + // lookup table or similar, since there the resulting string is completely controlled + // if the index is controlled. + // On the other hand, here is an example where we probably don't want to apply taint: + // + // var fortunes = [ //.. array of strings ]; + // function fortune(i) { + // return fortunes[i]; + // } + // + // In this case we aren't able to control the content of the string but only which + // string is choosen, which probably isn't relevant security wise. + // + // Our heuristic here tries to differentiate both cases simply by looking at the length + // of the returned string. + if (res.toString()->length() < 3) { + // In case of a string/char lookup, the result may very well be an atom. + // In that case "deatomize" and apply taint. + JSLinearString* str = res.toString()->ensureLinear(cx); + if (str->isAtom()) + // Cannot use NewDependentString here, so need to use this function directly. + str = JSDependentString::new_(cx, str, 0, str->length()); + str->setTaint(StringTaint(taint, str->length())); + } } else if (res.isNumber()) { res.setObject(*NumberObject::createTainted(cx, res.toNumber(), taint)); } From c6d2369ec03571f359ab479482aa060f2331d89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Thu, 16 Jun 2016 12:30:06 +0200 Subject: [PATCH 10/70] Tainted element access working --- js/src/jsstr.cpp | 8 ++++++-- js/src/tests/taint/element_access.js | 20 ++++++++++++++++++++ js/src/vm/Interpreter-inl.h | 20 ++++++++++++-------- 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 js/src/tests/taint/element_access.js diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 3e8716e19dccc..3cd0941939f06 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -693,8 +693,12 @@ str_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) int32_t slot = JSID_TO_INT(id); if ((size_t)slot < str->length()) { - // TaintFox: code modified to avoid atoms. - JSString* str1 = NewDependentString(cx, str, slot, 1); + // TaintFox: must not create atoms here if the base string is tainted. + JSString* str1; + if (str->isTainted()) + str1 = NewDependentString(cx, str, slot, 1); + else + str1 = cx->staticStrings().getUnitStringForElement(cx, str, slot); if (!str1) return false; RootedValue value(cx, StringValue(str1)); diff --git a/js/src/tests/taint/element_access.js b/js/src/tests/taint/element_access.js new file mode 100644 index 0000000000000..b5f6045b0172d --- /dev/null +++ b/js/src/tests/taint/element_access.js @@ -0,0 +1,20 @@ +function elementAccesstest() { + var str = randomString(); + var index = rand(0, str.length); + var taintedIndex = taint(index); + + // Short strings/characters accessd through a tainted index should also be tainted + // as they could represent lookup tables. + + assertTainted(str[taintedIndex]); + assertNotTainted(str[index]); + + var chars = str.split(''); + assertTainted(chars[taintedIndex]); + assertNotTainted(chars[index]); +} + +runTaintTest(elementAccesstest); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 0621100073c6a..5a83f7f6d4ca5 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -420,7 +420,6 @@ ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue id return true; } -#include static MOZ_ALWAYS_INLINE bool GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleObject receiver, HandleValue key, MutableHandleValue res) @@ -491,13 +490,9 @@ GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::Hand // Our heuristic here tries to differentiate both cases simply by looking at the length // of the returned string. if (res.toString()->length() < 3) { - // In case of a string/char lookup, the result may very well be an atom. - // In that case "deatomize" and apply taint. - JSLinearString* str = res.toString()->ensureLinear(cx); - if (str->isAtom()) - // Cannot use NewDependentString here, so need to use this function directly. - str = JSDependentString::new_(cx, str, 0, str->length()); - str->setTaint(StringTaint(taint, str->length())); + // We only want to taint the returned string, not the element of the object. + RootedString str(cx, res.toString()); + res.setString(NewTaintedDependentString(cx, str, StringTaint(taint, str->length()))); } } else if (res.isNumber()) { res.setObject(*NumberObject::createTainted(cx, res.toNumber(), taint)); @@ -544,11 +539,20 @@ GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver, } } + RootedId id(cx); if (!ToPropertyKey(cx, key, &id)) return false; if (!GetProperty(cx, boxed, receiver, id, res)) return false; + + // TaintFox:Like with arrays, taint should be propagated here + // if the index is a tainted number and the receiver a string. + if (isTaintedNumber(key) && res.isString()) { + RootedString str(cx, res.toString()); + StringTaint taint(getNumberTaint(key), str->length()); + res.setString(NewTaintedDependentString(cx, str, taint)); + } } while (false); assertSameCompartmentDebugOnly(cx, res); From 41bf60db38a5847a8468e97f7b4dffced980c675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 28 Jun 2016 10:02:34 +0200 Subject: [PATCH 11/70] Improved GetElement taint propagation --- js/src/jit/BaselineIC.cpp | 8 +++++++- js/src/vm/Interpreter-inl.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 085632448e7c7..f0f4968466496 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -1583,7 +1583,13 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_ // Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses. if (obj->isNative() || obj->is()) { RootedScript rootedScript(cx, script); - if (rhs.isString()) { + // TaintFox: don't optimize if the key is tainted. This would + // lead to key atomization and possibly lookup caching, both resulting + // in loss of taint information. + // This is more of a heuristic as optimization/lookup caching can + // still happen if the same property is first looked up with an untainted + // key. + if (rhs.isString() && !rhs.toString()->isTainted()) { if (!TryAttachNativeOrUnboxedGetValueElemStub(cx, rootedScript, pc, stub, obj, rhs, attached)) { diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 5a83f7f6d4ca5..af4f8d4d997a7 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -840,7 +840,7 @@ UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValu return false; left >>= right & 31; if (isTaintedNumber(lhs)) - out.setObject(*NumberObject::createTainted(cx, left, getNumberTaint(lhs))); + out.setObject(*NumberObject::createTainted(cx, uint32_t(left), getNumberTaint(lhs))); else out.setNumber(uint32_t(left)); return true; From cde3aade74f1fbb5bce9dc5120362e5a18cdadde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 23 Aug 2016 12:26:28 +0200 Subject: [PATCH 12/70] Added some documentation --- taint/docs/NumberTainting.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 taint/docs/NumberTainting.md diff --git a/taint/docs/NumberTainting.md b/taint/docs/NumberTainting.md new file mode 100644 index 0000000000000..b3900c391b247 --- /dev/null +++ b/taint/docs/NumberTainting.md @@ -0,0 +1,29 @@ +# Making Numbers Taint-aware + +There are several approaches to making the number type taint aware, ranging from +changing the JSValue type itself to creating lookup tables for the taint +information. + +Our approach works by extending the NumberObject type (new Number(...)) to +containt the taint information, and then changing various functions (str.charCodeAt(), +arithmetic operations, binary operations, etc.) throughout the engine to produce NumberObjects if the +result of the computation is tainted. +Moreover, features such as typeof were modified to make tainted numbers look +like primitive numbers to the JavaScript code. +This works quite well as it gives us basic JIT support "for free" since the +NumberObject type isn't specifically optimized for, and so fallbacks to the +interpreter happen automatically (unlike with strings, where we have to +force a fallback if we detect a tainted string in the JIT code). + +However, there are still issues with approach. For example, the bitshift +operations are defined to produce a 32 bit integer as result [1], a fact that the Ion +JIT compiler optimizes for. So, given e.g. a sequence of bitshifts and +additions, ((a << 16) + (b << 8) + c), the Ion compiler will likely inline the +additions (instead of calling AddOperation), since it now assumes the result +must be an integer. This assumption is violated by our approch (now the result +is an Object) and so this code breaks. +The easiest way to deal with this for now is to disable the Ion JIT compiler. Fixing +these specific cases would be preferable though. + + +[1] http://www.ecma-international.org/ecma-262/6.0/#sec-left-shift-operator-runtime-semantics-evaluation From 0d4dd48a3610fe5ac15c19c76e2e5313a50b67f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 25 Oct 2016 13:32:31 +0200 Subject: [PATCH 13/70] Disabled IonMonkey for now --- js/src/jit/Ion.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/src/jit/Ion.h b/js/src/jit/Ion.h index c2a6062549fb1..59130f382a764 100644 --- a/js/src/jit/Ion.h +++ b/js/src/jit/Ion.h @@ -154,6 +154,10 @@ uint8_t* LazyLinkTopActivation(JSContext* cx); static inline bool IsIonEnabled(JSContext* cx) { + // TaintFox: Currently disabled since some optimizations don't work + // with the integer tainting. TODO + return false; + // The ARM64 Ion engine is not yet implemented. #if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64) return false; From 1f7e3afee984b9a60d84d0c26fa70a5ff6f90f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Gro=C3=9F?= Date: Tue, 25 Oct 2016 13:53:47 +0200 Subject: [PATCH 14/70] Fixed regression in String.fromCharCode() --- js/src/vm/String.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 5caf486b69d0f..b16aa2db6eac9 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -1135,8 +1135,9 @@ template static JSFlatString* NewStringDeflated(ExclusiveContext* cx, const char16_t* s, size_t n) { - if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) - return str; + // TaintFox: disabled to avoid atoms here. TODO modify callers where necessary instead? + //if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) + // return str; if (JSInlineString::lengthFits(n)) return NewInlineStringDeflated(cx, mozilla::Range(s, n)); From ae506854f5176e68af7526195cd2249082ad0aa3 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Wed, 31 Aug 2022 13:13:09 +0000 Subject: [PATCH 15/70] Foxhound: basic number taint get and set working --- js/src/jsnum.cpp | 98 ++++++++++++++++++++-------------------- js/src/vm/NumberObject.h | 22 +++------ taint/Taint.cpp | 15 +++--- taint/Taint.h | 1 + 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 694df93ed8de6..6df965a7e1b77 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1363,81 +1363,81 @@ static const JSFunctionSpec number_methods[] = { JS_FN("toPrecision", num_toPrecision, 1, 0), JS_FS_END}; -static bool -num_taint_getter(JSContext* cx, unsigned argc, Value* vp) +static bool num_taint_getter(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setNull(); - - // This will be the case for unboxed integers. In that case just return null. - if (!args.thisv().isObject()) - return true; + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNull(); - RootedObject number(cx, &args.thisv().toObject()); - if (!number->is()) - return true; + // This will be the case for unboxed integers. In that case just return null. + if (!args.thisv().isObject()) { + return true; + } - const TaintFlow& taint = number->as().taint(); + RootedObject number(cx, &args.thisv().toObject()); + if (!number->is()) { + return true; + } - // TODO(samuel) refactor into separate function - ValueVector taint_flow(cx); - for (TaintNode& taint_node : taint) { - RootedObject node(cx, JS_NewObject(cx, nullptr)); - if (!node) - return false; + const TaintFlow& taint = number->as().taint(); + // TODO(samuel) refactor into separate function + RootedValueVector taint_flow(cx); + for (TaintNode& taint_node : taint) { + RootedObject node(cx, JS_NewObject(cx, nullptr)); + if (!node) + return false; - RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); - if (!operation) - return false; + RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); + if (!operation) + return false; - if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; + if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; - // Wrap the arguments. - ValueVector taint_arguments(cx); - for (auto& taint_argument : taint_node.operation().arguments()) { - RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); - if (!argument) - return false; + // Wrap the arguments. + RootedValueVector taint_arguments(cx); + for (auto& taint_argument : taint_node.operation().arguments()) { + RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); + if (!argument) + return false; - if (!taint_arguments.append(StringValue(argument))) - return false; - } + if (!taint_arguments.append(StringValue(argument))) + return false; + } - RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); - if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; + RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); + if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; - taint_flow.append(ObjectValue(*node)); - } + taint_flow.append(ObjectValue(*node)); + } - args.rval().setObject(*NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); + args.rval().setObject(*NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); - return true; + return true; } /* TaintFox: Add |taint| property. */ static const JSPropertySpec number_taint_properties[] = { - JS_PSG("taint", num_taint_getter, JSPROP_PERMANENT), - JS_PS_END + JS_PSG("taint", num_taint_getter, JSPROP_PERMANENT), + JS_PS_END }; // TaintFox: taint numbers manually using this method. bool js::Number_tainted(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); + CallArgs args = CallArgsFromVp(argc, vp); - double d; - if (!ToNumber(cx, args.get(0), &d)) { - return false; - } + double d; + if (!ToNumber(cx, args.get(0), &d)) { + return false; + } - JSObject* number = NumberObject::createTainted(cx, d, TaintFlow(TaintOperation("manual taint source", { taintarg(cx, d) }))); - args.rval().setObject(*number); + JSObject* number = NumberObject::createTainted(cx, d, TaintFlow(TaintOperation("manual taint source", { taintarg(cx, d) }))); + args.rval().setObject(*number); - return true; + return true; } bool js::IsInteger(const Value& val) { diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index b41e610bd3320..65acfbdb528c3 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -7,6 +7,8 @@ #ifndef vm_NumberObject_h #define vm_NumberObject_h +#include + #include "jsnum.h" #include "Taint.h" @@ -20,12 +22,12 @@ class NumberObject : public NativeObject { static const unsigned PRIMITIVE_VALUE_SLOT = 0; /* Taintfox: Stores the Number object's taint information */ - static const unsigned TAINT_SLOT = 0; + static const unsigned TAINT_SLOT = 1; static const ClassSpec classSpec_; public: - static const unsigned RESERVED_SLOTS = 1; + static const unsigned RESERVED_SLOTS = 2; static const JSClass class_; @@ -82,23 +84,13 @@ class NumberObject : public NativeObject { setFixedSlot(PRIMITIVE_VALUE_SLOT, NumberValue(d)); } - inline TaintNode** getTaintNodeImpl() const { - return maybePtrFromReservedSlot(TAINT_SLOT); - } - inline TaintNode* getTaintNode() const { - TaintNode** n = getTaintNodeImpl(); - if (!n) { - return nullptr; - } - return *n; + TaintNode* n = maybePtrFromReservedSlot(TAINT_SLOT); + return n; } inline void setTaintNode(TaintNode* node) { - TaintNode** n = getTaintNodeImpl(); - if (n != nullptr) { - *n = node; - } + setReservedSlot(TAINT_SLOT, PrivateValue(node)); } }; diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 3fd4b5cef4d12..d66c30c525abe 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -1101,16 +1101,19 @@ void DumpTaint(const StringTaint& taint) { for (auto& range : taint) { std::cout << " " << range.begin() << " - " << range.end() << " : " << range.flow().source().name() << ":\n"; - auto& flow = range.flow(); - - for(auto& node : flow) { - auto& op = node.operation(); - DumpTaintOperation(op); + DumpTaintFlow(range.flow()); + } +} - } +void DumpTaintFlow(const TaintFlow& flow) +{ + for(auto& node : flow) { + auto& op = node.operation(); + DumpTaintOperation(op); } } + void DumpTaintOperation(const TaintOperation& operation) { std::wstring_convert, char16_t> convert; std::cout << "\t\t" << operation.name() << "["; diff --git a/taint/Taint.h b/taint/Taint.h index 2b83f299105df..7c852999a7b71 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -763,6 +763,7 @@ StringTaint ParseTaint(const std::string& str); void PrintTaint(const StringTaint& taint); void DumpTaint(const StringTaint& taint); +void DumpTaintFlow(const TaintFlow& flow); void DumpTaintOperation(const TaintOperation& operation); #endif From 623571c98e0f72fca416d27344f5d32240754c4b Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Thu, 1 Sep 2022 09:22:55 +0000 Subject: [PATCH 16/70] Foxhound: propagate tainting for subtract operation --- js/src/vm/Interpreter.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index dad92e7caa7fe..3c769b85f6b26 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1463,6 +1463,10 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } @@ -1473,8 +1477,8 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, res.setNumber(lhs.toNumber() - rhs.toNumber()); // TaintFox: Taint propagation when subtracting tainted numbers. - if (isAnyTaintedNumber(lhs, rhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + if (isAnyTaintedNumber(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } From ef6eddc92f9f76d420ffd0be7246897d2e7c2b91 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 1 Sep 2022 15:34:02 +0200 Subject: [PATCH 17/70] fix number mul,div,mod,pow and add test for pow --- js/src/tests/taint/number_tainting.js | 6 +++++ js/src/vm/Interpreter.cpp | 32 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index cdaa51718021c..1b20e339e65c4 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -35,6 +35,12 @@ function numberTaintingTest() { assertEq(a % b, 42 % 13.37); assertEq(a % b == 42 % 13.37, true); + assertNumberTainted(a ** 13.37); + assertNumberTainted(13.37 ** a); + assertNumberTainted(a ** b); + assertEq(a ** b, 42 ** 13.37); + assertEq(a ** b == 42 ** 13.37, true); + assertNumberTainted(a++); assertNumberTainted(a); assertNumberTainted(a--); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3c769b85f6b26..648dd25741db1 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1487,6 +1487,10 @@ static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } @@ -1497,8 +1501,8 @@ static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, res.setNumber(lhs.toNumber() * rhs.toNumber()); // TaintFox: Taint propagation when multiplying tainted numbers. - if (isAnyTaintedNumber(lhs, rhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + if (isAnyTaintedNumber(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1507,6 +1511,10 @@ static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } @@ -1517,8 +1525,8 @@ static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when dividing tainted numbers. - if (isAnyTaintedNumber(lhs, rhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + if (isAnyTaintedNumber(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1527,6 +1535,10 @@ static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + int32_t l, r; if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { @@ -1545,8 +1557,8 @@ static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx, res.setNumber(NumberMod(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when modding tainted numbers. - if (isAnyTaintedNumber(lhs, rhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + if (isAnyTaintedNumber(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1555,6 +1567,10 @@ static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } @@ -1565,8 +1581,8 @@ static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx, res.setNumber(ecmaPow(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when taking power of tainted numbers. - if (isAnyTaintedNumber(lhs, rhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(lhs, rhs))); + if (isAnyTaintedNumber(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } From 9f242f65fcaa5c09dbb6d89fdfe44f5c80526fa7 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Fri, 2 Sep 2022 13:52:10 +0000 Subject: [PATCH 18/70] Foxhound: Add taintability to WebIDL files --- dom/bindings/Codegen.py | 33 +++++++++++++++++++++++++++++++-- dom/bindings/parser/WebIDL.py | 1 + dom/webidl/Screen.webidl | 4 ++-- js/src/jsapi.cpp | 3 +++ taint/README.md | 3 +++ 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 48c3fa2519e56..4938d636c1555 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -7885,6 +7885,7 @@ def getWrapTemplateForType( exceptionCode, spiderMonkeyInterfacesAreStructs, isConstructorRetval=False, + taintSource=None ): """ Reflect a C++ value stored in "result", of IDL type "type" into JS. The @@ -7964,6 +7965,18 @@ def _setValue(value, wrapAsType=None, setter="set"): exceptionCode=exceptionCode, successCode=successCode, ) + # Attach taint metadata to the return value if it is a source + if taintSource is not None: + print("Generating taint source:", taintSource) + taintHandler = dedent( + ( + """ + // Add taint source + MarkTaintSource(cx, ${jsvalRef}, "%s"); + """ + % (taintSource)) + ) + tail = taintHandler + tail return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail def wrapAndSetPtr(wrapCall, failureCode=None): @@ -8398,7 +8411,7 @@ def wrapAndSetPtr(wrapCall, failureCode=None): raise TypeError("Need to learn to wrap primitive: %s" % type) -def wrapForType(type, descriptorProvider, templateValues): +def wrapForType(type, descriptorProvider, templateValues, taintSource = None): """ Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict that should contain: @@ -8441,6 +8454,7 @@ def wrapForType(type, descriptorProvider, templateValues): templateValues.get("exceptionCode", "return false;\n"), templateValues.get("spiderMonkeyInterfacesAreStructs", False), isConstructorRetval=templateValues.get("isConstructorRetval", False), + taintSource=taintSource )[0] defaultValues = {"obj": "obj"} @@ -9201,6 +9215,10 @@ def __init__( self.setSlot = ( not dontSetSlot and idlNode.isAttr() and idlNode.slotIndices is not None ) + + # Taintfox: create a label for the taint source + self.taintSource = GetLabelForErrorReporting(descriptor, idlNode, isConstructor) if memberIsTaintSource(self.idlNode) else None + cgThings = [] deprecated = idlNode.getExtendedAttribute("Deprecated") or ( @@ -9692,7 +9710,7 @@ def wrap_return_value(self): "obj": "conversionScope" if self.setSlot else "obj", } - wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues) + wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues, self.taintSource) if self.setSlot: if self.idlNode.isStatic(): @@ -11617,6 +11635,9 @@ def error_reporting_label(self): def memberReturnsNewObject(member): return member.getExtendedAttribute("NewObject") is not None +def memberIsTaintSource(member): + # Taintfox: check if this function is marked as a taint source: + return member.getExtendedAttribute("TaintSource") is not None class CGMemberJITInfo(CGThing): """ @@ -18309,8 +18330,16 @@ def descriptorClearsPropsInSlots(descriptor): for m in descriptor.interface.members ) + def hasAtLeastOneTaintsource(descriptor): + return any( + m.isAttr() and m.getExtendedAttribute("TaintSource") + for m in descriptor.interface.members + ) + bindingHeaders["nsJSUtils.h"] = any( descriptorClearsPropsInSlots(d) for d in descriptors + ) or any ( + hasAtLeastOneTaintsource(d) for d in descriptors ) # Make sure we can sanely use binding_detail in generated code. diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 6ae4bfa335445..5247f353aea16 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -5410,6 +5410,7 @@ def handleExtendedAttribute(self, attr): or identifier == "ReturnValueNeedsContainsHack" or identifier == "BinaryName" or identifier == "NonEnumerable" + or identifier == "TaintSource" # Taintfox added ): # Known attributes that we don't need to do anything with here pass diff --git a/dom/webidl/Screen.webidl b/dom/webidl/Screen.webidl index 74db942702fe9..a31106a213ddb 100644 --- a/dom/webidl/Screen.webidl +++ b/dom/webidl/Screen.webidl @@ -11,9 +11,9 @@ interface Screen : EventTarget { readonly attribute long availWidth; [Throws] readonly attribute long availHeight; - [Throws] + [Throws, TaintSource] readonly attribute long width; - [Throws] + [Throws, TaintSource] readonly attribute long height; [Throws] readonly attribute long colorDepth; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 37840b53d8305..83e6393548e07 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4545,6 +4545,9 @@ JS_MarkTaintSource(JSContext* cx, JS::MutableHandleValue value, const TaintOpera if (value.isString()) { // If we have a string, set taint directly JS_MarkTaintSource(cx, value.toString(), op); + } else if (value.isNumber()) { + double d = value.toNumber(); + value.setObject(*NumberObject::createTainted(cx, d, TaintFlow(op))); } else if (value.isObject()) { // If it is an object, loop over contents // This function is used for convenience to taint all diff --git a/taint/README.md b/taint/README.md index ad65c4ae7b64d..a610aa8df1410 100644 --- a/taint/README.md +++ b/taint/README.md @@ -83,6 +83,9 @@ going to [](about:config) and looking for the ```dom.storage.next_gen``` option. TODO: intercept the local storage call and add the taint information to storage as well (e.g. via JSON?) +#### WebIDL Sources +It is now possible to add taint sources to WebIDL getters by adding the TaintSource attribute. +See dom/webidl/Screen.webidl for an example. ### Sinks From 1743c7409976adef044395cfc149fab153d75b10 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 6 Sep 2022 09:24:54 +0200 Subject: [PATCH 19/70] Fix taint propagation for bitwise operations --- js/src/tests/taint/number_tainting.js | 9 ++++ js/src/vm/Interpreter.cpp | 69 +++++++++++++++++++++------ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/js/src/tests/taint/number_tainting.js b/js/src/tests/taint/number_tainting.js index 1b20e339e65c4..9195abb52c627 100644 --- a/js/src/tests/taint/number_tainting.js +++ b/js/src/tests/taint/number_tainting.js @@ -41,6 +41,9 @@ function numberTaintingTest() { assertEq(a ** b, 42 ** 13.37); assertEq(a ** b == 42 ** 13.37, true); + + // + // Number increment/decrement assertNumberTainted(a++); assertNumberTainted(a); assertNumberTainted(a--); @@ -50,6 +53,7 @@ function numberTaintingTest() { assertNumberTainted(--a); assertNumberTainted(a); + // // Bitwise operations b = taint(3); @@ -61,6 +65,11 @@ function numberTaintingTest() { assertNumberTainted(a >> b); assertEq(a >> b, 42 >> 3); + assertNumberTainted(a >>> 1); + assertNumberTainted(a >>> b); + assertEq(a >> b, 42 >>> 3); + assertEq(taint(-5) >>> taint(2), 1073741822); + assertNumberTainted(a & 1); assertNumberTainted(a & b); assertEq(a & b, 42 & 3); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 648dd25741db1..b4f8253da223c 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1590,6 +1590,9 @@ static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx, static MOZ_ALWAYS_INLINE bool BitNotOperation(JSContext* cx, MutableHandleValue in, MutableHandleValue out) { + // TaintFox: copy in since it is mutable. + RootedValue origIn(cx, in); + if (!ToInt32OrBigInt(cx, in)) { return false; } @@ -1599,8 +1602,10 @@ static MOZ_ALWAYS_INLINE bool BitNotOperation(JSContext* cx, } out.setInt32(~in.toInt32()); - if (isTaintedNumber(in)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getNumberTaint(in))); + + // TaintFox: Taint propagation for bitwise not. + if (isTaintedNumber(origIn)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getNumberTaint(origIn))); } return true; } @@ -1609,6 +1614,10 @@ static MOZ_ALWAYS_INLINE bool BitXorOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; } @@ -1618,8 +1627,10 @@ static MOZ_ALWAYS_INLINE bool BitXorOperation(JSContext* cx, } out.setInt32(lhs.toInt32() ^ rhs.toInt32()); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for bitwise xor. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1628,6 +1639,10 @@ static MOZ_ALWAYS_INLINE bool BitOrOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; } @@ -1637,8 +1652,10 @@ static MOZ_ALWAYS_INLINE bool BitOrOperation(JSContext* cx, } out.setInt32(lhs.toInt32() | rhs.toInt32()); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for bitwise or. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1647,6 +1664,10 @@ static MOZ_ALWAYS_INLINE bool BitAndOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; } @@ -1656,8 +1677,10 @@ static MOZ_ALWAYS_INLINE bool BitAndOperation(JSContext* cx, } out.setInt32(lhs.toInt32() & rhs.toInt32()); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for bitwise and. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1666,6 +1689,10 @@ static MOZ_ALWAYS_INLINE bool BitLshOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; } @@ -1680,8 +1707,10 @@ static MOZ_ALWAYS_INLINE bool BitLshOperation(JSContext* cx, uint32_t left = static_cast(lhs.toInt32()); uint8_t right = rhs.toInt32() & 31; out.setInt32(mozilla::WrapToSigned(left << right)); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for bitwise left shift. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1690,6 +1719,10 @@ static MOZ_ALWAYS_INLINE bool BitRshOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; } @@ -1699,8 +1732,10 @@ static MOZ_ALWAYS_INLINE bool BitRshOperation(JSContext* cx, } out.setInt32(lhs.toInt32() >> (rhs.toInt32() & 31)); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for bitwise right shift. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); } return true; } @@ -1709,6 +1744,10 @@ static MOZ_ALWAYS_INLINE bool UrshOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + // TaintFox: copy lhs and rhs since they are mutable. + RootedValue origLhs(cx, lhs); + RootedValue origRhs(cx, rhs); + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } @@ -1726,8 +1765,10 @@ static MOZ_ALWAYS_INLINE bool UrshOperation(JSContext* cx, } left >>= right & 31; out.setNumber(uint32_t(left)); - if (isAnyTaintedNumber(lhs, rhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toNumber(), getAnyNumberTaint(lhs, rhs))); + + // TaintFox: Taint propagation for unsigned right shift. + if (isAnyTaintedNumber(origLhs, origRhs)) { + out.setObject(*NumberObject::createTainted(cx, out.toNumber(), getAnyNumberTaint(origLhs, origRhs))); } return true; } From a247d5eaec3ebb0ac1e8bb6f69c229a130752327 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 6 Sep 2022 11:10:08 +0200 Subject: [PATCH 20/70] Foxound: Add taint propagation for charCodeAt() and fromCharCode() --- js/src/builtin/String.cpp | 17 +++++++++++++++++ js/src/tests/taint/number_string_conversions.js | 1 + 2 files changed, 18 insertions(+) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 69db5ee2ac60e..e154628e1328f 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -2016,6 +2016,12 @@ bool js::str_charCodeAt_impl(JSContext* cx, HandleString string, return false; } res.setInt32(c); + + // TaintFox: Taint propagation for char codes of tainted strings via charCodeAt(). + if (string->taint().at(i)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), string->taint().at(i))); + } + return true; out_of_range: @@ -4077,10 +4083,13 @@ bool js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) { MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX); + // FoxHound: Disable optimization to simplify changes needed + /* // Optimize the single-char case. if (args.length() == 1) { return str_fromCharCode_one_arg(cx, args[0], args.rval()); } + */ // Optimize the case where the result will definitely fit in an inline // string (thin or fat) and so we don't need to malloc the chars. (We could @@ -4107,6 +4116,14 @@ bool js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox: todo: loop at input args, scan for tainted numbers and propagate + // taint to string + for (unsigned i = 0; i < args.length(); i++) { + if (isTaintedNumber(args[i])) { + str->taint().set(i, getNumberTaint(args[i])); + } + } + args.rval().setString(str); return true; } diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index e4fa30512e33a..ec60aeeb4a523 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -1,6 +1,7 @@ function numberStringConversionTests() { // Test single character taint propagation var taintedStr = randomTaintedString(); + assertTainted(taintedStr.charCodeAt(0)); assertTainted(String.fromCharCode(taintedStr.charCodeAt(0))); // Test multi character taint propagation From 73eacb4b5f51ba33585825f318ee0ec45ad3ef8a Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 6 Sep 2022 11:16:28 +0200 Subject: [PATCH 21/70] Foxhound: Cleanup comment --- js/src/builtin/String.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index e154628e1328f..321f8d3dd320f 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -4116,8 +4116,7 @@ bool js::str_fromCharCode(JSContext* cx, unsigned argc, Value* vp) { return false; } - // TaintFox: todo: loop at input args, scan for tainted numbers and propagate - // taint to string + // TaintFox: loop at input args, scan for tainted numbers and propagate taint to string for (unsigned i = 0; i < args.length(); i++) { if (isTaintedNumber(args[i])) { str->taint().set(i, getNumberTaint(args[i])); From 5b62fd41458c50d1570ee2ac4d4c142e7b70d6cf Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 6 Sep 2022 13:42:19 +0200 Subject: [PATCH 22/70] Foxhound: Improve tests and asserts --- js/src/tests/taint/number_string_conversions.js | 13 +++++++++++-- js/src/tests/taint/shell.js | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index ec60aeeb4a523..dddeb2c8dd385 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -1,7 +1,7 @@ function numberStringConversionTests() { // Test single character taint propagation var taintedStr = randomTaintedString(); - assertTainted(taintedStr.charCodeAt(0)); + assertNumberTainted(taintedStr.charCodeAt(0)); assertTainted(String.fromCharCode(taintedStr.charCodeAt(0))); // Test multi character taint propagation @@ -14,7 +14,16 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); - // TODO do we care about number.toString()? + // Test explicit number.toString() + var taintedNumber = taint(42); + assertTainted(taintedNumber.toString()); + + // Test implicit string conversion + var taintedNumber = taint(42); + assertTainted(taintedNumber + "abc"); + assertTainted((taintedNumber + "abc")[1]); + assertNotTainted((taintedNumber + "abc")[2]); + } runTaintTest(numberStringConversionTests); diff --git a/js/src/tests/taint/shell.js b/js/src/tests/taint/shell.js index 669424f07c1b5..3512c0df5cb13 100644 --- a/js/src/tests/taint/shell.js +++ b/js/src/tests/taint/shell.js @@ -255,8 +255,17 @@ if (typeof runTaintTest === 'undefined') { if (typeof assertNumberTainted === 'undefined') { // Assert that the given number is tainted. var assertNumberTainted = function(num) { - if (num.taint.length == 0) { - throw Error("Number ('" + num + "') is not tainted"); + if (!num.taint || num.taint.length == 0) { + throw Error("Number (" + num + ") is not tainted"); + } + } +} + +if (typeof assertNumberNotTainted === 'undefined') { + // Assert that the given number is not tainted. + var assertNumberNotTainted = function(num) { + if (num.taint && num.taint.length > 0) { + throw Error("Number (" + num + ") is tainted"); } } } From feb74b0fce9bb95790843973324e96682393ec4e Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 6 Sep 2022 17:17:34 +0200 Subject: [PATCH 23/70] Foxhound: Add tests for type conversions --- .../tests/taint/number_string_conversions.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index dddeb2c8dd385..48d26f7b9bb9f 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -14,16 +14,32 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); - // Test explicit number.toString() + // Test explicit string conversion var taintedNumber = taint(42); assertTainted(taintedNumber.toString()); + assertTainted(String(taintedNumber)); // Test implicit string conversion - var taintedNumber = taint(42); assertTainted(taintedNumber + "abc"); assertTainted((taintedNumber + "abc")[1]); assertNotTainted((taintedNumber + "abc")[2]); + // Test explicit number conversion + assertNumberTainted(Number(taint("42"))); + assertNumberTainted(parseInt(taint("42"))); + assertNumberTainted(parseFloat(taint("42.42"))); + + // Test implicit number conversion + assertNumberTainted(42 - taint("2")); + assertNumberTainted(taint("42") - 2); + assertNumberTainted(taint("42") - taint("2")); + assertNumberTainted(42 * taint("2")); + assertNumberTainted(taint("42") * 2); + assertNumberTainted(taint("42") * taint("2")); + assertNumberTainted(42 / taint("2")); + assertNumberTainted(taint("42") / 2); + assertNumberTainted(taint("42") / taint("2")); + } runTaintTest(numberStringConversionTests); From e0956a4932349c5a1b444c0702a424496744e0c5 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Fri, 9 Sep 2022 09:13:20 +0200 Subject: [PATCH 24/70] Foxhound: Add taint propagation for num->string conversion --- js/src/jsnum.cpp | 13 ++++++++++++ .../tests/taint/number_string_conversions.js | 21 +++++++++++++------ js/src/vm/Interpreter.cpp | 17 +++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 6df965a7e1b77..b7e23704eab75 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -957,6 +957,19 @@ static bool num_toString(JSContext* cx, unsigned argc, Value* vp) { if (!str) { return false; } + + // TaintFox: Propagate Number Taint to String + if (isTaintedNumber(args.thisv())) { + // Atoms cannot be tainted. Atoms are created for ints<6bit and other + // common strings. If we are dealing with an Atom, wrap it inside a + // dependent String, which can be tainted. + if(str->isAtom()){ + str = NewDependentString(cx,str,0,str->length()); + } + SafeStringTaint newTaint(getNumberTaint(args.thisv()),str->length()); + str->setTaint(cx, newTaint); + } + args.rval().setString(str); return true; } diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index 48d26f7b9bb9f..4b2c100a75ccc 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -14,15 +14,24 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); + // Small numbers <=6bit are sometimes transformed to Atoms, when + // converted to strings, which can cause problems + var taintedNumberSmall = taint(42); + var taintedNumberLarge = taint(42); + // Test explicit string conversion - var taintedNumber = taint(42); - assertTainted(taintedNumber.toString()); - assertTainted(String(taintedNumber)); + assertTainted(taintedNumberLarge.toString()); + assertTainted(String(taintedNumberLarge)); + assertTainted(taintedNumberSmall.toString()); + assertTainted(String(taintedNumberSmall)); // Test implicit string conversion - assertTainted(taintedNumber + "abc"); - assertTainted((taintedNumber + "abc")[1]); - assertNotTainted((taintedNumber + "abc")[2]); + assertTainted(taintedNumberLarge + "abc"); + assertTainted((taintedNumberLarge + "abc")[1]); + assertNotTainted((taintedNumberLarge + "abc")[2]); + assertTainted(taintedNumberSmall + "abc"); + assertTainted((taintedNumberSmall + "abc")[1]); + assertNotTainted((taintedNumberSmall + "abc")[2]); // Test explicit number conversion assertNumberTainted(Number(taint("42"))); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b4f8253da223c..bd9fb2ff826ee 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1398,12 +1398,19 @@ static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx, } } + // TaintFox: Deactivate generic ToPrimitive() without preffered type here. + // This would cause taintedNumber objects to be converted to primitive numbers + // without taint. This is bad, if we later have to convert to String. + // ToString() is internally using ToPrimitive() with a preffered type, which + // supports taint propagation. + /* if (!ToPrimitive(cx, lhs)) { return false; } if (!ToPrimitive(cx, rhs)) { return false; } + */ bool lIsString = lhs.isString(); bool rIsString = rhs.isString(); @@ -1442,6 +1449,16 @@ static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx, return true; } + // TaintFox: If no string conversion of numbers is needed, we can safely + // use the generic ToPrimitive() taint is also lost in this case but will + // later be added based on origLhs and origRhs + if (!ToPrimitive(cx, lhs)) { + return false; + } + if (!ToPrimitive(cx, rhs)) { + return false; + } + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { return false; } From 1fa7709b72cdf65efcce68d8c2050fd620af9be2 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Fri, 9 Sep 2022 14:55:25 +0200 Subject: [PATCH 25/70] Foxhound: Add taint propagation for explicit str->num conversion Support for: - new Number() - Number() - parseInt() - parseFloat() --- js/src/jsnum.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index b7e23704eab75..b6f825513225f 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -463,7 +463,12 @@ static bool num_parseFloat(JSContext* cx, unsigned argc, Value* vp) { return false; } - if (str->hasIndexValue()) { + // TaintFox: create copy of string taint to be safe in case string is + // affected by garbage collection + SafeStringTaint taint = str->taint().safeCopy(); + + // TaintFox: Only allow shortcut via index value for untainted strings + if (str->hasIndexValue() && !taint.hasTaint()) { args.rval().setNumber(str->getIndexValue()); return true; } @@ -474,6 +479,9 @@ static bool num_parseFloat(JSContext* cx, unsigned argc, Value* vp) { } double d; + // TaintFox: add scope around AutoCheckCannotGC because it would later + // interfer with creating the NumberObject + { AutoCheckCannotGC nogc; if (linear->hasLatin1Chars()) { const Latin1Char* begin = linear->latin1Chars(nogc); @@ -494,8 +502,17 @@ static bool num_parseFloat(JSContext* cx, unsigned argc, Value* vp) { d = GenericNaN(); } } + } - args.rval().setDouble(d); + // TaintFox: if string was tainted, propagate taint to number using NumberObj + if (taint.hasTaint()) { + // Currently gets taint from any one TaintRange in tainted string. + // In the future, it should get all taints. + args.rval().setObject(*NumberObject::createTainted(cx, d, taint.begin()->flow())); + } else { + // Default case from original code: set number primitive + args.rval().setDouble(d); + } return true; } @@ -587,7 +604,8 @@ static bool num_parseInt(JSContext* cx, unsigned argc, Value* vp) { if (args[0].isString()) { JSString* str = args[0].toString(); - if (str->hasIndexValue()) { + // TaintFox: Only allow shortcut via index value for untainted strings + if (str->hasIndexValue() && str->taint().hasTaint()) { args.rval().setNumber(str->getIndexValue()); return true; } @@ -601,6 +619,10 @@ static bool num_parseInt(JSContext* cx, unsigned argc, Value* vp) { } args[0].setString(inputString); + // TaintFox: create copy of string taint to be safe in case string is + // affected by garbage collection + SafeStringTaint taint = inputString->taint().safeCopy(); + /* Steps 6-9. */ bool stripPrefix = true; int32_t radix; @@ -628,9 +650,13 @@ static bool num_parseInt(JSContext* cx, unsigned argc, Value* vp) { return false; } + + double number; + // TaintFox: add scope around AutoCheckCannotGC because it would later + // interfer with creating the NumberObject + { AutoCheckCannotGC nogc; size_t length = inputString->length(); - double number; if (linear->hasLatin1Chars()) { if (!ParseIntImpl(cx, linear->latin1Chars(nogc), length, stripPrefix, radix, &number)) { @@ -642,8 +668,18 @@ static bool num_parseInt(JSContext* cx, unsigned argc, Value* vp) { return false; } } + } - args.rval().setNumber(number); + // TaintFox: if string was tainted, propagate taint to number using NumberObj + if (taint.hasTaint()) { + // Currently gets taint from any one TaintRange in tainted string. + // In the future, it should get all taints. + args.rval().setObject(*NumberObject::createTainted(cx, number, taint.begin()->flow())); + } else { + // Default case from original code: set number primitive + args.rval().setDouble(number); + } + // args.rval().setNumber(number); return true; } @@ -662,6 +698,13 @@ const JSClass NumberObject::class_ = { static bool Number(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + // TaintFox: create copy of string taint to be safe in case string is + // affected by garbage collection + SafeStringTaint taint; + if (args[0].isString()){ + taint = args[0].toString()->taint().safeCopy(); + } + if (args.length() > 0) { // BigInt proposal section 6.2, steps 2a-c. if (!ToNumeric(cx, args[0])) { @@ -675,7 +718,16 @@ static bool Number(JSContext* cx, unsigned argc, Value* vp) { if (!args.isConstructing()) { if (args.length() > 0) { - args.rval().set(args[0]); + + // Taintfox: this takes care of [Number()] without the explicit new. By + // default it will output a number, not a NumberObject. If a tainted + // string was used as an argument, we create a tainted NumberObject. + if (taint.hasTaint()) { + args.rval().setObject(*NumberObject::createTainted(cx, args[0].toNumber(), taint.begin()->flow())); + } else{ + // Default branch from original code + args.rval().set(args[0]); + } } else { args.rval().setInt32(0); } @@ -688,7 +740,17 @@ static bool Number(JSContext* cx, unsigned argc, Value* vp) { } double d = args.length() > 0 ? args[0].toNumber() : 0; - JSObject* obj = NumberObject::create(cx, d, proto); + + // Taintfox: this takes care of explicit [new Number()] if a string was used + // as an argument + JSObject* obj; + if (taint.hasTaint()) { + obj = NumberObject::createTainted(cx, d, taint.begin()->flow()); + } else{ + // Default branch from original code + obj = NumberObject::create(cx, d, proto); + } + if (!obj) { return false; } From 182048fb7583d5594d8734ecf2b2f51be0c373a2 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Fri, 9 Sep 2022 16:01:42 +0200 Subject: [PATCH 26/70] Foxhound: add test for hashing functions * Just two functions for the moment. more to be added later * Hashing functions from fingerprinting scripts to be added later * Test currently fails, because it triggers JIT-compiler; Jit-Compiler does currently not fully support primitive tainting --- js/src/tests/taint/hash.js | 222 +++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 js/src/tests/taint/hash.js diff --git a/js/src/tests/taint/hash.js b/js/src/tests/taint/hash.js new file mode 100644 index 0000000000000..a87ff7acc2003 --- /dev/null +++ b/js/src/tests/taint/hash.js @@ -0,0 +1,222 @@ +/* + This test contains a variety of hashing functions. + Both very simple ones as well as more complicated and obfoscated ones. + Some are based on the hashing functions used by fingerprinting scripts. +*/ + +// Based on: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ +simpleHash = (inputString)=>{ + var hash = 0; + if (inputString.length == 0) return hash; + for (i = 0; i < inputString.length; i++) { + char = inputString.charCodeAt(i); + hash = ((hash<<5)-hash)+char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + +// Based on: http://www.myersdaily.org/joseph/javascript/md5.js +// More details at: http://www.myersdaily.org/joseph/javascript/md5-text.html +md5Hash = (inputString)=>{ + md5cycle = (x, k) => { + var a = x[0], b = x[1], c = x[2], d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + + } + + cmn = (q, a, b, x, s, t) => { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + } + + ff = (a, b, c, d, x, s, t) => { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + } + + gg = (a, b, c, d, x, s, t) => { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + } + + hh = (a, b, c, d, x, s, t) => { + return cmn(b ^ c ^ d, a, b, x, s, t); + } + + ii = (a, b, c, d, x, s, t) => { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + } + + md51 = (s) => { + txt = ''; + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], i; + for (i=64; i<=s.length; i+=64) { + md5cycle(state, md5blk(s.substring(i-64, i))); + } + s = s.substring(i-64); + var tail = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; + for (i=0; i>2] |= s.charCodeAt(i) << ((i%4) << 3); + tail[i>>2] |= 0x80 << ((i%4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i=0; i<16; i++) tail[i] = 0; + } + tail[14] = n*8; + md5cycle(state, tail); + return state; + } + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = (s) => { /* I figured global was faster. */ + var md5blks = [], i; /* Andy King said do it this way. */ + for (i=0; i<64; i+=4) { + md5blks[i>>2] = s.charCodeAt(i) + + (s.charCodeAt(i+1) << 8) + + (s.charCodeAt(i+2) << 16) + + (s.charCodeAt(i+3) << 24); + } + return md5blks; + } + + var hex_chr = '0123456789abcdef'.split(''); + + rhex = (n) => + { + var s='', j=0; + for(; j<4; j++) + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + + hex_chr[(n >> (j * 8)) & 0x0F]; + return s; + } + + hex = (x) => { + for (var i=0; i { + return hex(md51(s)); + } + + /* this is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + + add32 = (a, b) => { + return (a + b) & 0xFFFFFFFF; + } + + if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') { + add32 = (x, y) => { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + } + } + + return md5(inputString); +} + + + +hashTaintTest = () => { + var s = randomTaintedString(); + + assertTainted(simpleHash(s)); + assertTainted(md5Hash(s)); +} + +runTaintTest(hashTaintTest); + +if (typeof reportCompare === 'function') + reportCompare(true, true); From d7df25023a03b97d1aaa34e1944377cd0686fa01 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Mon, 12 Sep 2022 10:36:37 +0200 Subject: [PATCH 27/70] Foxhound: Disable Baseline Interpreter Baseline Interpreter is not compatible with new features in their current state --- js/src/jit/JitOptions.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index 170368f53bece..a9ee47a230066 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -121,7 +121,9 @@ DefaultJitOptions::DefaultJitOptions() { SET_DEFAULT(disableBailoutLoopCheck, false); // Whether the Baseline Interpreter is enabled. - SET_DEFAULT(baselineInterpreter, true); + // TaintFox: disable Baseline Interpreter because it is not compatible with the current + // number tainting functionality + SET_DEFAULT(baselineInterpreter, false); // Whether the Baseline JIT is enabled. SET_DEFAULT(baselineJit, true); From f14dc471bfdf4993c7bd10607002f432e2e6759b Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Mon, 12 Sep 2022 13:15:20 +0200 Subject: [PATCH 28/70] Foxhound: add two hashing functions to hash test Hashing test currently fails because taint is not propagated when calling charAt() with a tainted number as a parameter. This causes issues when "lookup strings" are used for conversion. --- js/src/tests/taint/hash.js | 186 ++++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 2 deletions(-) diff --git a/js/src/tests/taint/hash.js b/js/src/tests/taint/hash.js index a87ff7acc2003..81cea3ff43dc6 100644 --- a/js/src/tests/taint/hash.js +++ b/js/src/tests/taint/hash.js @@ -72,7 +72,7 @@ md5Hash = (inputString)=>{ d = hh(d, a, b, c, k[12], 11, -421815835); c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651); - + a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415); c = ii(c, d, a, b, k[14], 15, -1416354905); @@ -207,13 +207,195 @@ md5Hash = (inputString)=>{ return md5(inputString); } +// Based on the hashing algorithm used in FingerprintJS v3.3.3 by Fingerprint Inc, +// which itself is based on MurmurHash3 by Karan Lyons +// FingerprintJS: https://github.com/fingerprintjs/fingerprintjs +// MurmurHash3: https://github.com/karanlyons/murmurHash3.js +// Minified code +fingerprintJsHash = (inputString)=>{ + c = (e, t) => { + e = [e[0] >>> 16, 65535 & e[0], e[1] >>> 16, 65535 & e[1]], + t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]]; + var n = [0, 0, 0, 0]; + return n[3] += e[3] + t[3], + n[2] += n[3] >>> 16, + n[3] &= 65535, + n[2] += e[2] + t[2], + n[1] += n[2] >>> 16, + n[2] &= 65535, + n[1] += e[1] + t[1], + n[0] += n[1] >>> 16, + n[1] &= 65535, + n[0] += e[0] + t[0], + n[0] &= 65535, + [n[0] << 16 | n[1], n[2] << 16 | n[3]] + } + u = (e, t) => { + e = [e[0] >>> 16, 65535 & e[0], e[1] >>> 16, 65535 & e[1]], + t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]]; + var n = [0, 0, 0, 0]; + return n[3] += e[3] * t[3], + n[2] += n[3] >>> 16, + n[3] &= 65535, + n[2] += e[2] * t[3], + n[1] += n[2] >>> 16, + n[2] &= 65535, + n[2] += e[3] * t[2], + n[1] += n[2] >>> 16, + n[2] &= 65535, + n[1] += e[1] * t[3], + n[0] += n[1] >>> 16, + n[1] &= 65535, + n[1] += e[2] * t[2], + n[0] += n[1] >>> 16, + n[1] &= 65535, + n[1] += e[3] * t[1], + n[0] += n[1] >>> 16, + n[1] &= 65535, + n[0] += e[0] * t[3] + e[1] * t[2] + e[2] * t[1] + e[3] * t[0], + n[0] &= 65535, + [n[0] << 16 | n[1], n[2] << 16 | n[3]] + } + s = (e, t) => { + return 32 === (t %= 64) ? [e[1], e[0]] : t < 32 ? [e[0] << t | e[1] >>> 32 - t, e[1] << t | e[0] >>> 32 - t] : (t -= 32, + [e[1] << t | e[0] >>> 32 - t, e[0] << t | e[1] >>> 32 - t]) + } + l = (e, t) => { + return 0 === (t %= 64) ? e : t < 32 ? [e[0] << t | e[1] >>> 32 - t, e[1] << t] : [e[1] << t - 32, 0] + } + d = (e, t) => { + return [e[0] ^ t[0], e[1] ^ t[1]] + } + f = (e) => { + return e = d(e, [0, e[0] >>> 1]), + e = d(e = u(e, [4283543511, 3981806797]), [0, e[0] >>> 1]), + e = d(e = u(e, [3301882366, 444984403]), [0, e[0] >>> 1]) + } + h = (e, t) => { + t = t || 0; + var n, r = (e = e || "").length % 16, a = e.length - r, o = [0, t], i = [0, t], h = [0, 0], v = [0, 0], p = [2277735313, 289559509], m = [1291169091, 658871167]; + for (n = 0; n < a; n += 16) + h = [255 & e.charCodeAt(n + 4) | (255 & e.charCodeAt(n + 5)) << 8 | (255 & e.charCodeAt(n + 6)) << 16 | (255 & e.charCodeAt(n + 7)) << 24, 255 & e.charCodeAt(n) | (255 & e.charCodeAt(n + 1)) << 8 | (255 & e.charCodeAt(n + 2)) << 16 | (255 & e.charCodeAt(n + 3)) << 24], + v = [255 & e.charCodeAt(n + 12) | (255 & e.charCodeAt(n + 13)) << 8 | (255 & e.charCodeAt(n + 14)) << 16 | (255 & e.charCodeAt(n + 15)) << 24, 255 & e.charCodeAt(n + 8) | (255 & e.charCodeAt(n + 9)) << 8 | (255 & e.charCodeAt(n + 10)) << 16 | (255 & e.charCodeAt(n + 11)) << 24], + h = s(h = u(h, p), 31), + o = c(o = s(o = d(o, h = u(h, m)), 27), i), + o = c(u(o, [0, 5]), [0, 1390208809]), + v = s(v = u(v, m), 33), + i = c(i = s(i = d(i, v = u(v, p)), 31), o), + i = c(u(i, [0, 5]), [0, 944331445]); + switch (h = [0, 0], + v = [0, 0], + r) { + case 15: + v = d(v, l([0, e.charCodeAt(n + 14)], 48)); + case 14: + v = d(v, l([0, e.charCodeAt(n + 13)], 40)); + case 13: + v = d(v, l([0, e.charCodeAt(n + 12)], 32)); + case 12: + v = d(v, l([0, e.charCodeAt(n + 11)], 24)); + case 11: + v = d(v, l([0, e.charCodeAt(n + 10)], 16)); + case 10: + v = d(v, l([0, e.charCodeAt(n + 9)], 8)); + case 9: + v = u(v = d(v, [0, e.charCodeAt(n + 8)]), m), + i = d(i, v = u(v = s(v, 33), p)); + case 8: + h = d(h, l([0, e.charCodeAt(n + 7)], 56)); + case 7: + h = d(h, l([0, e.charCodeAt(n + 6)], 48)); + case 6: + h = d(h, l([0, e.charCodeAt(n + 5)], 40)); + case 5: + h = d(h, l([0, e.charCodeAt(n + 4)], 32)); + case 4: + h = d(h, l([0, e.charCodeAt(n + 3)], 24)); + case 3: + h = d(h, l([0, e.charCodeAt(n + 2)], 16)); + case 2: + h = d(h, l([0, e.charCodeAt(n + 1)], 8)); + case 1: + h = u(h = d(h, [0, e.charCodeAt(n)]), p), + o = d(o, h = u(h = s(h, 31), m)) + } + return o = c(o = d(o, [0, e.length]), i = d(i, [0, e.length])), + i = c(i, o), + o = c(o = f(o), i = f(i)), + i = c(i, o), + ("00000000" + (o[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (o[1] >>> 0).toString(16)).slice(-8) + ("00000000" + (i[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (i[1] >>> 0).toString(16)).slice(-8) + } + + return h(inputString); +} + +// Based on the hashing algorithm used in fingerprinting script found by +// FpFlow (https://github.com/FPFlow/FPFlow-project) +// Originally at: https://wl.jd.com/wl.js +// Stored at FpFlow: https://github.com/FPFlow/FPFlow-project/blob/master/scripts/4.formatted.js +// Minified code +fpFlow4Hash = (inputString)=>{ + + MD5 = { + chrsz: 8, + G: "", + hex_md5: function(e) { + return this.binl2hex(this.core_md5(this.str2binl(e), e.length * this.chrsz)) + }, + core_md5: function(e, t) { + e[t >> 5] |= 128 << t % 32, e[14 + (t + 64 >>> 9 << 4)] = t; + for (var r = 1732584193, n = -271733879, i = -1732584194, o = 271733878, a = 0; a < e.length; a += 16) { + var d = r, + s = n, + c = i, + u = o; + r = this.md5_ff(r, n, i, o, e[a + 0], 7, -680876936), o = this.md5_ff(o, r, n, i, e[a + 1], 12, -389564586), i = this.md5_ff(i, o, r, n, e[a + 2], 17, 606105819), n = this.md5_ff(n, i, o, r, e[a + 3], 22, -1044525330), r = this.md5_ff(r, n, i, o, e[a + 4], 7, -176418897), o = this.md5_ff(o, r, n, i, e[a + 5], 12, 1200080426), i = this.md5_ff(i, o, r, n, e[a + 6], 17, -1473231341), n = this.md5_ff(n, i, o, r, e[a + 7], 22, -45705983), r = this.md5_ff(r, n, i, o, e[a + 8], 7, 1770035416), o = this.md5_ff(o, r, n, i, e[a + 9], 12, -1958414417), i = this.md5_ff(i, o, r, n, e[a + 10], 17, -42063), n = this.md5_ff(n, i, o, r, e[a + 11], 22, -1990404162), r = this.md5_ff(r, n, i, o, e[a + 12], 7, 1804603682), o = this.md5_ff(o, r, n, i, e[a + 13], 12, -40341101), i = this.md5_ff(i, o, r, n, e[a + 14], 17, -1502002290), n = this.md5_ff(n, i, o, r, e[a + 15], 22, 1236535329), r = this.md5_gg(r, n, i, o, e[a + 1], 5, -165796510), o = this.md5_gg(o, r, n, i, e[a + 6], 9, -1069501632), i = this.md5_gg(i, o, r, n, e[a + 11], 14, 643717713), n = this.md5_gg(n, i, o, r, e[a + 0], 20, -373897302), r = this.md5_gg(r, n, i, o, e[a + 5], 5, -701558691), o = this.md5_gg(o, r, n, i, e[a + 10], 9, 38016083), i = this.md5_gg(i, o, r, n, e[a + 15], 14, -660478335), n = this.md5_gg(n, i, o, r, e[a + 4], 20, -405537848), r = this.md5_gg(r, n, i, o, e[a + 9], 5, 568446438), o = this.md5_gg(o, r, n, i, e[a + 14], 9, -1019803690), i = this.md5_gg(i, o, r, n, e[a + 3], 14, -187363961), n = this.md5_gg(n, i, o, r, e[a + 8], 20, 1163531501), r = this.md5_gg(r, n, i, o, e[a + 13], 5, -1444681467), o = this.md5_gg(o, r, n, i, e[a + 2], 9, -51403784), i = this.md5_gg(i, o, r, n, e[a + 7], 14, 1735328473), n = this.md5_gg(n, i, o, r, e[a + 12], 20, -1926607734), r = this.md5_hh(r, n, i, o, e[a + 5], 4, -378558), o = this.md5_hh(o, r, n, i, e[a + 8], 11, -2022574463), i = this.md5_hh(i, o, r, n, e[a + 11], 16, 1839030562), n = this.md5_hh(n, i, o, r, e[a + 14], 23, -35309556), r = this.md5_hh(r, n, i, o, e[a + 1], 4, -1530992060), o = this.md5_hh(o, r, n, i, e[a + 4], 11, 1272893353), i = this.md5_hh(i, o, r, n, e[a + 7], 16, -155497632), n = this.md5_hh(n, i, o, r, e[a + 10], 23, -1094730640), r = this.md5_hh(r, n, i, o, e[a + 13], 4, 681279174), o = this.md5_hh(o, r, n, i, e[a + 0], 11, -358537222), i = this.md5_hh(i, o, r, n, e[a + 3], 16, -722521979), n = this.md5_hh(n, i, o, r, e[a + 6], 23, 76029189), r = this.md5_hh(r, n, i, o, e[a + 9], 4, -640364487), o = this.md5_hh(o, r, n, i, e[a + 12], 11, -421815835), i = this.md5_hh(i, o, r, n, e[a + 15], 16, 530742520), n = this.md5_hh(n, i, o, r, e[a + 2], 23, -995338651), r = this.md5_ii(r, n, i, o, e[a + 0], 6, -198630844), o = this.md5_ii(o, r, n, i, e[a + 7], 10, 1126891415), i = this.md5_ii(i, o, r, n, e[a + 14], 15, -1416354905), n = this.md5_ii(n, i, o, r, e[a + 5], 21, -57434055), r = this.md5_ii(r, n, i, o, e[a + 12], 6, 1700485571), o = this.md5_ii(o, r, n, i, e[a + 3], 10, -1894986606), i = this.md5_ii(i, o, r, n, e[a + 10], 15, -1051523), n = this.md5_ii(n, i, o, r, e[a + 1], 21, -2054922799), r = this.md5_ii(r, n, i, o, e[a + 8], 6, 1873313359), o = this.md5_ii(o, r, n, i, e[a + 15], 10, -30611744), i = this.md5_ii(i, o, r, n, e[a + 6], 15, -1560198380), n = this.md5_ii(n, i, o, r, e[a + 13], 21, 1309151649), r = this.md5_ii(r, n, i, o, e[a + 4], 6, -145523070), o = this.md5_ii(o, r, n, i, e[a + 11], 10, -1120210379), i = this.md5_ii(i, o, r, n, e[a + 2], 15, 718787259), n = this.md5_ii(n, i, o, r, e[a + 9], 21, -343485551), r = this.safe_add(r, d), n = this.safe_add(n, s), i = this.safe_add(i, c), o = this.safe_add(o, u) + } + return Array(r, n, i, o) + }, + md5_cmn: function(e, t, r, n, i, o) { + return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(t, e), this.safe_add(n, o)), i), r) + }, + md5_ff: function(e, t, r, n, i, o, a) { + return this.md5_cmn(t & r | ~t & n, e, t, i, o, a) + }, + md5_gg: function(e, t, r, n, i, o, a) { + return this.md5_cmn(t & n | r & ~n, e, t, i, o, a) + }, + md5_hh: function(e, t, r, n, i, o, a) { + return this.md5_cmn(t ^ r ^ n, e, t, i, o, a) + }, + md5_ii: function(e, t, r, n, i, o, a) { + return this.md5_cmn(r ^ (t | ~n), e, t, i, o, a) + }, + safe_add: function(e, t) { + var r = (65535 & e) + (65535 & t); + return (e >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r + }, + bit_rol: function(e, t) { + return e << t | e >>> 32 - t + }, + str2binl: function(e) { + for (var t = Array(), r = (1 << this.chrsz) - 1, n = 0; n < e.length * this.chrsz; n += this.chrsz) t[n >> 5] |= (e.charCodeAt(n / this.chrsz) & r) << n % 32; + return t + }, + binl2hex: function(e) { + for (var t = "0123456789abcdef", r = "", n = 0; n < 4 * e.length; n++) r += t.charAt(e[n >> 2] >> n % 4 * 8 + 4 & 15) + t.charAt(e[n >> 2] >> n % 4 * 8 & 15); + return r + } + } + + return MD5.core_md5(inputString); +} hashTaintTest = () => { - var s = randomTaintedString(); + var s = randomTaintedString() + randomTaintedString() + randomTaintedString(); assertTainted(simpleHash(s)); assertTainted(md5Hash(s)); + assertTainted(fingerprintJsHash(s)); + assertTainted(fpFlow4Hash(s)); } runTaintTest(hashTaintTest); From 877a77ff1bf1a354811ce092ef0114030d8f9a3d Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 13 Sep 2022 13:31:09 +0200 Subject: [PATCH 29/70] Foxhound: Enhance taint propagation for str.charAt Propagate taint to return value if parameter is a tainted number Missing: Taint propagation if both string and parameter are tainted --- js/src/builtin/String.cpp | 14 +++++++++++++- js/src/tests/taint/number_string_conversions.js | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 321f8d3dd320f..840149a605bc4 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -895,7 +895,7 @@ JSString* js::SubstringKernel(JSContext* cx, HandleString str, int32_t beginInt, // TaintFox SafeStringTaint newTaint = str->taint().safeCopy().subtaint(begin, begin + len); - + /* * Optimization for one level deep ropes. * This is common for the following pattern: @@ -1949,6 +1949,13 @@ static bool str_charAt(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedString str(cx); size_t i; + + // TaintFox: try to get taint from parameter + SafeStringTaint parameterTaint; + if(args.length() != 0 && isTaintedNumber(args[0])){ + parameterTaint.set(0, getNumberTaint(args[0])); + } + if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) { str = args.thisv().toString(); i = size_t(args[0].toInt32()); @@ -1973,10 +1980,15 @@ static bool str_charAt(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox: avoid atoms here if the base string is tainted. TODO(samuel) + // Taintfox: there is currently no branch for both the this-string and the + // parameter-number being tainted; should be added later once model is expanded str = NewDependentString(cx, str, i, 1); if (str->isTainted()) { str->taint().extend(TaintOperation("charAt", true, TaintLocationFromContext(cx), { taintarg(cx, i) })); + } else if(parameterTaint.hasTaint()) { + str->setTaint(cx,parameterTaint); } + // Taintfox: avoid creating atoms //str = cx->staticStrings().getUnitStringForElement(cx, str, i); diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index 4b2c100a75ccc..8f3c796251f4a 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -14,6 +14,11 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); + //Test number as parameter taint propagation + index = taint(3) + untaintedStr = "Hello World" + assertTainted(untaintedStr.charAt(index)) + // Small numbers <=6bit are sometimes transformed to Atoms, when // converted to strings, which can cause problems var taintedNumberSmall = taint(42); From bba31b05562dfb72f4f17682fe9841a3d874a2d6 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 13 Sep 2022 13:34:31 +0200 Subject: [PATCH 30/70] Foxhound: Fix hashing algorithm in test was using wrong function for return value --- js/src/tests/taint/hash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/tests/taint/hash.js b/js/src/tests/taint/hash.js index 81cea3ff43dc6..be264aacaf980 100644 --- a/js/src/tests/taint/hash.js +++ b/js/src/tests/taint/hash.js @@ -385,7 +385,7 @@ fpFlow4Hash = (inputString)=>{ } } - return MD5.core_md5(inputString); + return MD5.hex_md5(inputString); } From 6f2deaf10e93050c81c0567f9cc811d13706c1e5 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Wed, 14 Sep 2022 09:25:59 +0200 Subject: [PATCH 31/70] Foxhound: Add number taint propagation in JSON.stringify --- js/src/builtin/JSON.cpp | 16 ++++++++++++++++ js/src/tests/taint/number_string_conversions.js | 12 +++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/js/src/builtin/JSON.cpp b/js/src/builtin/JSON.cpp index 9b06df90bff2a..868e3f22405b8 100644 --- a/js/src/builtin/JSON.cpp +++ b/js/src/builtin/JSON.cpp @@ -404,6 +404,14 @@ static bool PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, } if (cls == ESClass::Number) { + + // TaintFox: Abort, if object is a tainted number, to prevent taint loss. + // special handling for tainted numbers at later stage (method Str()) + if (isTaintedNumber(vp)){ + return true; + } + + double d; if (!ToNumber(cx, vp, &d)) { return false; @@ -786,6 +794,14 @@ static bool Str(JSContext* cx, const Value& v, StringifyContext* scx) { return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false"); } + + // TaintFox: Convert tainted number to string (internally propagates taint) + // and append to string builder + if (isTaintedNumber(v)){ + HandleValue hv = HandleValue::fromMarkedLocation(&v); + return scx->sb.append(JS::ToString(cx, hv)); + } + /* Step 9. */ if (v.isNumber()) { if (v.isDouble()) { diff --git a/js/src/tests/taint/number_string_conversions.js b/js/src/tests/taint/number_string_conversions.js index 8f3c796251f4a..87f2098433076 100644 --- a/js/src/tests/taint/number_string_conversions.js +++ b/js/src/tests/taint/number_string_conversions.js @@ -14,7 +14,7 @@ function numberStringConversionTests() { var copiedString = String.fromCharCode.apply(null, chars); assertEqualTaint(copiedString, taintedStr); - //Test number as parameter taint propagation + // Test number as parameter taint propagation index = taint(3) untaintedStr = "Hello World" assertTainted(untaintedStr.charAt(index)) @@ -22,7 +22,13 @@ function numberStringConversionTests() { // Small numbers <=6bit are sometimes transformed to Atoms, when // converted to strings, which can cause problems var taintedNumberSmall = taint(42); - var taintedNumberLarge = taint(42); + var taintedNumberLarge = taint(9000); + + // Text JSON.stringify with inputs including tainted numbers + assertTainted(JSON.stringify(taintedNumberSmall)) + assertTainted(JSON.stringify([1,2,3,taintedNumberSmall,5])) + assertTainted(JSON.stringify({"num":taintedNumberSmall})) + assertTainted(JSON.stringify([1,2,3,{"num":taintedNumberSmall},5])) // Test explicit string conversion assertTainted(taintedNumberLarge.toString()); @@ -33,7 +39,7 @@ function numberStringConversionTests() { // Test implicit string conversion assertTainted(taintedNumberLarge + "abc"); assertTainted((taintedNumberLarge + "abc")[1]); - assertNotTainted((taintedNumberLarge + "abc")[2]); + assertNotTainted((taintedNumberLarge + "abc")[5]); assertTainted(taintedNumberSmall + "abc"); assertTainted((taintedNumberSmall + "abc")[1]); assertNotTainted((taintedNumberSmall + "abc")[2]); From 2281eadf033e3a3ab76bf6bc89bd31b20562af43 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 15 Sep 2022 12:51:29 +0000 Subject: [PATCH 32/70] Foxhound: add more screen attributes as taint source --- dom/webidl/Screen.webidl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dom/webidl/Screen.webidl b/dom/webidl/Screen.webidl index a31106a213ddb..5dec916b8e112 100644 --- a/dom/webidl/Screen.webidl +++ b/dom/webidl/Screen.webidl @@ -7,17 +7,17 @@ interface Screen : EventTarget { // CSSOM-View // http://dev.w3.org/csswg/cssom-view/#the-screen-interface - [Throws] + [Throws, TaintSource] readonly attribute long availWidth; - [Throws] + [Throws, TaintSource] readonly attribute long availHeight; [Throws, TaintSource] readonly attribute long width; [Throws, TaintSource] readonly attribute long height; - [Throws] + [Throws, TaintSource] readonly attribute long colorDepth; - [Throws] + [Throws, TaintSource] readonly attribute long pixelDepth; [Throws] From a4ea399c1de91be8bbe84c96916c24005b764e4e Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Mon, 19 Sep 2022 09:10:26 +0000 Subject: [PATCH 33/70] Foxhound: add capability to mark cashed values as taint sources * Only works for cached values, not when they are calculated for the first time * Add various attributes of the navigator-object as taint sources --- dom/bindings/Codegen.py | 20 ++++++++++++++++++++ dom/webidl/Navigator.webidl | 20 ++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 4938d636c1555..83150856e8b66 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -11167,6 +11167,11 @@ class CGSpecializedGetter(CGAbstractStaticMethod): def __init__(self, descriptor, attr): self.attr = attr + + # TaintFox: Check if return value should de marked as taint source and + # get name for taint source if needed + self.taintSource = GetLabelForErrorReporting(descriptor, attr, False) if memberIsTaintSource(attr) else None + name = "get_" + IDLToCIdentifier(attr.identifier.name) args = [ Argument("JSContext*", "cx"), @@ -11288,6 +11293,17 @@ def definition_body(self): slotIndex=memberReservedSlot(self.attr, self.descriptor), ) + # TaintFox: create source code for tainting cached return value + markTaintSnippet = "" + if self.taintSource is not None: + print("Generating taint source for cached value:", self.taintSource) + markTaintSnippet = \ + f""" + // Add taint source for cached value + MarkTaintSource(cx, args.rval(), "{self.taintSource}"); + """ + + prefix += fill( """ MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage)) > slotIndex); @@ -11296,6 +11312,9 @@ def definition_body(self): JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex); if (!cachedVal.isUndefined()) { args.rval().set(cachedVal); + + ${markTaintSnippet} + // The cached value is in the compartment of slotStorage, // so wrap into the caller compartment as needed. return ${maybeWrap}(cx, args.rval()); @@ -11303,6 +11322,7 @@ def definition_body(self): } """, + markTaintSnippet=markTaintSnippet, maybeWrap=getMaybeWrapValueFuncForType(self.attr.type), ) diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 2eb6359aa429c..6a64044f4cd4e 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -44,17 +44,17 @@ Navigator includes GPUProvider; interface mixin NavigatorID { // WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla"). - [Constant, Cached, Throws] + [Constant, Cached, Throws, TaintSource] readonly attribute DOMString appCodeName; // constant "Mozilla" - [Constant, Cached, NeedsCallerType] + [Constant, Cached, NeedsCallerType, TaintSource] readonly attribute DOMString appName; - [Constant, Cached, Throws, NeedsCallerType] + [Constant, Cached, Throws, NeedsCallerType, TaintSource] readonly attribute DOMString appVersion; - [Pure, Cached, Throws, NeedsCallerType] + [Pure, Cached, Throws, NeedsCallerType, TaintSource] readonly attribute DOMString platform; - [Pure, Cached, Throws, NeedsCallerType] + [Pure, Cached, Throws, NeedsCallerType, TaintSource] readonly attribute DOMString userAgent; - [Constant, Cached] + [Constant, Cached, TaintSource] readonly attribute DOMString product; // constant "Gecko" // Everyone but WebKit/Blink supports this. See bug 679971. @@ -69,7 +69,7 @@ interface mixin NavigatorLanguage { // main-thread from the worker thread anytime we need to retrieve them. They // are updated when pref intl.accept_languages is changed. - [Pure, Cached] + [Pure, Cached, TaintSource] readonly attribute DOMString? language; [Pure, Cached, Frozen] readonly attribute sequence languages; @@ -149,7 +149,7 @@ partial interface Navigator { // http://www.w3.org/TR/pointerevents/#extensions-to-the-navigator-interface partial interface Navigator { - [NeedsCallerType] + [NeedsCallerType, TaintSource] readonly attribute long maxTouchPoints; }; @@ -174,7 +174,7 @@ partial interface Navigator { }; partial interface Navigator { - [Throws, Constant, Cached, NeedsCallerType] + [Throws, Constant, Cached, NeedsCallerType, TaintSource] readonly attribute DOMString oscpu; // WebKit/Blink support this; Trident/Presto do not. readonly attribute DOMString vendor; @@ -184,7 +184,7 @@ partial interface Navigator { readonly attribute DOMString productSub; // WebKit/Blink/Trident/Presto support this. readonly attribute boolean cookieEnabled; - [Throws, Constant, Cached, NeedsCallerType] + [Throws, Constant, Cached, NeedsCallerType, TaintSource] readonly attribute DOMString buildID; // WebKit/Blink/Trident/Presto support this. From df8b8b1ec7a3822c98fb74b9ef2c96831c8125e3 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Mon, 19 Sep 2022 12:16:58 +0000 Subject: [PATCH 34/70] Foxhound: Add multiple numeric APIs as taint source --- dom/webidl/History.webidl | 2 +- dom/webidl/Screen.webidl | 4 ++-- dom/webidl/VisualViewport.webidl | 7 +++++++ dom/webidl/WebGLRenderingContext.webidl | 3 +++ dom/webidl/Window.webidl | 26 ++++++++++++------------- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/dom/webidl/History.webidl b/dom/webidl/History.webidl index 2be766e7ed267..dab2331b4eb7a 100644 --- a/dom/webidl/History.webidl +++ b/dom/webidl/History.webidl @@ -15,7 +15,7 @@ enum ScrollRestoration { "auto", "manual" }; [Exposed=Window] interface History { - [Throws] + [Throws, TaintSource] readonly attribute unsigned long length; [Throws] attribute ScrollRestoration scrollRestoration; diff --git a/dom/webidl/Screen.webidl b/dom/webidl/Screen.webidl index 5dec916b8e112..88046c3ac9ae4 100644 --- a/dom/webidl/Screen.webidl +++ b/dom/webidl/Screen.webidl @@ -24,9 +24,9 @@ interface Screen : EventTarget { readonly attribute long top; [Throws] readonly attribute long left; - [Throws] + [Throws, TaintSource] readonly attribute long availTop; - [Throws] + [Throws, TaintSource] readonly attribute long availLeft; /** diff --git a/dom/webidl/VisualViewport.webidl b/dom/webidl/VisualViewport.webidl index bde4b9a1b530a..2dca48b1f949d 100644 --- a/dom/webidl/VisualViewport.webidl +++ b/dom/webidl/VisualViewport.webidl @@ -10,15 +10,22 @@ [Pref="dom.visualviewport.enabled", Exposed=Window] interface VisualViewport : EventTarget { + [TaintSource] readonly attribute double offsetLeft; + [TaintSource] readonly attribute double offsetTop; + [TaintSource] readonly attribute double pageLeft; + [TaintSource] readonly attribute double pageTop; + [TaintSource] readonly attribute double width; + [TaintSource] readonly attribute double height; + [TaintSource] readonly attribute double scale; attribute EventHandler onresize; diff --git a/dom/webidl/WebGLRenderingContext.webidl b/dom/webidl/WebGLRenderingContext.webidl index 1909a89045936..1d29ed3565a06 100644 --- a/dom/webidl/WebGLRenderingContext.webidl +++ b/dom/webidl/WebGLRenderingContext.webidl @@ -104,8 +104,11 @@ interface WebGLActiveInfo { [Exposed=(Window,Worker), Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLShaderPrecisionFormat { + [TaintSource] readonly attribute GLint rangeMin; + [TaintSource] readonly attribute GLint rangeMax; + [TaintSource] readonly attribute GLint precision; }; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 8a01f62aea68a..c180ea46c1488 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -347,8 +347,8 @@ partial interface Window { // like a [Replaceable] attribute would, which needs the original JS value. //[Replaceable, Throws] readonly attribute double innerWidth; //[Replaceable, Throws] readonly attribute double innerHeight; - [Throws, NeedsCallerType] attribute any innerWidth; - [Throws, NeedsCallerType] attribute any innerHeight; + [Throws, NeedsCallerType, TaintSource] attribute any innerWidth; + [Throws, NeedsCallerType, TaintSource] attribute any innerHeight; // viewport scrolling void scroll(unrestricted double x, unrestricted double y); @@ -364,14 +364,14 @@ partial interface Window { [ChromeOnly] void mozScrollSnap(); // The four properties below are double per spec at the moment, but whether // that will continue is unclear. - [Replaceable, Throws] readonly attribute double scrollX; - [Replaceable, Throws] readonly attribute double pageXOffset; - [Replaceable, Throws] readonly attribute double scrollY; - [Replaceable, Throws] readonly attribute double pageYOffset; + [Replaceable, Throws, TaintSource] readonly attribute double scrollX; + [Replaceable, Throws, TaintSource] readonly attribute double pageXOffset; + [Replaceable, Throws, TaintSource] readonly attribute double scrollY; + [Replaceable, Throws, TaintSource] readonly attribute double pageYOffset; // Aliases for screenX / screenY. - [Replaceable, Throws, NeedsCallerType] readonly attribute double screenLeft; - [Replaceable, Throws, NeedsCallerType] readonly attribute double screenTop; + [Replaceable, Throws, NeedsCallerType, TaintSource] readonly attribute double screenLeft; + [Replaceable, Throws, NeedsCallerType, TaintSource] readonly attribute double screenTop; // client // These are writable because we allow chrome to write them. And they need @@ -381,10 +381,10 @@ partial interface Window { //[Replaceable, Throws] readonly attribute double screenY; //[Replaceable, Throws] readonly attribute double outerWidth; //[Replaceable, Throws] readonly attribute double outerHeight; - [Throws, NeedsCallerType] attribute any screenX; - [Throws, NeedsCallerType] attribute any screenY; - [Throws, NeedsCallerType] attribute any outerWidth; - [Throws, NeedsCallerType] attribute any outerHeight; + [Throws, NeedsCallerType, TaintSource] attribute any screenX; + [Throws, NeedsCallerType, TaintSource] attribute any screenY; + [Throws, NeedsCallerType, TaintSource] attribute any outerWidth; + [Throws, NeedsCallerType, TaintSource] attribute any outerHeight; }; // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames @@ -445,7 +445,7 @@ partial interface Window { readonly attribute float mozInnerScreenX; [Throws, NeedsCallerType] readonly attribute float mozInnerScreenY; - [Replaceable, Throws, NeedsCallerType] + [Replaceable, Throws, NeedsCallerType, TaintSource] readonly attribute double devicePixelRatio; /* The maximum offset that the window can be scrolled to From faaed1155cc3a61b223b0188702eeff1c171673e Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 20 Sep 2022 07:56:05 +0000 Subject: [PATCH 35/70] Foxhound: Add capability to use methods and wrapped values as taint sources + Add some of these as taint sources --- dom/bindings/Codegen.py | 25 ++++++++++++++++++++----- dom/bindings/parser/WebIDL.py | 1 + dom/webidl/HTMLCanvasElement.webidl | 2 +- dom/webidl/Navigator.webidl | 5 +++++ dom/webidl/WebGLRenderingContext.webidl | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 83150856e8b66..ed1fd6e7a5663 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -7986,16 +7986,31 @@ def wrapAndSetPtr(wrapCall, failureCode=None): """ if failureCode is None: failureCode = exceptionCode + + # TaintFox: create source code for tainting wrapped return value + markTaintSnippet = "" + if taintSource is not None: + print("Generating taint source for wrapped value:", taintSource) + markTaintSnippet = dedent( + f""" + // Add taint source for wrapped value + MarkTaintSource(cx, args.rval(), "{taintSource}");""" + ) + return fill( """ if (!${wrapCall}) { $*{failureCode} } + + ${markTaintSnippet} + $*{successCode} """, wrapCall=wrapCall, failureCode=failureCode, successCode=successCode, + markTaintSnippet=markTaintSnippet, ) if type is None or type.isVoid(): @@ -11297,11 +11312,11 @@ def definition_body(self): markTaintSnippet = "" if self.taintSource is not None: print("Generating taint source for cached value:", self.taintSource) - markTaintSnippet = \ + markTaintSnippet = dedent( f""" - // Add taint source for cached value - MarkTaintSource(cx, args.rval(), "{self.taintSource}"); - """ + // Add taint source for cached value + MarkTaintSource(cx, args.rval(), "{self.taintSource}");""" + ) prefix += fill( @@ -18352,7 +18367,7 @@ def descriptorClearsPropsInSlots(descriptor): def hasAtLeastOneTaintsource(descriptor): return any( - m.isAttr() and m.getExtendedAttribute("TaintSource") + (m.isAttr() or m.isMethod()) and m.getExtendedAttribute("TaintSource") for m in descriptor.interface.members ) diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 5247f353aea16..1df57611bd00d 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -6396,6 +6396,7 @@ def handleExtendedAttribute(self, attr): or identifier == "NonEnumerable" or identifier == "Unexposed" or identifier == "WebExtensionStub" + or identifier == "TaintSource" # Taintfox added ): # Known attributes that we don't need to do anything with here pass diff --git a/dom/webidl/HTMLCanvasElement.webidl b/dom/webidl/HTMLCanvasElement.webidl index 12ece2222e9d6..0f4e843f60d79 100644 --- a/dom/webidl/HTMLCanvasElement.webidl +++ b/dom/webidl/HTMLCanvasElement.webidl @@ -27,7 +27,7 @@ interface HTMLCanvasElement : HTMLElement { [Throws] nsISupports? getContext(DOMString contextId, optional any contextOptions = null); - [Throws, NeedsSubjectPrincipal] + [Throws, NeedsSubjectPrincipal, TaintSource] DOMString toDataURL(optional DOMString type = "", optional any encoderOptions); [Throws, NeedsSubjectPrincipal] diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index 6a64044f4cd4e..db28070a8f54a 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -116,6 +116,7 @@ partial interface Navigator { // http://www.w3.org/TR/tracking-dnt/ sort of partial interface Navigator { + [TaintSource] readonly attribute DOMString doNotTrack; }; @@ -177,10 +178,13 @@ partial interface Navigator { [Throws, Constant, Cached, NeedsCallerType, TaintSource] readonly attribute DOMString oscpu; // WebKit/Blink support this; Trident/Presto do not. + [TaintSource] readonly attribute DOMString vendor; // WebKit/Blink supports this (hardcoded ""); Trident/Presto do not. + [TaintSource] readonly attribute DOMString vendorSub; // WebKit/Blink supports this (hardcoded "20030107"); Trident/Presto don't + [TaintSource] readonly attribute DOMString productSub; // WebKit/Blink/Trident/Presto support this. readonly attribute boolean cookieEnabled; @@ -287,6 +291,7 @@ partial interface Navigator { }; interface mixin NavigatorConcurrentHardware { + [TaintSource] readonly attribute unsigned long long hardwareConcurrency; }; diff --git a/dom/webidl/WebGLRenderingContext.webidl b/dom/webidl/WebGLRenderingContext.webidl index 1d29ed3565a06..9a08d260e47f7 100644 --- a/dom/webidl/WebGLRenderingContext.webidl +++ b/dom/webidl/WebGLRenderingContext.webidl @@ -629,7 +629,7 @@ interface mixin WebGLRenderingContextBase { [WebGLHandlesContextLoss] GLint getAttribLocation(WebGLProgram program, DOMString name); any getBufferParameter(GLenum target, GLenum pname); - [Throws] + [Throws, TaintSource] any getParameter(GLenum pname); [WebGLHandlesContextLoss] GLenum getError(); From 4d3a5206546d7d64d5b64a9794af6d8c4d40d93d Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Fri, 21 Oct 2022 13:53:22 +0000 Subject: [PATCH 36/70] Foxhound: Start extending taint data structure * String taint setter currently broken * Still WIP --- js/src/builtin/String.cpp | 5 +- js/src/jsapi.cpp | 11 +- taint/Taint.cpp | 207 +++++++++++++++++++++++++++++++++----- taint/Taint.h | 62 +++++++++++- 4 files changed, 250 insertions(+), 35 deletions(-) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 0045056f44c0f..6427bf6db8c99 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -299,8 +299,9 @@ construct_taint_flow(JSContext* cx, HandleObject flow_object, TaintNode** flow) UniqueChars op_str = JS_EncodeStringToUTF8(cx, operation); *flow = new TaintNode(*flow, TaintOperation(op_str.get())); - if ((*flow)->parent()) - (*flow)->parent()->release(); + // Foxhound: Commented out for testing 2022-10-20 + // if ((*flow)->parent()) + // (*flow)->parent()->release(); } return true; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 83e6393548e07..ba2007bc432ef 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4622,7 +4622,16 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha // DumpBacktrace(cx); // Report a warning to show up on the web console - JS_ReportWarningUTF8(cx, "Tainted flow from %s into %s!", firstRange.flow().source().name(), sink); + std::string sourceString = ""; + for (auto source : firstRange.flow().sources()){ + if (sourceString == ""){ + sourceString = source->name(); + } else{ + sourceString = sourceString + "," + source->name(); + } + } + // JS_ReportWarningUTF8(cx, "Tainted flow from %s into %s!", firstRange.flow().source().name(), sink); + JS_ReportWarningUTF8(cx, "Tainted flow from %s into %s!", sourceString.c_str(), sink); // Extend the taint flow to include the sink function str->taint().extend(TaintOperationFromContext(cx, sink, true, arg)); diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 8999eb633396f..a2268e1c396d1 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -9,11 +9,18 @@ #include "Taint.h" +#include +#include +#include +#include #include // wstring_convert #include // codecvt_utf8 #include // cout +#include #include // stoi and u32string #include +#include +#include #include "mozilla/Assertions.h" @@ -141,30 +148,42 @@ void TaintOperation::dump(const TaintOperation& op) { void TaintOperation::dump(const TaintOperation& op) {} #endif +TaintNode::TaintNode(const std::vector parents, const TaintOperation& operation) + : refcount_(1), operation_(operation) +{ + MOZ_COUNT_CTOR(TaintNode); + addParents(parents); +} + +TaintNode::TaintNode(const std::vector parents, TaintOperation&& operation) + : refcount_(1), operation_(operation) +{ + MOZ_COUNT_CTOR(TaintNode); + addParents(parents); +} + TaintNode::TaintNode(TaintNode* parent, const TaintOperation& operation) - : parent_(parent), refcount_(1), operation_(operation) + : refcount_(1), operation_(operation) { MOZ_COUNT_CTOR(TaintNode); - if (parent_) - parent_->addref(); + addParent(parent); } TaintNode::TaintNode(TaintNode* parent, TaintOperation&& operation) - : parent_(parent), refcount_(1), operation_(operation) + : refcount_(1), operation_(operation) { MOZ_COUNT_CTOR(TaintNode); - if (parent_) - parent_->addref(); + addParent(parent); } TaintNode::TaintNode(const TaintOperation& operation) - : parent_(nullptr), refcount_(1), operation_(operation) + : refcount_(1), operation_(operation) { MOZ_COUNT_CTOR(TaintNode); } TaintNode::TaintNode(TaintOperation&& operation) - : parent_(nullptr), refcount_(1), operation_(operation) + : refcount_(1), operation_(operation) { MOZ_COUNT_CTOR(TaintNode); } @@ -189,36 +208,157 @@ void TaintNode::release() TaintNode::~TaintNode() { MOZ_COUNT_DTOR(TaintNode); - if (parent_) - parent_->release(); + std::for_each(parents_.begin(), parents_.end(), + [](auto * parent){parent->release();;} + ); } +void TaintNode::addParents(const std::vector &parents) +{ + std::for_each(parents_.begin(), parents_.end(), + [this](auto * parent){addParent(parent);} + ); +} -TaintFlow::Iterator::Iterator(TaintNode* head) : current_(head) { } +void TaintNode::addParent(TaintNode* parent) +{ + parents_.push_back(parent); + parent->addref(); +} + +TaintNode::Iterator::Iterator(TaintNode* head) : + root_(head), + current_(root_) + { + if (parentNumparents().size()) { + currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + } else{ + currentParentIterator_ = nullptr; + } + } + +TaintNode::Iterator::Iterator() : + current_(nullptr), + currentParentIterator_(nullptr), + root_(nullptr) { } + +TaintNode::Iterator::Iterator(const Iterator& other) : + current_(other.current_), + parentNum(other.parentNum), + root_(other.root_) + { + if (other.currentParentIterator_ == nullptr){ + currentParentIterator_ = nullptr; + } else { + currentParentIterator_ = new Iterator(*other.currentParentIterator_); + } + } -TaintFlow::Iterator::Iterator() : current_(nullptr) { } -TaintFlow::Iterator::Iterator(const Iterator& other) : current_(other.current_) { } +// TaintNode::Iterator::Iterator(Iterator&& other) : +// current_(other.current_), +// parentNum(other.parentNum), +// root_(other.root_){ +// currentParentIterator_ = other.currentParentIterator_; +// other.currentParentIterator_ = nullptr; +// }; + +TaintNode::Iterator::~Iterator() +{ + delete currentParentIterator_; +} + +TaintNode::Iterator& TaintNode::Iterator::operator++() +{ + if (currentParentIterator_ == nullptr) { + current_ = nullptr; + return *this; + } + + // try to get next element from current parent node + // if current parent node still has elements + if (*currentParentIterator_ != root_->parents()[parentNum]->end()) { + current_ = & *(*currentParentIterator_); + ++(*currentParentIterator_); + return *this; + } + // no elements left --> go to next parent + else{ + // next parent + parentNum++; + // if next parent exists + if (parentNumparents().size()) { + // get iterator from this parent + currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + // use value of thisiterator + current_ = &*(*currentParentIterator_); + ++(*currentParentIterator_); + return *this; + } else { + // no parents left --> no values left + delete currentParentIterator_; + currentParentIterator_ = nullptr; + current_ = nullptr; + return *this; + } + } +} + +TaintNode& TaintNode::Iterator::operator*() const +{ + return *current_; +} + +bool TaintNode::Iterator::operator==(const Iterator& other) const +{ + return current_ == other.current_; +} + +bool TaintNode::Iterator::operator!=(const Iterator& other) const +{ + return current_ != other.current_; +} + +TaintNode::Iterator TaintNode::begin() +{ + return Iterator(this); +} + +TaintNode::Iterator TaintNode::end() +{ + return Iterator(); +} + + + + + + +TaintFlow::Iterator::Iterator(TaintNode* head) : nodeIterator_(head->begin()){} + +TaintFlow::Iterator::Iterator() : nodeIterator_() {} + +TaintFlow::Iterator::Iterator(const Iterator& other) : nodeIterator_(other.nodeIterator_) { } TaintFlow::Iterator& TaintFlow::Iterator::operator++() { - current_ = current_->parent(); + ++nodeIterator_; return *this; } TaintNode& TaintFlow::Iterator::operator*() const { - return *current_; + return *nodeIterator_; } bool TaintFlow::Iterator::operator==(const Iterator& other) const { - return current_ == other.current_; + return nodeIterator_ == other.nodeIterator_; } bool TaintFlow::Iterator::operator!=(const Iterator& other) const { - return current_ != other.current_; + return nodeIterator_ != other.nodeIterator_; } TaintFlow::TaintFlow() @@ -291,13 +431,21 @@ const TaintFlow& TaintFlow::getEmptyTaintFlow() { return TaintFlow::empty_flow_; } -const TaintOperation& TaintFlow::source() const +std::vector TaintFlow::sources() const { - TaintNode* source = head_; - while (source->parent() != nullptr) - source = source->parent(); + std::set taintSources = {}; - return source->operation(); + for (auto node = this->begin(); node != this->end(); ++node) { + if((*node).parents().empty()){ + taintSources.insert(& ((*node).operation())); + } + } + + std::vector taintSources_vector(taintSources.begin(), taintSources.end()); + + std::copy(taintSources.begin(), taintSources.end(), std::back_inserter(taintSources_vector)); + + return taintSources_vector; } TaintFlow& TaintFlow::extend(const TaintOperation& operation) @@ -1097,15 +1245,20 @@ StringTaint ParseTaint(const std::string& str) void PrintTaint(const StringTaint& taint) { - for (auto& range : taint) - std::cout << " " << range.begin() << " - " << range.end() << " : " << range.flow().source().name() << std::endl; + for (auto& range : taint){ + for (auto source : range.flow().sources()){ + std::cout << " " << range.begin() << " - " << range.end() << " : " << source->name() << std::endl; + } + } } void DumpTaint(const StringTaint& taint) { - for (auto& range : taint) { - std::cout << " " << range.begin() << " - " << range.end() << " : " << range.flow().source().name() << ":\n"; - DumpTaintFlow(range.flow()); + for (auto& range : taint){ + for (auto source : range.flow().sources()){ + std::cout << " " << range.begin() << " - " << range.end() << " : " << source->name() << ":\n"; + DumpTaintFlow(range.flow()); + } } } diff --git a/taint/Taint.h b/taint/Taint.h index c0c7aea33f00d..fbf8926e6e73e 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -13,6 +13,8 @@ #define _Taint_h #include +#include +#include #include #include #include @@ -180,16 +182,45 @@ class TaintOperation */ class TaintNode { + private: + // Iterate over this node and its parents + class Iterator { + public: + Iterator(TaintNode * head); + Iterator(); + + Iterator(const Iterator& other); + // Iterator(Iterator&& other); + + ~Iterator(); + + Iterator& operator++(); + TaintNode& operator*() const; + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + + private: + TaintNode* root_; + TaintNode* current_; + TaintNode::Iterator * currentParentIterator_; + int parentNum = 0; + }; + friend class TaintFlow; + public: // Constructing a taint node sets the initial reference count to 1. // Constructs an intermediate node. TaintNode(TaintNode* parent, const TaintOperation& operation); + // Constructs an intermediate node with multiple parents. + TaintNode(const std::vector, const TaintOperation& operation); // Constructs a root node. TaintNode(const TaintOperation& operation); // Constructing a taint node sets the initial reference count to 1. // Constructs an intermediate node. TaintNode(TaintNode* parent, TaintOperation&& operation); + // Constructs an intermediate node with multiple parents. + TaintNode(const std::vector, TaintOperation&& operation); // Constructs a root node. TaintNode(TaintOperation&& operation); @@ -201,18 +232,29 @@ class TaintNode void release(); // Returns the parent node of this node or nullptr if this is a root node. - TaintNode* parent() { return parent_; } + std::vector parents() { return parents_; } // Returns the operation associated with this taint node. const TaintOperation& operation() const { return operation_; } + // Iterator support + // + // Makes it possible to conveniently iterate over all taint nodes of a flow, + // starting at the newest node. + // Since TaintNodes are inherently immutable, we can safely return non-const + // pointers here. + TaintNode::Iterator begin(); + TaintNode::Iterator end(); + private: // Prevent clients from deleting us. TaintNodes can only be destroyed // through release(). ~TaintNode(); - // A node takes care of correctly addref()ing and release()ing its parent node. - TaintNode* parent_; + // A node takes care of correctly addref()ing and release()ing its parent nodes. + // parents_ is a vector, which enables taint nodes to form a directed acyclic graph + // TaintNode* parent_; + std::vector parents_ = std::vector(); uint32_t refcount_; @@ -223,6 +265,13 @@ class TaintNode // refcount them), so these operations are unavailable. TaintNode(const TaintNode& other) = delete; TaintNode& operator=(const TaintNode& other) = delete; + + // Wrapper around addParent() for multiple parents + void addParents(const std::vector &parents); + + // Add parent to internal parents_ vector and addref() parent + void addParent(TaintNode* parent); + }; /* @@ -283,7 +332,8 @@ class TaintFlow bool operator!=(const Iterator& other) const; private: - TaintNode* current_; + // TaintNode* current_; + TaintNode::Iterator nodeIterator_; }; public: @@ -307,6 +357,8 @@ class TaintFlow TaintFlow(TaintFlow&& other); TaintFlow(const TaintFlow* other); + + TaintFlow(const TaintOperation& operation, const std::vector parents); ~TaintFlow(); @@ -317,7 +369,7 @@ class TaintFlow TaintNode* head() const { return head_; } // Returns the source of this taint flow. - const TaintOperation& source() const; + std::vector sources() const; // Constructs a new taint node as child of the current head node and sets // the newly constructed node as head of this taint flow. From f5ab5fc22c743800366925cca9f98350a75c0618 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Tue, 25 Oct 2022 08:42:26 +0000 Subject: [PATCH 37/70] Foxhound: Propagate taint from both operands of number operations * Even simple has calculations lead to too many calculations * Hash of 9-char string will take multiple seconds and lead to 0.5M-element array --- js/src/jsapi.cpp | 2 +- js/src/jsnum.cpp | 5 +++++ js/src/jstaint.cpp | 7 ++++++- taint/Taint.cpp | 50 ++++++++++++++++++++++++++++++---------------- taint/Taint.h | 10 +++++++--- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ba2007bc432ef..f063c1707eed9 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4624,7 +4624,7 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha // Report a warning to show up on the web console std::string sourceString = ""; for (auto source : firstRange.flow().sources()){ - if (sourceString == ""){ + if (sourceString.empty()){ sourceString = source->name(); } else{ sourceString = sourceString + "," + source->name(); diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index b6f825513225f..4872c161c11e1 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1453,6 +1453,11 @@ static bool num_taint_getter(JSContext* cx, unsigned argc, Value* vp) return true; } + // Return, if number is not tainted + if (!number->as().isTainted()) { + return true; + } + const TaintFlow& taint = number->as().taint(); // TODO(samuel) refactor into separate function RootedValueVector taint_flow(cx); diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index e15bdad9c3665..626f3c2bb92c8 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -390,7 +390,12 @@ bool JS::isAnyTaintedNumber(const Value& val1, const Value& val2) TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) { - if (isTaintedNumber(val1)) { + // add info for operation + // add getting combined taint flow + if (isTaintedNumber(val1) && isTaintedNumber(val2)){ + return TaintFlow::extend(getNumberTaint(val1),getNumberTaint(val2),TaintOperation("Unspecified Operation")); + } + else if (isTaintedNumber(val1)) { return getNumberTaint(val1); } else { return getNumberTaint(val2); diff --git a/taint/Taint.cpp b/taint/Taint.cpp index a2268e1c396d1..5abf4ad146385 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -215,9 +215,9 @@ TaintNode::~TaintNode() void TaintNode::addParents(const std::vector &parents) { - std::for_each(parents_.begin(), parents_.end(), - [this](auto * parent){addParent(parent);} - ); + for(auto parent : parents){ + addParent(parent); + } } void TaintNode::addParent(TaintNode* parent) @@ -231,21 +231,22 @@ TaintNode::Iterator::Iterator(TaintNode* head) : current_(root_) { if (parentNumparents().size()) { - currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + // currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + currentParentIterator_ = new Iterator(std::move(root_->parents()[parentNum]->begin())); } else{ currentParentIterator_ = nullptr; } } TaintNode::Iterator::Iterator() : + root_(nullptr), current_(nullptr), - currentParentIterator_(nullptr), - root_(nullptr) { } + currentParentIterator_(nullptr) { } TaintNode::Iterator::Iterator(const Iterator& other) : + root_(other.root_), current_(other.current_), - parentNum(other.parentNum), - root_(other.root_) + parentNum(other.parentNum) { if (other.currentParentIterator_ == nullptr){ currentParentIterator_ = nullptr; @@ -255,13 +256,13 @@ TaintNode::Iterator::Iterator(const Iterator& other) : } -// TaintNode::Iterator::Iterator(Iterator&& other) : -// current_(other.current_), -// parentNum(other.parentNum), -// root_(other.root_){ -// currentParentIterator_ = other.currentParentIterator_; -// other.currentParentIterator_ = nullptr; -// }; +TaintNode::Iterator::Iterator(Iterator&& other) : + root_(other.root_), + current_(other.current_), + parentNum(other.parentNum){ + currentParentIterator_ = other.currentParentIterator_; + other.currentParentIterator_ = nullptr; +}; TaintNode::Iterator::~Iterator() { @@ -270,7 +271,7 @@ TaintNode::Iterator::~Iterator() TaintNode::Iterator& TaintNode::Iterator::operator++() { - if (currentParentIterator_ == nullptr) { + if (currentParentIterator_ == nullptr || root_ == nullptr) { current_ = nullptr; return *this; } @@ -289,7 +290,8 @@ TaintNode::Iterator& TaintNode::Iterator::operator++() // if next parent exists if (parentNumparents().size()) { // get iterator from this parent - currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + // currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); + currentParentIterator_ = new Iterator(std::move(root_->parents()[parentNum]->begin())); // use value of thisiterator current_ = &*(*currentParentIterator_); ++(*currentParentIterator_); @@ -486,6 +488,20 @@ TaintFlow TaintFlow::extend(const TaintFlow& flow, const TaintOperation& operati return TaintFlow(new TaintNode(flow.head_, operation)); } +TaintFlow TaintFlow::extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation) +{ + return TaintFlow(new TaintNode({flow1.head_ ,flow2.head_}, operation)); +} + +TaintFlow TaintFlow::extend(const std::vector flows, const TaintOperation& operation) +{ + std::vector parents = {}; + for (auto &&flow : flows) { + parents.push_back(flow->head()); + } + return TaintFlow(new TaintNode(parents, operation)); +} + TaintRange::TaintRange() : begin_(0), end_(0), flow_() diff --git a/taint/Taint.h b/taint/Taint.h index fbf8926e6e73e..fe8848b86e77c 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -190,7 +190,7 @@ class TaintNode Iterator(); Iterator(const Iterator& other); - // Iterator(Iterator&& other); + Iterator(Iterator&& other); ~Iterator(); @@ -212,7 +212,7 @@ class TaintNode // Constructs an intermediate node. TaintNode(TaintNode* parent, const TaintOperation& operation); // Constructs an intermediate node with multiple parents. - TaintNode(const std::vector, const TaintOperation& operation); + TaintNode(const std::vector parents, const TaintOperation& operation); // Constructs a root node. TaintNode(const TaintOperation& operation); @@ -220,7 +220,7 @@ class TaintNode // Constructs an intermediate node. TaintNode(TaintNode* parent, TaintOperation&& operation); // Constructs an intermediate node with multiple parents. - TaintNode(const std::vector, TaintOperation&& operation); + TaintNode(const std::vector parents, TaintOperation&& operation); // Constructs a root node. TaintNode(TaintOperation&& operation); @@ -396,6 +396,10 @@ class TaintFlow // returns a new taint flow starting at that node. static TaintFlow extend(const TaintFlow& flow, const TaintOperation& operation); + static TaintFlow extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation); + + static TaintFlow extend(const std::vector flows, const TaintOperation& operation); + // Two TaintFlows are equal if they point to the same taint node. bool operator==(const TaintFlow& other) const { return head_ == other.head_; } bool operator!=(const TaintFlow& other) const { return head_ != other.head_; } From 04c78ca6c0f520dba21566e86a86c4d550ff8897 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Wed, 26 Oct 2022 14:20:26 +0000 Subject: [PATCH 38/70] Foxhound: Change TaintFlow iterator to only output each node once --- layout/style/ServoBindings.toml | 6 ++ taint/Taint.cpp | 108 ++++++++++++++++++-------------- taint/Taint.h | 24 ++++++- 3 files changed, 88 insertions(+), 50 deletions(-) diff --git a/layout/style/ServoBindings.toml b/layout/style/ServoBindings.toml index 5e1303db5de80..fb4cf7ac345f0 100644 --- a/layout/style/ServoBindings.toml +++ b/layout/style/ServoBindings.toml @@ -347,6 +347,12 @@ whitelist-types = [ "nsStyleTransformMatrix::MatrixTransformOperator", ] opaque-types = [ + #"TaintNodeIterator", + #"std:set", + #"std:shared_ptr", + #"_NodeHandle", + #"_Node_handle", + "StringTaint", "mozilla::StyleThinArc", # https://github.com/rust-lang/rust-bindgen/issues/1557 "std::pair__PCCP", "std::namespace::atomic___base", "std::atomic__My_base", diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 5abf4ad146385..ba17fdf396ecf 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -16,6 +16,7 @@ #include // wstring_convert #include // codecvt_utf8 #include // cout +#include #include #include // stoi and u32string #include @@ -226,27 +227,38 @@ void TaintNode::addParent(TaintNode* parent) parent->addref(); } -TaintNode::Iterator::Iterator(TaintNode* head) : +TaintNode::Iterator::Iterator(TaintNode* head) + : Iterator(head, std::make_shared>()){}; + + + +TaintNode::Iterator::Iterator(TaintNode * head, std::shared_ptr> visited) : root_(head), - current_(root_) - { - if (parentNumparents().size()) { - // currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); - currentParentIterator_ = new Iterator(std::move(root_->parents()[parentNum]->begin())); + current_(root_), + visited_(visited) + { + parentCount_ = root_->parents().size(); + if (parentCount_ > 0) { + currentParent_ = root_->parents()[currentParentNum_]; + currentParentIterator_ = new Iterator(currentParent_,visited_); } else{ currentParentIterator_ = nullptr; + finished = true; } } TaintNode::Iterator::Iterator() : root_(nullptr), current_(nullptr), - currentParentIterator_(nullptr) { } + currentParentIterator_(nullptr), + finished(true) { } TaintNode::Iterator::Iterator(const Iterator& other) : root_(other.root_), current_(other.current_), - parentNum(other.parentNum) + currentParent_(other.currentParent_), + // parentNum(other.parentNum), + visited_(other.visited_) { if (other.currentParentIterator_ == nullptr){ currentParentIterator_ = nullptr; @@ -256,54 +268,56 @@ TaintNode::Iterator::Iterator(const Iterator& other) : } -TaintNode::Iterator::Iterator(Iterator&& other) : - root_(other.root_), - current_(other.current_), - parentNum(other.parentNum){ - currentParentIterator_ = other.currentParentIterator_; - other.currentParentIterator_ = nullptr; -}; +// TaintNode::Iterator::Iterator(Iterator&& other) : +// root_(other.root_), +// current_(other.current_), +// parentNum(other.parentNum), +// visited_(other.visited_){ +// currentParentIterator_ = other.currentParentIterator_; +// other.currentParentIterator_ = nullptr; +// }; TaintNode::Iterator::~Iterator() { delete currentParentIterator_; -} +} -TaintNode::Iterator& TaintNode::Iterator::operator++() -{ - if (currentParentIterator_ == nullptr || root_ == nullptr) { - current_ = nullptr; - return *this; - } +TaintNode::Iterator& TaintNode::Iterator::operator++() { + if (finished) { + current_ = nullptr; + return *this; + } - // try to get next element from current parent node - // if current parent node still has elements - if (*currentParentIterator_ != root_->parents()[parentNum]->end()) { - current_ = & *(*currentParentIterator_); + // if current parent node still has elements + if (*currentParentIterator_ != currentParent_->end()) { + // get next element from current parent node + current_ = &*(*currentParentIterator_); + ++(*currentParentIterator_); + return *this; + } + // no elements left --> go to next parent + else { + // while there are parents, try to get their iterator + while (++currentParentNum_ < parentCount_) { + // go to next parent + currentParent_ = root_->parents()[currentParentNum_]; + // if next parent has not yet been visited + if (visited_->insert(currentParent_).second) { + // get iterator from this parent + delete currentParentIterator_; + currentParentIterator_ = new Iterator(currentParent_, visited_); + // use value of this iterator + current_ = &*(*currentParentIterator_); ++(*currentParentIterator_); return *this; + } } - // no elements left --> go to next parent - else{ - // next parent - parentNum++; - // if next parent exists - if (parentNumparents().size()) { - // get iterator from this parent - // currentParentIterator_ = new Iterator(root_->parents()[parentNum]->begin()); - currentParentIterator_ = new Iterator(std::move(root_->parents()[parentNum]->begin())); - // use value of thisiterator - current_ = &*(*currentParentIterator_); - ++(*currentParentIterator_); - return *this; - } else { - // no parents left --> no values left - delete currentParentIterator_; - currentParentIterator_ = nullptr; - current_ = nullptr; - return *this; - } - } + + // no parents left --> no values left + current_ = nullptr; + finished = true; + return *this; + } } TaintNode& TaintNode::Iterator::operator*() const diff --git a/taint/Taint.h b/taint/Taint.h index fe8848b86e77c..57015bb713300 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -167,6 +169,8 @@ class TaintOperation }; +class TaintNodeIterator; + /* * The nodes of the taint flow graph. * @@ -190,7 +194,7 @@ class TaintNode Iterator(); Iterator(const Iterator& other); - Iterator(Iterator&& other); + // Iterator(Iterator&& other); ~Iterator(); @@ -200,10 +204,24 @@ class TaintNode bool operator!=(const Iterator& other) const; private: + + struct PtrComp + { + bool operator()(const TaintNode* lhs, const TaintNode* rhs) const { return lhs> visited); + TaintNode* root_; TaintNode* current_; - TaintNode::Iterator * currentParentIterator_; - int parentNum = 0; + Iterator * currentParentIterator_; + + int parentCount_; + int currentParentNum_ = 0; + TaintNode * currentParent_ = nullptr; + + bool finished = false; + std::shared_ptr> visited_; }; friend class TaintFlow; From 34b80787a15d33d7edc0be4fad049f936ecc0039 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 27 Oct 2022 07:28:25 +0000 Subject: [PATCH 39/70] Foxhound: Refactor data model to not create history * WIP * TaintFlows now have Set of TaintNodes * TaintNodes only created for sources * No relationship between TaintNodes --- js/src/builtin/String.cpp | 24 ++-- js/src/jsnum.cpp | 6 +- js/src/vm/NumberObject.h | 46 +++--- taint/Taint.cpp | 290 +++++++------------------------------- taint/Taint.h | 124 ++++------------ 5 files changed, 118 insertions(+), 372 deletions(-) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 6427bf6db8c99..1c338a9ec8d6a 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -181,12 +181,12 @@ str_taint_getter(JSContext* cx, unsigned argc, Value* vp) // Wrap the taint flow for the current range. RootedValueVector taint_flow(cx); - for (TaintNode& taint_node : taint_range.flow()) { + for (auto& taint_node : taint_range.flow()) { RootedObject node(cx, JS_NewObject(cx, nullptr)); if (!node) return false; - RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); + RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node->operation().name())); if (!operation) return false; @@ -194,13 +194,13 @@ str_taint_getter(JSContext* cx, unsigned argc, Value* vp) return false; RootedValue isBuiltIn(cx); - isBuiltIn.setBoolean(taint_node.operation().is_native()); + isBuiltIn.setBoolean(taint_node->operation().is_native()); if (!JS_DefineProperty(cx, node, "builtin", isBuiltIn, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) return false; RootedValue isSource(cx); - isSource.setBoolean(taint_node.operation().isSource()); + isSource.setBoolean(taint_node->operation().isSource()); if (!JS_DefineProperty(cx, node, "source", isSource, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) return false; @@ -209,22 +209,22 @@ str_taint_getter(JSContext* cx, unsigned argc, Value* vp) RootedObject location(cx, JS_NewObject(cx, nullptr)); if (!location) return false; - RootedString filename(cx, JS_NewUCStringCopyZ(cx, taint_node.operation().location().filename().c_str())); + RootedString filename(cx, JS_NewUCStringCopyZ(cx, taint_node->operation().location().filename().c_str())); if (!filename) return false; - RootedString function(cx, JS_NewUCStringCopyZ(cx, taint_node.operation().location().function().c_str())); + RootedString function(cx, JS_NewUCStringCopyZ(cx, taint_node->operation().location().function().c_str())); if (!function) return false; // Also add the MD5 hash of the containing function - RootedString hash(cx, JS_NewStringCopyZ(cx, JS::convertDigestToHexString(taint_node.operation().location().scriptHash()).c_str())); + RootedString hash(cx, JS_NewStringCopyZ(cx, JS::convertDigestToHexString(taint_node->operation().location().scriptHash()).c_str())); if (!hash) return false; if (!JS_DefineProperty(cx, location, "filename", filename, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || !JS_DefineProperty(cx, location, "function", function, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "line", taint_node.operation().location().line(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "pos", taint_node.operation().location().pos(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "scriptline", taint_node.operation().location().scriptStartLine(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "line", taint_node->operation().location().line(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "pos", taint_node->operation().location().pos(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "scriptline", taint_node->operation().location().scriptStartLine(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || !JS_DefineProperty(cx, location, "scripthash", hash, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) return false; @@ -233,7 +233,7 @@ str_taint_getter(JSContext* cx, unsigned argc, Value* vp) // Wrap the arguments RootedValueVector taint_arguments(cx); - for (auto& taint_argument : taint_node.operation().arguments()) { + for (auto& taint_argument : taint_node->operation().arguments()) { RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); if (!argument) return false; @@ -298,7 +298,7 @@ construct_taint_flow(JSContext* cx, HandleObject flow_object, TaintNode** flow) // TODO process arguments as well UniqueChars op_str = JS_EncodeStringToUTF8(cx, operation); - *flow = new TaintNode(*flow, TaintOperation(op_str.get())); + *flow = new TaintNode(TaintOperation(op_str.get())); // Foxhound: Commented out for testing 2022-10-20 // if ((*flow)->parent()) // (*flow)->parent()->release(); diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 4872c161c11e1..afd84c742fb82 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1461,12 +1461,12 @@ static bool num_taint_getter(JSContext* cx, unsigned argc, Value* vp) const TaintFlow& taint = number->as().taint(); // TODO(samuel) refactor into separate function RootedValueVector taint_flow(cx); - for (TaintNode& taint_node : taint) { + for (auto& taint_node : taint) { RootedObject node(cx, JS_NewObject(cx, nullptr)); if (!node) return false; - RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); + RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node->operation().name())); if (!operation) return false; @@ -1475,7 +1475,7 @@ static bool num_taint_getter(JSContext* cx, unsigned argc, Value* vp) // Wrap the arguments. RootedValueVector taint_arguments(cx); - for (auto& taint_argument : taint_node.operation().arguments()) { + for (auto& taint_argument : taint_node->operation().arguments()) { RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); if (!argument) return false; diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index 65acfbdb528c3..ff0b197f22d00 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -47,34 +47,36 @@ class NumberObject : public NativeObject { // TaintFox: A finalizer is required for correct memory handling. static void Finalize(JSFreeOp* fop, JSObject* obj) { NumberObject& number = obj->as(); - TaintNode* head = number.getTaintNode(); - if (head) { - head->release(); - } + delete number.maybePtrFromReservedSlot(TAINT_SLOT); + // TaintNode* head = number.getTaintNode(); + // if (head) { + // head->release(); + // } } TaintFlow taint() const { - TaintNode* head = getTaintNode(); - if (head) { - head->addref(); + TaintFlow* flow = getTaintFlow(); + if (flow) { + // head->addref(); + return *flow; } - return TaintFlow(head); + return TaintFlow(); } void setTaint(const TaintFlow& taint) { - TaintNode* current = getTaintNode(); - if (current) { - current->release(); - } - TaintNode* head = taint.head(); - if (head) { - head->addref(); - } - setTaintNode(head); + // TaintNode* current = getTaintNode(); + // if (current) { + // current->release(); + // } + // TaintNode* head = taint.head(); + // if (head) { + // head->addref(); + // } + setTaintFlow(taint); } bool isTainted() const { - return !!getTaintNode(); + return !!getTaintFlow(); } private: @@ -84,13 +86,13 @@ class NumberObject : public NativeObject { setFixedSlot(PRIMITIVE_VALUE_SLOT, NumberValue(d)); } - inline TaintNode* getTaintNode() const { - TaintNode* n = maybePtrFromReservedSlot(TAINT_SLOT); + inline TaintFlow* getTaintFlow() const { + TaintFlow* n = maybePtrFromReservedSlot(TAINT_SLOT); return n; } - inline void setTaintNode(TaintNode* node) { - setReservedSlot(TAINT_SLOT, PrivateValue(node)); + inline void setTaintFlow(TaintFlow flow) { + setReservedSlot(TAINT_SLOT, PrivateValue(new TaintFlow(flow))); } }; diff --git a/taint/Taint.cpp b/taint/Taint.cpp index ba17fdf396ecf..85579dac88b93 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -149,33 +149,6 @@ void TaintOperation::dump(const TaintOperation& op) { void TaintOperation::dump(const TaintOperation& op) {} #endif -TaintNode::TaintNode(const std::vector parents, const TaintOperation& operation) - : refcount_(1), operation_(operation) -{ - MOZ_COUNT_CTOR(TaintNode); - addParents(parents); -} - -TaintNode::TaintNode(const std::vector parents, TaintOperation&& operation) - : refcount_(1), operation_(operation) -{ - MOZ_COUNT_CTOR(TaintNode); - addParents(parents); -} - -TaintNode::TaintNode(TaintNode* parent, const TaintOperation& operation) - : refcount_(1), operation_(operation) -{ - MOZ_COUNT_CTOR(TaintNode); - addParent(parent); -} - -TaintNode::TaintNode(TaintNode* parent, TaintOperation&& operation) - : refcount_(1), operation_(operation) -{ - MOZ_COUNT_CTOR(TaintNode); - addParent(parent); -} TaintNode::TaintNode(const TaintOperation& operation) : refcount_(1), operation_(operation) @@ -214,229 +187,92 @@ TaintNode::~TaintNode() ); } -void TaintNode::addParents(const std::vector &parents) -{ - for(auto parent : parents){ - addParent(parent); - } -} - -void TaintNode::addParent(TaintNode* parent) -{ - parents_.push_back(parent); - parent->addref(); -} - -TaintNode::Iterator::Iterator(TaintNode* head) - : Iterator(head, std::make_shared>()){}; - - - -TaintNode::Iterator::Iterator(TaintNode * head, std::shared_ptr> visited) : - root_(head), - current_(root_), - visited_(visited) - { - parentCount_ = root_->parents().size(); - if (parentCount_ > 0) { - currentParent_ = root_->parents()[currentParentNum_]; - currentParentIterator_ = new Iterator(currentParent_,visited_); - } else{ - currentParentIterator_ = nullptr; - finished = true; - } - } - -TaintNode::Iterator::Iterator() : - root_(nullptr), - current_(nullptr), - currentParentIterator_(nullptr), - finished(true) { } - -TaintNode::Iterator::Iterator(const Iterator& other) : - root_(other.root_), - current_(other.current_), - currentParent_(other.currentParent_), - // parentNum(other.parentNum), - visited_(other.visited_) - { - if (other.currentParentIterator_ == nullptr){ - currentParentIterator_ = nullptr; - } else { - currentParentIterator_ = new Iterator(*other.currentParentIterator_); - } - } - - -// TaintNode::Iterator::Iterator(Iterator&& other) : -// root_(other.root_), -// current_(other.current_), -// parentNum(other.parentNum), -// visited_(other.visited_){ -// currentParentIterator_ = other.currentParentIterator_; -// other.currentParentIterator_ = nullptr; -// }; - -TaintNode::Iterator::~Iterator() -{ - delete currentParentIterator_; -} - -TaintNode::Iterator& TaintNode::Iterator::operator++() { - if (finished) { - current_ = nullptr; - return *this; - } - - // if current parent node still has elements - if (*currentParentIterator_ != currentParent_->end()) { - // get next element from current parent node - current_ = &*(*currentParentIterator_); - ++(*currentParentIterator_); - return *this; - } - // no elements left --> go to next parent - else { - // while there are parents, try to get their iterator - while (++currentParentNum_ < parentCount_) { - // go to next parent - currentParent_ = root_->parents()[currentParentNum_]; - // if next parent has not yet been visited - if (visited_->insert(currentParent_).second) { - // get iterator from this parent - delete currentParentIterator_; - currentParentIterator_ = new Iterator(currentParent_, visited_); - // use value of this iterator - current_ = &*(*currentParentIterator_); - ++(*currentParentIterator_); - return *this; - } - } - - // no parents left --> no values left - current_ = nullptr; - finished = true; - return *this; - } -} - -TaintNode& TaintNode::Iterator::operator*() const -{ - return *current_; -} - -bool TaintNode::Iterator::operator==(const Iterator& other) const -{ - return current_ == other.current_; -} - -bool TaintNode::Iterator::operator!=(const Iterator& other) const -{ - return current_ != other.current_; -} - -TaintNode::Iterator TaintNode::begin() -{ - return Iterator(this); -} - -TaintNode::Iterator TaintNode::end() -{ - return Iterator(); -} - - - - - - -TaintFlow::Iterator::Iterator(TaintNode* head) : nodeIterator_(head->begin()){} - -TaintFlow::Iterator::Iterator() : nodeIterator_() {} - -TaintFlow::Iterator::Iterator(const Iterator& other) : nodeIterator_(other.nodeIterator_) { } - -TaintFlow::Iterator& TaintFlow::Iterator::operator++() -{ - ++nodeIterator_; - return *this; -} - -TaintNode& TaintFlow::Iterator::operator*() const -{ - return *nodeIterator_; -} - -bool TaintFlow::Iterator::operator==(const Iterator& other) const -{ - return nodeIterator_ == other.nodeIterator_; -} - -bool TaintFlow::Iterator::operator!=(const Iterator& other) const -{ - return nodeIterator_ != other.nodeIterator_; -} TaintFlow::TaintFlow() - : head_(nullptr) + : nodes_() { MOZ_COUNT_CTOR(TaintFlow); } TaintFlow::TaintFlow(TaintNode* head) - : head_(head) + : nodes_() { MOZ_COUNT_CTOR(TaintFlow); + nodes_.insert(head); + // ToDo: probably remove addref + // addrefNodes(); } TaintFlow::TaintFlow(const TaintOperation& source) - : head_(new TaintNode(source)) + : nodes_() { + nodes_.insert(new TaintNode(source)); MOZ_COUNT_CTOR(TaintFlow); } TaintFlow::TaintFlow(const TaintFlow& other) - : head_(other.head_) { MOZ_COUNT_CTOR(TaintFlow); - if (head_) - head_->addref(); + if (other) { + nodes_=other.nodes_; + addrefNodes(); + } } TaintFlow::TaintFlow(const TaintFlow* other) - : head_(nullptr) { MOZ_COUNT_CTOR(TaintFlow); if (other) { - head_ = other->head_; - if (head_) - head_->addref(); + nodes_=other->nodes_; + addrefNodes(); } } TaintFlow::TaintFlow(TaintFlow&& other) - : head_(other.head_) + : nodes_(other.nodes_) { MOZ_COUNT_CTOR(TaintFlow); - other.head_ = nullptr; + other.nodes_ = std::set(); +} + +TaintFlow::TaintFlow(const TaintFlow& flow1, const TaintFlow& flow2) +{ + nodes_ = flow1.nodes_; + nodes_.insert(flow2.nodes_.begin(), flow2.nodes_.end()); + addrefNodes(); } TaintFlow::~TaintFlow() { MOZ_COUNT_DTOR(TaintFlow); - if (head_) - head_->release(); + releaseNodes(); } -TaintFlow& TaintFlow::operator=(const TaintFlow& other) +void TaintFlow::addrefNodes() { - if (head_) - head_->release(); + for(auto node : nodes_){ + node->addref(); + } +} - head_ = other.head_; - if (head_) - head_->addref(); +void TaintFlow::releaseNodes() +{ + for(auto node : nodes_){ + node->release(); + } +} + +TaintFlow& TaintFlow::operator=(const TaintFlow& other) +{ + MOZ_COUNT_CTOR(TaintFlow); + if (other) { + nodes_=other.nodes_; + addrefNodes(); + } + // releaseNodes(); + // nodes_ = other.nodes_; + // addrefNodes(); return *this; } @@ -452,23 +288,20 @@ std::vector TaintFlow::sources() const std::set taintSources = {}; for (auto node = this->begin(); node != this->end(); ++node) { - if((*node).parents().empty()){ - taintSources.insert(& ((*node).operation())); + if((*node)->operation().isSource()){ + taintSources.insert(& ((*node)->operation())); } } std::vector taintSources_vector(taintSources.begin(), taintSources.end()); - std::copy(taintSources.begin(), taintSources.end(), std::back_inserter(taintSources_vector)); + // std::copy(taintSources.begin(), taintSources.end(), std::back_inserter(taintSources_vector)); return taintSources_vector; } TaintFlow& TaintFlow::extend(const TaintOperation& operation) { - TaintNode* newhead = new TaintNode(head_, operation); - head_->release(); - head_ = newhead; return *this; } @@ -481,40 +314,21 @@ TaintFlow& TaintFlow::extend(const TaintOperation& operation) const TaintFlow& TaintFlow::extend(TaintOperation&& operation) { - TaintNode* newhead = new TaintNode(head_, operation); - head_->release(); - head_ = newhead; return *this; } -TaintFlow::Iterator TaintFlow::begin() const -{ - return Iterator(head_); -} - -TaintFlow::Iterator TaintFlow::end() const -{ - return Iterator(); -} TaintFlow TaintFlow::extend(const TaintFlow& flow, const TaintOperation& operation) { - return TaintFlow(new TaintNode(flow.head_, operation)); + return flow; } TaintFlow TaintFlow::extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation) { - return TaintFlow(new TaintNode({flow1.head_ ,flow2.head_}, operation)); + return TaintFlow(flow1,flow2); + // return TaintFlow(new TaintNode({flow1.head_ ,flow2.head_}, operation)); } -TaintFlow TaintFlow::extend(const std::vector flows, const TaintOperation& operation) -{ - std::vector parents = {}; - for (auto &&flow : flows) { - parents.push_back(flow->head()); - } - return TaintFlow(new TaintNode(parents, operation)); -} TaintRange::TaintRange() @@ -1295,7 +1109,7 @@ void DumpTaint(const StringTaint& taint) void DumpTaintFlow(const TaintFlow& flow) { for(auto& node : flow) { - auto& op = node.operation(); + auto& op = node->operation(); DumpTaintOperation(op); } } diff --git a/taint/Taint.h b/taint/Taint.h index 57015bb713300..8566be852e1a6 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -168,9 +168,6 @@ class TaintOperation TaintLocation location_; }; - -class TaintNodeIterator; - /* * The nodes of the taint flow graph. * @@ -186,59 +183,13 @@ class TaintNodeIterator; */ class TaintNode { - private: - // Iterate over this node and its parents - class Iterator { - public: - Iterator(TaintNode * head); - Iterator(); - - Iterator(const Iterator& other); - // Iterator(Iterator&& other); - - ~Iterator(); - - Iterator& operator++(); - TaintNode& operator*() const; - bool operator==(const Iterator& other) const; - bool operator!=(const Iterator& other) const; - - private: - - struct PtrComp - { - bool operator()(const TaintNode* lhs, const TaintNode* rhs) const { return lhs> visited); - - TaintNode* root_; - TaintNode* current_; - Iterator * currentParentIterator_; - - int parentCount_; - int currentParentNum_ = 0; - TaintNode * currentParent_ = nullptr; - - bool finished = false; - std::shared_ptr> visited_; - }; - friend class TaintFlow; public: // Constructing a taint node sets the initial reference count to 1. - // Constructs an intermediate node. - TaintNode(TaintNode* parent, const TaintOperation& operation); - // Constructs an intermediate node with multiple parents. - TaintNode(const std::vector parents, const TaintOperation& operation); // Constructs a root node. TaintNode(const TaintOperation& operation); // Constructing a taint node sets the initial reference count to 1. - // Constructs an intermediate node. - TaintNode(TaintNode* parent, TaintOperation&& operation); - // Constructs an intermediate node with multiple parents. - TaintNode(const std::vector parents, TaintOperation&& operation); // Constructs a root node. TaintNode(TaintOperation&& operation); @@ -249,21 +200,9 @@ class TaintNode // count drops to zero then this instance will be destroyed. void release(); - // Returns the parent node of this node or nullptr if this is a root node. - std::vector parents() { return parents_; } - // Returns the operation associated with this taint node. const TaintOperation& operation() const { return operation_; } - // Iterator support - // - // Makes it possible to conveniently iterate over all taint nodes of a flow, - // starting at the newest node. - // Since TaintNodes are inherently immutable, we can safely return non-const - // pointers here. - TaintNode::Iterator begin(); - TaintNode::Iterator end(); - private: // Prevent clients from deleting us. TaintNodes can only be destroyed // through release(). @@ -284,12 +223,6 @@ class TaintNode TaintNode(const TaintNode& other) = delete; TaintNode& operator=(const TaintNode& other) = delete; - // Wrapper around addParent() for multiple parents - void addParents(const std::vector &parents); - - // Add parent to internal parents_ vector and addref() parent - void addParent(TaintNode* parent); - }; /* @@ -332,27 +265,13 @@ class TaintNode class TaintFlow { private: - // Iterate over the nodes in this flow. - // - // Note: The iterator does not increment the reference count. Instead, the - // caller must ensure that the TaintFlow instance is alive during the - // lifetime of any iterator instance. - class Iterator { - public: - Iterator(TaintNode* head); - Iterator(); - - Iterator(const Iterator& other); - - Iterator& operator++(); - TaintNode& operator*() const; - bool operator==(const Iterator& other) const; - bool operator!=(const Iterator& other) const; - - private: - // TaintNode* current_; - TaintNode::Iterator nodeIterator_; - }; + // Last (newest) node of this flow. + + struct PtrComp { + bool operator()(const TaintNode* lhs, const TaintNode* rhs) const { + return lhs < rhs; + } + }; public: @@ -377,6 +296,8 @@ class TaintFlow TaintFlow(const TaintFlow* other); TaintFlow(const TaintOperation& operation, const std::vector parents); + + TaintFlow(const TaintFlow& flow1, const TaintFlow& flow2); ~TaintFlow(); @@ -384,7 +305,8 @@ class TaintFlow TaintFlow& operator=(const TaintFlow& other); // Returns the head of this flow, i.e. the newest node in the path. - TaintNode* head() const { return head_; } + // TaintNode* head() const { return nodes_[0]; } + std::set nodes() const { return nodes_; } // Returns the source of this taint flow. std::vector sources() const; @@ -407,29 +329,37 @@ class TaintFlow // starting at the newest node. // Since TaintNodes are inherently immutable, we can safely return non-const // pointers here. - TaintFlow::Iterator begin() const; - TaintFlow::Iterator end() const; + std::set::iterator begin() const {return nodes_.begin();}; + std::set::iterator end() const {return nodes_.end();}; // Constructs a new taint node as child of the head node in this flow and // returns a new taint flow starting at that node. static TaintFlow extend(const TaintFlow& flow, const TaintOperation& operation); static TaintFlow extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation); - - static TaintFlow extend(const std::vector flows, const TaintOperation& operation); // Two TaintFlows are equal if they point to the same taint node. - bool operator==(const TaintFlow& other) const { return head_ == other.head_; } - bool operator!=(const TaintFlow& other) const { return head_ != other.head_; } + bool operator==(const TaintFlow& other) const { return nodes_ == other.nodes_; } + bool operator!=(const TaintFlow& other) const { return nodes_ != other.nodes_; } // Boolean operator, indicates whether this taint flow is empty or now. - operator bool() const { return !!head_; } + operator bool() const { return !nodes_.empty(); } static const TaintFlow& getEmptyTaintFlow(); + void addrefNodes(); + void releaseNodes(); + private: // Last (newest) node of this flow. - TaintNode* head_; + + // struct PtrComp { + // bool operator()(const TaintNode* lhs, const TaintNode* rhs) const { + // return lhs < rhs; + // } + // }; + + std::set nodes_; static TaintFlow empty_flow_; }; From 49ac4c1d0de60bfbafaf87fa68b9834a6977d0b4 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 27 Oct 2022 09:58:59 +0000 Subject: [PATCH 40/70] Foxhound: Remove various TaintNode creators --- js/src/builtin/Array.cpp | 3 -- js/src/builtin/JSON.cpp | 3 -- js/src/builtin/RegExp.cpp | 3 -- js/src/builtin/String.cpp | 60 +------------------------------- js/src/jstaint.cpp | 7 +--- js/src/vm/StringType.cpp | 9 ----- xpcom/string/nsReadableUtils.cpp | 2 -- 7 files changed, 2 insertions(+), 85 deletions(-) diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp index cd4c39ee6b648..7db52223f1ff3 100644 --- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -1397,9 +1397,6 @@ bool js::array_join(JSContext* cx, unsigned argc, Value* vp) { return false; } - // TaintFox: add taint operation. - str->taint().extend(TaintOperationFromContext(cx, "Array.join", true, sepstr)); - args.rval().setString(str); return true; } diff --git a/js/src/builtin/JSON.cpp b/js/src/builtin/JSON.cpp index 868e3f22405b8..fb64ab541b56a 100644 --- a/js/src/builtin/JSON.cpp +++ b/js/src/builtin/JSON.cpp @@ -1418,9 +1418,6 @@ bool json_stringify(JSContext* cx, unsigned argc, Value* vp) { return false; } - // TaintFox: Add stringify operation to taint flows. - str->taint().extend(TaintOperationFromContext(cx, "JSON.stringify", true)); - args.rval().setString(str); } else { args.rval().setUndefined(); diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index a858259a36ca7..3b341de642902 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -129,9 +129,6 @@ bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re, if (str->taint().hasTaint()) { RootedAtom src(cx, re->getSource()); JSString* srcStr = EscapeRegExpPattern(cx, src); - str->taint().extend( - TaintOperation("RegExp.prototype.exec", true, TaintLocationFromContext(cx), - { taintarg_jsstring_full(cx, srcStr), taintarg_jsstring(cx, str), taintarg(cx, i) })); } arr->setDenseInitializedLength(i + 1); arr->initDenseElement(i, StringValue(str)); diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 1c338a9ec8d6a..74106333da54d 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -520,7 +520,6 @@ static bool str_escape(JSContext* cx, unsigned argc, Value* vp) { } // Taintfox: set new taint - newtaint.extend(TaintOperationFromContext(cx, "escape", true, str)); res->setTaint(cx, newtaint); args.rval().setString(res); @@ -690,7 +689,6 @@ static bool str_unescape(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox: add taint operation. - newtaint.extend(op); result->setTaint(cx, newtaint); args.rval().setString(result); @@ -1119,9 +1117,6 @@ static JSString* ToLowerCase(JSContext* cx, JSLinearString* str) { InlineCharBuffer newChars; // Taintfox: cache the taint up here to prevent GC issues SafeStringTaint taint = str->taint(); - if (taint.hasTaint()) { - taint.extend(TaintOperationFromContextJSString(cx, "toLowerCase", true, str)); - } const size_t length = str->length(); size_t resultLength; { @@ -1542,9 +1537,6 @@ static JSString* ToUpperCase(JSContext* cx, JSLinearString* str) { mozilla::MaybeOneOf newChars; SafeStringTaint taint = str->taint(); - if (taint.hasTaint()) { - taint.extend(TaintOperationFromContextJSString(cx, "toUpperCase", true, str)); - } const size_t length = str->length(); size_t resultLength; { @@ -1896,9 +1888,6 @@ static bool str_normalize(JSContext* cx, unsigned argc, Value* vp) { // Latin-1 strings are already in Normalization Form C. if (form == NormalizationForm::NFC && str->hasLatin1Chars()) { - if (str->taint().hasTaint()) { - str->taint().extend(TaintOperationFromContext(cx, "normalize", true, str)); - } // Step 7. args.rval().setString(str); return true; @@ -1927,9 +1916,6 @@ static bool str_normalize(JSContext* cx, unsigned argc, Value* vp) { // Return if the input string is already normalized. if (alreadyNormalized.unwrap() == AlreadyNormalized::Yes) { - if (str->taint().hasTaint()) { - str->taint().extend(TaintOperationFromContext(cx, "normalize", true, str)); - } // Step 7. args.rval().setString(str); return true; @@ -1942,7 +1928,7 @@ static bool str_normalize(JSContext* cx, unsigned argc, Value* vp) { // TaintFox: Add taint operation. if (str->taint().hasTaint()) { - ns->setTaint(cx, str->taint().safeCopy().extend(TaintOperationFromContext(cx, "normalize", true, str))); + ns->setTaint(cx, str->taint().safeCopy()); } // Step 7. @@ -2952,22 +2938,6 @@ static bool TrimString(JSContext* cx, const CallArgs& args, const char* funName, return false; } - // TaintFox: Add trim operation to current taint flow. - // the acutal trimming of taint ranges has been done in - // NewDependentString (StringType-inl.h, JSDependentString::init) - if (result->taint().hasTaint()) { - AutoCheckCannotGC nogc; - if (trimStart && trimEnd) { - result->taint().extend(TaintOperationFromContext(cx, "trim", true)); - } else if (trimStart) { - result->taint().extend(TaintOperationFromContext(cx, "trimLeft", true)); - } else if (trimEnd) { - result->taint().extend(TaintOperationFromContext(cx, "trimRight", true)); - } else { - result->taint().extend(TaintOperationFromContext(cx, "trim", true)); - } - } - args.rval().setString(result); return true; } @@ -3552,10 +3522,6 @@ static JSString* ReplaceAll(JSContext* cx, JSLinearString* string, } } - // Taintfox: extend the taint flow - result.taint().extend( - TaintOperationFromContextJSString(cx, "replaceAll", true, searchString, replaceString)); - // Step 16. return result.finishString(); } @@ -3803,10 +3769,6 @@ static ArrayObject* SplitHelper(JSContext* cx, HandleLinearString str, return nullptr; } - // TaintFox: extend taint flow - sub->taint().extend(TaintOperation("split", true, TaintLocationFromContext(cx), - { taintarg(cx, sep), taintarg(cx, count++) })); - // Step 14.c.ii.5. if (splits.length() == limit) { return NewDenseCopiedArray(cx, splits.length(), splits.begin()); @@ -3828,9 +3790,6 @@ static ArrayObject* SplitHelper(JSContext* cx, HandleLinearString str, return nullptr; } - // TaintFox: extend taint flow - sub->taint().extend(TaintOperation("split", true, TaintLocationFromContext(cx), { taintarg(cx, sep), taintarg(cx, count++) })); - // Step 18. return NewDenseCopiedArray(cx, splits.length(), splits.begin()); } @@ -3868,8 +3827,6 @@ static ArrayObject* CharSplitHelper(JSContext* cx, HandleLinearString str, if (!sub) { return nullptr; } - // TaintFox: extend taint flow - sub->taint().extend(TaintOperation("split", true, TaintLocationFromContext(cx), { taintarg(cx, u""), taintarg(cx, count++) })); splits->initDenseElement(i, StringValue(sub)); } @@ -3913,9 +3870,6 @@ static MOZ_ALWAYS_INLINE ArrayObject* SplitSingleCharHelper( } splits->initDenseElement(splitsIndex++, StringValue(sub)); - // TaintFox: extend taint flow - sub->taint().extend(TaintOperation("split", true, TaintLocationFromContext(cx), { taintarg_char(cx, patCh), taintarg(cx, count++) })); - lastEndIndex = index + 1; } } @@ -3926,8 +3880,6 @@ static MOZ_ALWAYS_INLINE ArrayObject* SplitSingleCharHelper( if (!sub) { return nullptr; } - // TaintFox: extend taint flow - sub->taint().extend(TaintOperation("split", true, TaintLocationFromContext(cx), { taintarg_char(cx, patCh), taintarg(cx, count++) })); splits->initDenseElement(splitsIndex++, StringValue(sub)); @@ -4635,11 +4587,6 @@ static MOZ_ALWAYS_INLINE bool Encode(JSContext* cx, HandleLinearString str, // TaintFox: Add encode operation to output taint. SafeStringTaint taint = sb.empty() ? str->taint() : sb.taint(); - if (unescapedSet == js_isUriReservedPlusPound) { - taint.extend(TaintOperationFromContext(cx, "encodeURI", true, str)); - } else { - taint.extend(TaintOperationFromContext(cx, "encodeURIComponent", true, str)); - } MOZ_ASSERT(res == Encode_Success); return TransferBufferToString(cx, sb, str, taint, rval); @@ -4799,11 +4746,6 @@ static bool Decode(JSContext* cx, HandleLinearString str, // TaintFox: Add decode operation to output taint. SafeStringTaint taint = sb.empty() ? str->taint() : sb.taint(); - if(reservedSet == js_isUriReservedPlusPound) { - taint.extend(TaintOperationFromContext(cx, "decodeURI", true, str)); - } else { - taint.extend(TaintOperationFromContext(cx, "decodeURIComponent", true, str)); - } MOZ_ASSERT(res == Decode_Success); return TransferBufferToString(cx, sb, str, taint, rval); diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 626f3c2bb92c8..a18a492a80796 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -356,11 +356,6 @@ void JS::MarkTaintedFunctionArguments(JSContext* cx, JSFunction* function, const for (unsigned i = 0; i < args.length(); i++) { if (args[i].isString()) { RootedString arg(cx, args[i].toString()); - if (arg->isTainted()) { - arg->taint().extend( - TaintOperation("function", location, - { taintarg(cx, name), sourceinfo, taintarg(cx, i), taintarg(cx, args.length()) } )); - } } } } @@ -393,7 +388,7 @@ TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) // add info for operation // add getting combined taint flow if (isTaintedNumber(val1) && isTaintedNumber(val2)){ - return TaintFlow::extend(getNumberTaint(val1),getNumberTaint(val2),TaintOperation("Unspecified Operation")); + return TaintFlow::extend(getNumberTaint(val1),getNumberTaint(val2)); } else if (isTaintedNumber(val1)) { return getNumberTaint(val1); diff --git a/js/src/vm/StringType.cpp b/js/src/vm/StringType.cpp index 5cf0b10d55e95..dd7c0ee49f392 100644 --- a/js/src/vm/StringType.cpp +++ b/js/src/vm/StringType.cpp @@ -1009,17 +1009,8 @@ JSString* js::ConcatStrings( typename MaybeRooted::HandleType right, gc::InitialHeap heap) { - TaintOperation op("concat"); - if ((left && right) && (left->taint().hasTaint() || right->taint().hasTaint())) { - op = JS::TaintOperationConcat(cx, "concat", true, left, right); - } - JSString* str = ConcatStringsQuiet(cx, left, right, heap); - if (str && str->taint().hasTaint()) { - str->taint().extend(op); - } - return str; } diff --git a/xpcom/string/nsReadableUtils.cpp b/xpcom/string/nsReadableUtils.cpp index 508e53d06dbe1..b008968f85867 100644 --- a/xpcom/string/nsReadableUtils.cpp +++ b/xpcom/string/nsReadableUtils.cpp @@ -242,7 +242,6 @@ void ToUpperCase(const nsACString& aSource, nsACString& aDest) { // TaintFox: propagate taint into aDest. aDest.AssignTaint(aSource.Taint()); - aDest.Taint().extend(TaintOperation("ToUpperCase", true)); } void ToLowerCase(nsACString& aCString) { @@ -275,7 +274,6 @@ void ToLowerCase(const nsACString& aSource, nsACString& aDest) { // TaintFox: propagate taint into aDest. aDest.AssignTaint(aSource.Taint()); - aDest.Taint().extend(TaintOperation("ToLowerCase", true)); } void ParseString(const nsACString& aSource, char aDelimiter, From bcab16316e1525e95f050287ecbcef24ca42e585 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 27 Oct 2022 09:59:41 +0000 Subject: [PATCH 41/70] Foxhound: Update TaintFlow expansion to fit new data model --- taint/Taint.cpp | 18 ++++++++---------- taint/Taint.h | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 85579dac88b93..824e166548ea3 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -285,23 +285,18 @@ const TaintFlow& TaintFlow::getEmptyTaintFlow() { std::vector TaintFlow::sources() const { - std::set taintSources = {}; - + std::vector taintSources_vector; for (auto node = this->begin(); node != this->end(); ++node) { if((*node)->operation().isSource()){ - taintSources.insert(& ((*node)->operation())); + taintSources_vector.push_back(& ((*node)->operation())); } } - - std::vector taintSources_vector(taintSources.begin(), taintSources.end()); - - // std::copy(taintSources.begin(), taintSources.end(), std::back_inserter(taintSources_vector)); - return taintSources_vector; } TaintFlow& TaintFlow::extend(const TaintOperation& operation) { + nodes_.insert(new TaintNode(operation)); return *this; } @@ -314,16 +309,19 @@ TaintFlow& TaintFlow::extend(const TaintOperation& operation) const TaintFlow& TaintFlow::extend(TaintOperation&& operation) { + nodes_.insert(new TaintNode(operation)); return *this; } TaintFlow TaintFlow::extend(const TaintFlow& flow, const TaintOperation& operation) { - return flow; + auto newFlow = flow; + newFlow.extend(operation); + return newFlow; } -TaintFlow TaintFlow::extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation) +TaintFlow TaintFlow::extend(const TaintFlow& flow1, const TaintFlow& flow2) { return TaintFlow(flow1,flow2); // return TaintFlow(new TaintNode({flow1.head_ ,flow2.head_}, operation)); diff --git a/taint/Taint.h b/taint/Taint.h index 8566be852e1a6..bbbed54569bc1 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -336,7 +336,7 @@ class TaintFlow // returns a new taint flow starting at that node. static TaintFlow extend(const TaintFlow& flow, const TaintOperation& operation); - static TaintFlow extend(const TaintFlow& flow1, const TaintFlow& flow2, const TaintOperation& operation); + static TaintFlow extend(const TaintFlow& flow1, const TaintFlow& flow2); // Two TaintFlows are equal if they point to the same taint node. bool operator==(const TaintFlow& other) const { return nodes_ == other.nodes_; } From 9925b207d3e9b3eae6d6703bf08e3a694659e32f Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Mon, 14 Nov 2022 17:27:59 +0000 Subject: [PATCH 42/70] Fx error when using add with arrays - Prev/Incorrect: [1]+1=2 - Now/Correct: [1]+1='11' --- js/src/vm/Interpreter.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index bd9fb2ff826ee..18033667e7922 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1414,6 +1414,22 @@ static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx, bool lIsString = lhs.isString(); bool rIsString = rhs.isString(); + + // TaintFox: Cast to primitive if values are neither strings nor tainted numbers + // Required for some special cases like arrays + if (!lIsString && !isTaintedNumber(lhs)) { + if (!ToPrimitive(cx, lhs)) { + return false; + } + lIsString = lhs.isString(); + } + if (!rIsString && !isTaintedNumber(rhs)) { + if (!ToPrimitive(cx, rhs)) { + return false; + } + rIsString = rhs.isString(); + } + if (lIsString || rIsString) { JSString* lstr; if (lIsString) { From b641c335a605d1da3a59d82f8d1f172101fe6e56 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Wed, 16 Nov 2022 08:20:57 +0000 Subject: [PATCH 43/70] Foxhound: Update test for features which were broken --- .../number_tainting_breaking_features.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 js/src/tests/taint/number_tainting_breaking_features.js diff --git a/js/src/tests/taint/number_tainting_breaking_features.js b/js/src/tests/taint/number_tainting_breaking_features.js new file mode 100644 index 0000000000000..d729ce64f87ba --- /dev/null +++ b/js/src/tests/taint/number_tainting_breaking_features.js @@ -0,0 +1,29 @@ +// During the development of number tainting, some JavaScript features got +// broken. This led to some pages not loading correctly. +// In an effort to fixe these errors, this test explicity tests the currently +// or previously broken functionality to ensure it does not get broken again. + + +function numberTaintingBreakingFeatures() { + + var a = taint(42); + var b = taint(13.37); + + // Addition using arrays + assertEq([1] + 1, '11') + assertEq([12345] + 6789, '123456789') + + + // Number.isInteger() + assertEq(Number.isInteger(a), True) + assertEq(Number.isInteger(b), False) + assertEq(Number.isInteger(45542), True) + assertEq(Number.isInteger(4525.123), False) + + +} + +runTaintTest(numberTaintingBreakingFeatures); + +if (typeof reportCompare === 'function') + reportCompare(true, true); From d084ff4887eb2a1cc4534776157c04ceb3279787 Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Wed, 16 Nov 2022 16:25:59 +0000 Subject: [PATCH 44/70] Foxhound: Fix static Number functions for tainted numbers --- js/src/builtin/Number.js | 16 ++++ .../number_tainting_breaking_features.js | 83 +++++++++++++++++-- js/src/vm/SelfHosting.cpp | 20 +++++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/js/src/builtin/Number.js b/js/src/builtin/Number.js index b86a8112cfaad..a9e2fc624a1a9 100644 --- a/js/src/builtin/Number.js +++ b/js/src/builtin/Number.js @@ -41,6 +41,10 @@ function Number_toLocaleString() { // ES6 draft ES6 20.1.2.4 function Number_isFinite(num) { + + // TaintFox: get primitive value, if value is tainted number object + num = GetNumberObjectValueIfTainted(num); + if (typeof num !== "number") return false; return num - num === 0; @@ -48,6 +52,10 @@ function Number_isFinite(num) { // ES6 draft ES6 20.1.2.2 function Number_isNaN(num) { + + // TaintFox: get primitive value, if value is tainted number object + num = GetNumberObjectValueIfTainted(num); + if (typeof num !== "number") return false; return num !== num; @@ -56,6 +64,10 @@ function Number_isNaN(num) { // ES2021 draft rev 889f2f30cf554b7ed812c0984626db1c8a4997c7 // 20.1.2.3 Number.isInteger ( number ) function Number_isInteger(number) { + + // TaintFox: get primitive value, if value is tainted number object + number = GetNumberObjectValueIfTainted(number); + // Step 1. (Inlined call to IsIntegralNumber) // 7.2.6 IsIntegralNumber, step 1. @@ -73,6 +85,10 @@ function Number_isInteger(number) { // ES2021 draft rev 889f2f30cf554b7ed812c0984626db1c8a4997c7 // 20.1.2.5 Number.isSafeInteger ( number ) function Number_isSafeInteger(number) { + + // TaintFox: get primitive value, if value is tainted number object + number = GetNumberObjectValueIfTainted(number); + // Step 1. (Inlined call to IsIntegralNumber) // 7.2.6 IsIntegralNumber, step 1. diff --git a/js/src/tests/taint/number_tainting_breaking_features.js b/js/src/tests/taint/number_tainting_breaking_features.js index d729ce64f87ba..0f019178c387c 100644 --- a/js/src/tests/taint/number_tainting_breaking_features.js +++ b/js/src/tests/taint/number_tainting_breaking_features.js @@ -3,6 +3,17 @@ // In an effort to fixe these errors, this test explicity tests the currently // or previously broken functionality to ensure it does not get broken again. +// Test a function using a primitive, a tainted number (object) and an untainted +// number object +function testPrimitiveTaintedObject(numberValue, expectedPrimitive, expectedTainted, expectedNumber, testFunc) { + let primitive = numberValue + let tainted = taint(numberValue) + let number = new Number(numberValue) + testFunc(primitive, expectedPrimitive) + testFunc(tainted, expectedTainted) + testFunc(number, expectedNumber) +} + function numberTaintingBreakingFeatures() { @@ -14,11 +25,73 @@ function numberTaintingBreakingFeatures() { assertEq([12345] + 6789, '123456789') - // Number.isInteger() - assertEq(Number.isInteger(a), True) - assertEq(Number.isInteger(b), False) - assertEq(Number.isInteger(45542), True) - assertEq(Number.isInteger(4525.123), False) + // Is Integer + testPrimitiveTaintedObject(123, true, true, false, (value, expected) => { + assertEq(Number.isInteger(value), expected) + }) + + testPrimitiveTaintedObject(123.456, false, false, false, (value, expected) => { + assertEq(Number.isInteger(value), expected) + }) + + + // IsSafeInteger + testPrimitiveTaintedObject(123, true, true, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + testPrimitiveTaintedObject(2 ** 53 - 1, true, true, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + testPrimitiveTaintedObject(NaN, false, false, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + testPrimitiveTaintedObject(Infinity, false, false, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + testPrimitiveTaintedObject("3", false, false, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + testPrimitiveTaintedObject(3.1, false, false, false, (value, expected) => { + assertEq(Number.isSafeInteger(value), expected) + }) + + + // IsFinite + testPrimitiveTaintedObject(2531,true,true,false,(value,expected)=>{ + assertEq(Number.isFinite(value), expected) + }) + + testPrimitiveTaintedObject(Infinity, false, false, false, (value, expected) => { + assertEq(Number.isFinite(value), expected) + }) + + testPrimitiveTaintedObject(NaN, false, false, false, (value, expected) => { + assertEq(Number.isFinite(value), expected) + }) + + testPrimitiveTaintedObject(-Infinity, false, false, false, (value, expected) => { + assertEq(Number.isFinite(value), expected) + }) + + + // IsNaN + testPrimitiveTaintedObject(NaN,true,true,false,(value,expected)=>{ + assertEq(Number.isNaN(value), expected) + }) + + testPrimitiveTaintedObject(Number.NaN,true,true,false,(value,expected)=>{ + assertEq(Number.isNaN(value), expected) + }) + + testPrimitiveTaintedObject(2531, false, false, false, (value, expected) => { + assertEq(Number.isNaN(value), expected) + }) + } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 7f5f0099dd594..734c69f93355a 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -23,6 +23,7 @@ #include "jsfriendapi.h" #include "jsmath.h" #include "jsnum.h" +#include "jstaint.h" #include "selfhosted.out.h" #include "builtin/Array.h" @@ -2163,6 +2164,24 @@ taint_copyString(JSContext* cx, unsigned argc, Value* vp) return true; } +// TaintFox: Returns a the value of a tainted number object, if the input is a +// tainted number object. Otherwise returns the input value without changes. +static bool taint_getNumberObjectValueIfTainted(JSContext* cx, unsigned argc, + Value* vp) { + // String, operation, args... + CallArgs args = CallArgsFromVp(argc, vp); + if (JS::isTaintedNumber(args[0])) { + if (!ToNumeric(cx, args[0])) { + return false; + } + auto value = args[0].toNumber(); + args.rval().setNumber(value); + } else { + args.rval().set(args[0]); + } + return true; +} + static bool intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); @@ -2361,6 +2380,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_INLINABLE_FN("GetNextSetEntryForIterator", intrinsic_GetNextSetEntryForIterator, 2, 0, IntrinsicGetNextSetEntryForIterator), + JS_FN("GetNumberObjectValueIfTainted", taint_getNumberObjectValueIfTainted, 1, 0), JS_FN("GetOwnPropertyDescriptorToArray", GetOwnPropertyDescriptorToArray, 2, 0), JS_FN("GetStringDataProperty", intrinsic_GetStringDataProperty, 2, 0), From fccf6a65c92e3d59c4c2034090df4c9999f0133c Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Wed, 16 Nov 2022 16:46:32 +0000 Subject: [PATCH 45/70] Foxhound: Add more taint sources * These new sources should generally work but are types contained in arrays. Most of the time, these arrays will be emptty and the taint source will not be observable --- dom/webidl/MediaDeviceInfo.webidl | 4 ++++ dom/webidl/MimeType.webidl | 3 +++ dom/webidl/Plugin.webidl | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/dom/webidl/MediaDeviceInfo.webidl b/dom/webidl/MediaDeviceInfo.webidl index 7bceadc22eb1a..7968a65702048 100644 --- a/dom/webidl/MediaDeviceInfo.webidl +++ b/dom/webidl/MediaDeviceInfo.webidl @@ -16,9 +16,13 @@ enum MediaDeviceKind { [Func="Navigator::HasUserMediaSupport", Exposed=Window] interface MediaDeviceInfo { + [TaintSource] readonly attribute DOMString deviceId; + [TaintSource] readonly attribute MediaDeviceKind kind; + [TaintSource] readonly attribute DOMString label; + [TaintSource] readonly attribute DOMString groupId; [Default] object toJSON(); diff --git a/dom/webidl/MimeType.webidl b/dom/webidl/MimeType.webidl index bccf7db7d841f..ba8cb523e9d05 100644 --- a/dom/webidl/MimeType.webidl +++ b/dom/webidl/MimeType.webidl @@ -6,8 +6,11 @@ [Exposed=Window] interface MimeType { + [TaintSource] readonly attribute DOMString description; readonly attribute Plugin? enabledPlugin; + [TaintSource] readonly attribute DOMString suffixes; + [TaintSource] readonly attribute DOMString type; }; diff --git a/dom/webidl/Plugin.webidl b/dom/webidl/Plugin.webidl index 7bc1fa97dcf1d..bece219150af5 100644 --- a/dom/webidl/Plugin.webidl +++ b/dom/webidl/Plugin.webidl @@ -7,9 +7,13 @@ [LegacyUnenumerableNamedProperties, Exposed=Window] interface Plugin { + [TaintSource] readonly attribute DOMString description; + [TaintSource] readonly attribute DOMString filename; + [TaintSource] readonly attribute DOMString version; + [TaintSource] readonly attribute DOMString name; readonly attribute unsigned long length; From d630772a9d3a585ef59fa46a384e0771b1ec5e5d Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Thu, 17 Nov 2022 16:09:30 +0000 Subject: [PATCH 46/70] Foxhound: disguise tainted numbers in typeof() --- .../taint/number_tainting_breaking_features.js | 14 ++++++++++++++ js/src/vm/Interpreter.cpp | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/js/src/tests/taint/number_tainting_breaking_features.js b/js/src/tests/taint/number_tainting_breaking_features.js index 0f019178c387c..7d4d34bd2e6ec 100644 --- a/js/src/tests/taint/number_tainting_breaking_features.js +++ b/js/src/tests/taint/number_tainting_breaking_features.js @@ -92,6 +92,20 @@ function numberTaintingBreakingFeatures() { assertEq(Number.isNaN(value), expected) }) + // typeof + testPrimitiveTaintedObject(2531, 'number', 'number', 'object', (value, expected) => { + assertEq(typeof value, expected) + }) + + testPrimitiveTaintedObject(2531, 'number', 'number', 'object', (value, expected) => { + assertEq(typeof(value), expected) + }) + + testPrimitiveTaintedObject(1, 'number', 'number', 'object', (value, expected) => { + assertEq(typeof(value), expected) + }) + + } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 18033667e7922..74160fccc4840 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -26,6 +26,7 @@ #include "jslibmath.h" #include "jsmath.h" #include "jsnum.h" +#include "jstaint.h" #include "builtin/Array.h" #include "builtin/Eval.h" @@ -890,7 +891,6 @@ JSType TypeOfExtendedPrimitive(JSObject* obj) { #endif JSType js::TypeOfValue(const Value& v) { - // Taintfox: where do tainted numbers fit in here? switch (v.type()) { case ValueType::Double: case ValueType::Int32: @@ -902,6 +902,10 @@ JSType js::TypeOfValue(const Value& v) { case ValueType::Undefined: return JSTYPE_UNDEFINED; case ValueType::Object: + // TaintFox: Hide the fact, that tainted numbers are number objects + if (JS::isTaintedNumber(v)){ + return JSTYPE_NUMBER; + } return TypeOfObject(&v.toObject()); #ifdef ENABLE_RECORD_TUPLE case ValueType::ExtendedPrimitive: From 3de1744b269f8acfd204954431a5c41dd18cbb7e Mon Sep 17 00:00:00 2001 From: Lukas Hock Date: Fri, 18 Nov 2022 17:02:09 +0000 Subject: [PATCH 47/70] Foxhound: Add new Taint Sources --- dom/webidl/AudioContext.webidl | 2 ++ dom/webidl/AudioDestinationNode.webidl | 1 + dom/webidl/AudioNode.webidl | 4 +++- dom/webidl/BaseAudioContext.webidl | 2 ++ dom/webidl/HTMLElement.webidl | 2 ++ dom/webidl/Navigator.webidl | 2 +- 6 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dom/webidl/AudioContext.webidl b/dom/webidl/AudioContext.webidl index c991e66fcd445..2e63a0d024055 100644 --- a/dom/webidl/AudioContext.webidl +++ b/dom/webidl/AudioContext.webidl @@ -25,7 +25,9 @@ interface AudioContext : BaseAudioContext { [Throws] constructor(optional AudioContextOptions contextOptions = {}); + [TaintSource] readonly attribute double baseLatency; + [TaintSource] readonly attribute double outputLatency; AudioTimestamp getOutputTimestamp(); diff --git a/dom/webidl/AudioDestinationNode.webidl b/dom/webidl/AudioDestinationNode.webidl index 3a02b3d45f119..303ef3634ed37 100644 --- a/dom/webidl/AudioDestinationNode.webidl +++ b/dom/webidl/AudioDestinationNode.webidl @@ -14,6 +14,7 @@ Exposed=Window] interface AudioDestinationNode : AudioNode { + [TaintSource] readonly attribute unsigned long maxChannelCount; }; diff --git a/dom/webidl/AudioNode.webidl b/dom/webidl/AudioNode.webidl index 7e96a50706526..85d91817b65bc 100644 --- a/dom/webidl/AudioNode.webidl +++ b/dom/webidl/AudioNode.webidl @@ -51,11 +51,13 @@ interface AudioNode : EventTarget { void disconnect(AudioParam destination, unsigned long output); readonly attribute BaseAudioContext context; + [TaintSource] readonly attribute unsigned long numberOfInputs; + [TaintSource] readonly attribute unsigned long numberOfOutputs; // Channel up-mixing and down-mixing rules for all inputs. - [SetterThrows] + [SetterThrows, TaintSource] attribute unsigned long channelCount; [SetterThrows, BinaryName="channelCountModeValue"] attribute ChannelCountMode channelCountMode; diff --git a/dom/webidl/BaseAudioContext.webidl b/dom/webidl/BaseAudioContext.webidl index d8c04eed09821..1a77acb37355e 100644 --- a/dom/webidl/BaseAudioContext.webidl +++ b/dom/webidl/BaseAudioContext.webidl @@ -22,7 +22,9 @@ enum AudioContextState { [Exposed=Window] interface BaseAudioContext : EventTarget { readonly attribute AudioDestinationNode destination; + [TaintSource] readonly attribute float sampleRate; + [TaintSource] readonly attribute double currentTime; readonly attribute AudioListener listener; readonly attribute AudioContextState state; diff --git a/dom/webidl/HTMLElement.webidl b/dom/webidl/HTMLElement.webidl index 471e5358824ec..3cedde32abe48 100644 --- a/dom/webidl/HTMLElement.webidl +++ b/dom/webidl/HTMLElement.webidl @@ -80,7 +80,9 @@ partial interface HTMLElement { readonly attribute Element? offsetParent; readonly attribute long offsetTop; readonly attribute long offsetLeft; + [TaintSource] readonly attribute long offsetWidth; + [TaintSource] readonly attribute long offsetHeight; }; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index db28070a8f54a..1effa05b4c13f 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -71,7 +71,7 @@ interface mixin NavigatorLanguage { [Pure, Cached, TaintSource] readonly attribute DOMString? language; - [Pure, Cached, Frozen] + [Pure, Cached, Frozen, TaintSource] readonly attribute sequence languages; }; From 2a1a515c0b004d7ead7d56454371c4d847e33334 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Wed, 8 May 2024 07:39:57 +0000 Subject: [PATCH 48/70] Additional tainted value helper functions --- js/src/jstaint.cpp | 48 +++++++++++++++++++++++++++++- js/src/jstaint.h | 14 +++++++++ js/src/tests/non262/taint/shell.js | 2 +- js/src/vm/Interpreter-inl.h | 12 ++++---- 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 74b6e36d42f20..d48afc9a69798 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -376,6 +376,33 @@ bool JS::isTaintedNumber(const Value& val) return false; } +bool JS::isTaintedValue(const Value& val) +{ + if (val.isObject() && val.toObject().is()) { + NumberObject& number = val.toObject().as(); + printf("isTaintedNum!!!!\n"); + return number.isTainted(); + } else if (val.isString()) { + printf("isTaintedStr!!!!\n"); + return val.toString()->isTainted(); + } + return false; +} + +const TaintFlow& JS::getValueTaint(const Value& val) +{ + if (val.isObject() && val.toObject().is()) { + NumberObject& number = val.toObject().as(); + return number.taint(); + } else if (val.isString()) { + printf("isTaintedStr!!!!\n"); + for (auto range: val.toString()->Taint()) { + return range.flow(); + } + } + return TaintFlow::getEmptyTaintFlow(); +} + const TaintFlow& JS::getNumberTaint(const Value& val) { if (val.isObject() && val.toObject().is()) { @@ -390,11 +417,16 @@ bool JS::isAnyTaintedNumber(const Value& val1, const Value& val2) return isTaintedNumber(val1) || isTaintedNumber(val2); } +bool JS::isAnyTaintedValue(const Value& val1, const Value& val2) +{ + return isTaintedValue(val1) || isTaintedValue(val2); +} + TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) { // add info for operation // add getting combined taint flow - if (isTaintedNumber(val1) && isTaintedNumber(val2)){ + if (isTaintedNumber(val1) && isTaintedNumber(val2)) { return TaintFlow::append(getNumberTaint(val1), getNumberTaint(val2)); } else if (isTaintedNumber(val1)) { @@ -404,6 +436,20 @@ TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) } } +TaintFlow JS::getAnyValueTaint(const Value& val1, const Value& val2) +{ + // add info for operation + // add getting combined taint flow + //if (isTaintedValue(val1) && isTaintedValue(val2)) { + // return TaintFlow::append(getValueTaint(val1), getValueTaint(val2)); + //} else + if (isTaintedValue(val1)) { + return getValueTaint(val1); + } else { + return getValueTaint(val2); + } +} + // Print a message to stdout. void JS::TaintFoxReport(JSContext* cx, const char* msg) { diff --git a/js/src/jstaint.h b/js/src/jstaint.h index a7c622e3a7ddd..44e740fa4e160 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -88,17 +88,31 @@ void MarkTaintedFunctionArguments(JSContext* cx, JSFunction* function, const JS: // Check if the argument value is a tainted number object. bool isTaintedNumber(const JS::Value& val); + +// Check if the argument value is a tainted number object. +bool isTaintedValue(const JS::Value& val); + // Extract the taint information from a number. const TaintFlow& getNumberTaint(const JS::Value& val); +// Extract the taint information from a number. +const TaintFlow& getValueTaint(const JS::Value& val); + // Check if any of the argument values is a tainted number object. // TODO make this accept a variable amount of arguments using variadic templates bool isAnyTaintedNumber(const JS::Value& val1, const JS::Value& val2); +// Check if any of the argument values is a tainted number object. +// TODO make this accept a variable amount of arguments using variadic templates +bool isAnyTaintedValue(const JS::Value& val1, const JS::Value& val2); + // Extract the taint information from the first tainted number argument. // TODO make this accept a variable amount of arguments using variadic templates TaintFlow getAnyNumberTaint(const JS::Value& val1, const JS::Value& val2); +// Extract the taint information from the first tainted argument. +TaintFlow getAnyValueTaint(const JS::Value& val1, const JS::Value& val2); + // Print a message to stdout. void TaintFoxReport(JSContext* cx, const char* msg); diff --git a/js/src/tests/non262/taint/shell.js b/js/src/tests/non262/taint/shell.js index 5bc88b9a41d3f..acc8e2cf709bf 100644 --- a/js/src/tests/non262/taint/shell.js +++ b/js/src/tests/non262/taint/shell.js @@ -247,7 +247,7 @@ if (typeof runTaintTest === 'undefined') { // Separate function so it's visible in the backtrace var runJITTest = function(doTest) { // Force JIT compilation - for (var i = 0; i < 100; i++) { + for (var i = 0; i < 10; i++) { //console.log(i); doTest(); } diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 4c94650b639a8..96fe044913da7 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -847,8 +847,8 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, res.setNumber(lhs.toNumber() - rhs.toNumber()); // TaintFox: Taint propagation when subtracting tainted numbers. - if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + if (isAnyTaintedValue(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); } return true; } @@ -871,8 +871,8 @@ static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, res.setNumber(lhs.toNumber() * rhs.toNumber()); // TaintFox: Taint propagation when multiplying tainted numbers. - if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + if (isAnyTaintedValue(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); } return true; } @@ -895,8 +895,8 @@ static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when dividing tainted numbers. - if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + if (isAnyTaintedValue(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); } return true; } From 51373a98207f5d22400c3c86101341c538537c20 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Mon, 17 Jun 2024 14:01:04 +0000 Subject: [PATCH 49/70] Foxhound: fixing number taint for inc/dec operations --- js/src/jsnum.cpp | 4 +-- js/src/jsnum.h | 20 +++++++++++ js/src/jstaint.cpp | 2 -- js/src/tests/non262/taint/number_tainting.js | 35 +++++++++++++++++--- js/src/tests/non262/taint/shell.js | 10 +++--- js/src/vm/Interpreter-inl.h | 33 ++++++++++++++---- js/src/vm/SelfHosting.cpp | 4 +-- 7 files changed, 88 insertions(+), 20 deletions(-) diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 7c6bcf1417ff5..ed4fe7d93e67a 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -2227,7 +2227,7 @@ bool js::ToNumericSlow(JSContext* cx, MutableHandleValue vp) { MOZ_ASSERT(!vp.isNumeric()); // Step 1. - if (!vp.isPrimitive()) { + if (!vp.isPrimitive() && !isTaintedNumber(vp)) { if (!ToPrimitive(cx, JSTYPE_NUMBER, vp)) { return false; } @@ -2361,7 +2361,7 @@ bool js::ToInt32OrBigIntSlow(JSContext* cx, MutableHandleValue vp) { return true; } - if (!ToNumeric(cx, vp)) { + if (!ToNumericUnboxTainted(cx, vp)) { return false; } diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 44a9961280720..157350a1e5263 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -13,6 +13,8 @@ #include +#include "jstaint.h" + #include "NamespaceImports.h" #include "js/Conversions.h" @@ -264,6 +266,24 @@ bool ToNumericSlow(JSContext* cx, JS::MutableHandleValue vp); return ToNumericSlow(cx, vp); } +// Additional function to also convert tainted numbers to real numerics (to toNumber() works as expected) +[[nodiscard]] MOZ_ALWAYS_INLINE bool ToNumericUnboxTainted(JSContext* cx, + JS::MutableHandleValue vp) { + if (!ToNumeric(cx, vp)) { + return false; + } + + if (JS::isTaintedNumber(vp)) { + // Also unbox the tainted numbers + double d; + if (!ToNumber(cx, vp, &d)) + return false; + vp.setNumber(d); + } + + return true; +} + bool ToInt32OrBigIntSlow(JSContext* cx, JS::MutableHandleValue vp); [[nodiscard]] MOZ_ALWAYS_INLINE bool ToInt32OrBigInt( diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index d48afc9a69798..9b5d4a15f4fff 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -380,10 +380,8 @@ bool JS::isTaintedValue(const Value& val) { if (val.isObject() && val.toObject().is()) { NumberObject& number = val.toObject().as(); - printf("isTaintedNum!!!!\n"); return number.isTainted(); } else if (val.isString()) { - printf("isTaintedStr!!!!\n"); return val.toString()->isTainted(); } return false; diff --git a/js/src/tests/non262/taint/number_tainting.js b/js/src/tests/non262/taint/number_tainting.js index 9195abb52c627..7ab4512f573f9 100644 --- a/js/src/tests/non262/taint/number_tainting.js +++ b/js/src/tests/non262/taint/number_tainting.js @@ -6,6 +6,8 @@ function numberTaintingTest() { // // Basic arithmetic tests + assertNumberTainted(a + 1); + assertNumberTainted(a + 13.37); assertNumberTainted(13.37 + a); assertNumberTainted(a + b); @@ -19,7 +21,9 @@ function numberTaintingTest() { assertEq(a - b == 42 - 13.37, true); assertNumberTainted(a * 13.37); + assertEq(a * 13.37, 561.54); assertNumberTainted(13.37 * a); + assertEq(13.37 * a, 13.37 * 42); assertNumberTainted(a * b); assertEq(a * b, 42 * 13.37); assertEq(a * b == 42 * 13.37, true); @@ -41,22 +45,43 @@ function numberTaintingTest() { assertEq(a ** b, 42 ** 13.37); assertEq(a ** b == 42 ** 13.37, true); +} + +function incrementNumberTaintingTest() { - // // Number increment/decrement - assertNumberTainted(a++); + var a = taint(42); assertNumberTainted(a); + assertNumberTainted(a++); + assertEq(43, a); assertNumberTainted(a--); + assertEq(42, a); assertNumberTainted(a); assertNumberTainted(++a); + assertEq(43, a); assertNumberTainted(a); assertNumberTainted(--a); + assertEq(42, a); assertNumberTainted(a); - + // Number increment/decrement + var b = taint(3.14159); + assertNumberTainted(b); + assertNumberTainted(b++); + assertNumberTainted(b--); + assertNumberTainted(b); + assertNumberTainted(++b); + assertNumberTainted(b); + assertNumberTainted(--b); + assertNumberTainted(b); + +} + +function bitwiseNumberTaintingTest() { // // Bitwise operations - b = taint(3); + var a = taint(42); + var b = taint(3); assertNumberTainted(a << 1); assertNumberTainted(a << b); assertEq(a << b, 42 << 3); @@ -91,6 +116,8 @@ function numberTaintingTest() { } runTaintTest(numberTaintingTest); +runTaintTest(incrementNumberTaintingTest); +runTaintTest(bitwiseNumberTaintingTest); if (typeof reportCompare === 'function') reportCompare(true, true); diff --git a/js/src/tests/non262/taint/shell.js b/js/src/tests/non262/taint/shell.js index acc8e2cf709bf..100cb9a9d6b1f 100644 --- a/js/src/tests/non262/taint/shell.js +++ b/js/src/tests/non262/taint/shell.js @@ -222,22 +222,24 @@ if (typeof assertNotHasTaintOperation === 'undefined') { if (typeof assertLastTaintOperationEquals === 'undefined') { var assertLastTaintOperationEquals = function(str, opName) { + var lastOp = "Unknown"; for (var i = 0; i < str.taint.length; i++) { var range = str.taint[i]; // Quirk: ignore "function call arguments" nodes for now... var index = 0; while (index < range.flow.length && - range.flow[index].operation == "function" && - range.flow[index].arguments[0].startsWith("assert")) + range.flow[index].operation == "function" && + range.flow[index].arguments[0].startsWith("assert")) index++; var node = range.flow[index]; + lastOp = node.operation; if (node.operation === opName) { return true; } } - throw Error("String '" + str + "' does not contain \"" + opName + "\" as last taint operation. Taint: " + JSON.stringify(str.taint)); + throw Error("String '" + str + "' does not contain \"" + opName + "\" as last taint operation (\"" + lastOp + "\"). Taint: " + JSON.stringify(str.taint)); } } @@ -247,7 +249,7 @@ if (typeof runTaintTest === 'undefined') { // Separate function so it's visible in the backtrace var runJITTest = function(doTest) { // Force JIT compilation - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 1; i++) { //console.log(i); doTest(); } diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 96fe044913da7..b8e569278c407 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -315,7 +315,8 @@ static MOZ_ALWAYS_INLINE bool NegOperation(JSContext* cx, double d; if (!ToNumber(cx, val, &d)) return false; - res.setObject(*NumberObject::createTainted(cx, d, getNumberTaint(val))); + res.setObject(*NumberObject::createTainted(cx, -d, getNumberTaint(val))); + return true; } int32_t i; @@ -349,6 +350,15 @@ static MOZ_ALWAYS_INLINE bool IncOperation(JSContext* cx, HandleValue val, return true; } + if (isTaintedNumber(val)) { + double d; + if (!ToNumber(cx, val, &d)) { + return false; + } + res.setObject(*NumberObject::createTainted(cx, d + 1, getNumberTaint(val))); + return true; + } + MOZ_ASSERT(val.isBigInt(), "+1 only callable on result of JSOp::ToNumeric"); return BigInt::incValue(cx, val, res); } @@ -366,6 +376,15 @@ static MOZ_ALWAYS_INLINE bool DecOperation(JSContext* cx, HandleValue val, return true; } + if (isTaintedNumber(val)) { + double d; + if (!ToNumber(cx, val, &d)) { + return false; + } + res.setObject(*NumberObject::createTainted(cx, d - 1, getNumberTaint(val))); + return true; + } + MOZ_ASSERT(val.isBigInt(), "-1 only callable on result of JSOp::ToNumeric"); return BigInt::decValue(cx, val, res); } @@ -837,7 +856,7 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, RootedValue origLhs(cx, lhs); RootedValue origRhs(cx, rhs); - if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + if (!ToNumericUnboxTainted(cx, lhs) || !ToNumericUnboxTainted(cx, rhs)) { return false; } @@ -845,6 +864,8 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, return BigInt::subValue(cx, lhs, rhs, res); } + + res.setNumber(lhs.toNumber() - rhs.toNumber()); // TaintFox: Taint propagation when subtracting tainted numbers. if (isAnyTaintedValue(origLhs, origRhs)) { @@ -861,7 +882,7 @@ static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, RootedValue origLhs(cx, lhs); RootedValue origRhs(cx, rhs); - if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + if (!ToNumericUnboxTainted(cx, lhs) || !ToNumericUnboxTainted(cx, rhs)) { return false; } @@ -885,7 +906,7 @@ static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, RootedValue origLhs(cx, lhs); RootedValue origRhs(cx, rhs); - if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + if (!ToNumericUnboxTainted(cx, lhs) || !ToNumericUnboxTainted(cx, rhs)) { return false; } @@ -917,7 +938,7 @@ static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx, return true; } - if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + if (!ToNumericUnboxTainted(cx, lhs) || !ToNumericUnboxTainted(cx, rhs)) { return false; } @@ -941,7 +962,7 @@ static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx, RootedValue origLhs(cx, lhs); RootedValue origRhs(cx, rhs); - if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + if (!ToNumericUnboxTainted(cx, lhs) || !ToNumericUnboxTainted(cx, rhs)) { return false; } diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index c2d755866f34c..f82536870fcfd 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1883,10 +1883,10 @@ static bool taint_getNumberObjectValueIfTainted(JSContext* cx, unsigned argc, // String, operation, args... CallArgs args = CallArgsFromVp(argc, vp); if (JS::isTaintedNumber(args[0])) { - if (!ToNumeric(cx, args[0])) { + double value; + if (!ToNumber(cx, args[0], &value)) { return false; } - auto value = args[0].toNumber(); args.rval().setNumber(value); } else { args.rval().set(args[0]); From e474d5a87c06c2b8dbef956c20a42c4ad6fefb62 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Mon, 17 Jun 2024 15:13:22 +0000 Subject: [PATCH 50/70] Foxhound: adding missing taint operations --- js/src/builtin/JSON.cpp | 3 +++ js/src/builtin/String.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/js/src/builtin/JSON.cpp b/js/src/builtin/JSON.cpp index 3a766f0c0228a..49da30340d7f5 100644 --- a/js/src/builtin/JSON.cpp +++ b/js/src/builtin/JSON.cpp @@ -2173,6 +2173,9 @@ bool json_stringify(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox: Add stringify operation to taint flows. + str->taint().extend(TaintOperationFromContext(cx, "JSON.stringify", true)); + args.rval().setString(str); } else { args.rval().setUndefined(); diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 98643967e6da1..920a876bd17e5 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -1209,6 +1209,9 @@ static JSString* ToLowerCase(JSContext* cx, JSLinearString* str) { InlineCharBuffer newChars; // Taintfox: cache the taint up here to prevent GC issues SafeStringTaint taint = str->taint(); + if (taint.hasTaint()) { + taint.extend(TaintOperationFromContextJSString(cx, "toLowerCase", true, str)); + } const size_t length = str->length(); size_t resultLength; { @@ -1620,6 +1623,9 @@ static JSString* ToUpperCase(JSContext* cx, JSLinearString* str) { mozilla::MaybeOneOf newChars; SafeStringTaint taint = str->taint(); + if (taint.hasTaint()) { + taint.extend(TaintOperationFromContextJSString(cx, "toUpperCase", true, str)); + } const size_t length = str->length(); size_t resultLength; { From 807eaf53d48c397e3e6d1dbbf70199566bda889b Mon Sep 17 00:00:00 2001 From: David Klein Date: Wed, 26 Jun 2024 09:24:17 +0200 Subject: [PATCH 51/70] Number tainting with maps test case --- js/src/tests/non262/taint/maps.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 js/src/tests/non262/taint/maps.js diff --git a/js/src/tests/non262/taint/maps.js b/js/src/tests/non262/taint/maps.js new file mode 100644 index 0000000000000..05bc1188e7df6 --- /dev/null +++ b/js/src/tests/non262/taint/maps.js @@ -0,0 +1,18 @@ +function mapTaintTest() { + + let map = new Map(); + let tainted = taint(42); + let untainted = 42; + let value = "foo"; + map.set(tainted, value); + let ret_val_untainted = map.get(untainted) + let ret_val_tainted = map.get(tainted) + assertEq(ret_val_tainted, ret_val_untainted); +} + +runTaintTest(mapTaintTest); + + +if (typeof reportCompare === 'function') + reportCompare(true, true); + From a442a54781420f24fffb37f12babafee962052c6 Mon Sep 17 00:00:00 2001 From: David Klein Date: Thu, 27 Jun 2024 13:17:40 +0200 Subject: [PATCH 52/70] Added tests for failing playwright issue --- js/src/tests/non262/taint/maps.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/js/src/tests/non262/taint/maps.js b/js/src/tests/non262/taint/maps.js index 05bc1188e7df6..443053387ac99 100644 --- a/js/src/tests/non262/taint/maps.js +++ b/js/src/tests/non262/taint/maps.js @@ -1,5 +1,4 @@ -function mapTaintTest() { - +function mapTaintedKeyTest() { let map = new Map(); let tainted = taint(42); let untainted = 42; @@ -10,7 +9,19 @@ function mapTaintTest() { assertEq(ret_val_tainted, ret_val_untainted); } +function mapUntaintedKeyTest() { + let map = new Map(); + let tainted = taint(42); + let untainted = 42; + let value = "foo"; + map.set(untainted, value); + let ret_val_untainted = map.get(untainted) + let ret_val_tainted = map.get(tainted) + assertEq(ret_val_tainted, ret_val_untainted); +} + runTaintTest(mapTaintTest); +runTaintTest(mapUntaintedKeyTest); if (typeof reportCompare === 'function') From 301b663bc193aef6152efb9b9f34df3fa1a3dbd0 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Tue, 30 Jul 2024 12:15:28 +0000 Subject: [PATCH 53/70] Foxhound: Adding nursery memory sweep for tainted numbers --- js/src/gc/Nursery.cpp | 16 +++++++++++++-- js/src/gc/Nursery.h | 10 +++++++++- js/src/jstaint.cpp | 9 +++++---- js/src/tests/non262/taint/hash.js | 32 ++++++++++++++++++++++-------- js/src/tests/non262/taint/shell.js | 2 +- js/src/vm/NumberObject-inl.h | 31 +++++++++++++++++++++++++++++ js/src/vm/NumberObject.h | 15 ++++++++++---- taint/Taint.cpp | 2 +- 8 files changed, 96 insertions(+), 21 deletions(-) diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 4cef797491fd7..06feee580e8ef 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -32,6 +32,7 @@ #include "util/GetPidProvider.h" // getpid() #include "util/Poison.h" #include "vm/JSONPrinter.h" +#include "vm/NumberObject.h" #include "vm/Realm.h" #include "vm/Time.h" @@ -39,6 +40,7 @@ #include "gc/Marking-inl.h" #include "gc/StableCellHasher-inl.h" #include "vm/GeckoProfiler-inl.h" +#include "vm/NumberObject-inl.h" using namespace js; using namespace js::gc; @@ -1708,7 +1710,7 @@ void js::Nursery::sweep() { sweepMapAndSetObjects(); // Taintfox: clean up strings (mostly taint) - sweepStrings(); + sweepTaintableThings(); runtime()->caches().sweepAfterMinorGC(&trc); } @@ -2085,14 +2087,24 @@ void js::Nursery::sweepMapAndSetObjects() { SetObject::sweepAfterMinorGC(gcx, setobj); } setsWithNurseryMemory_.clearAndFree(); + } -void js::Nursery::sweepStrings() { +void js::Nursery::sweepTaintableThings() { auto* gcx = runtime()->gcContext(); + + // Strings for (auto* str : stringsWithNurseryMemory_) { JSString::sweepAfterMinorGC(gcx, str); } stringsWithNurseryMemory_.clearAndFree(); + + // Number Objects + for (auto* obj : numberObjectsWithNurseryMemory_) { + NumberObject::sweepAfterMinorGC(gcx, obj); + } + numberObjectsWithNurseryMemory_.clearAndFree(); + } void js::Nursery::joinDecommitTask() { decommitTask->join(); } diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index 758e63b897a8f..98b7884f2a6e7 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -61,6 +61,7 @@ struct NurseryChunk; class HeapSlot; class JSONPrinter; class MapObject; +class NumberObject; class SetObject; class JS_PUBLIC_API Sprinter; @@ -323,6 +324,12 @@ class Nursery { bool enableProfiling() const { return enableProfiling_; } + bool addNumberObjectWithNurseryMemory(NumberObject* num) { + MOZ_ASSERT_IF(!numberObjectsWithNurseryMemory_.empty(), + numberObjectsWithNurseryMemory_.back() != num); + return numberObjectsWithNurseryMemory_.append(num); + } + bool addStringWithNurseryMemory(JSString* str) { MOZ_ASSERT_IF(!stringsWithNurseryMemory_.empty(), stringsWithNurseryMemory_.back() != str); @@ -478,7 +485,7 @@ class Nursery { void sweepMapAndSetObjects(); // Taintfox: we also need to sweep strings to clean up taint information - void sweepStrings(); + void sweepTaintableThings(); // Allocate a buffer for a given zone, using the nursery if possible. void* allocateBuffer(JS::Zone* zone, size_t nbytes); @@ -634,6 +641,7 @@ class Nursery { Vector mapsWithNurseryMemory_; Vector setsWithNurseryMemory_; Vector stringsWithNurseryMemory_; + Vector numberObjectsWithNurseryMemory_; UniquePtr decommitTask; diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index 9b5d4a15f4fff..e565ae97c6e97 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -424,10 +424,11 @@ TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) { // add info for operation // add getting combined taint flow - if (isTaintedNumber(val1) && isTaintedNumber(val2)) { - return TaintFlow::append(getNumberTaint(val1), getNumberTaint(val2)); - } - else if (isTaintedNumber(val1)) { + //if (isTaintedNumber(val1) && isTaintedNumber(val2)) { + // return TaintFlow::append(getNumberTaint(val1), getNumberTaint(val2)); + //} + //else + if (isTaintedNumber(val1)) { return getNumberTaint(val1); } else { return getNumberTaint(val2); diff --git a/js/src/tests/non262/taint/hash.js b/js/src/tests/non262/taint/hash.js index be264aacaf980..325d57d486c8e 100644 --- a/js/src/tests/non262/taint/hash.js +++ b/js/src/tests/non262/taint/hash.js @@ -9,8 +9,8 @@ simpleHash = (inputString)=>{ var hash = 0; if (inputString.length == 0) return hash; for (i = 0; i < inputString.length; i++) { - char = inputString.charCodeAt(i); - hash = ((hash<<5)-hash)+char; + var c = inputString.charCodeAt(i); + hash = ((hash << 5) - hash) + c; hash = hash & hash; // Convert to 32bit integer } return hash; @@ -389,16 +389,32 @@ fpFlow4Hash = (inputString)=>{ } -hashTaintTest = () => { +hashTaintTest = (hashFunction) => { var s = randomTaintedString() + randomTaintedString() + randomTaintedString(); + assertTainted(hashFunction(s)); +} + +simpleHashtest = () => { + hashTaintTest(simpleHash); +} + +md5Hashtest = () => { + hashTaintTest(md5Hash); +} + +fingerprintHashtest = () => { + hashTaintTest(fingerprintJsHash); +} - assertTainted(simpleHash(s)); - assertTainted(md5Hash(s)); - assertTainted(fingerprintJsHash(s)); - assertTainted(fpFlow4Hash(s)); +fpFlow4HashTest = () => { + hashTaintTest(fpFlow4Hash); } -runTaintTest(hashTaintTest); +runTaintTest(simpleHashtest); +runTaintTest(md5Hashtest); +runTaintTest(fingerprintHashtest); +runTaintTest(fpFlow4HashTest); + if (typeof reportCompare === 'function') reportCompare(true, true); diff --git a/js/src/tests/non262/taint/shell.js b/js/src/tests/non262/taint/shell.js index 100cb9a9d6b1f..4ad538e1e638e 100644 --- a/js/src/tests/non262/taint/shell.js +++ b/js/src/tests/non262/taint/shell.js @@ -249,7 +249,7 @@ if (typeof runTaintTest === 'undefined') { // Separate function so it's visible in the backtrace var runJITTest = function(doTest) { // Force JIT compilation - for (var i = 0; i < 1; i++) { + for (var i = 0; i < 1000; i++) { //console.log(i); doTest(); } diff --git a/js/src/vm/NumberObject-inl.h b/js/src/vm/NumberObject-inl.h index eeadc051b114d..21690786130f2 100644 --- a/js/src/vm/NumberObject-inl.h +++ b/js/src/vm/NumberObject-inl.h @@ -24,6 +24,13 @@ inline NumberObject* NumberObject::create(JSContext* cx, double d, // Taintfox: initialize the taint slot to null obj->initReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); + bool insideNursery = IsInsideNursery(obj); + if (insideNursery) { + if (!cx->nursery().addNumberObjectWithNurseryMemory(obj)) { + ReportOutOfMemory(cx); + return nullptr; + } + } return obj; } @@ -37,9 +44,33 @@ NumberObject::createTainted(JSContext* cx, double d, const TaintFlow& taint, Han return nullptr; } obj->setTaint(taint); + return obj; } + +inline void NumberObject::finalize(JS::GCContext* gcx, JSObject* obj) { + MOZ_ASSERT(gcx->onMainThread()); + NumberObject* numobj = static_cast(obj); + if (obj) { + TaintFlow* flow = numobj->getTaintFlow(); + if (flow) { + delete flow; + numobj->setReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); + } + } +} + + +inline void NumberObject::sweepAfterMinorGC(JS::GCContext* gcx, NumberObject *obj) { + bool wasInsideNursery = IsInsideNursery(obj); + if (wasInsideNursery && !IsForwarded(obj)) { + finalize(gcx, obj); + return; + } +} + + } // namespace js #endif /* vm_NumberObject_inl_h */ diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index fa76b9f8ca5fe..c6ab38376b630 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -44,13 +44,16 @@ class NumberObject : public NativeObject { const TaintFlow& taint, HandleObject proto = nullptr); - // TaintFox: A finalizer is required for correct memory handling. void finalize(JS::GCContext* gcx) { - TaintFlow* flow = getTaintFlow(); - delete flow; - setReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); + NumberObject::finalize(gcx, this); + JSObject::finalize(gcx); } + // TaintFox: A finalizer is required for correct memory handling. + static void finalize(JS::GCContext* gcx, JSObject* obj); + + static void sweepAfterMinorGC(JS::GCContext* gcx, NumberObject* numobj); + const TaintFlow& taint() const { TaintFlow* flow = getTaintFlow(); if (flow) { @@ -69,6 +72,10 @@ class NumberObject : public NativeObject { // if (head) { // head->addref(); // } + TaintFlow* flow = getTaintFlow(); + if (flow) { + delete flow; + } setTaintFlow(taint); } diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 0868b9415af1c..18b240c0370b4 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -1335,4 +1335,4 @@ void TaintDebug(std::string_view message, << message << std::endl; } -#endif \ No newline at end of file +#endif From af544cfffe6eb21b954894f77db1c7136ba27998 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Tue, 30 Jul 2024 13:04:27 +0000 Subject: [PATCH 54/70] Foxhound: unbox tainted numbers used as map keys --- js/src/builtin/MapObject.cpp | 11 +++++++++++ js/src/tests/non262/taint/maps.js | 15 ++++++++++++++- js/src/tests/shell.js | 11 +++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 4dd5bfa30e42b..68efa3da24175 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -82,6 +82,17 @@ bool HashableValue::setValue(JSContext* cx, HandleValue v) { value = v; } + // Additional check to unbox if we have a tainted Number + // Required to fix breakage with playwright integration + // But only if tainted as default JavaScript behaviour is that + // primitive and NumberObject keys are distinct. + if (value.isObject() && value.toObject().is()) { + NumberObject& number = value.toObject().as(); + if (number.taint()) { + value = NumberValue(number.unbox()); + } + } + MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() || value.isString() || value.isSymbol() || value.isObject() || value.isBigInt() || diff --git a/js/src/tests/non262/taint/maps.js b/js/src/tests/non262/taint/maps.js index 443053387ac99..8ee88c1ccc292 100644 --- a/js/src/tests/non262/taint/maps.js +++ b/js/src/tests/non262/taint/maps.js @@ -1,3 +1,15 @@ +function mapNumberObjectKeyTest() { + let map = new Map(); + let tainted = new Number(42); + let untainted = 42; + let value = "foo"; + map.set(untainted, value); + let ret_val_untainted = map.get(untainted) + let ret_val_tainted = map.get(tainted) + // Rather unintuitively, Number object and primitive keys are treated differently in JavaScript + assertNotEq(ret_val_tainted, ret_val_untainted); +} + function mapTaintedKeyTest() { let map = new Map(); let tainted = taint(42); @@ -20,7 +32,8 @@ function mapUntaintedKeyTest() { assertEq(ret_val_tainted, ret_val_untainted); } -runTaintTest(mapTaintTest); +runTaintTest(mapNumberObjectKeyTest); +runTaintTest(mapTaintedKeyTest); runTaintTest(mapUntaintedKeyTest); diff --git a/js/src/tests/shell.js b/js/src/tests/shell.js index fbc7dc102c732..efd1f0b91eb62 100644 --- a/js/src/tests/shell.js +++ b/js/src/tests/shell.js @@ -131,6 +131,17 @@ global.assertEq = assertEq; } + var assertNotEq = global.assertNotEq; + if (typeof assertNotEq !== "function") { + assertNotEq = function assertNotEq(actual, expected, message) { + if (SameValue(actual, expected)) { + throw new TypeError(`Assertion failed: got "${actual}", expected "${expected}"` + + (message ? ": " + message : "")); + } + }; + global.assertNotEq = assertNotEq; + } + function assertEqArray(actual, expected) { var len = actual.length; assertEq(len, expected.length, "mismatching array lengths"); From 9d2dd31d38c8527ec6e534fdacd71b96dbe69ecb Mon Sep 17 00:00:00 2001 From: David Klein Date: Tue, 30 Jul 2024 10:56:57 +0200 Subject: [PATCH 55/70] Adding webIDL sources to preferences In this branch properties of WebIDL files can be marked as taint sources with an attribute. This is super nice, but if one wants to disable one of them one has to note down their names during a build. This is pretty cumbersome. This commit tries to resolve this issue, as it simply adds every IDL based source to the properties defaults. --- dom/bindings/Codegen.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index dab4ee145f90f..c2eecdee7620d 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -53,12 +53,40 @@ NEW_ENUMERATE_HOOK_NAME = "_newEnumerate" ENUM_ENTRY_VARIABLE_NAME = "strings" INSTANCE_RESERVED_SLOTS = 1 +PREFERENCE_PREFIX = "tainting.source." + # This size is arbitrary. It is a power of 2 to make using it as a modulo # operand cheap, and is usually around 1/3-1/5th of the set size (sometimes # smaller for very large sets). GLOBAL_NAMES_PHF_SIZE = 256 +def format_preference_entry(taintSource): + return "%s%s" % (PREFERENCE_PREFIX, taintSource) + +def format_preference(taintSource): + return ( + "pref(\"%s\", true);" + % (format_preference_entry(taintSource)) + ) + +def maybe_add_preference(taintSource): + import pathlib + root = pathlib.Path(__file__).parent.parent.parent.resolve() + prefs = root / 'modules' / 'libpref' / 'init' / 'all.js' + prefs = prefs.resolve() + if(prefs.exists()): + preference_entry = format_preference_entry(taintSource) + with prefs.open(mode='r+') as pf: + if preference_entry in pf.read(): + print("%s already defined in %s... Skipping...\n" % (preference_entry, prefs)) + else: + pf.write("%s\n" % (format_preference(taintSource))) + print("Added preference '%s' to '%s'..\n" % (preference_entry, prefs)) + return True + else: + print("Preference file does not exist: %s!\n"% prefs) + return False def memberReservedSlot(member, descriptor): return ( @@ -7823,6 +7851,7 @@ def _setValue(value, wrapAsType=None, setter="set"): # Attach taint metadata to the return value if it is a source if taintSource is not None: print("Generating taint source:", taintSource) + maybe_add_preference(taintSource) taintHandler = dedent( ( """ @@ -7846,6 +7875,7 @@ def wrapAndSetPtr(wrapCall, failureCode=None): markTaintSnippet = "" if taintSource is not None: print("Generating taint source for wrapped value:", taintSource) + maybe_add_preference(taintSource) markTaintSnippet = dedent( f""" // Add taint source for wrapped value @@ -11189,6 +11219,7 @@ def definition_body(self): markTaintSnippet = "" if self.taintSource is not None: print("Generating taint source for cached value:", self.taintSource) + maybe_add_preference(self.taintSource) markTaintSnippet = dedent( f""" // Add taint source for cached value From c1caef145a4102f9bfb9544bd1b13bada5de2001 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Fri, 2 Aug 2024 11:29:23 +0000 Subject: [PATCH 56/70] Foxhound: adding binary taint nodes --- js/src/builtin/String.cpp | 98 +----------- js/src/jsnum.cpp | 41 +---- js/src/jstaint.cpp | 177 +++++++++++++++++++-- js/src/jstaint.h | 11 +- js/src/tests/non262/taint/hash.js | 17 +++ js/src/vm/Interpreter-inl.h | 26 ++-- taint/Taint.cpp | 158 +++++++++++++++---- taint/Taint.h | 245 ++++++++++++++++++++++++++---- 8 files changed, 560 insertions(+), 213 deletions(-) diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 920a876bd17e5..79e0189ceb166 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -169,103 +169,15 @@ str_taint_getter(JSContext* cx, unsigned argc, Value* vp) if (!str) return false; - // Wrap all taint ranges of the string. - RootedValueVector ranges(cx); - for (const TaintRange& taint_range : str->taint()) { - RootedObject range(cx, JS_NewObject(cx, nullptr)); - if(!range) - return false; - - if (!JS_DefineProperty(cx, range, "begin", taint_range.begin(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, range, "end", taint_range.end(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - // Wrap the taint flow for the current range. - RootedValueVector taint_flow(cx); - for (TaintNode& taint_node : taint_range.flow()) { - RootedObject node(cx, JS_NewObject(cx, nullptr)); - if (!node) - return false; - - RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); - if (!operation) - return false; - - if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - RootedValue isBuiltIn(cx); - isBuiltIn.setBoolean(taint_node.operation().is_native()); - - if (!JS_DefineProperty(cx, node, "builtin", isBuiltIn, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - RootedValue isSource(cx); - isSource.setBoolean(taint_node.operation().isSource()); - - if (!JS_DefineProperty(cx, node, "source", isSource, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - // Wrap the location - RootedObject location(cx, JS_NewObject(cx, nullptr)); - if (!location) - return false; - RootedString filename(cx, JS_NewUCStringCopyZ(cx, taint_node.operation().location().filename().c_str())); - if (!filename) - return false; - RootedString function(cx, JS_NewUCStringCopyZ(cx, taint_node.operation().location().function().c_str())); - if (!function) - return false; - // Also add the MD5 hash of the containing function - RootedString hash(cx, JS_NewStringCopyZ(cx, JS::convertDigestToHexString(taint_node.operation().location().scriptHash()).c_str())); - if (!hash) - return false; - - if (!JS_DefineProperty(cx, location, "filename", filename, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "function", function, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "line", taint_node.operation().location().line(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "pos", taint_node.operation().location().pos(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "scriptline", taint_node.operation().location().scriptStartLine(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || - !JS_DefineProperty(cx, location, "scripthash", hash, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - if (!JS_DefineProperty(cx, node, "location", location, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - // Wrap the arguments - RootedValueVector taint_arguments(cx); - for (auto& taint_argument : taint_node.operation().arguments()) { - RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); - if (!argument) - return false; - - if (!taint_arguments.append(StringValue(argument))) - return false; - } - - RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); - if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - if (!taint_flow.append(ObjectValue(*node))) - return false; - } - - RootedObject flow(cx, NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); - if (!flow) - return false; - if (!JS_DefineProperty(cx, range, "flow", flow, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - if (!ranges.append(ObjectValue(*range))) - return false; + RootedObject taint_obj(cx, JS_NewObject(cx, nullptr)); + if (!getStringTaintObject(cx, str->taint(), taint_obj)) { + return false; } - JSObject* array = NewDenseCopiedArray(cx, ranges.length(), ranges.begin()); - if (!array) + if (!JS_GetProperty(cx, taint_obj, "ranges", args.rval())) { return false; + } - args.rval().setObject(*array); return true; } diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index ed4fe7d93e67a..8a026d73ec779 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1528,42 +1528,13 @@ static bool num_taint_getter(JSContext* cx, unsigned argc, Value* vp) } const TaintFlow& taint = number->as().taint(); - // TODO(samuel) refactor into separate function - RootedValueVector taint_flow(cx); - for (auto& taint_node : taint) { - RootedObject node(cx, JS_NewObject(cx, nullptr)); - if (!node) - return false; - - RootedString operation(cx, JS_NewStringCopyZ(cx, taint_node.operation().name())); - if (!operation) - return false; - - if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - - // Wrap the arguments. - RootedValueVector taint_arguments(cx); - for (auto& taint_argument : taint_node.operation().arguments()) { - RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); - if (!argument) - return false; - - if (!taint_arguments.append(StringValue(argument))) - return false; - } - - RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); - if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return false; - if (!taint_flow.append(ObjectValue(*node))) { - return false; - } + RootedObject taint_obj(cx, JS_NewObject(cx, nullptr)); + if (!getTaintFlowObject(cx, taint, taint_obj)) { + return false; } - args.rval().setObject(*NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); - + args.rval().setObject(*taint_obj); return true; } @@ -1584,8 +1555,10 @@ js::Number_tainted(JSContext* cx, unsigned argc, Value* vp) if (!ToNumber(cx, args.get(0), &d)) { return false; } + TaintOperation op("manual taint source", { taintarg(cx, d) }); + op.setSource(); + JSObject* number = NumberObject::createTainted(cx, d, TaintFlow(op)); - JSObject* number = NumberObject::createTainted(cx, d, TaintFlow(TaintOperation("manual taint source", { taintarg(cx, d) }))); args.rval().setObject(*number); return true; diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index e565ae97c6e97..d7a232d62f39e 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -11,10 +11,13 @@ #include #include + #include "jsapi.h" +#include "NamespaceImports.h" #include "js/Array.h" #include "js/CharacterEncoding.h" #include "js/ErrorReport.h" +#include "js/PropertyAndElement.h" // JS_DefineFunctions #include "js/UniquePtr.h" #include "vm/FrameIter.h" #include "vm/JSContext.h" @@ -393,8 +396,8 @@ const TaintFlow& JS::getValueTaint(const Value& val) NumberObject& number = val.toObject().as(); return number.taint(); } else if (val.isString()) { - printf("isTaintedStr!!!!\n"); - for (auto range: val.toString()->Taint()) { + for (auto& range: val.toString()->Taint()) { + // Just return first taint range return range.flow(); } } @@ -420,35 +423,181 @@ bool JS::isAnyTaintedValue(const Value& val1, const Value& val2) return isTaintedValue(val1) || isTaintedValue(val2); } -TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2) +TaintFlow JS::getAnyNumberTaint(const Value& val1, const Value& val2, const char* name) { // add info for operation - // add getting combined taint flow - //if (isTaintedNumber(val1) && isTaintedNumber(val2)) { - // return TaintFlow::append(getNumberTaint(val1), getNumberTaint(val2)); - //} - //else - if (isTaintedNumber(val1)) { + // add getting combined taint flow + if (isTaintedNumber(val1) && isTaintedNumber(val2) && (val1 != val2)) { + // Use a very simple taint operation here to keep things fast + return TaintFlow::append(getNumberTaint(val1), getNumberTaint(val2), TaintOperation(name)); + } else if (isTaintedNumber(val1)) { return getNumberTaint(val1); } else { return getNumberTaint(val2); } } -TaintFlow JS::getAnyValueTaint(const Value& val1, const Value& val2) +TaintFlow JS::getAnyValueTaint(const Value& val1, const Value& val2, const char* name) { // add info for operation // add getting combined taint flow - //if (isTaintedValue(val1) && isTaintedValue(val2)) { - // return TaintFlow::append(getValueTaint(val1), getValueTaint(val2)); - //} else - if (isTaintedValue(val1)) { + if (isTaintedValue(val1) && isTaintedValue(val2) && (val1 != val2)) { + // Use a very simple taint operation here to keep things fast + return TaintFlow::append(getValueTaint(val1), getValueTaint(val2), TaintOperation(name)); + } else if (isTaintedValue(val1)) { return getValueTaint(val1); } else { return getValueTaint(val2); } } +bool JS::getTaintOperationObject(JSContext* cx, const TaintOperation& op, JS::Handle node) +{ + if (!node) + return false; + + RootedString operation(cx, JS_NewStringCopyZ(cx, op.name())); + if (!operation) + return false; + + if (!JS_DefineProperty(cx, node, "operation", operation, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + RootedValue isBuiltIn(cx); + isBuiltIn.setBoolean(op.is_native()); + + if (!JS_DefineProperty(cx, node, "builtin", isBuiltIn, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + RootedValue isSource(cx); + isSource.setBoolean(op.isSource()); + + if (!JS_DefineProperty(cx, node, "source", isSource, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + // Wrap the location + RootedObject location(cx, JS_NewObject(cx, nullptr)); + if (!location) + return false; + RootedString filename(cx, JS_NewUCStringCopyZ(cx, op.location().filename().c_str())); + if (!filename) + return false; + RootedString function(cx, JS_NewUCStringCopyZ(cx, op.location().function().c_str())); + if (!function) + return false; + // Also add the MD5 hash of the containing function + RootedString hash(cx, JS_NewStringCopyZ(cx, JS::convertDigestToHexString(op.location().scriptHash()).c_str())); + if (!hash) + return false; + + if (!JS_DefineProperty(cx, location, "filename", filename, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "function", function, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "line", op.location().line(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "pos", op.location().pos(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "scriptline", op.location().scriptStartLine(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, location, "scripthash", hash, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + if (!JS_DefineProperty(cx, node, "location", location, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + // Wrap the arguments + RootedValueVector taint_arguments(cx); + for (auto& taint_argument : op.arguments()) { + RootedString argument(cx, JS_NewUCStringCopyZ(cx, taint_argument.c_str())); + if (!argument) + return false; + + if (!taint_arguments.append(StringValue(argument))) + return false; + } + + RootedObject arguments(cx, NewDenseCopiedArray(cx, taint_arguments.length(), taint_arguments.begin())); + if (!JS_DefineProperty(cx, node, "arguments", arguments, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + return true; +} + +bool JS::getTaintFlowObject(JSContext* cx, const TaintFlow& flow, JS::Handle obj) +{ + if(!obj) + return false; + + // Wrap the taint flow for the current range. + RootedValueVector taint_flow(cx); + for (TaintNodeBase& taint_node : flow) { + RootedObject node(cx, JS_NewObject(cx, nullptr)); + if (!node) + return false; + if (!getTaintOperationObject(cx, taint_node.operation(), node)) { + return false; + } + if (!taint_flow.append(ObjectValue(*node))) { + return false; + } + } + + RootedObject flow_obj(cx, NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); + if (!flow_obj) + return false; + if (!JS_DefineProperty(cx, obj, "flow", flow_obj, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + // Also output the sources + RootedValueVector sources(cx); + for (TaintOperation op: flow.getSources()) { + RootedObject node(cx, JS_NewObject(cx, nullptr)); + if (!node) + return false; + if (!getTaintOperationObject(cx, op, node)) { + return false; + } + if (!sources.append(ObjectValue(*node))) { + return false; + } + } + + RootedObject sources_obj(cx, NewDenseCopiedArray(cx, sources.length(), sources.begin())); + if (!sources_obj) { + return false; + } + if (!JS_DefineProperty(cx, obj, "sources", sources_obj, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + return true; +} + +bool JS::getStringTaintObject(JSContext* cx, const StringTaint& taint, JS::Handle result) +{ + // Wrap all taint ranges of the string. + RootedValueVector ranges(cx); + for (const TaintRange& taint_range : taint) { + RootedObject range(cx, JS_NewObject(cx, nullptr)); + if(!range) + return false; + + if (!JS_DefineProperty(cx, range, "begin", taint_range.begin(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT) || + !JS_DefineProperty(cx, range, "end", taint_range.end(), JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + if (!getTaintFlowObject(cx, taint_range.flow(), range)) { + return false; + } + + if (!ranges.append(ObjectValue(*range))) + return false; + } + + RootedObject ranges_obj(cx, NewDenseCopiedArray(cx, ranges.length(), ranges.begin())); + if (!ranges_obj) + return false; + if (!JS_DefineProperty(cx, result, "ranges", ranges_obj, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return false; + + return true; +} + // Print a message to stdout. void JS::TaintFoxReport(JSContext* cx, const char* msg) { diff --git a/js/src/jstaint.h b/js/src/jstaint.h index 44e740fa4e160..e2d71a9cfbd0a 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -88,7 +88,6 @@ void MarkTaintedFunctionArguments(JSContext* cx, JSFunction* function, const JS: // Check if the argument value is a tainted number object. bool isTaintedNumber(const JS::Value& val); - // Check if the argument value is a tainted number object. bool isTaintedValue(const JS::Value& val); @@ -108,10 +107,16 @@ bool isAnyTaintedValue(const JS::Value& val1, const JS::Value& val2); // Extract the taint information from the first tainted number argument. // TODO make this accept a variable amount of arguments using variadic templates -TaintFlow getAnyNumberTaint(const JS::Value& val1, const JS::Value& val2); +TaintFlow getAnyNumberTaint(const JS::Value& val1, const JS::Value& val2, const char* name); // Extract the taint information from the first tainted argument. -TaintFlow getAnyValueTaint(const JS::Value& val1, const JS::Value& val2); +TaintFlow getAnyValueTaint(const JS::Value& val1, const JS::Value& val2, const char* name); + +bool getTaintOperationObject(JSContext* cx, const TaintOperation& op, JS::Handle result); + +bool getTaintFlowObject(JSContext* cx, const TaintFlow& flow, JS::Handle result); + +bool getStringTaintObject(JSContext* cx, const StringTaint& taint, JS::Handle result); // Print a message to stdout. void TaintFoxReport(JSContext* cx, const char* msg); diff --git a/js/src/tests/non262/taint/hash.js b/js/src/tests/non262/taint/hash.js index 325d57d486c8e..be5330cac742c 100644 --- a/js/src/tests/non262/taint/hash.js +++ b/js/src/tests/non262/taint/hash.js @@ -4,6 +4,18 @@ Some are based on the hashing functions used by fingerprinting scripts. */ +noHash = (inputString)=>{ + var hash = 0; + if (inputString.length == 0) return hash; + for (i = 0; i < inputString.length; i++) { + var c = inputString.charCodeAt(i); + hash = hash + c; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + + // Based on: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ simpleHash = (inputString)=>{ var hash = 0; @@ -394,6 +406,10 @@ hashTaintTest = (hashFunction) => { assertTainted(hashFunction(s)); } +noHashtest = () => { + hashTaintTest(noHash); +} + simpleHashtest = () => { hashTaintTest(simpleHash); } @@ -410,6 +426,7 @@ fpFlow4HashTest = () => { hashTaintTest(fpFlow4Hash); } +runTaintTest(noHashtest); runTaintTest(simpleHashtest); runTaintTest(md5Hashtest); runTaintTest(fingerprintHashtest); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index b8e569278c407..fc56a403fe69c 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -842,8 +842,8 @@ static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx, res.setNumber(lhs.toNumber() + rhs.toNumber()); // TaintFox: Taint propagation when adding tainted numbers. - if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + if (isAnyTaintedValue(origLhs, origRhs)) { + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs, "+"))); } return true; } @@ -869,7 +869,7 @@ static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx, res.setNumber(lhs.toNumber() - rhs.toNumber()); // TaintFox: Taint propagation when subtracting tainted numbers. if (isAnyTaintedValue(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs, "-"))); } return true; } @@ -893,7 +893,7 @@ static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx, res.setNumber(lhs.toNumber() * rhs.toNumber()); // TaintFox: Taint propagation when multiplying tainted numbers. if (isAnyTaintedValue(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs, "*"))); } return true; } @@ -917,7 +917,7 @@ static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx, res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when dividing tainted numbers. if (isAnyTaintedValue(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs))); + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyValueTaint(origLhs, origRhs, "/"))); } return true; } @@ -949,7 +949,7 @@ static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx, res.setNumber(NumberMod(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when modding tainted numbers. if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs, "%"))); } return true; } @@ -973,7 +973,7 @@ static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx, res.setNumber(ecmaPow(lhs.toNumber(), rhs.toNumber())); // TaintFox: Taint propagation when taking power of tainted numbers. if (isAnyTaintedNumber(origLhs, origRhs)) { - res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + res.setObject(*NumberObject::createTainted(cx, res.toNumber(), getAnyNumberTaint(origLhs, origRhs, "**"))); } return true; } @@ -1021,7 +1021,7 @@ static MOZ_ALWAYS_INLINE bool BitXorOperation(JSContext* cx, // TaintFox: Taint propagation for bitwise xor. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs, "^"))); } return true; } @@ -1046,7 +1046,7 @@ static MOZ_ALWAYS_INLINE bool BitOrOperation(JSContext* cx, // TaintFox: Taint propagation for bitwise or. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs, "|"))); } return true; } @@ -1071,7 +1071,7 @@ static MOZ_ALWAYS_INLINE bool BitAndOperation(JSContext* cx, // TaintFox: Taint propagation for bitwise and. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs, "&"))); } return true; } @@ -1101,7 +1101,7 @@ static MOZ_ALWAYS_INLINE bool BitLshOperation(JSContext* cx, // TaintFox: Taint propagation for bitwise left shift. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs, "<<"))); } return true; } @@ -1126,7 +1126,7 @@ static MOZ_ALWAYS_INLINE bool BitRshOperation(JSContext* cx, // TaintFox: Taint propagation for bitwise right shift. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toInt32(), getAnyNumberTaint(origLhs, origRhs, ">>"))); } return true; } @@ -1159,7 +1159,7 @@ static MOZ_ALWAYS_INLINE bool UrshOperation(JSContext* cx, // TaintFox: Taint propagation for unsigned right shift. if (isAnyTaintedNumber(origLhs, origRhs)) { - out.setObject(*NumberObject::createTainted(cx, out.toNumber(), getAnyNumberTaint(origLhs, origRhs))); + out.setObject(*NumberObject::createTainted(cx, out.toNumber(), getAnyNumberTaint(origLhs, origRhs, ">>"))); } return true; } diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 18b240c0370b4..9c4b08d01e323 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -142,8 +142,59 @@ void TaintOperation::dump(const TaintOperation& op) { void TaintOperation::dump(const TaintOperation& op) {} #endif -TaintNode::TaintNode(TaintNode* parent, const TaintOperation& operation) - : parent_(parent), refcount_(1), operation_(operation) +bool TaintNodeSourceCollector::visit(TaintNode& node) { + n++; + if (visited_.count(&node)) { + return false; + } + if (node.operation().isSource()) { + sources_.insert(node.operation()); + } + visited_.insert(&node); + return true; +} + +bool TaintNodeSourceCollector::visit(BinaryTaintNode& node) { + n++; + if (visited_.count(&node)) { + return false; + } + if (node.operation().isSource()) { + sources_.insert(node.operation()); + } + visited_.insert(&node); + return true; +} + +TaintNodeBase::TaintNodeBase(const TaintOperation& operation) + : refcount_(1), operation_(operation) +{} + +TaintNodeBase::TaintNodeBase(TaintOperation&& operation) + : refcount_(1), operation_(std::move(operation)) +{} + +void TaintNodeBase::addref() +{ + if (refcount_ == 0xffffffff) { + MOZ_CRASH("TaintNode refcount overflow"); + } + + ++refcount_; +} + +void TaintNodeBase::release() +{ + MOZ_ASSERT(refcount_ > 0); + + --refcount_; + if (refcount_ == 0) { + delete this; + } +} + +TaintNode::TaintNode(TaintNodeBase* parent, const TaintOperation& operation) + : TaintNodeBase(operation), parent_(parent) { MOZ_COUNT_CTOR(TaintNode); if (parent_) { @@ -151,8 +202,8 @@ TaintNode::TaintNode(TaintNode* parent, const TaintOperation& operation) } } -TaintNode::TaintNode(TaintNode* parent, TaintOperation&& operation) noexcept - : parent_(parent), refcount_(1), operation_(std::move(operation)) +TaintNode::TaintNode(TaintNodeBase* parent, TaintOperation&& operation) noexcept + : TaintNodeBase(operation), parent_(parent) { MOZ_COUNT_CTOR(TaintNode); if (parent_) { @@ -161,46 +212,84 @@ TaintNode::TaintNode(TaintNode* parent, TaintOperation&& operation) noexcept } TaintNode::TaintNode(const TaintOperation& operation) - : parent_(nullptr), refcount_(1), operation_(operation) + : TaintNodeBase(operation), parent_(nullptr) { MOZ_COUNT_CTOR(TaintNode); } TaintNode::TaintNode(TaintOperation&& operation) noexcept - : parent_(nullptr), refcount_(1), operation_(std::move(operation)) + : TaintNodeBase(operation), parent_(nullptr) { MOZ_COUNT_CTOR(TaintNode); } -void TaintNode::addref() +TaintNode::~TaintNode() { - if (refcount_ == 0xffffffff) { - MOZ_CRASH("TaintNode refcount overflow"); + MOZ_COUNT_DTOR(TaintNode); + if (parent_) { + parent_->release(); } +} - ++refcount_; +void TaintNode::accept(TaintNodeVisitor& visitor) +{ + if (visitor.visit(*this)) { + if (parent_) { + parent_->accept(visitor); + } + } } -void TaintNode::release() + +BinaryTaintNode::BinaryTaintNode(TaintNodeBase* p1, TaintNodeBase* p2, const TaintOperation& operation) + : TaintNodeBase(operation), parent1_(p1), parent2_(p2) { - MOZ_ASSERT(refcount_ > 0); + MOZ_COUNT_CTOR(BinaryTaintNode); + if (parent1_) { + parent1_->addref(); + } + if (parent2_) { + parent2_->addref(); + } +} - --refcount_; - if (refcount_ == 0) { - delete this; +BinaryTaintNode::BinaryTaintNode(TaintNodeBase* p1, TaintNodeBase* p2, TaintOperation&& operation) noexcept + : TaintNodeBase(operation), parent1_(p1), parent2_(p2) +{ + MOZ_COUNT_CTOR(BinaryTaintNode); + if (parent1_) { + parent1_->addref(); + } + if (parent2_) { + parent2_->addref(); } } -TaintNode::~TaintNode() +BinaryTaintNode::~BinaryTaintNode() { - MOZ_COUNT_DTOR(TaintNode); - if (parent_) { - parent_->release(); + MOZ_COUNT_DTOR(BinaryTaintNode); + if (parent1_) { + parent1_->release(); + } + if (parent2_) { + parent2_->release(); + } +} + +void BinaryTaintNode::accept(TaintNodeVisitor& visitor) +{ + if (visitor.visit(*this)) { + if (parent1_) { + parent1_->accept(visitor); + } + if (parent2_) { + parent2_->accept(visitor); + } } } -TaintFlow::Iterator::Iterator(TaintNode* head) : current_(head) { } +TaintFlow::Iterator::Iterator(TaintNodeBase* head) : current_(head) { } TaintFlow::Iterator::Iterator() : current_(nullptr) { } @@ -212,7 +301,7 @@ TaintFlow::Iterator& TaintFlow::Iterator::operator++() return *this; } -TaintNode& TaintFlow::Iterator::operator*() const +TaintNodeBase& TaintFlow::Iterator::operator*() const { return *current_; } @@ -233,7 +322,7 @@ TaintFlow::TaintFlow() MOZ_COUNT_CTOR(TaintFlow); } -TaintFlow::TaintFlow(TaintNode* head) +TaintFlow::TaintFlow(TaintNodeBase* head) : head_(head) { MOZ_COUNT_CTOR(TaintFlow); @@ -307,7 +396,7 @@ const TaintFlow& TaintFlow::getEmptyTaintFlow() { const TaintOperation& TaintFlow::source() const { - TaintNode* source = head_; + TaintNodeBase* source = head_; while (source->parent() != nullptr) { source = source->parent(); } @@ -357,20 +446,37 @@ TaintFlow TaintFlow::extend(const TaintFlow& flow, const TaintOperation& operati return TaintFlow(new TaintNode(flow.head_, operation)); } +TaintFlow TaintFlow::append(const TaintFlow& first, const TaintFlow& second, const TaintOperation& operation) +{ + if (first.head_ == second.head_) { + return TaintFlow(new TaintNode(first.head_, operation)); + } + return TaintFlow(new BinaryTaintNode(first.head_, second.head_, operation)); +} + TaintFlow TaintFlow::append(const TaintFlow& first, const TaintFlow& second) { TaintFlow outFlow(first); - std::stack q; - for (const TaintNode& node : second) { + std::stack q; + for (const TaintNodeBase& node : second) { q.push(&node); } for (; !q.empty(); q.pop()) { - const TaintNode* node = q.top(); + const TaintNodeBase* node = q.top(); outFlow.extend(node->operation()); } return outFlow; } +std::unordered_set TaintFlow::getSources() const { + if (!head_) { + return std::unordered_set(); + } + TaintNodeSourceCollector collector; + head_->accept(collector); + return collector.getSources(); +} + TaintRange::TaintRange() : begin_(0), end_(0), flow_() { diff --git a/taint/Taint.h b/taint/Taint.h index 5d17114bb8f72..f36ccc40314c8 100644 --- a/taint/Taint.h +++ b/taint/Taint.h @@ -16,15 +16,24 @@ #include #include #include +#include #include #include +#define TAINT_DEBUG + // It appears that source_location is not supported as standard and // breaks windows builds. Keep it behind a debug macro for now. #ifdef TAINT_DEBUG #include #endif +// Forward Declarations +class TaintNode; +class BinaryTaintNode; +class TaintFlow; +class TaintNodeBase; + /* * How to taint: * @@ -48,8 +57,12 @@ * as well as a set of operations that were performed on * the original data. * - * TaintNode A node in a taint flow. These represent either a taint - * source or an operation. + * TaintNodeBase A node in a taint flow. These represent either a taint + * source or an operation. This is the base class. + * + * TaintNode A node in a taint flow with a single parent. + * + * BinaryTaintNode A node in a taint flow with two parents. * * TaintRange A range of tainted bytes within a string that all share * the same taint flow. @@ -96,6 +109,15 @@ class TaintLocation const TaintMd5& scriptHash() const { return scriptHash_; } const std::u16string& function() const { return function_; } + bool operator==(const TaintLocation& other) const { + return (filename_ == other.filename_) && + (line_ == other.line_) && + (pos_ == other.pos_) && + (scriptStartLine_ == other.scriptStartLine_) && + (scriptHash_ == other.scriptHash_) && + (function_ == other.function_); + }; + private: std::u16string filename_; uint32_t line_; @@ -154,7 +176,15 @@ class TaintOperation void set_native() { is_native_ = 1; } static void dump(const TaintOperation& op); - + + bool operator==(const TaintOperation& other) const { + return (name_ == other.name_) && + (arguments_ == other.arguments_) && + (source_ == other.source_) && + (is_native_ == other.is_native_) && + (location_ == other.location_); + }; + private: // The operation name is owned by this instance. It will be copied from the // argument string during construction. @@ -172,6 +202,94 @@ class TaintOperation TaintLocation location_; }; +// The following are all hash implementations required to use TaintOperation in a std::hash +namespace std { + template <> + struct hash { + size_t operator()(const TaintMd5& digest) const { + size_t h = 0; + size_t i = 0; + for (const auto& byte : digest) { + if (i == 0) { + h = hash()(byte); + } + h = h ^ (hash()(byte) << i); + i++; + } + return h; + } + }; + template <> + struct hash { + size_t operator()(const TaintLocation& p) const { + size_t h1 = 0; + h1 = hash()(p.filename()); + h1 = h1 ^ (hash()(p.line()) << 1); + h1 = h1 ^ (hash()(p.line()) << 2); + h1 = h1 ^ (hash()(p.pos()) << 3); + h1 = h1 ^ (hash()(p.scriptStartLine()) << 4); + h1 = h1 ^ (hash()(p.scriptHash()) << 5); + h1 = h1 ^ (hash()(p.function()) << 6); + return h1; + } + }; + + template <> + struct hash> { + size_t operator()(const std::vector& v) const { + size_t h = 0; + size_t i = 0; + for (const auto& s : v) { + if (i == 0) { + h = hash()(s); + } + h = h ^ (hash()(s) << i); + i++; + } + return h; + } + }; + + template <> + struct hash { + size_t operator()(const TaintOperation& p) const { + size_t h1 = 0; + h1 = hash()(p.name()); + h1 = h1 ^ (hash>()(p.arguments()) << 1); + h1 = h1 ^ (hash()(p.isSource()) << 2); + h1 = h1 ^ (hash()(p.is_native()) << 3); + h1 = h1 ^ (hash()(p.location()) << 4); + return h1; + } + }; +} + +// Base class to visit all nodes of a TaintFlow +class TaintNodeVisitor +{ + public: + virtual bool visit(TaintNode& node) = 0; + virtual bool visit(BinaryTaintNode& node) = 0; +}; + +// Concrete implementation to collect unique sources in a TaintFlow +class TaintNodeSourceCollector : public TaintNodeVisitor +{ + public: + TaintNodeSourceCollector() : n(0), sources_(), visited_() {}; + + virtual bool visit(TaintNode& node); + virtual bool visit(BinaryTaintNode& node); + + // Use an unordered set to prevent duplication of same sources + std::unordered_set& getSources() { return sources_; } + int n; + private: + std::unordered_set sources_; + std::unordered_set visited_; +}; + + /* * The nodes of the taint flow graph. @@ -186,21 +304,13 @@ class TaintOperation * integrated into other engines) we are implementing our own reference counting * here. */ -class TaintNode +class TaintNodeBase { public: - // Constructing a taint node sets the initial reference count to 1. - // Constructs an intermediate node. - TaintNode(TaintNode* parent, const TaintOperation& operation); - // Constructs a root node. - TaintNode(const TaintOperation& operation); + TaintNodeBase(const TaintOperation& operation); + + TaintNodeBase(TaintOperation&& operation); - // Constructing a taint node sets the initial reference count to 1. - // Constructs an intermediate node. - TaintNode(TaintNode* parent, TaintOperation&& operation) noexcept; - // Constructs a root node. - TaintNode(TaintOperation&& operation) noexcept; - // Increments the reference count of this object by one. void addref(); @@ -208,31 +318,98 @@ class TaintNode // count drops to zero then this instance will be destroyed. void release(); - // Returns the parent node of this node or nullptr if this is a root node. - TaintNode* parent() { return parent_; } + virtual TaintNodeBase* parent() = 0; + + virtual void accept(TaintNodeVisitor& visitor) = 0; // Returns the operation associated with this taint node. const TaintOperation& operation() const { return operation_; } - private: - // Prevent clients from deleting us. TaintNodes can only be destroyed - // through release(). - ~TaintNode(); + protected: - // A node takes care of correctly addref()ing and release()ing its parent node. - TaintNode* parent_; + virtual ~TaintNodeBase() {}; std::atomic_uint32_t refcount_; // The operation that led to the creation of this node. TaintOperation operation_; +}; + + +// A TaintNode has a single parent, mostly used to model unary operations +// e.g. most string operations +class TaintNode : public TaintNodeBase +{ + public: + // Constructing a taint node sets the initial reference count to 1. + // Constructs an intermediate node. + TaintNode(TaintNodeBase* parent, const TaintOperation& operation); + // Constructs a root node. + TaintNode(const TaintOperation& operation); + + // Constructing a taint node sets the initial reference count to 1. + // Constructs an intermediate node. + TaintNode(TaintNodeBase* parent, TaintOperation&& operation) noexcept; + // Constructs a root node. + TaintNode(TaintOperation&& operation) noexcept; + + // Returns the parent node of this node or nullptr if this is a root node. + TaintNodeBase* parent() { return parent_; } + + virtual void accept(TaintNodeVisitor& visitor); + + private: + // Prevent clients from deleting us. TaintNodes can only be destroyed + // through release(). + ~TaintNode(); + + // A node takes care of correctly addref()ing and release()ing its parent node. + TaintNodeBase* parent_; + // TaintNodes aren't supposed to be copied or assigned to (that's why we // refcount them), so these operations are unavailable. TaintNode(const TaintNode& other) = delete; TaintNode& operator=(const TaintNode& other) = delete; }; + +// A BinaryTaintNode has two parents, common for all numeric operations +class BinaryTaintNode : public TaintNodeBase +{ + public: + // Constructing a taint node sets the initial reference count to 1. + // Constructs an intermediate node. + BinaryTaintNode(TaintNodeBase* p1, TaintNodeBase* p2, const TaintOperation& operation); + + // Constructing a taint node sets the initial reference count to 1. + // Constructs an intermediate node. + BinaryTaintNode(TaintNodeBase* p1, TaintNodeBase* p2, TaintOperation&& operation) noexcept; + + // Always return the first parent + TaintNodeBase* parent() { return parent1_; } + + virtual void accept(TaintNodeVisitor& visitor); + + TaintNodeBase* parent1() { return parent1_; } + TaintNodeBase* parent2() { return parent2_; } + + private: + // Prevent clients from deleting us. TaintNodes can only be destroyed + // through release(). + ~BinaryTaintNode(); + + // A node takes care of correctly addref()ing and release()ing its parent node. + TaintNodeBase* parent1_; + TaintNodeBase* parent2_; + + // TaintNodes aren't supposed to be copied or assigned to (that's why we + // refcount them), so these operations are unavailable. + BinaryTaintNode(const BinaryTaintNode& other) = delete; + BinaryTaintNode& operator=(const BinaryTaintNode& other) = delete; +}; + + /* * Taint flows. * @@ -275,23 +452,27 @@ class TaintFlow private: // Iterate over the nodes in this flow. // - // Note: The iterator does not increment the reference count. Instead, the + // Warning: The iterator does not increment the reference count. Instead, the // caller must ensure that the TaintFlow instance is alive during the // lifetime of any iterator instance. + // + // Note: In the presence of binary nodes, the iterator will only visit the + // first parent in each case. This is to maintain backward compatibility with + // previous versions which only contained single-parent Nodes. class Iterator { public: - Iterator(TaintNode* head); + Iterator(TaintNodeBase* head); Iterator(); Iterator(const Iterator& other); Iterator& operator++(); - TaintNode& operator*() const; + TaintNodeBase& operator*() const; bool operator==(const Iterator& other) const; bool operator!=(const Iterator& other) const; private: - TaintNode* current_; + TaintNodeBase* current_; }; public: @@ -303,7 +484,7 @@ class TaintFlow // *not* increment its refcount. // This is usually what the caller wants: // TaintFlow flow(new TaintNode(...)); - explicit TaintFlow(TaintNode* head); + explicit TaintFlow(TaintNodeBase* head); // Construct a new taint flow from the provided taint source. TaintFlow(const TaintOperation& source); @@ -322,7 +503,7 @@ class TaintFlow TaintFlow& operator=(const TaintFlow& other); // Returns the head of this flow, i.e. the newest node in the path. - TaintNode* head() const { return head_; } + TaintNodeBase* head() const { return head_; } // Returns the source of this taint flow. const TaintOperation& source() const; @@ -353,6 +534,7 @@ class TaintFlow static TaintFlow extend(const TaintFlow& flow, const TaintOperation& operation); // Append two taint flows together + static TaintFlow append(const TaintFlow& first, const TaintFlow& second, const TaintOperation& operation); static TaintFlow append(const TaintFlow& first, const TaintFlow& second); // Two TaintFlows are equal if they point to the same taint node. @@ -366,9 +548,12 @@ class TaintFlow static const TaintFlow& getEmptyTaintFlow(); + // Traverse the entire TaintFlow tree and extract unique sources + std::unordered_set getSources() const; + private: // Last (newest) node of this flow. - TaintNode* head_; + TaintNodeBase* head_; static TaintFlow empty_flow_; }; From 1b66b5637c414e6df611ca83605a88053209cbe5 Mon Sep 17 00:00:00 2001 From: Thomas Barber Date: Fri, 2 Aug 2024 12:14:32 +0000 Subject: [PATCH 57/70] Adding fingerprinting source configuration flags --- modules/libpref/init/all.js | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index d5bc3321c6e76..aef3cf60d893a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4112,6 +4112,86 @@ pref("tainting.source.document.elementsFromPoint", true); pref("tainting.source.element.attribute", true); pref("tainting.source.element.closest", true); +// These are all WebIDL-generated fingerprinting sources +pref("tainting.source.AudioContext.baseLatency", true); +pref("tainting.source.AudioContext.outputLatency", true); +pref("tainting.source.AudioDestinationNode.maxChannelCount", true); +pref("tainting.source.AudioNode.numberOfInputs", true); +pref("tainting.source.AudioNode.numberOfOutputs", true); +pref("tainting.source.AudioNode.channelCount", true); +pref("tainting.source.BaseAudioContext.sampleRate", true); +pref("tainting.source.BaseAudioContext.currentTime", true); +pref("tainting.source.HTMLCanvasElement.toDataURL", true); +pref("tainting.source.HTMLElement.offsetWidth", true); +pref("tainting.source.HTMLElement.offsetHeight", true); +pref("tainting.source.History.length", true); +pref("tainting.source.MediaDeviceInfo.deviceId", true); +pref("tainting.source.MediaDeviceInfo.label", true); +pref("tainting.source.MediaDeviceInfo.groupId", true); +pref("tainting.source.MimeType.type", true); +pref("tainting.source.MimeType.description", true); +pref("tainting.source.MimeType.suffixes", true); +pref("tainting.source.Navigator.doNotTrack", true); +pref("tainting.source.Navigator.maxTouchPoints", true); +pref("tainting.source.Navigator.oscpu", true); +pref("tainting.source.Navigator.vendor", true); +pref("tainting.source.Navigator.vendorSub", true); +pref("tainting.source.Navigator.productSub", true); +pref("tainting.source.Navigator.buildID", true); +pref("tainting.source.Navigator.hardwareConcurrency", true); +pref("tainting.source.Navigator.appCodeName", true); +pref("tainting.source.Navigator.appName", true); +pref("tainting.source.Navigator.appVersion", true); +pref("tainting.source.Navigator.platform", true); +pref("tainting.source.Navigator.userAgent", true); +pref("tainting.source.Navigator.language", true); +pref("tainting.source.Navigator.languages", true); +pref("tainting.source.Plugin.description", true); +pref("tainting.source.Plugin.filename", true); +pref("tainting.source.Plugin.name", true); +pref("tainting.source.Screen.availWidth", true); +pref("tainting.source.Screen.availHeight", true); +pref("tainting.source.Screen.width", true); +pref("tainting.source.Screen.height", true); +pref("tainting.source.Screen.colorDepth", true); +pref("tainting.source.Screen.pixelDepth", true); +pref("tainting.source.Screen.availTop", true); +pref("tainting.source.Screen.availLeft", true); +pref("tainting.source.VisualViewport.offsetLeft", true); +pref("tainting.source.VisualViewport.offsetTop", true); +pref("tainting.source.VisualViewport.pageLeft", true); +pref("tainting.source.VisualViewport.pageTop", true); +pref("tainting.source.VisualViewport.width", true); +pref("tainting.source.VisualViewport.height", true); +pref("tainting.source.VisualViewport.scale", true); +pref("tainting.source.WebGL2RenderingContext.getParameter", true); +pref("tainting.source.WebGLRenderingContext.getParameter", true); +pref("tainting.source.WebGLShaderPrecisionFormat.rangeMin", true); +pref("tainting.source.WebGLShaderPrecisionFormat.rangeMax", true); +pref("tainting.source.WebGLShaderPrecisionFormat.precision", true); +pref("tainting.source.WorkerNavigator.hardwareConcurrency", true); +pref("tainting.source.WorkerNavigator.appCodeName", true); +pref("tainting.source.WorkerNavigator.appName", true); +pref("tainting.source.WorkerNavigator.appVersion", true); +pref("tainting.source.WorkerNavigator.platform", true); +pref("tainting.source.WorkerNavigator.userAgent", true); +pref("tainting.source.WorkerNavigator.product", true); +pref("tainting.source.WorkerNavigator.language", true); +pref("tainting.source.WorkerNavigator.languages", true); +pref("tainting.source.Window.innerWidth", true); +pref("tainting.source.Window.innerHeight", true); +pref("tainting.source.Window.scrollX", true); +pref("tainting.source.Window.pageXOffset", true); +pref("tainting.source.Window.scrollY", true); +pref("tainting.source.Window.pageYOffset", true); +pref("tainting.source.Window.screenLeft", true); +pref("tainting.source.Window.screenTop", true); +pref("tainting.source.Window.screenX", true); +pref("tainting.source.Window.screenY", true); +pref("tainting.source.Window.outerWidth", true); +pref("tainting.source.Window.outerHeight", true); +pref("tainting.source.Window.devicePixelRatio", true); + // Sinks pref("tainting.sink.element.after", true); pref("tainting.sink.element.before", true); From 64e50a25efc094d091b178ca324b6d530a96c26f Mon Sep 17 00:00:00 2001 From: Oussama Draissi Date: Sun, 4 Aug 2024 13:59:13 +0000 Subject: [PATCH 58/70] fixed optimized switch cases with tainted discriminants --- js/src/tests/non262/taint/switch-case.js | 28 ++++++++++++++++++++++++ js/src/vm/Interpreter.cpp | 3 +++ 2 files changed, 31 insertions(+) create mode 100644 js/src/tests/non262/taint/switch-case.js diff --git a/js/src/tests/non262/taint/switch-case.js b/js/src/tests/non262/taint/switch-case.js new file mode 100644 index 0000000000000..e292d4810a343 --- /dev/null +++ b/js/src/tests/non262/taint/switch-case.js @@ -0,0 +1,28 @@ +function taintSwitchCaseTest() { + + let tableSwitchOptimized = 1; + let tableSwitchOptimizedTainted = Number.tainted(tableSwitchOptimized); + + switch (tableSwitchOptimizedTainted) { + case tableSwitchOptimized: + break; + default: + throw new Error("Taints should not be propagated in optimized switch cases"); + } + + let tableSwitchUnoptimized = 123456789; + let tableSwitchUnoptimizedTainted = Number.tainted(tableSwitchUnoptimized); + + switch (tableSwitchUnoptimizedTainted) { + case tableSwitchUnoptimized: + break; + default: + throw new Error("Taints should not be propagated in optimized switch cases"); + } +} + +runTaintTest(taintSwitchCaseTest); + +if (typeof reportCompare === 'function') + reportCompare(true, true); + diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index a2d58b5590b04..d1bfdf1786dfb 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -3330,6 +3330,9 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx, int32_t i; if (rref.isInt32()) { i = rref.toInt32(); + } + else if (isTaintedNumber(rref)) { + i = rref.toObject().as().unbox(); } else { /* Use mozilla::NumberEqualsInt32 to treat -0 (double) as 0. */ if (!rref.isDouble() || !NumberEqualsInt32(rref.toDouble(), &i)) { From a8cd65a59592d6ed154d347e126e52a1502e5ec8 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 09:32:22 -0400 Subject: [PATCH 59/70] Added taint propogation to Math.round --- js/src/jsmath.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 41a9911b33d89..f5d8a61523060 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -27,6 +27,7 @@ #include "js/PropertySpec.h" #include "util/DifferentialTesting.h" #include "vm/JSContext.h" +#include "vm/NumberObject.h" #include "vm/Realm.h" #include "vm/Time.h" @@ -589,6 +590,14 @@ static bool math_round(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_round_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_round_impl(x)); return true; } From cdaaa3d5328416ffd41ef2a8fe0b537ae0102af5 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 09:33:16 -0400 Subject: [PATCH 60/70] Added ability to write a primitive value to an object. This is needed to make Math library taint aware --- js/src/vm/NumberObject.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index c6ab38376b630..fd01b03b8c201 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -39,6 +39,7 @@ class NumberObject : public NativeObject { HandleObject proto = nullptr); double unbox() const { return getFixedSlot(PRIMITIVE_VALUE_SLOT).toNumber(); } + void setPrimitiveValue(JS::Value value) { setFixedSlot(PRIMITIVE_VALUE_SLOT, value);} static inline NumberObject* createTainted(JSContext* cx, double d, const TaintFlow& taint, @@ -98,7 +99,7 @@ class NumberObject : public NativeObject { inline void setTaintFlow(const TaintFlow& flow) { setReservedSlot(TAINT_SLOT, PrivateValue(new TaintFlow(flow))); } - + }; } // namespace js From 8aee3092b3b0302c7629ec5b3beab83a3b315a42 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 11:03:38 -0400 Subject: [PATCH 61/70] Added taint propagation to most of the JSMath library --- js/src/jsmath.cpp | 119 +++++++++++++++++++++++++++++++++++++++ js/src/vm/NumberObject.h | 10 ++-- 2 files changed, 124 insertions(+), 5 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index f5d8a61523060..3cc688684ec94 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -79,6 +79,15 @@ static bool math_function(JSContext* cx, CallArgs& args) { // NB: Always stored as a double so the math function can be inlined // through MMathFunction. double z = F(x); + + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(z)); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setDouble(z); return true; } @@ -98,6 +107,14 @@ bool js::math_abs(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_abs_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_abs_impl(x)); return true; } @@ -150,6 +167,29 @@ static bool math_atan2(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + //TODO Make this a method and also simplify + if(args[0].isObject() && args[0].toObject().is() && ((args[1].isObject() && !args[1].toObject().is()) || !args[1].isObject())){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); + args.rval().setObjectOrNull(obj); + return true; + } + if(((args[0].isObject() && !args[0].toObject().is()) || !args[0].isObject()) && args[1].isObject() && args[1].toObject().is()){ + NumberObject *obj = &args[1].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); + args.rval().setObjectOrNull(obj); + return true; + } + if(args[0].isObject() && args[0].toObject().is() && args[1].isObject() && args[1].toObject().is()){ + NumberObject *obj0 = &args[0].toObject().as(); + NumberObject *obj1 = &args[1].toObject().as(); + obj0->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); + obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); + args.rval().setObjectOrNull(obj0); + return true; + } + double z = ecmaAtan2(y, x); args.rval().setDouble(z); return true; @@ -173,6 +213,14 @@ static bool math_ceil(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_ceil_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_ceil_impl(x)); return true; } @@ -190,6 +238,14 @@ static bool math_clz32(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(n == 0? 32: mozilla::CountLeadingZeroes32(n))); + args.rval().setObjectOrNull(obj); + return true; + } + if (n == 0) { args.rval().setInt32(32); return true; @@ -246,6 +302,14 @@ bool js::math_floor(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_floor_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_floor_impl(x)); return true; } @@ -260,6 +324,29 @@ bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, return false; } + // TaintFox + //TODO make a method for this + if(lhs.isObject() && lhs.toObject().is() && ((rhs.isObject() && !rhs.toObject().is()) || !rhs.isObject())){ + NumberObject *obj = &lhs.toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); + res.setObjectOrNull(obj); + return true; + } + if(((lhs.isObject() && !lhs.toObject().is()) || !lhs.isObject()) && rhs.isObject() && rhs.toObject().is()){ + NumberObject *obj = &rhs.toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); + res.setObjectOrNull(obj); + return true; + } + if(lhs.isObject() && lhs.toObject().is() && rhs.isObject() && rhs.toObject().is()){ + NumberObject *obj0 = &lhs.toObject().as(); + NumberObject *obj1 = &rhs.toObject().as(); + obj0->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); + obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); + res.setObjectOrNull(obj0); + return true; + } + res.setInt32(WrappingMultiply(a, b)); return true; } @@ -480,6 +567,30 @@ static bool math_pow(JSContext* cx, unsigned argc, Value* vp) { } double z = ecmaPow(x, y); + + // TaintFox + //TODO Make this a method and also simplify + if(args[0].isObject() && args[0].toObject().is() && ((args[1].isObject() && !args[1].toObject().is()) || !args[1].isObject())){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(z)); + args.rval().setObjectOrNull(obj); + return true; + } + if(((args[0].isObject() && !args[0].toObject().is()) || !args[0].isObject()) && args[1].isObject() && args[1].toObject().is()){ + NumberObject *obj = &args[1].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(z)); + args.rval().setObjectOrNull(obj); + return true; + } + if(args[0].isObject() && args[0].toObject().is() && args[1].isObject() && args[1].toObject().is()){ + NumberObject *obj0 = &args[0].toObject().as(); + NumberObject *obj1 = &args[1].toObject().as(); + obj0->setPrimitiveValue(JS::NumberValue(z)); + obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); + args.rval().setObjectOrNull(obj0); + return true; + } + args.rval().setNumber(z); return true; } @@ -866,6 +977,14 @@ bool js::math_trunc(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_trunc_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_trunc_impl(x)); return true; } diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index fd01b03b8c201..17b2d55c5f27e 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -84,6 +84,11 @@ class NumberObject : public NativeObject { return !!getTaintFlow(); } + inline TaintFlow* getTaintFlow() const { + TaintFlow* n = maybePtrFromReservedSlot(TAINT_SLOT); + return n; + } + private: static JSObject* createPrototype(JSContext* cx, JSProtoKey key); @@ -91,11 +96,6 @@ class NumberObject : public NativeObject { setFixedSlot(PRIMITIVE_VALUE_SLOT, NumberValue(d)); } - inline TaintFlow* getTaintFlow() const { - TaintFlow* n = maybePtrFromReservedSlot(TAINT_SLOT); - return n; - } - inline void setTaintFlow(const TaintFlow& flow) { setReservedSlot(TAINT_SLOT, PrivateValue(new TaintFlow(flow))); } From 13fa4b7b61877ba9119ed9041fdc4c24ee966e60 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 12:55:38 -0400 Subject: [PATCH 62/70] Finished making JSMath taint aware --- js/src/jsmath.cpp | 69 ++++++++++++++++++++++++++++++++++++++++ js/src/vm/NumberObject.h | 3 +- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 3cc688684ec94..e44e749c0af98 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -413,14 +413,32 @@ double js::math_max_impl(double x, double y) { bool js::math_max(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + double maxval = NegativeInfinity(); for (unsigned i = 0; i < args.length(); i++) { double x; if (!ToNumber(cx, args[i], &x)) { return false; } + + // TaintFox + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } + maxval = math_max_impl(x, maxval); } + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(maxval)); + args.rval().setObjectOrNull(taintedResult); + return true; + } + args.rval().setNumber(maxval); return true; } @@ -438,14 +456,32 @@ double js::math_min_impl(double x, double y) { bool js::math_min(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + double minval = PositiveInfinity(); for (unsigned i = 0; i < args.length(); i++) { double x; if (!ToNumber(cx, args[i], &x)) { return false; } + + // TaintFox + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } + minval = math_min_impl(x, minval); } + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(minval)); + args.rval().setObjectOrNull(taintedResult); + return true; + } + args.rval().setNumber(minval); return true; } @@ -911,6 +947,10 @@ static bool math_hypot(JSContext* cx, unsigned argc, Value* vp) { bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, MutableHandleValue res) { + + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + // IonMonkey calls the ecmaHypot function directly if two arguments are // given. Do that here as well to get the same results. if (args.length() == 2) { @@ -923,6 +963,21 @@ bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, } double result = ecmaHypot(x, y); + + // TaintFox + for (unsigned i = 0; i < args.length(); i++) { + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } + } + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(result)); + res.setObjectOrNull(taintedResult); + return true; + } + res.setDouble(result); return true; } @@ -945,12 +1000,26 @@ bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, continue; } + // TaintFox + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } + hypot_step(scale, sumsq, x); } double result = isInfinite ? PositiveInfinity() : isNaN ? GenericNaN() : scale * std::sqrt(sumsq); + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(result)); + res.setObjectOrNull(taintedResult); + return true; + } + res.setDouble(result); return true; } diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index 17b2d55c5f27e..e42d4329e3670 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -39,7 +39,6 @@ class NumberObject : public NativeObject { HandleObject proto = nullptr); double unbox() const { return getFixedSlot(PRIMITIVE_VALUE_SLOT).toNumber(); } - void setPrimitiveValue(JS::Value value) { setFixedSlot(PRIMITIVE_VALUE_SLOT, value);} static inline NumberObject* createTainted(JSContext* cx, double d, const TaintFlow& taint, @@ -89,6 +88,8 @@ class NumberObject : public NativeObject { return n; } + void setPrimitiveValue(JS::Value value) { setFixedSlot(PRIMITIVE_VALUE_SLOT, value);} + private: static JSObject* createPrototype(JSContext* cx, JSProtoKey key); From b0ed402cb40520f042c8d7a99ff5c7bdf24c4f00 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 13:24:35 -0400 Subject: [PATCH 63/70] refactoring --- js/src/jsmath.cpp | 112 +++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index e44e749c0af98..e1f9060713a2e 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -167,30 +167,25 @@ static bool math_atan2(JSContext* cx, unsigned argc, Value* vp) { return false; } + double z = ecmaAtan2(y, x); + // TaintFox - //TODO Make this a method and also simplify - if(args[0].isObject() && args[0].toObject().is() && ((args[1].isObject() && !args[1].toObject().is()) || !args[1].isObject())){ - NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); - args.rval().setObjectOrNull(obj); - return true; - } - if(((args[0].isObject() && !args[0].toObject().is()) || !args[0].isObject()) && args[1].isObject() && args[1].toObject().is()){ - NumberObject *obj = &args[1].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); - args.rval().setObjectOrNull(obj); - return true; + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + + for (unsigned i = 0; i < args.length(); i++) { + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } } - if(args[0].isObject() && args[0].toObject().is() && args[1].isObject() && args[1].toObject().is()){ - NumberObject *obj0 = &args[0].toObject().as(); - NumberObject *obj1 = &args[1].toObject().as(); - obj0->setPrimitiveValue(JS::NumberValue(ecmaAtan2(y,x))); - obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); - args.rval().setObjectOrNull(obj0); + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(z)); + args.rval().setObjectOrNull(taintedResult); return true; } - double z = ecmaAtan2(y, x); args.rval().setDouble(z); return true; } @@ -325,25 +320,23 @@ bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, } // TaintFox - //TODO make a method for this - if(lhs.isObject() && lhs.toObject().is() && ((rhs.isObject() && !rhs.toObject().is()) || !rhs.isObject())){ + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + + if(lhs.isObject() && lhs.toObject().is()){ + isTainted = true; NumberObject *obj = &lhs.toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); - res.setObjectOrNull(obj); - return true; + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); } - if(((lhs.isObject() && !lhs.toObject().is()) || !lhs.isObject()) && rhs.isObject() && rhs.toObject().is()){ + if(rhs.isObject() && rhs.toObject().is()){ + isTainted = true; NumberObject *obj = &rhs.toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); - res.setObjectOrNull(obj); - return true; + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); } - if(lhs.isObject() && lhs.toObject().is() && rhs.isObject() && rhs.toObject().is()){ - NumberObject *obj0 = &lhs.toObject().as(); - NumberObject *obj1 = &rhs.toObject().as(); - obj0->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a,b))); - obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); - res.setObjectOrNull(obj0); + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a, b))); + res.setObjectOrNull(taintedResult); return true; } @@ -387,6 +380,19 @@ static bool math_fround(JSContext* cx, unsigned argc, Value* vp) { return true; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + double x; + if (!ToNumber(cx, args[0], &x)) { + return false; + } + + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(RoundFloat32(x))); + args.rval().setObjectOrNull(obj); + return true; + } + return RoundFloat32(cx, args[0], args.rval()); } @@ -605,25 +611,19 @@ static bool math_pow(JSContext* cx, unsigned argc, Value* vp) { double z = ecmaPow(x, y); // TaintFox - //TODO Make this a method and also simplify - if(args[0].isObject() && args[0].toObject().is() && ((args[1].isObject() && !args[1].toObject().is()) || !args[1].isObject())){ - NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(z)); - args.rval().setObjectOrNull(obj); - return true; - } - if(((args[0].isObject() && !args[0].toObject().is()) || !args[0].isObject()) && args[1].isObject() && args[1].toObject().is()){ - NumberObject *obj = &args[1].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(z)); - args.rval().setObjectOrNull(obj); - return true; + NumberObject *taintedResult = NumberObject::create(cx, 0); + bool isTainted = false; + + for (unsigned i = 0; i < args.length(); i++) { + if(args[i].isObject() && args[i].toObject().is()){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + } } - if(args[0].isObject() && args[0].toObject().is() && args[1].isObject() && args[1].toObject().is()){ - NumberObject *obj0 = &args[0].toObject().as(); - NumberObject *obj1 = &args[1].toObject().as(); - obj0->setPrimitiveValue(JS::NumberValue(z)); - obj0->setTaint(TaintFlow::append(obj0->getTaintFlow(), obj1->getTaintFlow())); - args.rval().setObjectOrNull(obj0); + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(z)); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -1080,6 +1080,14 @@ static bool math_sign(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + if(args[0].isObject() && args[0].toObject().is()){ + NumberObject *obj = &args[0].toObject().as(); + obj->setPrimitiveValue(JS::NumberValue(math_sign_impl(x))); + args.rval().setObjectOrNull(obj); + return true; + } + args.rval().setNumber(math_sign_impl(x)); return true; } From 15609dfdb06eab3fa24f73a471994a6e98496f75 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 16:06:34 -0400 Subject: [PATCH 64/70] Ensured the result of an operation is stored in a new object --- js/src/jsmath.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index e1f9060713a2e..a21914bc73c15 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -83,8 +83,8 @@ static bool math_function(JSContext* cx, CallArgs& args) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(z)); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, z, obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -110,8 +110,8 @@ bool js::math_abs(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_abs_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_abs_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -211,8 +211,8 @@ static bool math_ceil(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_ceil_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_ceil_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -236,8 +236,8 @@ static bool math_clz32(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(n == 0? 32: mozilla::CountLeadingZeroes32(n))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, n == 0? 32: mozilla::CountLeadingZeroes32(n), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -300,8 +300,8 @@ bool js::math_floor(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_floor_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_floor_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -388,8 +388,8 @@ static bool math_fround(JSContext* cx, unsigned argc, Value* vp) { } NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(RoundFloat32(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, RoundFloat32(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -740,8 +740,8 @@ static bool math_round(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_round_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_round_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -1049,8 +1049,8 @@ bool js::math_trunc(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_trunc_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_trunc_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } @@ -1083,8 +1083,8 @@ static bool math_sign(JSContext* cx, unsigned argc, Value* vp) { // TaintFox if(args[0].isObject() && args[0].toObject().is()){ NumberObject *obj = &args[0].toObject().as(); - obj->setPrimitiveValue(JS::NumberValue(math_sign_impl(x))); - args.rval().setObjectOrNull(obj); + NumberObject *taintedResult = NumberObject::createTainted(cx, math_sign_impl(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); return true; } From 289cfd705a13f0a03b3dcc292a395d291abac971 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 16:07:42 -0400 Subject: [PATCH 65/70] Added base tests for JS math library testing --- js/src/tests/non262/taint/math.js | 135 +++++++++++++++++++++++++++++ js/src/tests/non262/taint/shell.js | 75 ++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 js/src/tests/non262/taint/math.js diff --git a/js/src/tests/non262/taint/math.js b/js/src/tests/non262/taint/math.js new file mode 100644 index 0000000000000..2ac9e744e751a --- /dev/null +++ b/js/src/tests/non262/taint/math.js @@ -0,0 +1,135 @@ +function absNumberTaintingTest(){ + var a = taint(42); + var b = taint(-42); + assertNumberTainted(Math.abs(a)); + assertNumberTainted(Math.abs(b)); + assertEq(42, Math.abs(a)); + assertEq(42, Math.abs(b)); +} + +function acosNumberTaintingTest(){ + var a = taint(0.5); + assertNumberTainted(Math.acos(a)); + assertNear(Math.PI / 3, Math.acos(0.5)); +} + +function asinNumberTaintingTest(){ + var a = taint(0.5); + assertNumberTainted(Math.asin(a)); + assertNear(Math.PI / 6, Math.asin(a)); +} + +function atanNumberTaintingTest(){ + var a = taint(1); + assertNumberTainted(Math.atan(a)); + assertNear(Math.PI / 4, Math.atan(a)); +} + +function atan2NumberTaintingTest(){ + var a = taint(1); + var b = taint(1); + assertNumberTainted(Math.atan2(a, b)); + assertNear(Math.PI / 4, Math.atan2(a, b)); +} + +function ceilNumberTaintingTest(){ + var a = taint(1.5); + assertNumberTainted(Math.ceil(a)); + assertEq(2, Math.ceil(a)); +} + +function cosNumberTaintingTest(){ + var a = taint(Math.PI / 3); + assertNumberTainted(Math.cos(a)); + assertNear(0.5, Math.cos(a)); +} + +function expNumberTaintingTest(){ + var a = taint(1); + assertNumberTainted(Math.exp(a)); + assertEq(Math.E, Math.exp(a)); +} + +function floorNumberTaintingTest(){ + var a = taint(1.5); + assertNumberTainted(Math.floor(a)); + assertEq(1, Math.floor(a)); +} + +function logNumberTaintingTest(){ + var a = taint(Math.E); + assertNumberTainted(Math.log(a)); + assertEq(1, Math.log(a)); +} + +function log10NumberTaintingTest(){ + var a = taint(100); + assertNumberTainted(Math.log10(a)); + assertEq(2, Math.log10(a)); +} + +function maxNumberTaintingTest(){ + var a = taint(10); + var b = taint(20); + assertNumberTainted(Math.max(a, b)); + assertEq(20, Math.max(a, b)); +} + +function minNumberTaintingTest(){ + var a = taint(10); + var b = taint(20); + assertNumberTainted(Math.min(a, b)); + assertEq(10, Math.min(a, b)); +} + +function powNumberTaintingTest(){ + var a = taint(2); + var b = taint(3); + assertNumberTainted(Math.pow(a, b)); + assertEq(8, Math.pow(a, b)); +} + +function roundNumberTaintingTest(){ + var a = taint(1.5); + assertNumberTainted(Math.round(a)); + assertEq(2, Math.round(a)); +} + +function sinNumberTaintingTest(){ + var a = taint(Math.PI / 2); + assertNumberTainted(Math.sin(a)); + assertEq(1, Math.sin(a)); +} + +function sqrtNumberTaintingTest(){ + var a = taint(4); + assertNumberTainted(Math.sqrt(a)); + assertEq(2, Math.sqrt(a)); +} + +function tanNumberTaintingTest(){ + var a = taint(Math.PI / 4); + assertNumberTainted(Math.tan(a)); + assertNear(1, Math.tan(a)); +} + +runTaintTest(absNumberTaintingTest); +runTaintTest(acosNumberTaintingTest); +runTaintTest(asinNumberTaintingTest); +runTaintTest(atanNumberTaintingTest); +runTaintTest(atan2NumberTaintingTest); +runTaintTest(ceilNumberTaintingTest); +runTaintTest(cosNumberTaintingTest); +runTaintTest(expNumberTaintingTest); +runTaintTest(floorNumberTaintingTest); +runTaintTest(logNumberTaintingTest); +runTaintTest(log10NumberTaintingTest); +runTaintTest(maxNumberTaintingTest); +runTaintTest(minNumberTaintingTest); +runTaintTest(powNumberTaintingTest); +runTaintTest(roundNumberTaintingTest); +runTaintTest(sinNumberTaintingTest); +runTaintTest(sqrtNumberTaintingTest); +runTaintTest(tanNumberTaintingTest); + +reportCompare(0, 0, "ok"); \ No newline at end of file diff --git a/js/src/tests/non262/taint/shell.js b/js/src/tests/non262/taint/shell.js index 4ad538e1e638e..51d2da4b19bb7 100644 --- a/js/src/tests/non262/taint/shell.js +++ b/js/src/tests/non262/taint/shell.js @@ -277,3 +277,78 @@ if (typeof assertNumberNotTainted === 'undefined') { } } } + +// The nearest representable values to +1.0. +const ONE_PLUS_EPSILON = 1 + Math.pow(2, -52); // 0.9999999999999999 +const ONE_MINUS_EPSILON = 1 - Math.pow(2, -53); // 1.0000000000000002 + +{ + const fail = function (msg) { + var exc = new Error(msg); + try { + // Try to improve on exc.fileName and .lineNumber; leave exc.stack + // alone. We skip two frames: fail() and its caller, an assertX() + // function. + var frames = exc.stack.trim().split("\n"); + if (frames.length > 2) { + var m = /@([^@:]*):([0-9]+)$/.exec(frames[2]); + if (m) { + exc.fileName = m[1]; + exc.lineNumber = +m[2]; + } + } + } catch (ignore) { throw ignore;} + throw exc; + }; + + let ENDIAN; // 0 for little-endian, 1 for big-endian. + + // Return the difference between the IEEE 754 bit-patterns for a and b. + // + // This is meaningful when a and b are both finite and have the same + // sign. Then the following hold: + // + // * If a === b, then diff(a, b) === 0. + // + // * If a !== b, then diff(a, b) === 1 + the number of representable values + // between a and b. + // + const f = new Float64Array([0, 0]); + const u = new Uint32Array(f.buffer); + const diff = function (a, b) { + f[0] = a; + f[1] = b; + //print(u[1].toString(16) + u[0].toString(16) + " " + u[3].toString(16) + u[2].toString(16)); + return Math.abs((u[3-ENDIAN] - u[1-ENDIAN]) * 0x100000000 + u[2+ENDIAN] - u[0+ENDIAN]); + }; + + // Set ENDIAN to the platform's endianness. + ENDIAN = 0; // try little-endian first + if (diff(2, 4) === 0x100000) // exact wrong answer we'll get on a big-endian platform + ENDIAN = 1; + assertEq(diff(2,4), 0x10000000000000); + assertEq(diff(0, Number.MIN_VALUE), 1); + assertEq(diff(1, ONE_PLUS_EPSILON), 1); + assertEq(diff(1, ONE_MINUS_EPSILON), 1); + + var assertNear = function assertNear(a, b, tolerance=1) { + if (!Number.isFinite(b)) { + fail("second argument to assertNear (expected value) must be a finite number"); + } else if (Number.isNaN(a)) { + fail("got NaN, expected a number near " + b); + } else if (!Number.isFinite(a)) { + if (b * Math.sign(a) < Number.MAX_VALUE) + fail("got " + a + ", expected a number near " + b); + } else { + // When the two arguments do not have the same sign bit, diff() + // returns some huge number. So if b is positive or negative 0, + // make target the zero that has the same sign bit as a. + var target = b === 0 ? a * 0 : b; + var err = diff(a, target); + if (err > tolerance) { + fail("got " + a + ", expected a number near " + b + + " (relative error: " + err + ")"); + } + } + }; +} From 8aba400ef431fbc01d837f0c17e15d4732c8688f Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 17:02:14 -0400 Subject: [PATCH 66/70] Added more comprehensive tests --- js/src/tests/non262/taint/math.js | 166 +++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 4 deletions(-) diff --git a/js/src/tests/non262/taint/math.js b/js/src/tests/non262/taint/math.js index 2ac9e744e751a..991ab03a72fdc 100644 --- a/js/src/tests/non262/taint/math.js +++ b/js/src/tests/non262/taint/math.js @@ -10,7 +10,13 @@ function absNumberTaintingTest(){ function acosNumberTaintingTest(){ var a = taint(0.5); assertNumberTainted(Math.acos(a)); - assertNear(Math.PI / 3, Math.acos(0.5)); + assertNear(Math.PI / 3, Math.acos(a)); +} + +function acoshNumberTaintingTest(){ + var a = taint(2); + assertNumberTainted(Math.acosh(a)); + assertNear(1.3169578969248166, Math.acosh(a)); } function asinNumberTaintingTest(){ @@ -19,6 +25,12 @@ function asinNumberTaintingTest(){ assertNear(Math.PI / 6, Math.asin(a)); } +function asinhNumberTaintingTest(){ + var a = taint(-1); + assertNumberTainted(Math.asinh(a)); + assertNear(-0.881373587019543, Math.asinh(a)); +} + function atanNumberTaintingTest(){ var a = taint(1); assertNumberTainted(Math.atan(a)); @@ -26,10 +38,26 @@ function atanNumberTaintingTest(){ } function atan2NumberTaintingTest(){ - var a = taint(1); - var b = taint(1); + var a = taint(10); + var b = taint(0); + var c = 0; + var d = 10; assertNumberTainted(Math.atan2(a, b)); - assertNear(Math.PI / 4, Math.atan2(a, b)); + assertNumberTainted(Math.atan2(a, c)); + assertNumberTainted(Math.atan2(d, b)); + assertNear(Math.PI / 2, Math.atan2(a, b)); +} + +function atanhNumberTaintingTest(){ + var a = taint(0.5); + assertNumberTainted(Math.atanh(a)); + assertNear(0.5493061443340548, Math.atanh(a)); +} + +function cbrtNumberTaintingTest(){ + var a = taint(64); + assertNumberTainted(Math.cbrt(a)); + assertEq(4, Math.cbrt(a)); } function ceilNumberTaintingTest(){ @@ -38,24 +66,95 @@ function ceilNumberTaintingTest(){ assertEq(2, Math.ceil(a)); } +function clz32NumberTaintingTest(){ + var a = taint(1000); + assertNumberTainted(Math.clz32(a)); + assertEq(22, Math.clz32(a)); +} + function cosNumberTaintingTest(){ var a = taint(Math.PI / 3); assertNumberTainted(Math.cos(a)); assertNear(0.5, Math.cos(a)); } +function coshNumberTaintingTest(){ + var a = taint(2); + assertNumberTainted(Math.cosh(a)); + assertNear(3.7621956910836314, Math.cosh(a)); +} + function expNumberTaintingTest(){ var a = taint(1); assertNumberTainted(Math.exp(a)); assertEq(Math.E, Math.exp(a)); } +function expm1NumberTaintingTest(){ + var a = taint(1); + assertNumberTainted(Math.expm1(a)); + assertEq(Math.E - 1, Math.expm1(a)); +} + function floorNumberTaintingTest(){ var a = taint(1.5); assertNumberTainted(Math.floor(a)); assertEq(1, Math.floor(a)); } +function froundNumberTaintingTest(){ + var a = taint(1.337); + assertNumberTainted(Math.fround(a)); + assertEq(Math.fround(1.337), Math.fround(a)); +} + +function hypotNumberTaintingTest(){ + var a = taint(3); + var b = taint(4); + var c = taint(12); + var d = taint(5); + var e = 3; + var f = 12; + + assertNumberTainted(Math.hypot(a, b)); + assertEq(5, Math.hypot(a, b)); + + assertNumberTainted(Math.hypot(a, b, c)); + assertEq(13, Math.hypot(a, b, c)); + + assertNumberTainted(Math.hypot(e, b)); + assertEq(5, Math.hypot(e, b)); + + assertNumberTainted(Math.hypot(e, b, f)); + assertEq(13, Math.hypot(e, b, f)); + + assertNumberTainted(Math.hypot(a, b, c, d)); + assertEq(13.92838827718412, Math.hypot(a, b, c, d)); +} + +function imulNumberTaintingTest(){ + var a = taint(2); + var b = taint(3); + + assertNumberTainted(Math.imul(a, b)); + assertEq(6, Math.imul(a, b)); + + assertNumberTainted(Math.imul(a, 0)); + assertNumberTainted(Math.imul(0, a)); + assertEq(0, Math.imul(a, 0)); + + var d = taint(-2); + var e = taint(-3); + assertNumberTainted(Math.imul(d, e)); + assertEq(6, Math.imul(d, e)); + + // Edge cases with large numbers + var f = taint(0x7fffffff); // Max positive 32-bit signed integer + var g = taint(0x80000000); // Min negative 32-bit signed integer + assertNumberTainted(Math.imul(f, g)); + assertEq(-0x80000000, Math.imul(f, g)); +} + function logNumberTaintingTest(){ var a = taint(Math.E); assertNumberTainted(Math.log(a)); @@ -68,6 +167,18 @@ function log10NumberTaintingTest(){ assertEq(2, Math.log10(a)); } +function log1pNumberTaintingTest(){ + var a = taint(100); + assertNumberTainted(Math.log1p(a)); + assertEq(4.61512051684126, Math.log1p(a)); +} + +function log2NumberTaintingTest(){ + var a = taint(8); + assertNumberTainted(Math.log2(a)); + assertEq(3, Math.log2(a)); +} + function maxNumberTaintingTest(){ var a = taint(10); var b = taint(20); @@ -85,7 +196,11 @@ function minNumberTaintingTest(){ function powNumberTaintingTest(){ var a = taint(2); var b = taint(3); + var c = 2; + var d = 3; assertNumberTainted(Math.pow(a, b)); + assertNumberTainted(Math.pow(a, d)); + assertNumberTainted(Math.pow(c, b)); assertEq(8, Math.pow(a, b)); } @@ -95,12 +210,27 @@ function roundNumberTaintingTest(){ assertEq(2, Math.round(a)); } +function signNumberTaintingTest(){ + var a = taint(42); + var b = taint(-42); + assertNumberTainted(Math.sign(a)); + assertEq(1, Math.sign(a)); + assertNumberTainted(Math.sign(b)); + assertEq(-1, Math.sign(b)); +} + function sinNumberTaintingTest(){ var a = taint(Math.PI / 2); assertNumberTainted(Math.sin(a)); assertEq(1, Math.sin(a)); } +function sinhNumberTaintingTest(){ + var a = taint(2); + assertNumberTainted(Math.sinh(a)); + assertEq(3.626860407847019, Math.sinh(a)); +} + function sqrtNumberTaintingTest(){ var a = taint(4); assertNumberTainted(Math.sqrt(a)); @@ -113,23 +243,51 @@ function tanNumberTaintingTest(){ assertNear(1, Math.tan(a)); } +function tanhNumberTaintingTest(){ + var a = taint(-1); + assertNumberTainted(Math.tanh(a)); + assertNear(-0.7615941559557649, Math.tanh(a)); +} + +function truncNumberTaintingTest(){ + var a = taint(42.43); + assertNumberTainted(Math.trunc(a)); + assertEq(42, Math.trunc(a)); +} + runTaintTest(absNumberTaintingTest); runTaintTest(acosNumberTaintingTest); +runTaintTest(acoshNumberTaintingTest); runTaintTest(asinNumberTaintingTest); +runTaintTest(asinhNumberTaintingTest); runTaintTest(atanNumberTaintingTest); runTaintTest(atan2NumberTaintingTest); +runTaintTest(atanhNumberTaintingTest); +runTaintTest(cbrtNumberTaintingTest); runTaintTest(ceilNumberTaintingTest); +runTaintTest(clz32NumberTaintingTest); runTaintTest(cosNumberTaintingTest); +runTaintTest(coshNumberTaintingTest); runTaintTest(expNumberTaintingTest); +runTaintTest(expm1NumberTaintingTest); runTaintTest(floorNumberTaintingTest); +runTaintTest(froundNumberTaintingTest); +runTaintTest(hypotNumberTaintingTest); +runTaintTest(imulNumberTaintingTest); runTaintTest(logNumberTaintingTest); runTaintTest(log10NumberTaintingTest); +runTaintTest(log1pNumberTaintingTest); +runTaintTest(log2NumberTaintingTest); runTaintTest(maxNumberTaintingTest); runTaintTest(minNumberTaintingTest); runTaintTest(powNumberTaintingTest); runTaintTest(roundNumberTaintingTest); +runTaintTest(signNumberTaintingTest); runTaintTest(sinNumberTaintingTest); +runTaintTest(sinhNumberTaintingTest); runTaintTest(sqrtNumberTaintingTest); runTaintTest(tanNumberTaintingTest); +runTaintTest(tanhNumberTaintingTest); +runTaintTest(truncNumberTaintingTest); reportCompare(0, 0, "ok"); \ No newline at end of file From ed0e7a2bf57cafefd6be852311f6020715138602 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Thu, 1 Aug 2024 17:19:26 -0400 Subject: [PATCH 67/70] Fixed min and max logic --- js/src/jsmath.cpp | 26 ++++++++++++++++++-------- js/src/tests/non262/taint/math.js | 12 ++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index a21914bc73c15..e7c34f4556b85 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -429,14 +429,19 @@ bool js::math_max(JSContext* cx, unsigned argc, Value* vp) { return false; } + maxval = math_max_impl(x, maxval); + // TaintFox - if(args[i].isObject() && args[i].toObject().is()){ + if(args[i].isObject() && args[i].toObject().is() && maxval == x){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); - taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + taintedResult->setTaint(obj->getTaintFlow()); + } + else if(maxval == x){ + //This is done since we only want to propogate the taint if it is the biggest value + // if a non tainted value is the biggest, we do not propogate the taint + isTainted = false; } - - maxval = math_max_impl(x, maxval); } if(isTainted){ @@ -472,14 +477,19 @@ bool js::math_min(JSContext* cx, unsigned argc, Value* vp) { return false; } + minval = math_min_impl(x, minval); + // TaintFox - if(args[i].isObject() && args[i].toObject().is()){ + if(args[i].isObject() && args[i].toObject().is() && minval == x){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); - taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); + taintedResult->setTaint(obj->getTaintFlow()); + } + else if(minval == x){ + //This is done since we only want to propogate the taint if it is the lowest value + // if a non tainted value is the lowest, we do not propogate the taint + isTainted = false; } - - minval = math_min_impl(x, minval); } if(isTainted){ diff --git a/js/src/tests/non262/taint/math.js b/js/src/tests/non262/taint/math.js index 991ab03a72fdc..f9fd5b7525345 100644 --- a/js/src/tests/non262/taint/math.js +++ b/js/src/tests/non262/taint/math.js @@ -182,14 +182,26 @@ function log2NumberTaintingTest(){ function maxNumberTaintingTest(){ var a = taint(10); var b = taint(20); + var c = 30; + var d = 0; assertNumberTainted(Math.max(a, b)); + assertNumberTainted(Math.max(a, d)); + assertNumberTainted(Math.max(a, d, b)); + assertNumberNotTainted(Math.max(a, c)); + assertNumberNotTainted(Math.max(c, b, d)); assertEq(20, Math.max(a, b)); } function minNumberTaintingTest(){ var a = taint(10); var b = taint(20); + var c = 30; + var d = 0; assertNumberTainted(Math.min(a, b)); + assertNumberTainted(Math.min(a, c)); + assertNumberTainted(Math.min(b, c, a)); + assertNumberNotTainted(Math.min(a, d)); + assertNumberNotTainted(Math.min(c, b, d)); assertEq(10, Math.min(a, b)); } From 2ab97aa9b3eebf69ce879324c9afa50b40f47954 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Fri, 2 Aug 2024 10:40:52 -0400 Subject: [PATCH 68/70] simplified if statement with a helper function --- js/src/jsmath.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index e7c34f4556b85..575bfbcace377 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -81,7 +81,7 @@ static bool math_function(JSContext* cx, CallArgs& args) { double z = F(x); // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, z, obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -108,7 +108,7 @@ bool js::math_abs(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_abs_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -174,7 +174,7 @@ static bool math_atan2(JSContext* cx, unsigned argc, Value* vp) { bool isTainted = false; for (unsigned i = 0; i < args.length(); i++) { - if(args[i].isObject() && args[i].toObject().is()){ + if(isTaintedNumber(args[i])){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); @@ -209,7 +209,7 @@ static bool math_ceil(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_ceil_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -234,7 +234,7 @@ static bool math_clz32(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, n == 0? 32: mozilla::CountLeadingZeroes32(n), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -298,7 +298,7 @@ bool js::math_floor(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_floor_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -323,12 +323,12 @@ bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, NumberObject *taintedResult = NumberObject::create(cx, 0); bool isTainted = false; - if(lhs.isObject() && lhs.toObject().is()){ + if(isTaintedNumber(lhs)){ isTainted = true; NumberObject *obj = &lhs.toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); } - if(rhs.isObject() && rhs.toObject().is()){ + if(isTaintedNumber(rhs)){ isTainted = true; NumberObject *obj = &rhs.toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); @@ -381,7 +381,7 @@ static bool math_fround(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ double x; if (!ToNumber(cx, args[0], &x)) { return false; @@ -432,7 +432,7 @@ bool js::math_max(JSContext* cx, unsigned argc, Value* vp) { maxval = math_max_impl(x, maxval); // TaintFox - if(args[i].isObject() && args[i].toObject().is() && maxval == x){ + if(isTaintedNumber(args[i]) && maxval == x){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(obj->getTaintFlow()); @@ -480,7 +480,7 @@ bool js::math_min(JSContext* cx, unsigned argc, Value* vp) { minval = math_min_impl(x, minval); // TaintFox - if(args[i].isObject() && args[i].toObject().is() && minval == x){ + if(isTaintedNumber(args[i]) && minval == x){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(obj->getTaintFlow()); @@ -625,7 +625,7 @@ static bool math_pow(JSContext* cx, unsigned argc, Value* vp) { bool isTainted = false; for (unsigned i = 0; i < args.length(); i++) { - if(args[i].isObject() && args[i].toObject().is()){ + if(isTaintedNumber(args[i])){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); @@ -748,7 +748,7 @@ static bool math_round(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_round_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -976,7 +976,7 @@ bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, // TaintFox for (unsigned i = 0; i < args.length(); i++) { - if(args[i].isObject() && args[i].toObject().is()){ + if(isTaintedNumber(args[i])){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); @@ -1011,7 +1011,7 @@ bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, } // TaintFox - if(args[i].isObject() && args[i].toObject().is()){ + if(isTaintedNumber(args[i])){ isTainted = true; NumberObject *obj = &args[i].toObject().as(); taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); @@ -1057,7 +1057,7 @@ bool js::math_trunc(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_trunc_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); @@ -1091,7 +1091,7 @@ static bool math_sign(JSContext* cx, unsigned argc, Value* vp) { } // TaintFox - if(args[0].isObject() && args[0].toObject().is()){ + if(isTaintedNumber(args[0])){ NumberObject *obj = &args[0].toObject().as(); NumberObject *taintedResult = NumberObject::createTainted(cx, math_sign_impl(x), obj->getTaintFlow()); args.rval().setObjectOrNull(taintedResult); From 58b6ef516479d9a68fc41c15282bba2a716cea83 Mon Sep 17 00:00:00 2001 From: Alexandru Bara Date: Mon, 5 Aug 2024 12:44:32 -0400 Subject: [PATCH 69/70] refactored math imul and atan2 --- js/src/jsmath.cpp | 38 ++++++-------------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 575bfbcace377..bc567a483461a 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -169,20 +169,9 @@ static bool math_atan2(JSContext* cx, unsigned argc, Value* vp) { double z = ecmaAtan2(y, x); - // TaintFox - NumberObject *taintedResult = NumberObject::create(cx, 0); - bool isTainted = false; - - for (unsigned i = 0; i < args.length(); i++) { - if(isTaintedNumber(args[i])){ - isTainted = true; - NumberObject *obj = &args[i].toObject().as(); - taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); - } - } - if(isTainted){ - taintedResult->setPrimitiveValue(JS::NumberValue(z)); - args.rval().setObjectOrNull(taintedResult); + // TaintFox: Taint propagation for math.atan2 + if (isAnyTaintedNumber(args[0], args[1])) { + args.rval().setObject(*NumberObject::createTainted(cx, z, getAnyNumberTaint(args[0], args[1], "Math.atan2"))); return true; } @@ -319,24 +308,9 @@ bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, return false; } - // TaintFox - NumberObject *taintedResult = NumberObject::create(cx, 0); - bool isTainted = false; - - if(isTaintedNumber(lhs)){ - isTainted = true; - NumberObject *obj = &lhs.toObject().as(); - taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); - } - if(isTaintedNumber(rhs)){ - isTainted = true; - NumberObject *obj = &rhs.toObject().as(); - taintedResult->setTaint(TaintFlow::append(taintedResult->getTaintFlow(), obj->getTaintFlow())); - } - - if(isTainted){ - taintedResult->setPrimitiveValue(JS::NumberValue(WrappingMultiply(a, b))); - res.setObjectOrNull(taintedResult); + // TaintFox: Taint propagation for math.imul. + if (isAnyTaintedNumber(lhs, rhs)) { + res.setObject(*NumberObject::createTainted(cx, WrappingMultiply(a, b), getAnyNumberTaint(lhs, rhs, "Math.imul"))); return true; } From c6599dafc02d2665525fcfce478a9118127bd8a7 Mon Sep 17 00:00:00 2001 From: David Klein Date: Wed, 7 Aug 2024 09:20:04 +0200 Subject: [PATCH 70/70] Fixes Array.indexOf/includes for tainted numbers Before Array.indexOf and Array.includes had subtle differences in behavior when it came to tainted values or keys when using said functions. This adds some special case for tainted numbers and unboxes them, so that they behave the same. This requires to flag tainted numbers as not bitwise comparable, which might have downstream effects (?) --- js/src/builtin/Array.cpp | 24 +++++++++++++---- js/src/tests/non262/taint/arrays.js | 41 +++++++++++++++++++++++++++++ js/src/vm/EqualityOperations.h | 3 ++- 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 js/src/tests/non262/taint/arrays.js diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp index 33a633151da46..e7cfaaeb5c021 100644 --- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -41,6 +41,7 @@ #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSObject.h" +#include "vm/NumberObject.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/SelfHosting.h" #include "vm/Shape.h" @@ -4087,7 +4088,6 @@ enum class SearchKind { // semantics are used. Includes, }; - template static bool SearchElementDense(JSContext* cx, HandleValue val, Iter iterator, MutableHandleValue rval) { @@ -4118,8 +4118,15 @@ static bool SearchElementDense(JSContext* cx, HandleValue val, Iter iterator, } // Fast path for numbers. - if (val.isNumber()) { - double dval = val.toNumber(); + if (val.isNumber() || isTaintedNumber(val)) { + double dval; + if(isTaintedNumber(val)) { + if (!ToNumber(cx, val, &dval)) { + return false; + } + } else { + dval = val.toNumber(); + } // For |includes|, two NaN values are considered equal, so we use a // different implementation for NaN. if (Kind == SearchKind::Includes && std::isnan(dval)) { @@ -4129,8 +4136,15 @@ static bool SearchElementDense(JSContext* cx, HandleValue val, Iter iterator, }; return iterator(cx, cmp, rval); } - auto cmp = [dval](JSContext*, const Value& element, bool* equal) { - *equal = (element.isNumber() && element.toNumber() == dval); + auto cmp = [dval](JSContext* context, const Value& element, bool* equal) { + if(isTaintedNumber(element)) { + NumberObject* obj = &element.toObject().as(); + double x = obj->unbox();; + + *equal = x == dval; + } else { + *equal = (element.isNumber() && element.toNumber() == dval); + } return true; }; return iterator(cx, cmp, rval); diff --git a/js/src/tests/non262/taint/arrays.js b/js/src/tests/non262/taint/arrays.js new file mode 100644 index 0000000000000..8d9903cd81d42 --- /dev/null +++ b/js/src/tests/non262/taint/arrays.js @@ -0,0 +1,41 @@ +function indexOfTestTaintedValue() { + let tnum = taint(42); + let nums_t = [1,tnum,3]; + let nums_ut = [1,42,3]; + let idx_t = nums_t.indexOf(42); + let idx_ut = nums_ut.indexOf(42); + assertEq(idx_ut, idx_t); +} + +function indexOfTestTaintedKey() { + let tnum = taint(42); + let nums = [1,42,3]; + let idx_t = nums.indexOf(tnum); + let idx_ut = nums.indexOf(42); + assertEq(idx_ut, idx_t); +} + +function includesTestTaintedKey() { + let tnum = taint(42); + let nums = [1,42,3]; + let inc_t = nums.includes(tnum); + let inc_ut = nums.includes(42); + assertEq(inc_ut, inc_t); +} + +function includesTestTaintedValue() { + let tnum = taint(42); + let nums = [1,tnum,3]; + let inc_t = nums.includes(tnum); + let inc_ut = nums.includes(42); + assertEq(inc_ut, inc_t); +} + +runTaintTest(indexOfTestTaintedValue); +runTaintTest(indexOfTestTaintedKey); +runTaintTest(includesTestTaintedKey); +runTaintTest(includesTestTaintedValue); + + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/vm/EqualityOperations.h b/js/src/vm/EqualityOperations.h index f08f179730472..2b92c9ad5e488 100644 --- a/js/src/vm/EqualityOperations.h +++ b/js/src/vm/EqualityOperations.h @@ -18,6 +18,7 @@ #ifndef vm_EqualityOperations_h #define vm_EqualityOperations_h +#include "jstaint.h" // JS::isTaintedNUmber #include "jstypes.h" // JS_PUBLIC_API #include "js/RootingAPI.h" // JS::Handle #include "js/Value.h" // JS::Value @@ -64,7 +65,7 @@ extern bool SameValueZero(JSContext* cx, JS::Handle v1, * integers too. */ inline bool CanUseBitwiseCompareForStrictlyEqual(const JS::Value& v) { - return v.isObject() || v.isSymbol() || v.isNullOrUndefined() || v.isBoolean(); + return (v.isObject() || v.isSymbol() || v.isNullOrUndefined() || v.isBoolean()) && (!JS::isTaintedNumber(v)); } } // namespace js