Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing writing Taint flow information to file #247

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ jobs:

# This workflow contains a single job called "build"
build:
strategy:
matrix:
taintspew: [ 'false', 'true' ]
jitspew: [ 'false', 'true' ]

name: 'SpiderMonkey (taintspew: ${{ matrix.taintspew }}, jitspew: ${{ matrix.jitspew }})'

# The type of runner that the job will run on
runs-on: ubuntu-latest

Expand Down Expand Up @@ -52,6 +59,14 @@ jobs:
cd build
cp taintfox_mozconfig_spidermonkey .mozconfig

- name: Enable JitSpew
if: ${{ matrix.jitspew == 'true' }}
run: echo "ac_add_options --enable-jitspew" >> .mozconfig

- name: Enable TaintSpew
if: ${{ matrix.taintspew == 'true' }}
run: echo "ac_add_options --enable-taintspew" >> .mozconfig

# Build
- name: Build
run: |
Expand All @@ -71,7 +86,7 @@ jobs:

- name: Upload Report
uses: actions/upload-artifact@v4 # upload test results
if: success() || failure() # run this step even if previous step failed
if: ${{ matrix.taintspew == 'false' && matrix.jitspew == 'false' && (success() || failure()) }} # run this step even if previous step failed
with:
name: test-results
path: build/jstest_output.xml
17 changes: 7 additions & 10 deletions js/moz.configure
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ def ion_perf(value, target):

set_define("JS_ION_PERF", ion_perf)

option(
"--enable-taintspew",
help="Enable writing taint flow information to file"
)

set_define("JS_TAINTSPEW", depends_if("--enable-taintspew")(lambda _: True))
set_config("JS_TAINTSPEW", depends_if("--enable-taintspew")(lambda _: True))

option(
"--enable-jitspew",
Expand All @@ -494,16 +501,6 @@ set_config("JS_JITSPEW", depends_if("--enable-jitspew")(lambda _: True))
set_define("JS_STRUCTURED_SPEW", depends_if("--enable-jitspew")(lambda _: True))
set_config("JS_STRUCTURED_SPEW", depends_if("--enable-jitspew")(lambda _: True))

option(
"--enable-taintspew",
default=False,
help="{Enable|Disable} the taint flow spew variable",
)

# Also enable the structured spewer for tainting
set_define("JS_STRUCTURED_SPEW", depends_if("--enable-taintspew")(lambda _: True))
set_config("JS_STRUCTURED_SPEW", depends_if("--enable-taintspew")(lambda _: True))

@depends("--enable-jit", "--enable-jitspew", simulator, target, moz_debug)
def jit_disasm_arm(jit_enabled, spew, simulator, target, debug):
if not jit_enabled:
Expand Down
2 changes: 1 addition & 1 deletion js/public/Id.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class PropertyKey {
return reinterpret_cast<JSLinearString*>(toString());
}

#if defined(DEBUG) || defined(JS_JITSPEW)
#if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_TAINTSPEW)
void dump() const;
void dump(js::GenericPrinter& out) const;
void dump(js::JSONPrinter& json) const;
Expand Down
35 changes: 27 additions & 8 deletions js/src/jsapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5025,19 +5025,30 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha
" pl = parent.location.href;\n"
" } catch (e) {\n"
" pl = 'different origin';\n"
" }\n"
" }\n"
" var timestamp = -1;\n"
" try {\n"
" timestamp = Date.now();\n"
" } catch (e) {\n"
" timestamp = -2;\n"
" }\n"
" var e = document.createEvent('CustomEvent');\n"
" e.initCustomEvent('__taintreport', true, false, {\n"
" var info = {\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"
" stack: stack,\n"
" timestamp: timestamp\n"
" }\n"
" e.initCustomEvent('__taintreport', true, false, info);\n"
" t.dispatchEvent(e);\n"
"}";
" return info;\n"
"} else {\n"
" return undefined;\n"
"}\n";
CompileOptions options(cx);
options.setFile("taint_reporting.js");

Expand Down Expand Up @@ -5069,11 +5080,19 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha
arguments[2].setUndefined();
}

RootedValue rval(cx);
JS_CallFunction(cx, nullptr, report, arguments, &rval);
RootedValue retVal(cx);
JS_CallFunction(cx, nullptr, report, arguments, &retVal);
MOZ_ASSERT(!cx->isExceptionPending());

