tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 55970eed95b4e319f0b7bad4e5a480fc17017f01
parent 71317ff104b565767484a7b469b81c161a0c878c
Author: Julian Descottes <jdescottes@mozilla.com>
Date:   Sat, 29 Nov 2025 10:37:40 +0000

Bug 1941780 - Implement bypassCSP option for Frame.eval r=arai

This option allows to bypass CSPs for scripts executed via Frame.eval, but only across synchronous frames triggered by the call to Frame.eval.
Default to false, so the current behavior should not change for existing consumer unless they start passing bypassCSP=true.

Differential Revision: https://phabricator.services.mozilla.com/D273224

Diffstat:
Mjs/src/builtin/Eval.cpp | 6++++--
Mjs/src/debugger/Debugger.cpp | 5+++++
Mjs/src/debugger/Debugger.h | 3+++
Mjs/src/debugger/Frame.cpp | 2++
Mjs/src/doc/Debugger/Debugger.Frame.md | 8++++++++
Mjs/src/vm/JSContext.cpp | 1+
Mjs/src/vm/JSContext.h | 18++++++++++++++++++
Mjs/src/vm/JSFunction.cpp | 6++++--
8 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp @@ -259,8 +259,10 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, // Steps 6-8. JS::RootedVector<JSString*> parameterStrings(cx); JS::RootedVector<Value> parameterArgs(cx); - bool canCompileStrings = false; - if (!cx->isRuntimeCodeGenEnabled( + bool canCompileStrings = cx->bypassCSPForDebugger; + + if (!canCompileStrings && + !cx->isRuntimeCodeGenEnabled( JS::RuntimeCode::JS, str, evalType == DIRECT_EVAL ? JS::CompilationType::DirectEval : JS::CompilationType::IndirectEval, diff --git a/js/src/debugger/Debugger.cpp b/js/src/debugger/Debugger.cpp @@ -393,6 +393,11 @@ bool js::ParseEvalOptions(JSContext* cx, HandleValue value, } options.setHideFromDebugger(ToBoolean(v)); + if (!JS_GetProperty(cx, opts, "bypassCSP", &v)) { + return false; + } + options.setBypassCSP(ToBoolean(v)); + if (options.kind() == EvalOptions::EnvKind::GlobalWithExtraOuterBindings) { if (!JS_GetProperty(cx, opts, "useInnerBindings", &v)) { return false; diff --git a/js/src/debugger/Debugger.h b/js/src/debugger/Debugger.h @@ -430,6 +430,7 @@ class MOZ_RAII EvalOptions { JS::UniqueChars filename_; unsigned lineno_ = 1; bool hideFromDebugger_ = false; + bool bypassCSP_ = false; EnvKind kind_; public: @@ -438,6 +439,7 @@ class MOZ_RAII EvalOptions { const char* filename() const { return filename_.get(); } unsigned lineno() const { return lineno_; } bool hideFromDebugger() const { return hideFromDebugger_; } + bool bypassCSP() const { return bypassCSP_; } EnvKind kind() const { return kind_; } void setUseInnerBindings() { MOZ_ASSERT(kind_ == EvalOptions::EnvKind::GlobalWithExtraOuterBindings); @@ -446,6 +448,7 @@ class MOZ_RAII EvalOptions { [[nodiscard]] bool setFilename(JSContext* cx, const char* filename); void setLineno(unsigned lineno) { lineno_ = lineno; } void setHideFromDebugger(bool hide) { hideFromDebugger_ = hide; } + void setBypassCSP(bool bypass) { bypassCSP_ = bypass; } }; /* diff --git a/js/src/debugger/Frame.cpp b/js/src/debugger/Frame.cpp @@ -1005,6 +1005,8 @@ static bool EvaluateInEnv( cx->check(envArg, frame); + AutoSetBypassCSPForDebugger setFlag(cx, evalOptions.bypassCSP()); + CompileOptions options(cx); const char* filename = evalOptions.filename() ? evalOptions.filename() : "debugger eval code"; diff --git a/js/src/doc/Debugger/Debugger.Frame.md b/js/src/doc/Debugger/Debugger.Frame.md @@ -435,6 +435,14 @@ recognizes the following properties: The line number at which the evaluated code should be claimed to begin within <i>url</i>. +* `bypassCSP` + + When this flag is true, `script-src` CSP restrictions will be bypassed for + synchronous frames executed from this evaluation. Any async frame / call will + not preserve the bypass flag. When the flag is set to true, the script can + typically use `eval` or `new Function` even if the page's CSP would normally + prevent it. + Accessing this property will throw if `.onStack == false`. ### `evalWithBindings(code, bindings, [options])` diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp @@ -1290,6 +1290,7 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options) canSkipEnqueuingJobs(this, false), promiseRejectionTrackerCallback(this, nullptr), promiseRejectionTrackerCallbackData(this, nullptr), + bypassCSPForDebugger(this, false), insideExclusiveDebuggerOnEval(this, nullptr), microTaskQueues(this) { MOZ_ASSERT(static_cast<JS::RootingContext*>(this) == diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h @@ -1013,6 +1013,11 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext, js::StructuredSpewer& spewer() { return structuredSpewer_.ref(); } #endif + // This flag indicates whether we should bypass CSP restrictions for + // eval() and Function() calls or not. This flag can be set when + // evaluating the code for Debugger.Frame.prototype.eval. + js::ContextData<bool> bypassCSPForDebugger; + // Debugger having set `exclusiveDebuggerOnEval` property to true // want their evaluations and calls to be ignore by all other Debuggers // except themself. This flag indicates whether we are in such debugger @@ -1201,6 +1206,19 @@ class MOZ_RAII AutoNoteExclusiveDebuggerOnEval { } }; +class MOZ_RAII AutoSetBypassCSPForDebugger { + JSContext* cx; + bool oldValue; + + public: + AutoSetBypassCSPForDebugger(JSContext* cx, bool value) + : cx(cx), oldValue(cx->bypassCSPForDebugger) { + cx->bypassCSPForDebugger = value; + } + + ~AutoSetBypassCSPForDebugger() { cx->bypassCSPForDebugger = oldValue; } +}; + enum UnsafeABIStrictness { NoExceptions, AllowPendingExceptions, diff --git a/js/src/vm/JSFunction.cpp b/js/src/vm/JSFunction.cpp @@ -1481,8 +1481,10 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args, } // Block this call if security callbacks forbid it. - bool canCompileStrings = false; - if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, functionText, + bool canCompileStrings = cx->bypassCSPForDebugger; + + if (!canCompileStrings && + !cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, functionText, JS::CompilationType::Function, parameterStrings, bodyString, parameterArgs, bodyArg, &canCompileStrings)) {