Skip to content

Commit b4da372

Browse files
committed
Foxhound: fixing memory leaks and crashses
The fix to clean up all the StringTaint from Nursery strings wasn't quite thorough enough and left some dangling pointers lying around. This fix makes sure we catch all the Nursery allocated Strings by moving String registration to Allocator-inl.h This fix revealed the root cause of the segmentation faults which were occuring on client-heavy webapps (UI5 I'm looking at you). There were a couple of occasions where strings were being created (but not rooted) and then additional strings constructed for TaintOperation arguments (e.g. JSONParser.cpp and RegExp.cpp). As the TaintOperations were trying to allocate memory, this could cause a GC which would clean up the original strings. This fix makes sure that the TaintOperations are created before the new Strings or that they are properly rooted.
1 parent ff7acfb commit b4da372

File tree

7 files changed

+78
-43
lines changed

7 files changed

+78
-43
lines changed

js/src/builtin/RegExp.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
109109
return false;
110110
}
111111

112+
Rooted<JSAtom*> src(cx, re->getSource());
113+
RootedString srcStr(cx, EscapeRegExpPattern(cx, src));
114+
112115
// Steps 28-29 and 33 a-d: Initialize the elements of the match result.
113116
// Store a Value for each match pair.
114117
for (size_t i = 0; i < numPairs; i++) {
@@ -125,10 +128,9 @@ bool js::CreateRegExpMatchResult(JSContext* cx, HandleRegExpShared re,
125128
return false;
126129
}
127130
// Taintfox: taint propagated by NewDependentString, just need
128-
// to add the operation here
131+
// to add the operation here. Do this after adding to the rooted
132+
// array to avoid GC issues.
129133
if (str->taint().hasTaint()) {
130-
Rooted<JSAtom*> src(cx, re->getSource());
131-
JSString* srcStr = EscapeRegExpPattern(cx, src);
132134
str->taint().extend(
133135
TaintOperation("RegExp.prototype.exec", true, TaintLocationFromContext(cx),
134136
{ taintarg_jsstring_full(cx, srcStr), taintarg_jsstring(cx, str), taintarg(cx, i) }));

js/src/gc/Allocator-inl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ T* CellAllocator::NewString(JSContext* cx, gc::Heap heap, Args&&... args) {
6969
return nullptr;
7070
}
7171

72+
JSString* str = reinterpret_cast<JSString*>(ptr);
73+
JSString::registerNurseryString(cx, str);
7274
return new (mozilla::KnownNotNull, ptr) T(std::forward<Args>(args)...);
7375
}
7476

js/src/jsapi.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4953,7 +4953,7 @@ JS_ReportTaintSink(JSContext* cx, JS::HandleString str, const char* sink, JS::Ha
49534953
// Print a message to stdout. Also include the current JS backtrace.
49544954
auto& firstRange = *str->taint().begin();
49554955

4956-
// std::cerr << "!!! Tainted flow into " << sink << " from " << firstRange.flow().source().name() << " !!!" << std::endl;
4956+
std::cerr << "!!! Tainted flow into " << sink << " from " << firstRange.flow().source().name() << " !!!" << std::endl;
49574957
// DumpBacktrace(cx);
49584958

49594959
// Report a warning to show up on the web console

js/src/vm/JSONParser.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ JSString* JSONFullParseHandlerAnyChar::CurrentJsonPath(const Vector<StackEntry,
690690
}
691691
}
692692
}
693-
return builder.finishAtom();
693+
return builder.finishString(gcHeap);
694694
}
695695