MaybeSpewStringTaint(cx, str);
// Enable this with ac_add_options --enable-taintspew
#if defined(JS_TAINTSPEW)
WriteTaintToFile(cx, str, retVal);
#endif

// Enable with ac_add_options --enable-jitspew
#if defined (JS_JITSPEW)
MaybeSpewStringTaint(cx, str, retval);
#endif
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add a call to MaybeSpewStringTaint here in the case that JitSpew is enabled.


JS_PUBLIC_API bool JS::FinishIncrementalEncoding(JSContext* cx,
Expand Down
215 changes: 171 additions & 44 deletions js/src/jstaint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
#include "jstaint.h"

#include "mozilla/Sprintf.h"

#include <algorithm>
#include <iomanip>
#include <iostream>
Expand All @@ -16,10 +18,12 @@
#include "js/CharacterEncoding.h"
#include "js/ErrorReport.h"
#include "js/UniquePtr.h"
#include "util/GetPidProvider.h" // getpid()
#include "vm/FrameIter.h"
#include "vm/JSAtomUtils.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSONPrinter.h"
#include "vm/StringType.h"

using namespace JS;
Expand Down Expand Up @@ -372,60 +376,183 @@ void JS::MarkTaintedFunctionArguments(JSContext* cx, JSFunction* function, const
}
}

