Skip to content
Draft
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
14 changes: 13 additions & 1 deletion js/src/builtin/RegExp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
Rooted<JSAtom*> src(cx, re->getSource());
RootedString srcStr(cx, EscapeRegExpPattern(cx, src));

// Foxhound: build the flags string in canonical order (dgimsuvy) so the
// taint operation records the full regular expression, not just its body.
std::u16string flagsStr;
if (re->hasIndices()) flagsStr += 'd';
if (re->global()) flagsStr += 'g';
if (re->ignoreCase()) flagsStr += 'i';
if (re->multiline()) flagsStr += 'm';
if (re->dotAll()) flagsStr += 's';
if (re->unicode()) flagsStr += 'u';
if (re->unicodeSets()) flagsStr += 'v';
if (re->sticky()) flagsStr += 'y';

// Steps 28-29 and 33 a-d: Initialize the elements of the match result.
// Store a Value for each match pair.
for (size_t i = 0; i < numPairs; i++) {
Expand All @@ -153,7 +165,7 @@ bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
if (str->taint().hasTaint()) {
str->taint().extend(
TaintOperation("RegExp.prototype.exec", TaintLocationFromContext(cx),
{ taintarg_jsstring_full(cx, srcStr), taintarg_jsstring(cx, str), taintarg(cx, i) }));
{ taintarg_jsstring_full(cx, srcStr), flagsStr, taintarg_jsstring(cx, str), taintarg(cx, i) }));
}
arr->setDenseInitializedLength(i + 1);
arr->initDenseElement(i, StringValue(str));
Expand Down
1 change: 1 addition & 0 deletions taint/test/mochitest/mochitest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ scheme = https
[test_json_stringify.html]
[test_toLowerCase.html]
[test_normalize.html]
[test_regexp_exec.html]
65 changes: 65 additions & 0 deletions taint/test/mochitest/test_regexp_exec.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>
Check that the RegExp.prototype.exec taint operation records both the
regular expression body and its flags
</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />

<script type="text/javascript">
// Returns the last "RegExp.prototype.exec" operation in the taint flow of
// the given tainted string, or undefined if there is none.
function getExecOperation(str) {
return str.taint[0].flow.find(
(op) => op.operation === "RegExp.prototype.exec"
);
}

add_task(function test_exec_records_body_and_flags() {
const input = String.tainted("hello world", "src");
const match = /\w+/g.exec(input);

check_tainted(match[0]);
const op = getExecOperation(match[0]);
SimpleTest.ok(op, "Found RegExp.prototype.exec operation in taint flow");
SimpleTest.is(op.arguments[0], "\\w+", "Operation records regex body");
SimpleTest.is(op.arguments[1], "g", "Operation records regex flags");
});

add_task(function test_exec_records_no_flags() {
const input = String.tainted("hello world", "src");
const match = /\w+/.exec(input);

check_tainted(match[0]);
const op = getExecOperation(match[0]);
SimpleTest.ok(op, "Found RegExp.prototype.exec operation in taint flow");
SimpleTest.is(op.arguments[0], "\\w+", "Operation records regex body");
SimpleTest.is(
op.arguments[1],
"",
"Operation records empty flags when none are set"
);
});

add_task(function test_exec_records_flags_in_canonical_order() {
const input = String.tainted("hello world", "src");
// Flags supplied out of order; they must be recorded as "gi".
const match = /\w+/ig.exec(input);

check_tainted(match[0]);
const op = getExecOperation(match[0]);
SimpleTest.ok(op, "Found RegExp.prototype.exec operation in taint flow");
SimpleTest.is(
op.arguments[1],
"gi",
"Operation records flags in canonical order"
);
});
</script>
</head>

<body></body>
</html>
Loading