696696
inline void JSONFullParseHandlerAnyChar::setNumberValue(double d) {
@@ -703,6 +703,14 @@ inline bool JSONFullParseHandler<CharT>::setStringValue(CharPtr start,
703703
size_t length,
704704
const StringTaint& taint,
705705
const ParserT* parser) {
706+
TaintOperation op("JSON.parse");
707+
// TaintFox: propagate taint.
708+
if (ST != JSONStringType::PropertyName && taint.hasTaint()) {
709+
JSString* path = parser->CurrentJsonPath();
710+
RootedString jsonPath(cx, path);
711+
op = TaintOperationFromContextJSString(cx, "JSON.parse", true, jsonPath);
712+
}
713+
706714
JSString* str;
707715
if constexpr (ST == JSONStringType::PropertyName) {
708716
str = AtomizeChars(cx, start.get(), length);
@@ -717,8 +725,6 @@ inline bool JSONFullParseHandler<CharT>::setStringValue(CharPtr start,
717725
// TaintFox: propagate taint.
718726
if (ST != JSONStringType::PropertyName && taint.hasTaint()) {
719727
str->setTaint(cx, taint);
720-
RootedString jsonPath(cx, parser->CurrentJsonPath());
721-
TaintOperation op = TaintOperationFromContextJSString(cx, "JSON.parse", true, jsonPath);
722728
str->taint().extend(op);
723729
}
724730

@@ -730,6 +736,15 @@ template <typename CharT>
730736
template <JSONStringType ST, typename ParserT>
731737
inline bool JSONFullParseHandler<CharT>::setStringValue(
732738
StringBuilder& builder, const ParserT* parser) {
739+
740+
TaintOperation op("JSON.parse");
741+
// TaintFox: propagate taint.
742+
if (ST != JSONStringType::PropertyName && builder.buffer.taint()) {
743+
JSString* path = parser->CurrentJsonPath();
744+
RootedString jsonPath(cx, path);
745+
op = TaintOperationFromContextJSString(cx, "JSON.parse", true, jsonPath);
746+
}
747+
733748
JSString* str;
734749
if constexpr (ST == JSONStringType::PropertyName) {
735750
str = builder.buffer.finishAtom();
@@ -743,8 +758,6 @@ inline bool JSONFullParseHandler<CharT>::setStringValue(
743758

744759
// TaintFox: Add taint operation.
745760
if (str->taint().hasTaint()) {
746-
RootedString jsonPath(cx, parser->CurrentJsonPath());
747-
TaintOperation op = TaintOperationFromContextJSString(cx, "JSON.parse", true, jsonPath);
748761
str->taint().extend(op);
749762
}
750763

js/src/vm/StringType-inl.h

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,7 @@ MOZ_ALWAYS_INLINE JSRope* JSRope::new_(
262262
if (MOZ_UNLIKELY(!validateLengthInternal<allowGC>(cx, length))) {
263263
return nullptr;
264264
}
265-
JSRope* str = cx->newCell<JSRope, allowGC>(heap, left, right, length);
266-
registerNurseryString(cx, str);
267-
return str;
265+
return cx->newCell<JSRope, allowGC>(heap, left, right, length);
268266
}
269267

270268
inline JSDependentString::JSDependentString(JSLinearString* base, size_t start,
@@ -318,14 +316,11 @@ MOZ_ALWAYS_INLINE JSLinearString* JSDependentString::new_(
318316
JSDependentString* str =
319317
cx->newCell<JSDependentString, js::NoGC>(heap, baseArg, start, length, &taint);
320318
if (str) {
321-
registerNurseryString(cx, str);
322319
return str;
323320
}
324321

325322
JS::Rooted<JSLinearString*> base(cx, baseArg);
326-
str = cx->newCell<JSDependentString>(heap, base, start, length, &taint);
327-
registerNurseryString(cx, str);
328-
return str;
323+
return cx->newCell<JSDependentString>(heap, base, start, length, &taint);
329324
}
330325

331326
inline JSLinearString::JSLinearString(const char16_t* chars, size_t length) {
@@ -362,9 +357,7 @@ MOZ_ALWAYS_INLINE JSLinearString* JSLinearString::new_(
362357
return nullptr;
363358
}
364359

365-
JSLinearString* str = newValidLength<allowGC>(cx, std::move(chars), length, heap);
366-
registerNurseryString(cx, str);
367-
return str;
360+
return newValidLength<allowGC>(cx, std::move(chars), length, heap);
368361
}
369362

370363
template <js::AllowGC allowGC, typename CharT>
@@ -440,18 +433,14 @@ template <js::AllowGC allowGC>
440433
MOZ_ALWAYS_INLINE JSThinInlineString* JSThinInlineString::new_(
441434
JSContext* cx, js::gc::Heap heap) {
442435
MOZ_ASSERT(!cx->zone()->isAtomsZone());
443-
JSThinInlineString* str = cx->newCell<JSThinInlineString, allowGC>(heap);
444-
registerNurseryString(cx, str);
445-
return str;
436+
return cx->newCell<JSThinInlineString, allowGC>(heap);
446437
}
447438

448439
template <js::AllowGC allowGC>
449440
MOZ_ALWAYS_INLINE JSFatInlineString* JSFatInlineString::new_(
450441
JSContext* cx, js::gc::Heap heap) {
451442
MOZ_ASSERT(!cx->zone()->isAtomsZone());
452-
JSFatInlineString* str = cx->newCell<JSFatInlineString, allowGC>(heap);
453-
registerNurseryString(cx, str);
454-
return str;
443+
return cx->newCell<JSFatInlineString, allowGC>(heap);
455444
}
456445

457446
inline JSThinInlineString::JSThinInlineString(size_t length,
@@ -523,7 +512,6 @@ MOZ_ALWAYS_INLINE JSExternalString* JSExternalString::new_(
523512
MOZ_ASSERT(str->isTenured());
524513
js::AddCellMemory(str, nbytes, js::MemoryUse::StringContents);
525514

526-
registerNurseryString(cx, str);
527515
return str;
528516
}
529517

@@ -681,4 +669,23 @@ inline void JSExternalString::finalize(JS::GCContext* gcx) {
681669
clearTaint();
682670
}
683671

672+
/* static */
673+
void JSString::registerNurseryString(JSContext* cx, JSString* str) {
674+
if (!str) {
675+
return;
676+
}
677+
678+
if (!IsInsideNursery(str)) {
679+
return;
680+
}
681+
682+
if (!cx->nursery().addStringWithNurseryMemory(str)) {
683+
ReportOutOfMemory(cx);
684+
return;
685+
}
686+
687+
return;
688+
}
689+
690+
684691
#endif /* vm_StringType_inl_h */

js/src/vm/StringType.cpp

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ void JSString::dumpRepresentation() const {
367367
out.putChar('\n');
368368
}
369369

370+
void JSString::dumpRepresentationHeader() const {
371+
js::Fprinter out(stderr);
372+
dumpRepresentationHeader(out, 0);
373+
out.putChar('\n');
374+
}
375+
370376
void JSString::dumpRepresentation(js::GenericPrinter& out, int indent) const {
371377
if (isRope()) {
372378
asRope().dumpRepresentation(out, indent);
@@ -392,6 +398,7 @@ void JSString::dumpRepresentationHeader(js::GenericPrinter& out,
392398
// copy-and-paste into a debugger.
393399
out.printf("((%s*) %p) length: %zu flags: 0x%x", subclass, this, length(),
394400
flags);
401+
if (isForwarded()) out.put(" FORWARDED");
395402
if (flags & LINEAR_BIT) out.put(" LINEAR");
396403
if (flags & DEPENDENT_BIT) out.put(" DEPENDENT");
397404
if (flags & INLINE_CHARS_BIT) out.put(" INLINE_CHARS");
@@ -404,7 +411,7 @@ void JSString::dumpRepresentationHeader(js::GenericPrinter& out,
404411
if (!isAtom() && inStringToAtomCache()) out.put(" IN_STRING_TO_ATOM_CACHE");
405412
if (flags & INDEX_VALUE_BIT) out.printf(" INDEX_VALUE(%u)", getIndexValue());
406413
if (!isTenured()) out.put(" NURSERY");
407-
if (!isTainted()) out.put(" TAINTED");
414+
if (isTainted()) out.put(" TAINTED");
408415
out.putChar('\n');
409416
}
410417

@@ -2387,21 +2394,24 @@ void JSString::sweepAfterMinorGC(JS::GCContext* gcx, JSString* str) {
23872394
}
23882395

23892396
if (IsInsideNursery(str) && !IsForwarded(str) && str->isTainted()) {
2397+
#ifdef TAINT_DEBUG_NURSERY
2398+
printf("-----------------------------------------------------\n");
2399+
printf("Str: %p\n", str);
2400+
str->dumpRepresentationHeader();
2401+
#endif
2402+
auto* ptr = reinterpret_cast<uint8_t*>(str) + offsetOfTaint();
2403+
#ifdef TAINT_DEBUG_NURSERY
2404+
printf("Ptr: %p\n", ptr);
2405+
printf("Before: %p\n", *reinterpret_cast<void**>(ptr));
2406+
#endif
23902407
str->clearTaint();
2408+
#ifdef TAINT_DEBUG_NURSERY
2409+
printf("After Clear: %p\n", *reinterpret_cast<void**>(ptr));
2410+
#endif
2411+
AlwaysPoison(ptr, 0x7A, sizeof(StringTaint), MemCheckKind::MakeNoAccess);
2412+
#ifdef TAINT_DEBUG_NURSERY
2413+
printf("After Poison: %p\n", *reinterpret_cast<void**>(ptr));
2414+
printf("-----------------------------------------------------\n");
2415+
#endif
23912416
}
23922417
}
2393-
2394-
/* static */
2395-
void JSString::registerNurseryString(JSContext* cx, JSString* str) {
2396-
if (!str) {
2397-
return;
2398-
}
2399-
2400-
if (!IsInsideNursery(str)) {
2401-
return;
2402-
}
2403-
2404-
if (!cx->nursery().addStringWithNurseryMemory(str)) {
2405-
ReportOutOfMemory(cx);
2406-
}
2407-
}

js/src/vm/StringType.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ class JSString : public js::gc::CellWithLengthAndFlags {
675675
static void sweepAfterMinorGC(JS::GCContext* gcx, JSString* str);
676676

677677
/* Taintfox: register string in nursery */
678-
static void registerNurseryString(JSContext* cx, JSString* str);
678+
static inline void registerNurseryString(JSContext* cx, JSString* str);
679679

680680
private:
681681
// To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
@@ -740,6 +740,7 @@ class JSString : public js::gc::CellWithLengthAndFlags {
740740
void dumpRepresentation(js::GenericPrinter& out, int indent) const;
741741
void dumpRepresentationHeader(js::GenericPrinter& out,
742742
const char* subclass) const;
743+
void dumpRepresentationHeader() const;
743744
void dumpCharsNoQuote(js::GenericPrinter& out);
744745

745746
template <typename CharT>

0 commit comments

Comments
 (0)