diff --git a/gfx/qcms/src/lib.rs b/gfx/qcms/src/lib.rs index c311964ee3f49..2751e5e96c6c1 100644 --- a/gfx/qcms/src/lib.rs +++ b/gfx/qcms/src/lib.rs @@ -7,12 +7,13 @@ #![allow(non_upper_case_globals)] // These are needed for the neon SIMD code and can be removed once the MSRV supports the // instrinsics we use -#![cfg_attr(feature = "neon", feature(stdsimd))] -#![cfg_attr( - feature = "neon", - feature(arm_target_feature, raw_ref_op) - -)] +// TODO(0drai): Cannot build taintfox unless this is removed :/ +//#![cfg_attr(feature = "neon", feature(stdsimd))] +//#![cfg_attr( +// feature = "neon", +// feature(arm_target_feature, raw_ref_op) +// +//)] /// These values match the Rendering Intent values from the ICC spec #[repr(C)] diff --git a/js/public/experimental/TypedData.h b/js/public/experimental/TypedData.h index 1bb6bdb173f05..9839f1ad1dc87 100644 --- a/js/public/experimental/TypedData.h +++ b/js/public/experimental/TypedData.h @@ -139,7 +139,7 @@ extern JS_PUBLIC_API JSObject* UnwrapReadableStream(JSObject* obj); namespace detail { constexpr size_t TypedArrayLengthSlot = 1; -constexpr size_t TypedArrayDataSlot = 3; +constexpr size_t TypedArrayDataSlot = 4; } // namespace detail diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp index 33a633151da46..c633386c0b9b3 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" @@ -3898,6 +3899,8 @@ static bool array_slice(JSContext* cx, unsigned argc, Value* vp) { /* Step 12. */ args.rval().setObject(*arr); + //TODO(SAM) - Trace the array object + /*arr->as()*/ return true; } @@ -4087,7 +4090,6 @@ enum class SearchKind { // semantics are used. Includes, }; - template static bool SearchElementDense(JSContext* cx, HandleValue val, Iter iterator, MutableHandleValue rval) { @@ -4118,8 +4120,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 +4138,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); @@ -4960,6 +4976,7 @@ static inline bool ArrayConstructorImpl(JSContext* cx, CallArgs& args, /* ES5 15.4.2 */ bool js::ArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { + //TODO(SAM) - Trace the array object AutoJSConstructorProfilerEntry pseudoFrame(cx, "Array"); CallArgs args = CallArgsFromVp(argc, vp); return ArrayConstructorImpl(cx, args, /* isConstructor = */ true); diff --git a/js/src/builtin/Boolean.cpp b/js/src/builtin/Boolean.cpp index 7806362d134e3..6f24b16cbeb35 100644 --- a/js/src/builtin/Boolean.cpp +++ b/js/src/builtin/Boolean.cpp @@ -21,6 +21,7 @@ #include "vm/JSObject.h" #include "vm/BooleanObject-inl.h" +#include "vm/NumberObject-inl.h" using namespace js; @@ -109,6 +110,16 @@ static const JSFunctionSpec boolean_methods[] = { static bool Boolean(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + //TODO(0drai): For cases where the Boolean constructor is used + //e.g., Boolean(taintedValue) + + if (isTaintedNumber(args[0])){ + // NOTE(0drai): Save since isTainted* checks for type + double d = args[0].toObject().as().unbox(); + JSObject *v = NumberObject::create(cx, d); + args[0].setObject(*v); + } + // Step 1. bool b = args.length() != 0 ? JS::ToBoolean(args[0]) : false; @@ -173,5 +184,13 @@ JS_PUBLIC_API bool js::ToBooleanSlow(HandleValue v) { #endif MOZ_ASSERT(v.isObject()); + // Workaround for #216 + if (isTaintedNumber(v)) { + // NOTE(0drai): Save since isTainted* checks for type + double d = v.toObject().as().unbox(); + if (std::isnan(d)) return false; + return d != 0; + } + return !EmulatesUndefined(&v.toObject()); } diff --git a/js/src/builtin/DataViewObject.cpp b/js/src/builtin/DataViewObject.cpp index 355e067c83b8a..a79d512c0d8fb 100644 --- a/js/src/builtin/DataViewObject.cpp +++ b/js/src/builtin/DataViewObject.cpp @@ -64,6 +64,13 @@ DataViewObject* DataViewObject::create( return nullptr; } + obj->setReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); + + if (arrayBuffer && arrayBuffer->isWasm()) { + obj->setTaint( + TaintFlow(TaintOperationFromContext(cx, "WASM Array taint source", true))); + } + return obj; } @@ -1017,6 +1024,7 @@ const JSPropertySpec DataViewObject::properties[] = { JS_PSG("buffer", DataViewObject::bufferGetter, 0), JS_PSG("byteLength", DataViewObject::byteLengthGetter, 0), JS_PSG("byteOffset", DataViewObject::byteOffsetGetter, 0), + JS_PSG("taint", js::Array_taintGetter, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "DataView", JSPROP_READONLY), JS_PS_END}; JS_PUBLIC_API JSObject* JS_NewDataView(JSContext* cx, HandleObject buffer, diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp index 79e0189ceb166..8fa401b4843e4 100644 --- a/js/src/builtin/String.cpp +++ b/js/src/builtin/String.cpp @@ -155,6 +155,51 @@ js::str_tainted(JSContext* cx, unsigned argc, Value* vp) return true; } +bool js::str_taintedFromArray(JSContext* cx, unsigned argc, Value* vp) +{ + // String.taintedFromArray(string, operation, array, array.taint.length) + CallArgs args = CallArgsFromVp(argc, vp); + + RootedString str(cx, ArgToLinearString(cx, args, 0)); + if (!str || str->length() == 0) { + return false; + } + + RootedString opName(cx, args[1].toString()); + if (!opName) { + return false; + } + + UniqueChars op_chars = JS_EncodeStringToUTF8(cx, opName); + if (!op_chars) { + return false; + } + + TaintFlow op = TaintFlow(TaintOperationFromContext(cx,op_chars.get(), true)); + TaintFlow arrayTaintFlow = JS::getValueTaint(args[2]); + TaintFlow combined = TaintFlow::append(arrayTaintFlow, op); + + double taintFlowSize = 0; + + if (!ToInteger(cx, args[3], &taintFlowSize)) { + return false; + } + + SafeStringTaint taint(combined, taintFlowSize + 1); + + JSString* tainted_str = NewDependentString(cx, str, 0, str->length()); + if (!tainted_str) { + return false; + } + + tainted_str->setTaint(taint); + + MOZ_ASSERT(tainted_str->isTainted()); + + args.rval().setString(tainted_str); + return true; +} + /* * TaintFox: taint property implementation. * @@ -4508,6 +4553,7 @@ static const JSFunctionSpec string_static_methods[] = { // TaintFox: Helper function for manual taint sources. JS_FN("tainted", str_tainted, 1,0), + JS_FN("taintedFromArray", str_taintedFromArray, 4,0), JS_FS_END}; diff --git a/js/src/builtin/String.h b/js/src/builtin/String.h index 42271d7a539d3..05e8efe83b33c 100644 --- a/js/src/builtin/String.h +++ b/js/src/builtin/String.h @@ -35,6 +35,8 @@ extern bool str_fromCodePoint_one_arg(JSContext* cx, HandleValue code, // TaintFox: Exported for the js shell: taint(str). bool str_tainted(JSContext* cx, unsigned argc, Value* vp); +bool str_taintedFromArray(JSContext* cx, unsigned argc, Value* vp); + extern bool str_includes(JSContext* cx, unsigned argc, Value* vp); extern bool str_indexOf(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 7cf77ff1ad6f5..c250e95904db6 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -603,6 +603,15 @@ function TypedArrayIndexOf(searchElement, fromIndex = 0) { // Steps 11.b.i-iii. if (O[k] === searchElement) { + // Taintfox: add taint to the result if the searchElement is tainted.... + if (searchElement?.taint?.length > 0) { + k = AddTaintOperationToNumberFromNumber(O, k, "indexOf", searchElement); + } + + //... or the source array + else if (O?.taint?.length > 0){ + //TODO(0drai): implement + } return k; } } @@ -677,6 +686,11 @@ function TypedArrayJoin(separator) { R += sep + ToString(element); } + // Taintfox: add taint to the result if the source array is tainted + if (O && O.taint?.length > 0){ + AddTaintOperationNative(R, "join", O.taint[0]); + } + // Step 9. return R; } @@ -1029,6 +1043,14 @@ function TypedArraySlice(start, end) { } } + // Taintfox: add taint to the result if the source array + // or the start index is tainted + if (start && start.taint?.length > 0) { + AddTaintOperationToArray(A, "slice", start); + } else if (O && O.taint?.length > 0){ + AddTaintOperationToArray(A, "slice", O); + } + // Step 16. return A; } @@ -1282,12 +1304,23 @@ function TypedArraySubarray(begin, end) { var beginByteOffset = srcByteOffset + beginIndex * elementSize; // Steps 15-16. - return TypedArraySpeciesCreateWithBuffer( + var result = TypedArraySpeciesCreateWithBuffer( obj, buffer, beginByteOffset, newLength ); + + // Taintfox: add taint to the result if the sliced array or any other parameter is tainted + if (begin?.taint?.length > 0) { + AddTaintOperationToArray(result, "subarray", begin); + } else if (end?.taint?.length > 0) { + AddTaintOperationToArray(result, "subarray", end); + } else if (obj?.taint?.length > 0){ + AddTaintOperationToArray(result, "subarray", obj); + } + + return result; } // https://tc39.es/proposal-relative-indexing-method @@ -1327,6 +1360,8 @@ function TypedArrayAt(index) { return undefined; } + //ReportWasmTaintSink(obj, obj[k]); + // Step 8. return obj[k]; } @@ -1562,6 +1597,11 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { targetObj[k] = source[k]; } + // Taintfox: add taint to the result if the source array is tainted + if (source?.taint?.length > 0){ + AddTaintOperationToArray(targetObj, "from", source); + } + // Step 7.g. return targetObj; } @@ -1578,6 +1618,10 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { // Steps 7.a, 7.d-f. TypedArrayInitFromPackedArray(targetObj, source); + if (source?.taint?.length > 0){ + AddTaintOperationToArray(targetObj, "from", source); + } + // Step 7.g. return targetObj; } @@ -1611,6 +1655,9 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { // the list's start in the loop above. That would introduce unacceptable overhead. // Additionally, the loop's logic is simple enough not to require the assert. + if (source?.taint?.length > 0){ + AddTaintOperationToArray(targetObj, "from", source); + } // Step 7.g. return targetObj; } @@ -1639,6 +1686,9 @@ function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) { // Step 13.e. targetObj[k] = mappedValue; + if (kValue?.taint?.length > 0){ + AddTaintOperationToArray(targetObj, "from", kValue); + } } // Step 14. @@ -1664,9 +1714,16 @@ function TypedArrayStaticOf(/*...items*/) { // Step 5. var newObj = TypedArrayCreateWithLength(C, len); + var value = null; + // Steps 6-7. for (var k = 0; k < len; k++) { - newObj[k] = GetArgument(k); + value = GetArgument(k); + newObj[k] = value; + // Taintfox: add taint to the result if any item is tainted + if (value?.taint?.length > 0){ + AddTaintOperationToArray(newObj, "of", value); + } } // Step 8. @@ -1978,6 +2035,11 @@ function TypedArrayToReversed() { A[k] = fromValue; } + if (O?.taint?.length > 0){ + // Taintfox: add taint to the result if the source array is tainted + AddTaintOperationToArray(A, "reversed", O); + } + // Step 7. Return A. return A; } diff --git a/js/src/gc/Tenuring.cpp b/js/src/gc/Tenuring.cpp index 5d7a646b8d756..578129fa9ecaf 100644 --- a/js/src/gc/Tenuring.cpp +++ b/js/src/gc/Tenuring.cpp @@ -553,22 +553,26 @@ JSObject* js::gc::TenuringTracer::moveToTenuredSlow(JSObject* src) { // For Arrays and Tuples we're reducing tenuredSize to the smaller srcSize // because moveElementsToTenured() accounts for all Array or Tuple elements, // even if they are inlined. - if (src->is()) { - TypedArrayObject* tarray = &src->as(); - // Typed arrays with inline data do not necessarily have the same - // AllocKind between src and dst. The nursery does not allocate an - // inline data buffer that has the same size as the slow path will do. - // In the slow path, the Typed Array Object stores the inline data - // in the allocated space that fits the AllocKind. In the fast path, - // the nursery will allocate another buffer that is directly behind the - // minimal JSObject. That buffer size plus the JSObject size is not - // necessarily as large as the slow path's AllocKind size. - if (tarray->hasInlineElements()) { - AllocKind srcKind = GetGCObjectKind(TypedArrayObject::FIXED_DATA_START); - size_t headerSize = Arena::thingSize(srcKind); - srcSize = headerSize + tarray->byteLength(); - } - } else if (src->canHaveFixedElements()) { + + /*TODO(0drai): Investigate why this leads to a segfault when running the*/ + /*TypedArray testcases.*/ + /*if (src->is()) {*/ + /* TypedArrayObject* tarray = &src->as();*/ + /* // Typed arrays with inline data do not necessarily have the same*/ + /* // AllocKind between src and dst. The nursery does not allocate an*/ + /* // inline data buffer that has the same size as the slow path will do.*/ + /* // In the slow path, the Typed Array Object stores the inline data*/ + /* // in the allocated space that fits the AllocKind. In the fast path,*/ + /* // the nursery will allocate another buffer that is directly behind the*/ + /* // minimal JSObject. That buffer size plus the JSObject size is not*/ + /* // necessarily as large as the slow path's AllocKind size.*/ + /* if (tarray->hasInlineElements()) {*/ + /* AllocKind srcKind = GetGCObjectKind(TypedArrayObject::FIXED_DATA_START);*/ + /* size_t headerSize = Arena::thingSize(srcKind);*/ + /* srcSize = headerSize + tarray->byteLength();*/ + /* }*/ + /*} else*/ + if (src->canHaveFixedElements()) { srcSize = sizeof(NativeObject); } diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 96984348be13b..f35fd79c77c6a 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -20,6 +20,7 @@ #include #include +#include #ifdef __linux__ # include #endif @@ -4956,14 +4957,34 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha // Print a message to stdout. Also include the current JS backtrace. auto& firstRange = *str->taint().begin(); - std::cerr << "!!! Tainted flow into " << sink << " from " << firstRange.flow().source().name() << " !!!" << std::endl; + bool hasWasm = false; + + // Check if the tainted string is a wasm string + for (TaintRange& range : str->taint()) { + for(TaintNodeBase& node: range.flow()){ + const auto *name = node.operation().name(); + if (std::strstr(name, "WASM")){ + hasWasm = true; + } + } + } + + if (!hasWasm) return; // DumpBacktrace(cx); // Report a warning to show up on the web console + if (hasWasm) { + JS_ReportWarningUTF8(cx, "WASM Tainted flow from %s into %s!", firstRange.flow().source().name(), sink); + } else { JS_ReportWarningUTF8(cx, "Tainted flow from %s into %s!", firstRange.flow().source().name(), sink); + } // Extend the taint flow to include the sink function str->taint().extend(TaintOperationFromContext(cx, sink, true, arg, true)); + RootedObject taint_obj(cx, JS_NewObject(cx, nullptr)); + if (!getStringTaintObject(cx, str->taint(), taint_obj)) { + JS_ReportWarningUTF8(cx, "Cant get string taint object!!"); + } // Trigger a custom event that can be caught by an extension. // To simplify things, this part is implemented in JavaScript. Since we don't want to recompile @@ -4975,42 +4996,8 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha RootedValue slot(cx, JS::GetReservedSlot(global, TAINT_REPORT_FUNCTION_SLOT)); if (slot.isUndefined()) { - // Need to compile. - const char* argnames[3] = {"str", "sink", "stack"}; - const char* funbody = - "if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n" - " var t = window;\n" - " if (location.protocol == 'javascript:' || location.protocol == 'data:' || location.protocol == 'about:') {\n" - " t = parent.window;\n" - " }\n" - " var pl;\n" - " try {\n" - " pl = parent.location.href;\n" - " } catch (e) {\n" - " pl = 'different origin';\n" - " }\n" - " var e = document.createEvent('CustomEvent');\n" - " e.initCustomEvent('__taintreport', true, false, {\n" - " subframe: t !== window,\n" - " loc: location.href,\n" - " parentloc: pl,\n" - " referrer: document.referrer,\n" - " str: str,\n" - " sink: sink,\n" - " stack: stack\n" - " });\n" - " t.dispatchEvent(e);\n" - "}"; - CompileOptions options(cx); - options.setFile("taint_reporting.js"); - - RootedObjectVector emptyScopeChain(cx); - report = CompileFunctionUtf8(cx, emptyScopeChain, - options, "ReportTaintSink", 3, - argnames, funbody, strlen(funbody)); + report = generateReportFunction(cx, global); MOZ_ASSERT(report); - - // Store the compiled function into the current global object. JS_SetReservedSlot(global, TAINT_REPORT_FUNCTION_SLOT, ObjectValue(*report)); } else { report = JS_ValueToFunction(cx, slot); @@ -5023,7 +5010,7 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha return; } - JS::RootedValueArray<3> arguments(cx); + JS::RootedValueArray<6> arguments(cx); arguments[0].setString(str); arguments[1].setString(NewStringCopyZ(cx, sink)); if (stack) { @@ -5031,12 +5018,172 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha } else { arguments[2].setUndefined(); } + arguments[3].setBoolean(hasWasm); + arguments[4].setString(NewStringCopyZ(cx, firstRange.flow().source().name())); + arguments[5].setObject(*taint_obj); RootedValue rval(cx); JS_CallFunction(cx, nullptr, report, arguments, &rval); MOZ_ASSERT(!cx->isExceptionPending()); } +JS_PUBLIC_API void +JS_ReportWasmTaintSink(JSContext* cx, JS::HandleValue arg, const char* sink) +{ + + const unsigned TAINT_REPORT_FUNCTION_SLOT = 5; + + const TaintFlow taint = getValueTaint(arg); + + // Print a message to stdout. Also include the current JS backtrace. + const char* firstRange = taint.source().name(); + + printf("!!! Tainted flow into %s from %s !!!\n", sink, firstRange); + JS_ReportWarningUTF8(cx, "WASM Tainted flow from %s into %s!", firstRange, sink); + + RootedFunction report(cx); + + JSObject* global = cx->global(); + + if (arg.toObject().is()){ + arg.toObject().as().taint().extend(TaintOperationFromContext(cx, sink, true)); + } else if (arg.toObject().canUnwrapAs()){ + arg.toObject().as().taint().extend(TaintOperationFromContext(cx, sink, true)); + } + + RootedObject taint_obj(cx, JS_NewObject(cx, nullptr)); + if (!getTaintFlowObject(cx, taint, taint_obj)) { + JS_ReportWarningUTF8(cx, "WASM Cant get taint object!!"); + } + + RootedValue slot(cx, JS::GetReservedSlot(global, TAINT_REPORT_FUNCTION_SLOT)); + if (slot.isUndefined()) { + report = generateReportFunction(cx, global); + MOZ_ASSERT(report); + JS_SetReservedSlot(global, TAINT_REPORT_FUNCTION_SLOT, ObjectValue(*report)); + } else { + report = JS_ValueToFunction(cx, slot); + } + + RootedObject stack(cx); + if (!JS::CaptureCurrentStack(cx, &stack, + JS::StackCapture(JS::AllFrames()))) { + JS_ReportErrorUTF8(cx, "Invalid stack object in CaptureCurrentStack!"); + return; + } + + JS::RootedValueArray<6> arguments(cx); + if (arg.isObject()){ + arguments[0].setObject(arg.toObject()); + } else if (arg.isString()){ + arguments[0].setString(arg.toString()); + } else if (arg.isNumber()) { + arguments[0].setNumber(arg.toNumber()); + } else { + arguments[0].setNull(); + } + + arguments[1].setString(NewStringCopyZ(cx, sink)); + if (stack) { + arguments[2].setObject(*stack); + } else { + arguments[2].setUndefined(); + } + arguments[3].setBoolean(true); + arguments[4].setString(NewStringCopyZ(cx, firstRange)); + arguments[5].setObject(*taint_obj); + + RootedValue rval(cx); + JS_CallFunction(cx, nullptr, report, arguments, &rval); + MOZ_ASSERT(!cx->isExceptionPending()); +} + +JSFunction* generateReportFunction(JSContext* cx, JSObject* global){ + const char* argnames[6] = {"str", "sink", "stack", "hasWasm", "firstRange", "taint"}; + // NOTE: Workers cant access "document", thus we need to use a different approach to report taints. + // We use the postMessage API to send the taint report to the main thread. + // The main thread will then dispatch a custom event to the window object. + // Needs smth like this in playwright: + // + // ctx.addInitScript({ content: ` + // const __worker = Worker; + // Worker = function(...args) { + // const worker = new __worker(...args); + // worker.addEventListener("message", function(event) { + // if (event.data.type === "__taintreport"){ + // __playwright_taint_report(event.data.detail) + // } + // }); + // return worker; + // + // } + // ` }); + + const char* funbody =R"( + if ( + typeof WorkerGlobalScope !== "undefined" && + self instanceof WorkerGlobalScope +) { + self.postMessage({ + type: "__taintreport", + detail: { + subframe: "worker", + loc: location.href, + parentloc: "", + referrer: "", + hasWasm: hasWasm, + str: str, + taint: taint, + sink: sink, + stack: stack, + firstRange: firstRange, + }, + }); +} else if (typeof window !== "undefined" && typeof document !== "undefined") { + var t = window; + if ( + location.protocol == "javascript:" || + location.protocol == "data:" || + location.protocol == "about:" + ) { + t = parent.window; + } + var pl; + try { + pl = parent.location.href; + } catch (e) { + pl = "different origin"; + } + + var e = new CustomEvent("__taintreport", { + bubbles: false, + cancelable: false, + detail: { + subframe: t !== window, + loc: location.href, + parentloc: pl, + referrer: document.referrer, + hasWasm: hasWasm, + str: str, + taint: taint, + sink: sink, + stack: stack, + firstRange: firstRange, + }, + }); + + t.dispatchEvent(e); +} + )"; + + CompileOptions options(cx); + options.setFile("taint_reporting.js"); + + RootedObjectVector emptyScopeChain(cx); + return CompileFunctionUtf8(cx, emptyScopeChain, options, "ReportTaintSink", 6, argnames, funbody, strlen(funbody)); +} + + JS_PUBLIC_API bool JS::FinishIncrementalEncoding(JSContext* cx, JS::HandleScript script, TranscodeBuffer& buffer) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 8340625f73de7..a5e7dfad218b8 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1046,6 +1046,11 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink); extern JS_PUBLIC_API void JS_ReportTaintSink(JSContext* cx, JS::HandleValue val, const char* sink); +extern JS_PUBLIC_API void +JS_ReportWasmTaintSink(JSContext* cx, JS::HandleValue args, const char* sink); + +JSFunction* generateReportFunction(JSContext* cx, JSObject* global); + namespace JS { /** diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 41a9911b33d89..bc567a483461a 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" @@ -78,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(isTaintedNumber(args[0])){ + NumberObject *obj = &args[0].toObject().as(); + NumberObject *taintedResult = NumberObject::createTainted(cx, z, obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); + return true; + } + args.rval().setDouble(z); return true; } @@ -97,6 +107,14 @@ bool js::math_abs(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_abs_impl(x)); return true; } @@ -150,6 +168,13 @@ static bool math_atan2(JSContext* cx, unsigned argc, Value* vp) { } double z = ecmaAtan2(y, x); + + // 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; + } + args.rval().setDouble(z); return true; } @@ -172,6 +197,14 @@ static bool math_ceil(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_ceil_impl(x)); return true; } @@ -189,6 +222,14 @@ static bool math_clz32(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + if (n == 0) { args.rval().setInt32(32); return true; @@ -245,6 +286,14 @@ bool js::math_floor(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_floor_impl(x)); return true; } @@ -259,6 +308,12 @@ bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, return false; } + // 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; + } + res.setInt32(WrappingMultiply(a, b)); return true; } @@ -299,6 +354,19 @@ static bool math_fround(JSContext* cx, unsigned argc, Value* vp) { return true; } + // TaintFox + if(isTaintedNumber(args[0])){ + double x; + if (!ToNumber(cx, args[0], &x)) { + return false; + } + + NumberObject *obj = &args[0].toObject().as(); + NumberObject *taintedResult = NumberObject::createTainted(cx, RoundFloat32(x), obj->getTaintFlow()); + args.rval().setObjectOrNull(taintedResult); + return true; + } + return RoundFloat32(cx, args[0], args.rval()); } @@ -325,14 +393,37 @@ 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; } + maxval = math_max_impl(x, maxval); + + // TaintFox + if(isTaintedNumber(args[i]) && maxval == x){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + 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; + } } + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(maxval)); + args.rval().setObjectOrNull(taintedResult); + return true; + } + args.rval().setNumber(maxval); return true; } @@ -350,14 +441,37 @@ 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; } + minval = math_min_impl(x, minval); + + // TaintFox + if(isTaintedNumber(args[i]) && minval == x){ + isTainted = true; + NumberObject *obj = &args[i].toObject().as(); + 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; + } } + + if(isTainted){ + taintedResult->setPrimitiveValue(JS::NumberValue(minval)); + args.rval().setObjectOrNull(taintedResult); + return true; + } + args.rval().setNumber(minval); return true; } @@ -479,6 +593,24 @@ static bool math_pow(JSContext* cx, unsigned argc, Value* vp) { } double z = ecmaPow(x, y); + + // 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); + return true; + } + args.rval().setNumber(z); return true; } @@ -589,6 +721,14 @@ static bool math_round(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_round_impl(x)); return true; } @@ -791,6 +931,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) { @@ -803,6 +947,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(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(result)); + res.setObjectOrNull(taintedResult); + return true; + } + res.setDouble(result); return true; } @@ -825,12 +984,26 @@ bool js::math_hypot_handle(JSContext* cx, HandleValueArray args, continue; } + // TaintFox + if(isTaintedNumber(args[i])){ + 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; } @@ -857,6 +1030,14 @@ bool js::math_trunc(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_trunc_impl(x)); return true; } @@ -883,6 +1064,14 @@ static bool math_sign(JSContext* cx, unsigned argc, Value* vp) { return false; } + // TaintFox + 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); + return true; + } + args.rval().setNumber(math_sign_impl(x)); return true; } diff --git a/js/src/jstaint.cpp b/js/src/jstaint.cpp index d7a232d62f39e..767ca66e12ffa 100644 --- a/js/src/jstaint.cpp +++ b/js/src/jstaint.cpp @@ -22,6 +22,7 @@ #include "vm/FrameIter.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" +#include "vm/JSObject.h" #include "vm/NumberObject.h" #include "vm/StringType.h" @@ -379,29 +380,68 @@ bool JS::isTaintedNumber(const Value& val) return false; } +bool JS::isTaintedArray(JSObject &obj){ + if (obj.canUnwrapAs()) { + ArrayBufferViewObject& abvo = obj.unwrapAs(); + return abvo.isTainted(); + } else if (obj.canUnwrapAs()){ + TypedArrayObject& abvo = obj.unwrapAs(); + return abvo.isTainted(); + } + + return false; +} + +const TaintFlow& JS::getArrayTaint(JSObject& obj) +{ + if (obj.canUnwrapAs()) { + ArrayBufferViewObject& abvo = obj.unwrapAs(); + return abvo.taint(); + } else if (obj.canUnwrapAs()){ + TypedArrayObject& abvo = obj.unwrapAs(); + return abvo.taint(); + } + return TaintFlow::getEmptyTaintFlow(); +} + + bool JS::isTaintedValue(const Value& val) { - if (val.isObject() && val.toObject().is()) { - NumberObject& number = val.toObject().as(); - return number.isTainted(); - } else if (val.isString()) { - return val.toString()->isTainted(); + if (val.isObject() ) { + if (val.toObject().is()){ + NumberObject& number = val.toObject().as(); + return number.isTainted(); } - return false; + + if (val.toObject().canUnwrapAs()) { + ArrayBufferViewObject& abvo = val.toObject().unwrapAs(); + return abvo.isTainted(); + } + + } else if (val.isString()) { + 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()) { - for (auto& range: val.toString()->Taint()) { - // Just return first taint range - return range.flow(); - } + if (val.isObject() ) { + if (val.toObject().is()){ + NumberObject& number = val.toObject().as(); + return number.taint(); } - return TaintFlow::getEmptyTaintFlow(); + + if (val.toObject().canUnwrapAs()) { + ArrayBufferViewObject& abvo = val.toObject().unwrapAs(); + return abvo.taint(); + } + } else if (val.isString()) { + for (auto range: val.toString()->Taint()) { + return range.flow(); + } + } + return TaintFlow::getEmptyTaintFlow(); } const TaintFlow& JS::getNumberTaint(const Value& val) diff --git a/js/src/jstaint.h b/js/src/jstaint.h index e2d71a9cfbd0a..8564f335dccc2 100644 --- a/js/src/jstaint.h +++ b/js/src/jstaint.h @@ -91,6 +91,12 @@ bool isTaintedNumber(const JS::Value& val); // Check if the argument value is a tainted number object. bool isTaintedValue(const JS::Value& val); +// Check if the argument value is a tainted number object. +bool isTaintedArray(JSObject& obj); + +// Extract the taint information from the first tainted argument. +const TaintFlow& getArrayTaint(JSObject& obj); + // Extract the taint information from a number. const TaintFlow& getNumberTaint(const JS::Value& val); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a8d1ebcdab47e..8cfc0b57d1b53 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -8582,9 +8582,13 @@ Taint(JSContext* cx, unsigned argc, Value* vp) if (args[0].isNumber()) { return Number_tainted(cx, argc, vp); - } else { - return str_tainted(cx, argc, vp); } + + if (args[0].isObject() && args[0].toObject().canUnwrapAs()) { + return Array_taintMe(cx, argc, vp); + } + + return str_tainted(cx, argc, vp); } #ifndef __wasi__ 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/tests/non262/taint/math.js b/js/src/tests/non262/taint/math.js new file mode 100644 index 0000000000000..f9fd5b7525345 --- /dev/null +++ b/js/src/tests/non262/taint/math.js @@ -0,0 +1,305 @@ +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(a)); +} + +function acoshNumberTaintingTest(){ + var a = taint(2); + assertNumberTainted(Math.acosh(a)); + assertNear(1.3169578969248166, Math.acosh(a)); +} + +function asinNumberTaintingTest(){ + var a = taint(0.5); + assertNumberTainted(Math.asin(a)); + 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)); + assertNear(Math.PI / 4, Math.atan(a)); +} + +function atan2NumberTaintingTest(){ + var a = taint(10); + var b = taint(0); + var c = 0; + var d = 10; + assertNumberTainted(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(){ + var a = taint(1.5); + assertNumberTainted(Math.ceil(a)); + 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)); + assertEq(1, Math.log(a)); +} + +function log10NumberTaintingTest(){ + var a = taint(100); + assertNumberTainted(Math.log10(a)); + 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); + 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)); +} + +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)); +} + +function roundNumberTaintingTest(){ + var a = taint(1.5); + assertNumberTainted(Math.round(a)); + 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)); + assertEq(2, Math.sqrt(a)); +} + +function tanNumberTaintingTest(){ + var a = taint(Math.PI / 4); + assertNumberTainted(Math.tan(a)); + 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 diff --git a/js/src/tests/non262/taint/shell.js b/js/src/tests/non262/taint/shell.js index 4ad538e1e638e..e2bfbb7dda292 100644 --- a/js/src/tests/non262/taint/shell.js +++ b/js/src/tests/non262/taint/shell.js @@ -277,3 +277,92 @@ 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 + ")"); + } + } + }; +} + +if (typeof randomStringAlphLowercase === 'undefined') { + // Assert that the given number is not tainted. + var randomStringAlphLowercase = function(length) { + const chars = "abcdefghijklmnopqrstuvwxyz"; + let result = ""; + for (let i = 0; i < length; i++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; + } +} + +// vim: set ts=2 sts=2 sw=2 et: 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/tests/non262/taint/wasm.js b/js/src/tests/non262/taint/wasm.js new file mode 100644 index 0000000000000..d90aa034f5f74 --- /dev/null +++ b/js/src/tests/non262/taint/wasm.js @@ -0,0 +1,170 @@ +// vim: set ts=2 sts=2 sw=2 et: + +async function loadWasm(str, imports = {}) { + const binary = wasmTextToBinary(str); + const module = await WebAssembly.compile(binary); + return new WebAssembly.Instance(module, imports) +} + +function wasmExportResultTaintSource() { + + const singleValueTaint = `(module +(func (export "run") (result i32) +(i32.const 10)))` + + loadWasm(singleValueTaint) + .then(instance => { + const { run } = instance.exports; + const result = run(); + assertEq(10, result); + assertNumberTainted(result); + }); +} + +function wasmExportMultiResultTaintSource() { + + const multiValueTaint = `(module +(func (export "run") (result i32 i32 i32) +(i32.const 0) +(i32.const 52) +(i32.const 10)))` + + loadWasm(multiValueTaint) + .then(instance => { + const { run } = instance.exports; + const [a, b, c] = run(); + assertEq(0, a); + assertEq(52, b); + assertEq(10, c); + assertNumberTainted(a); + assertNumberTainted(b); + assertNumberTainted(c); + }); + + +} + +function wasmExportMemoryTaintSource() { + const memoryTaint = `(module +(memory (export "memory") 1) +(func (export "run") (param i32) (param i32) (param i32) + (memory.fill (local.get 0) (local.get 1) (local.get 2))) +)` + + loadWasm(memoryTaint) + .then(instance => { + const { run, memory } = instance.exports; + //run(0, 42, 10); + //const m = new Uint8Array(memory.buffer); + //assertArrayTainted(m); + //const val = m[0]; + //assertEq(42, val); + //assertNumberTainted(val); + }); +} + +function wasmImportArgTaintSource() { + const importArgTaint = `(module +(func $timesTwo (import "env" "timesTwo") (param i32) (result i32)) +(func (export "run") (result i32) + (call $timesTwo (i32.const 10))) +) +` + const imports = { + env: { + timesTwo: x => { + assertNumberTainted(x); + return x * 2; + } + } + } + loadWasm(importArgTaint, imports) + .then(instance => { + const { run } = instance.exports; + run(); + }); +} + +function wasmImportMultiArgTaintSource() { + + const importMultiArgTaint = `(module +(func $multiMulti (import "env" "multiMulti") (param i32 i32) (result i32)) +(func (export "run") (result i32) + (call $multiMulti (i32.const 4) (i32.const 5))) +)` + + const imports = { + env: { + multiMulti: (x, y) => { + assertNumberTainted(x); + assertNumberTainted(y); + return x * y; + } + } + } + + loadWasm(importMultiArgTaint, imports) + .then(instance => { + const { run } = instance.exports; + run(); + }) +} + +function wasmTableResultTaintSource() { + + const importMultiArgTaint = `(module +(func $return42 (result i32) (i32.const 42)) +(table $table 1 anyfunc) +(elem (i32.const 0) $return42) +(export "table" (table $table)) +)`; + + loadWasm(importMultiArgTaint) + .then(instance => { + const { table } = instance.exports; + const result = table.get(0)(); + assertEq(42, result); + assertNumberTainted(result); + }) +} + +function wasmDecodeStringTaintSource() { + + const decodeStringTaint = `(module + (memory $mem 1) + (export "memory" (memory $mem)) + (data (i32.const 0) "\\77\\65\\6D\\62\\79") + (func $run (result i32) + (i32.const 0) + ) + (export "run" (func $run)) +)`; + + loadWasm(decodeStringTaint) + .then(instance => { + const { run, memory } = instance.exports; + const result = run(); + assertTainted(result); + const mem = new Uint8Array(memory.buffer); + assertArrayTainted(mem); + + let str = ""; + let i = result; + while (mem[i] != 0) { + str += String.fromCharCode(mem[i]); + i++; + } + assertTainted(str); + }) +} + +runTaintTest(wasmExportResultTaintSource); +runTaintTest(wasmExportMultiResultTaintSource); +runTaintTest(wasmExportMemoryTaintSource); +runTaintTest(wasmImportArgTaintSource); +runTaintTest(wasmImportMultiArgTaintSource); +runTaintTest(wasmTableResultTaintSource); +runTaintTest(wasmDecodeStringTaintSource); + +if (typeof reportCompare === 'function') + reportCompare(true, true); diff --git a/js/src/vm/ArrayBufferViewObject.cpp b/js/src/vm/ArrayBufferViewObject.cpp index 729c8fb0a7726..36cf79f8a6692 100644 --- a/js/src/vm/ArrayBufferViewObject.cpp +++ b/js/src/vm/ArrayBufferViewObject.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/ArrayBufferViewObject.h" +#include "jstaint.h" #include "builtin/DataViewObject.h" #include "gc/Nursery.h" @@ -21,6 +22,120 @@ using namespace js; +bool js::Array_taintGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + + RootedObject array(cx, &args.thisv().toObject()); + + // Return, if not tainted + if (!array->as().isTainted()) { + args.rval().setUndefined(); + return true; + } + + const TaintFlow& taint = array->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 (const 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; + } + } + + args.rval().setObject( + *NewDenseCopiedArray(cx, taint_flow.length(), taint_flow.begin())); + + return true; +} + +// TaintFox: taint arrays manually using this method. +bool js::Array_taintMe(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(TypedArrayObject::is(args.thisv())); + + RootedObject dataObj(cx, &args.thisv().toObject().as()); + + dataObj->as().setTaint(TaintFlow( + TaintOperationFromContext(cx, "manual array taint source", true))); + + args.rval().setObject(*dataObj); + + return true; +} +bool js::Array_taintFromString(JSContext* cx, unsigned argc, Value* vp) { + // TypedArray.taintedFromString(taintedString, "encode"); + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(TypedArrayObject::is(args.thisv())); + RootedObject dataObj(cx, &args.thisv().toObject().as()); + + Rooted str(cx, ToString(cx, args[0])->ensureLinear(cx)); + if (!str) { + return false; + } + + TaintFlow taint = str->taint().at(0); + + RootedString opName(cx, args[1].toString()); + if (!opName) { + return false; + } + + UniqueChars op_chars = JS_EncodeStringToUTF8(cx, opName); + if (!op_chars) { + return false; + } + + TaintFlow op =TaintOperationFromContext(cx,op_chars.get(), true, str); + TaintFlow arrayTaintFlow = JS::getValueTaint(args[2]); + auto combined = TaintFlow::append(taint, TaintFlow::append(op, arrayTaintFlow)); + + dataObj->as().setTaint(combined); + + args.rval().setObject(*dataObj); + + return true; +} + // This method is used to trace TypedArrayObjects and DataViewObjects. It // updates the object's data pointer if it points to inline data in an object // that was moved. @@ -60,6 +175,7 @@ void ArrayBufferViewObject::notifyBufferDetached() { setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0))); setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0))); setFixedSlot(DATA_SLOT, UndefinedValue()); + setReservedSlot(TAINT_SLOT, UndefinedValue()); } void ArrayBufferViewObject::notifyBufferMoved(uint8_t* srcBufStart, @@ -123,6 +239,7 @@ bool ArrayBufferViewObject::init(JSContext* cx, initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset)); initFixedSlot(LENGTH_SLOT, PrivateValue(length)); + initReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); if (buffer) { initFixedSlot(BUFFER_SLOT, ObjectValue(*buffer)); } else { diff --git a/js/src/vm/ArrayBufferViewObject.h b/js/src/vm/ArrayBufferViewObject.h index b20fe90705f57..d3d446a82006c 100644 --- a/js/src/vm/ArrayBufferViewObject.h +++ b/js/src/vm/ArrayBufferViewObject.h @@ -15,6 +15,9 @@ namespace js { +bool Array_taintMe(JSContext* cx, unsigned argc, Value* vp); +bool Array_taintGetter(JSContext* cx, unsigned argc, Value* vp); +bool Array_taintFromString(JSContext* cx, unsigned argc, Value* vp); /* * ArrayBufferViewObject * @@ -39,10 +42,12 @@ class ArrayBufferViewObject : public NativeObject { // Offset of view within underlying (Shared)ArrayBufferObject. static constexpr size_t BYTEOFFSET_SLOT = 2; + static constexpr size_t TAINT_SLOT = 3; + // Pointer to raw buffer memory. - static constexpr size_t DATA_SLOT = 3; + static constexpr size_t DATA_SLOT = 4; - static constexpr size_t RESERVED_SLOTS = 4; + static constexpr size_t RESERVED_SLOTS = 5; #ifdef DEBUG static const uint8_t ZeroLengthArrayData = 0x4A; @@ -68,11 +73,38 @@ class ArrayBufferViewObject : public NativeObject { return maybePtrFromReservedSlot(DATA_SLOT); } + inline TaintFlow* getTaintFlow() const { + TaintFlow* n = maybePtrFromReservedSlot(TAINT_SLOT); + return n; + } + + inline void setTaintFlow(const TaintFlow& flow) { + setReservedSlot(TAINT_SLOT, PrivateValue(new TaintFlow(flow))); + } + public: [[nodiscard]] bool init(JSContext* cx, ArrayBufferObjectMaybeShared* buffer, size_t byteOffset, size_t length, uint32_t bytesPerElement); + + const TaintFlow& taint() const { + TaintFlow* flow = getTaintFlow(); + if (flow) { + return *flow; + } + return TaintFlow::getEmptyTaintFlow(); + } + + void setTaint(const TaintFlow& taint) { + setTaintFlow(taint); + } + + bool isTainted() const { + return !!getTaintFlow(); + } + + static ArrayBufferObjectMaybeShared* ensureBufferObject( JSContext* cx, Handle obj); diff --git a/js/src/vm/EqualityOperations.cpp b/js/src/vm/EqualityOperations.cpp index 34d42435f24de..5ecd420a072cc 100644 --- a/js/src/vm/EqualityOperations.cpp +++ b/js/src/vm/EqualityOperations.cpp @@ -8,6 +8,8 @@ #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF +#include + #include "jsnum.h" // js::StringToNumber #include "jstypes.h" // JS_PUBLIC_API @@ -35,6 +37,18 @@ static bool EqualGivenSameType(JSContext* cx, JS::Handle lval, if (lval.isString()) { return js::EqualStrings(cx, lval.toString(), rval.toString(), equal); } + + // 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; + } if (lval.isDouble()) { *equal = (lval.toDouble() == rval.toDouble()); 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 diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index fc56a403fe69c..f6820a9272485 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -453,19 +453,37 @@ static MOZ_ALWAYS_INLINE bool GetObjectElementOperation( } } + // TODO(0drai) if the key is a tainted number, we should(?) to unbox + // it and use the unboxed value as key, which is most likely the desired behavior. + RootedId id(cx); if (isTaintedNumber(key)) { taint = getNumberTaint(key); - } - - RootedId id(cx); - if (!ToPropertyKey(cx, key, &id)) { - return false; - } - if (!GetProperty(cx, obj, receiver, id, res)) { - return false; + double tmp = key.toObject().as().unbox(); + RootedValue v(cx, NumberValue(tmp)); + + if (!ToPropertyKey(cx, v, &id)) { + return false; + } + if (!GetProperty(cx, obj, receiver, id, res)) { + return false; + } + } else { + + if (!ToPropertyKey(cx, key, &id)) { + return false; + } + if (!GetProperty(cx, obj, receiver, id, res)) { + return false; + } } } while (false); + // TaintFox: If the object is a ainted number, we should propagate the taint to + // the result. + if (!taint && JS::isTaintedArray(*obj)){ + taint = JS::getArrayTaint(*obj); + } + // TaintFox: add taint information to looked up element. if (taint) { if (res.isString()) { 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)) { diff --git a/js/src/vm/NumberObject.h b/js/src/vm/NumberObject.h index c6ab38376b630..e42d4329e3670 100644 --- a/js/src/vm/NumberObject.h +++ b/js/src/vm/NumberObject.h @@ -83,6 +83,13 @@ class NumberObject : public NativeObject { return !!getTaintFlow(); } + inline TaintFlow* getTaintFlow() const { + TaintFlow* n = maybePtrFromReservedSlot(TAINT_SLOT); + return n; + } + + void setPrimitiveValue(JS::Value value) { setFixedSlot(PRIMITIVE_VALUE_SLOT, value);} + private: static JSObject* createPrototype(JSContext* cx, JSProtoKey key); @@ -90,15 +97,10 @@ 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))); } - + }; } // namespace js diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index f82536870fcfd..a75ed00308dfa 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -98,6 +98,7 @@ #include "vm/JSFunction-inl.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" +#include "vm/NumberObject-inl.h" #include "vm/TypedArrayObject-inl.h" using namespace js; @@ -1858,6 +1859,89 @@ taint_addTaintOperation_native(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +taint_addTaintOperationToArray(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject dataObj(cx, &args[0].toObject()); + + if (!dataObj || !dataObj->canUnwrapAs()) { + return false; + } + + RootedString opName(cx, args[1].toString()); + if (!opName) { + return false; + } + + UniqueChars op_chars = JS_EncodeStringToUTF8(cx, opName); + if (!op_chars) { + return false; + } + + TaintFlow anyTaintFlow = getValueTaint(args[2]); + TaintFlow sliceTaintFlow = TaintFlow(TaintOperationFromContext(cx,op_chars.get(), true)); + TaintFlow combined = TaintFlow::append(anyTaintFlow, sliceTaintFlow); + + dataObj->as().setTaint(combined); + + return true; +} + + +static bool +taint_reportWasmTaintSink(JSContext* cx, unsigned argc, Value* vp){ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject dataObj(cx, &args[0].toObject()); + if (!dataObj || !dataObj->canUnwrapAs()) { + return false; + } + + const auto *abvo = &dataObj->as(); + + if (! (abvo && abvo->bufferEither() && abvo->bufferEither()->isWasm())) { + return false; + } + + JS_ReportWasmTaintSink(cx, args[1], "WASM Memory"); + + return true; + + +} + + +static bool +taint_addTaintOperationToNumberFromNumber(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!JS::isTaintedNumber(args[3])){ + return false; + } + + RootedString opName(cx, args[2].toString()); + if (!opName) { + return false; + } + + UniqueChars op_chars = JS_EncodeStringToUTF8(cx, opName); + if (!op_chars) { + return false; + } + + TaintFlow numberTaintFlow = JS::getNumberTaint(args[3]); + TaintFlow indexOfTaintFlow = TaintFlow(TaintOperationFromContext(cx, op_chars.get(), true)); + TaintFlow combined = TaintFlow::append(numberTaintFlow, indexOfTaintFlow); + + args.rval().setObject(*NumberObject::createTainted(cx, args[1].toNumber(), combined)); + + return true; + +} + // TaintFox: Returns a copy of the given string. static bool taint_copyString(JSContext* cx, unsigned argc, Value* vp) @@ -2080,6 +2164,9 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("AddTaintOperation", taint_addTaintOperation, 2, 0), JS_FN("AddTaintOperationNative", taint_addTaintOperation_native, 2, 0), JS_FN("AddTaintOperationNativeFull", taint_addTaintOperation_native_full, 2, 0), + JS_FN("AddTaintOperationToArray", taint_addTaintOperationToArray, 3, 0), + JS_FN("ReportWasmTaintSink", taint_reportWasmTaintSink, 2, 0 ), + JS_FN("AddTaintOperationToNumberFromNumber", taint_addTaintOperationToNumberFromNumber, 4, 0), JS_INLINABLE_FN("ArrayBufferByteLength", intrinsic_ArrayBufferByteLength, 1, 0, IntrinsicArrayBufferByteLength), diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index eb042e43a2a9a..51c96604807d8 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -440,6 +440,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject { if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) { return nullptr; } + obj->setReservedSlot(TAINT_SLOT, PrivateValue(nullptr)); + + if (buffer && buffer->isWasm()) { + obj->setTaint( + TaintFlow(TaintOperationFromContext(cx, "WASM Array taint source", true))); + } return obj; } @@ -997,6 +1003,10 @@ template TypedArrayObjectTemplate::setIndex(*obj, index, nativeValue); } + if (obj && obj->bufferEither() && obj->bufferEither()->isWasm() && isTaintedValue(v)){ + JS_ReportWasmTaintSink(cx, v, "WASM Memory (setElement)"); + } + // Step 5. return result.succeed(); } @@ -1468,6 +1478,7 @@ static bool TypedArray_toStringTagGetter(JSContext* cx, unsigned argc, JS_PSG("buffer", TypedArray_bufferGetter, 0), JS_PSG("byteLength", TypedArray_byteLengthGetter, 0), JS_PSG("byteOffset", TypedArray_byteOffsetGetter, 0), + JS_PSG("taint", js::Array_taintGetter, JSPROP_PERMANENT), JS_SYM_GET(toStringTag, TypedArray_toStringTagGetter, 0), JS_PS_END}; @@ -1649,6 +1660,11 @@ bool TypedArrayObject::set_impl(JSContext* cx, const CallArgs& args) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); return false; } + + if (isTaintedNumber(args[1])){ + target->setTaint(getNumberTaint(args[1])); + } + } // 23.2.3.24.1, steps 1-2. @@ -1684,6 +1700,17 @@ bool TypedArrayObject::set_impl(JSContext* cx, const CallArgs& args) { if (!SetTypedArrayFromTypedArray(cx, target, targetOffset, srcTypedArray)) { return false; } + + if (target && target->bufferEither() && target->bufferEither()->isWasm() && isTaintedValue(args.get(0))) { + JS_ReportWasmTaintSink(cx, args.get(0), "WASM Memory (TypedArray.set (0))"); + } + + if (args.length() > 1) { + if (target && target->bufferEither() && target->bufferEither()->isWasm() && isTaintedValue(args.get(1))){ + JS_ReportWasmTaintSink(cx, args.get(1), "WASM Memory (TypedArray.set (1))"); + } + } + } else { if (!SetTypedArrayFromArrayLike(cx, target, targetOffset, src)) { return false; @@ -1847,6 +1874,12 @@ bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) { JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0), JS_FN("set", TypedArrayObject::set, 1, 0), JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0), + // In contrast to Number and Strings, we cannot manually taint an array + // without losing the semantic type information. Thus we manually taint in two steps: + // js > a = new Uint8Array([...]) + // js > a.taintMe(); + JS_FN("taintMe", js::Array_taintMe, 0,0), + JS_FN("taintFromString", js::Array_taintFromString, 2,0), JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0), JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0), JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0), diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index be385d4c2d26b..fc743a03a3d1f 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -64,6 +64,7 @@ #include "gc/StoreBuffer-inl.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/JSObject-inl.h" +#include "vm/NumberObject-inl.h" #include "wasm/WasmGcObject-inl.h" using namespace js; @@ -246,6 +247,7 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, JS::AutoAssertNoGC nogc; for (size_t i = 0; i < argc; i++) { const void* rawArgLoc = &argv[i]; + if (argTypes.isSyntheticStackResultPointerArg(i)) { stackResultPointer = Some(*(char**)rawArgLoc); continue; @@ -261,8 +263,24 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, if (!ToJSValue(cx, rawArgLoc, type, argValue)) { return false; } + + //TODO(0drai): This is where we taint the input arguments for JS Calls issued from Wasm (import), + //however, these values are expected to be primitive values and **not objects**, + //which leads to undesired behavior. + //Could be related to #216. + + /*double d;*/ + /*if (ToNumber(cx, argValue, &d)){*/ + /* JSObject* number = NumberObject::createTainted(*/ + /* cx, d,*/ + /* TaintFlow(*/ + /* TaintOperation("WASM Import taint source", {taintarg(cx, d)})));*/ + /* args[i].setObject(*number);*/ + /*}*/ + } - } + +} // Visit arguments that need to perform allocation in a second loop // after the rest of arguments are converted. @@ -283,6 +301,17 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, if (!ToJSValue(cx, rawArgLoc, type, argValue)) { return false; } + + //TODO(0drai): see above + /* double d;*/ + /* if (ToNumber(cx, argValue, &d)){*/ + /* JSObject* number = NumberObject::createTainted(*/ + /* cx, d,*/ + /* TaintFlow(*/ + /* TaintOperation("WASM Import taint source", {taintarg(cx, d)})));*/ + /* args[i].setObject(*number);*/ + /*}*/ + } FuncImportInstanceData& import = funcImportInstanceData(fi); @@ -292,6 +321,7 @@ bool Instance::callImport(JSContext* cx, uint32_t funcImportIndex, RootedValue fval(cx, ObjectValue(*importCallable)); RootedValue thisv(cx, UndefinedValue()); RootedValue rval(cx); + if (!Call(cx, fval, thisv, args, &rval)) { return false; } @@ -2681,6 +2711,17 @@ bool wasm::ResultsToJSValue(JSContext* cx, ResultType type, MOZ_ASSERT((stackResultsLoc.isSome()) == (iter.count() > 1)); if (!stackResultsLoc) { // A single result: we're done. + + //TODO(0drai): for Wasm Calls issued from Wasm (export), same issue as above + double d; + if (ToNumber(cx, rval, &d)) { + JSObject* number = NumberObject::createTainted( + cx, d, + TaintFlow( + TaintOperation("WASM Export taint source", {taintarg(cx, d)}))); + rval.setObject(*number); + } + return true; } @@ -2706,6 +2747,21 @@ bool wasm::ResultsToJSValue(JSContext* cx, ResultType type, } } } + + //TODO(0drai): see above + for (uint32_t i = 0; i < array->length(); i++) { + const auto* element = &array->getDenseElement(i); + if (!element->isNumber()) { + continue; + } + + JSObject* number = NumberObject::createTainted( + cx, element->toNumber(), + TaintFlow(TaintOperation("WASM Export taint source", + {taintarg(cx, element->toNumber())}))); + array->setDenseElement(i, ObjectValue(*number)); + } + rval.set(ObjectValue(*array)); return true; } @@ -2907,6 +2963,7 @@ bool Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args, if (!results.collect(cx, registerResultLoc, args.rval(), level)) { return false; } + DebugCodegen(DebugChannel::Function, "]\n"); return true; diff --git a/taint/Taint.cpp b/taint/Taint.cpp index 9c4b08d01e323..01df2bca5bc94 100644 --- a/taint/Taint.cpp +++ b/taint/Taint.cpp @@ -1390,7 +1390,7 @@ StringTaint ParseTaint(const std::string& str) #if (DEBUG_E2E_TAINTING) std::cout << "Done parsing taint. Result: " << std::endl; - PrintTaint(taint); + /*PrintTaint(taint);*/ #endif return taint;