void JS::MaybeSpewStringTaint(JSContext* cx, JSString* str) {
#ifdef JS_STRUCTURED_SPEW
if (!str || !str->taint()) {
#if defined(JS_JITSPEW)
void JS::MaybeSpewStringTaint(JSContext* cx, JSString* str, HandleValue location) {
// Use the standard spew framework to create a single spew file
AutoStructuredSpewer spew(cx, SpewChannel::TaintFlowSpewer, cx->currentScript());
if (spew) {
// Dump the string and taint flow itself
PrintJsonTaint(cx, str, location, *spew);
spew->flush();
}
}
#endif

#if defined(JS_TAINTSPEW)

// Choose a sensible default directory.
//
// The preference here is to use the current working directory,
// except on Android.
# ifndef DEFAULT_TAINT_DIRECTORY
# if defined(_WIN32)
# define DEFAULT_TAINT_DIRECTORY "."
# elif defined(__ANDROID__)
# define DEFAULT_TAINT_DIRECTORY "/sdcard/Download"
# else
# define DEFAULT_TAINT_DIRECTORY "."
# endif
# endif

void JS::WriteTaintToFile(JSContext* cx, JSString* str, HandleValue location) {
// Don't use the standard spewer here, as we can't easily set the filename
static int counter = 0;

char filename[2048] = {0};
if (getenv("TAINT_FILE")) {
SprintfLiteral(filename, "%s", getenv("TAINT_FILE"));
} else {
SprintfLiteral(filename, "%s/taint_output", DEFAULT_TAINT_DIRECTORY);
}

char suffix_path[2048] = {0};
SprintfLiteral(suffix_path, "%s.%d.%u.json", filename, getpid(), counter++);

Fprinter output;
if (!output.init(suffix_path)) {
SEprinter p;
p.put("Error opening taint output file: ");
p.put(suffix_path);
p.put("\n");
p.flush();
return;
}

AutoStructuredSpewer spew(cx, SpewChannel::TaintFlowSpewer, cx->currentScript());
if (spew) {
JSLinearString* linear = str->ensureLinear(cx);
if (linear) {
spew->property("str", linear);
} else {
spew->property("str", "Non-linear String!");
}
JSONPrinter json(output);
json.beginObject();
PrintJsonTaint(cx, str, location, json);
json.endObject();

spew->beginListProperty("taint");
for (const TaintRange& range : str->taint()) {
spew->beginObject();
spew->property("begin", range.begin());
spew->property("end", range.end());

spew->beginListProperty("flow");
for (TaintNode& node : range.flow()) {
const TaintOperation& op = node.operation();
spew->beginObject();
spew->property("operation", op.name());
spew->boolProperty("builtin", op.is_native());
spew->boolProperty("source", op.isSource());

const TaintLocation& loc = op.location();
spew->beginObjectProperty("location");
spew->property("filename", loc.filename().c_str(), loc.filename().size());
spew->property("line", loc.line());
spew->property("pos", loc.pos());
spew->property("scriptline", loc.scriptStartLine());
spew->property("scripthash", JS::convertDigestToHexString(loc.scriptHash()).c_str());
spew->endObject(); // Location

spew->beginListProperty("arguments");
for (auto& arg : op.arguments()) {
spew->string(arg.c_str(), arg.size());
output.flush();
output.finish();
}
#endif

#if defined(JS_JITSPEW) || defined(JS_TAINTSPEW)
void JS::PrintJsonObject(JSContext* cx, JSObject* obj, js::JSONPrinter& json) {
// This code is adapted from JSObject::dumpFields, which was too verbose for our needs
if (obj && obj->is<NativeObject>()) {
const auto* nobj = &obj->as<NativeObject>();

if (PropMap* map = nobj->shape()->propMap()) {
Vector<PropMap*, 8, SystemAllocPolicy> maps;
while (true) {
if (!maps.append(map)) {
json.property("error", "*oom in JSObject::dumpFields*");
break;
}
if (!map->hasPrevious()) {
break;
}
spew->endList();
map = map->asLinked()->previous();
}

spew->endObject(); // Operation
for (size_t i = maps.length(); i > 0; i--) {
size_t index = i - 1;
PropMap* map = maps[index];
uint32_t len = (index == 0) ? obj->shape()->asNative().propMapLength()
: PropMap::Capacity;
for (uint32_t j = 0; j < len; j++) {
if (!map->hasKey(j)) {
MOZ_ASSERT(map->isDictionary());
continue;
}

JS::UniqueChars propChars = map->getPropertyNameAt(j);
if (!propChars) {
json.property("error", "*oom in PropMap::getPropertyNameAt*");
continue;
}

PropertyInfoWithKey prop = map->getPropertyInfoWithKey(j);
if (prop.isDataProperty()) {
const Value& val = nobj->getSlot(prop.slot());
if (val.isDouble()) {
double d = val.toDouble();
// JSONPrinter::floatProperty appears to ignore the precision argument
json.floatProperty(propChars.get(), d, 10);
} else if (val.isString()) {
JSString *str = val.toString();
JSLinearString* linear = str->ensureLinear(cx);
if (linear) {
json.property(propChars.get(), linear);
} else {
json.property(propChars.get(), "Non-linear String!");
}
}
}
}
}
spew->endList(); // flow
spew->endObject(); // range
}
spew->endList();
}
}

void JS::PrintJsonTaint(JSContext* cx, JSString* str, HandleValue location, js::JSONPrinter& json) {
if (!str || !str->taint()) {
return;
}
#endif

// Dump additional information from the taintreport
if (location.isObject()) {
JSObject* obj = ToObject(cx, location);
PrintJsonObject(cx, obj, json);
}

JSLinearString* linear = str->ensureLinear(cx);
if (linear) {
json.property("string", linear);
} else {
json.property("string", "Non-linear String!");
}

json.beginListProperty("taint");
for (const TaintRange& range : str->taint()) {
json.beginObject();
json.property("begin", range.begin());
json.property("end", range.end());

json.beginListProperty("flow");
for (TaintNode& node : range.flow()) {
const TaintOperation& op = node.operation();
json.beginObject();
json.property("operation", op.name());
json.boolProperty("builtin", op.is_native());
json.boolProperty("source", op.isSource());

const TaintLocation& loc = op.location();
json.beginObjectProperty("location");
json.property("filename", loc.filename().c_str(), loc.filename().size());
json.property("line", loc.line());
json.property("pos", loc.pos());
json.property("scriptline", loc.scriptStartLine());
json.property("scripthash", JS::convertDigestToHexString(loc.scriptHash()).c_str());
json.endObject(); // Location

json.beginListProperty("arguments");
for (auto& arg : op.arguments()) {
json.string(arg.c_str(), arg.size());
}
json.endList();

json.endObject(); // Operation
}
json.endList(); // flow
json.endObject(); // range
}
json.endList();

}
#endif

void JS::MaybeSpewMessage(JSContext* cx, JSString* str) {
// First print message to stderr
Expand All @@ -449,7 +576,7 @@ void JS::MaybeSpewMessage(JSContext* cx, JSString* str) {
#endif
}

// Print a message to stdout.
// Print a warning message to stdout and the JS console
void JS::TaintFoxReport(JSContext* cx, const char* msg)
{
JS_ReportWarningUTF8(cx, "%s", msg);
Expand Down
Loading
Loading