WIP: Bug 1723204: Infrastructure for causing a crash at most N times r=KrisWright,ckerschb,freddyb

Differential Revision: https://phabricator.services.mozilla.com/D121416
This commit is contained in:
Tom Ritter 2021-08-24 14:57:43 +00:00
parent 2312e1ba72
commit 15cda29aa2
5 changed files with 174 additions and 21 deletions

View File

@ -202,6 +202,66 @@ nsresult RegexEval(const nsAString& aPattern, const nsAString& aString,
return NS_OK;
}
/*
* MOZ_CRASH_UNSAFE_PRINTF has a sPrintfCrashReasonSize-sized buffer. We need
* to make sure we don't exceed it. These functions perform this check and
* munge things for us.
*
*/
/*
* Destructively truncates a string to fit within the limit
*/
char* nsContentSecurityUtils::SmartFormatCrashString(const char* str) {
return nsContentSecurityUtils::SmartFormatCrashString(strdup(str));
}
char* nsContentSecurityUtils::SmartFormatCrashString(char* str) {
auto str_len = strlen(str);
if (str_len > sPrintfCrashReasonSize) {
str[sPrintfCrashReasonSize - 1] = '\0';
str_len = strlen(str);
}
MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize > str_len);
return str;
}
/*
* Destructively truncates two strings to fit within the limit.
* format_string is a format string containing two %s entries
* The second string will be truncated to the _last_ 25 characters
* The first string will be truncated to the remaining limit.
*/
nsCString nsContentSecurityUtils::SmartFormatCrashString(
const char* part1, const char* part2, const char* format_string) {
return SmartFormatCrashString(strdup(part1), strdup(part2), format_string);
}
nsCString nsContentSecurityUtils::SmartFormatCrashString(
char* part1, char* part2, const char* format_string) {
auto part1_len = strlen(part1);
auto part2_len = strlen(part2);
auto constant_len = strlen(format_string) - 4;
if (part1_len + part2_len + constant_len > sPrintfCrashReasonSize) {
if (part2_len > 25) {
part2 += (part2_len - 25);
}
part2_len = strlen(part2);
part1[sPrintfCrashReasonSize - (constant_len + part2_len + 1)] = '\0';
part1_len = strlen(part1);
}
MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize >
constant_len + part1_len + part2_len);
auto parts = nsPrintfCString(format_string, part1, part2);
return std::move(parts);
}
/*
* Telemetry Events extra data only supports 80 characters, so we optimize the
* filename to be smaller and collect more data.
@ -405,6 +465,56 @@ FilenameTypeAndDetails nsContentSecurityUtils::FilenameToFilenameType(
return FilenameTypeAndDetails(kOther, Nothing());
}
#ifdef NIGHTLY_BUILD
// Crash String must be safe from a telemetry point of view.
// This will be ensured when this function is used.
void PossiblyCrash(const char* pref_suffix, const nsCString crash_string) {
if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
// We only crash in the parent (unfortunately) because it's
// the only place we can be sure that our only-crash-once
// pref-writing works.
return;
}
nsCString previous_crashes("security.crash_tracking.");
previous_crashes.Append(pref_suffix);
previous_crashes.Append(".prevCrashes");
nsCString max_crashes("security.crash_tracking.");
max_crashes.Append(pref_suffix);
max_crashes.Append(".maxCrashes");
int32_t numberOfPreviousCrashes = 0;
numberOfPreviousCrashes = Preferences::GetInt(previous_crashes.get(), 0);
int32_t maxAllowableCrashes = 0;
maxAllowableCrashes = Preferences::GetInt(max_crashes.get(), 0);
if (numberOfPreviousCrashes >= maxAllowableCrashes) {
return;
}
nsresult rv =
Preferences::SetInt(previous_crashes.get(), ++numberOfPreviousCrashes);
if (NS_FAILED(rv)) {
return;
}
nsCOMPtr<nsIPrefService> prefsCom = Preferences::GetService();
Preferences* prefs = static_cast<Preferences*>(prefsCom.get());
if (!prefs->AllowOffMainThreadSave()) {
// Do not crash if we can't save prefs off the main thread
return;
}
rv = prefs->SavePrefFileBlocking();
if (!NS_FAILED(rv)) {
MOZ_CRASH_UNSAFE_PRINTF("%s", nsContentSecurityUtils::SmartFormatCrashString(crash_string.get()));
}
}
#endif
class EvalUsageNotificationRunnable final : public Runnable {
public:
EvalUsageNotificationRunnable(bool aIsSystemPrincipal,
@ -570,22 +680,13 @@ bool nsContentSecurityUtils::IsEvalAllowed(JSContext* cx,
fileName.get(), NS_ConvertUTF16toUTF8(aScript).get()));
// Maybe Crash
#ifdef DEBUG
// MOZ_CRASH_UNSAFE_PRINTF gives us at most 1024 characters to print.
// The given string literal leaves us with ~950, so I'm leaving
// each 475 for fileName and aScript each.
if (fileName.Length() > 475) {
fileName.SetLength(475);
}
nsAutoCString trimmedScript = NS_ConvertUTF16toUTF8(aScript);
if (trimmedScript.Length() > 475) {
trimmedScript.SetLength(475);
}
MOZ_CRASH_UNSAFE_PRINTF(
"Blocking eval() %s from file %s and script provided "
"%s",
(aIsSystemPrincipal ? "with System Principal" : "in parent process"),
fileName.get(), trimmedScript.get());
#if defined(DEBUG)
auto crashString = nsContentSecurityUtils::SmartFormatCrashString(
NS_ConvertUTF16toUTF8(aScript).get(), fileName.get(),
(aIsSystemPrincipal
? "Blocking eval() with System Principal with script %s from file %s"
: "Blocking eval() in parent process with script %s from file %s"));
MOZ_CRASH_UNSAFE_PRINTF("%s", crashString.get());
#endif
return false;

View File

@ -37,8 +37,6 @@ class nsContentSecurityUtils {
static bool IsConsideredSameOriginForUIR(nsIPrincipal* aTriggeringPrincipal,
nsIPrincipal* aResultPrincipal);
static FilenameTypeAndDetails FilenameToFilenameType(
const nsString& fileName, bool collectAdditionalExtensionData);
static bool IsEvalAllowed(JSContext* cx, bool aIsSystemPrincipal,
const nsAString& aScript);
static void NotifyEvalUsage(bool aIsSystemPrincipal,
@ -67,6 +65,16 @@ class nsContentSecurityUtils {
static long ClassifyDownload(nsIChannel* aChannel,
const nsAutoCString& aMimeTypeGuess);
// Public only for testing
static FilenameTypeAndDetails FilenameToFilenameType(
const nsString& fileName, bool collectAdditionalExtensionData);
static char* SmartFormatCrashString(const char* str);
static char* SmartFormatCrashString(char* str);
static nsCString SmartFormatCrashString(const char* part1, const char* part2,
const char* format_string);
static nsCString SmartFormatCrashString(char* part1, char* part2,
const char* format_string);
#if defined(DEBUG)
static void AssertAboutPageHasCSP(mozilla::dom::Document* aDocument);
#endif

View File

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "nsContentSecurityUtils.h"
#include "nsTString.h"
#include "nsStringFwd.h"
#define ASSERT_STRCMP(first, second) ASSERT_TRUE(strcmp(first, second) == 0);
#define ASSERT_STRCMP_AND_PRINT(first, second) \
fprintf(stderr, "First: %s\n", first); \
fprintf(stderr, "Second: %s\n", second); \
fprintf(stderr, "strcmp = %i\n", strcmp(first, second)); \
ASSERT_EQUAL(first, second);
TEST(SmartCrashTrimmer, Test)
{
static_assert(sPrintfCrashReasonSize == 1024);
{
auto ret = nsContentSecurityUtils::SmartFormatCrashString(
std::string(1025, '.').c_str());
ASSERT_EQ(strlen(ret), 1023ul);
}
{
auto ret = nsContentSecurityUtils::SmartFormatCrashString(
std::string(1025, '.').c_str(), std::string(1025, 'A').c_str(),
"Hello %s world %s!");
char expected[1025];
sprintf(expected, "Hello %s world AAAAAAAAAAAAAAAAAAAAAAAAA!",
std::string(984, '.').c_str());
ASSERT_STRCMP(ret.get(), expected);
}
}

View File

@ -8,6 +8,7 @@ UNIFIED_SOURCES += [
"TestCSPParser.cpp",
"TestFilenameEvalParser.cpp",
"TestSecureContext.cpp",
"TestSmartCrashTrimmer.cpp",
]
if CONFIG["OS_TARGET"] != "Android":

View File

@ -421,6 +421,9 @@ class Preferences final : public nsIPrefService,
nsresult SavePrefFileBlocking();
nsresult SavePrefFileAsynchronous();
// If this is false, only blocking writes, on main thread are allowed.
bool AllowOffMainThreadSave();
private:
virtual ~Preferences();
@ -442,9 +445,6 @@ class Preferences final : public nsIPrefService,
nsresult SavePrefFileInternal(nsIFile* aFile, SaveMethod aSaveMethod);
nsresult WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod);
// If this is false, only blocking writes, on main thread are allowed.
bool AllowOffMainThreadSave();
// Helpers for implementing
// Register(Prefix)Callback/Unregister(Prefix)Callback.
public: