首页
社区
课程
招聘
[原创] Google CTF 2023 - v8box
发表于: 2025-5-16 14:53 1617

[原创] Google CTF 2023 - v8box

2025-5-16 14:53
1617

查看下build.dockerfile,本地编译一个环境

根据附件所给的编译选项,修改args.gn的内容

继续执行

留意上方的编译参数,这里的v8关闭了jit、maglev、turbofan、webassembly,然后开启了这个v8_expose_memory_corruption_api,这个api的作用是可以通过官方写的一些函数,对于sandbox内的空间进行操作,位于src/sandbox/testing.cc

有以下方法,相当于是直接有了oob原语。

后来查阅了新版本,大致是v8 12.x以上的,新增了一些方法,同时编译选项也发生了一些变化,变成了V8_ENABLE_MEMORY_CORRUPTION_API,同时多出了一些方法,见下方

同时开启了这个编译选项v8_code_pointer_sandboxing,实现的issue id。因为我们此时已经又了4GB空间任意读写,所以如果修改了某些函数指针的值,是可以很容易的控制rip,所以这个保护就是来阻止这种行为。对于可执行的函数指针,都会有一个固定的入口(code pointer table),然后根据偏移来索引对应的代码。

下面是v8.patch的内容

找到对应的源码看下,这里关掉了一些d8的选项,为了编译更轻量化?

接着是0001-Protect-chunk-headers-on-the-heap.patch的内容

先创建了三个方法

下面在RwMemoryWriteScope命名空间下新增了三个方法,分别是ProtectSpace、SetWritable、SetReadOnly。

ProtectSpace的作用是将一段地址赋为指定的权限,核心是这里mprotect(addr, RoundDown(sizeof(MemoryChunk), 0x1000), prot),就是熟悉的权限修改。

SetWritable的作用是将当前的地址空间赋为rw权限,需要注意其处理,对于young generation且没有被gc处理过的内存空间,会将from_space和to_space的地址空间权限赋为rw,反之就是已经处理过的old generaion 直接调用ProtectSpace去赋权限;SetReadOnly的作用同理,结构和SetWritable是一样的。

这里其实就已经存在了一个条件竞争的问题,两个函数使用了nesting_level_变量来约束,但是对于多线程的情况其实就会出现问题,但这个并不是预期解法。

Ignition作为v8的字节码解释器,JavaScripts代码会被转化为字节码,然后字节码通过Ignition解释执行,所以即使Maglev/Jit关闭,也不会影响到Ignition。

Ignition 会从字节码数组中读取指令执行,这些字节码通常是从源代码编译而来的,所以通过修改这些字节码数组的内容,可以实现指令的执行,但是要如何实现沙箱逃逸呢?

v8的js解释器定义了一些opcode,这些其中Ldar和Star出现了缺乏边界检查的问题,我们可以利用这两个实现沙箱逃逸

这里是Ldar指令对应的字节码

下面是Ldar指令对应的汇编,r12寄存器是bytecodearray的基址,r9是索引。可以不难看出这里的返回值rac由rbx赋值,rbx是通过rdx+rbx索引得到,然后rdx由rbp赋值,也就是说这个指令会返回一个栈上的数值。

通过demo可以看下这个的效果

此时的指令执行情况

此时栈和leak的情况

由于指针压缩的存在,只能泄漏出低32位的值,同时由于类型是smi,所以会对应的 >>1 ,这里0x000000000fdb6c0e * 2 = 0x1fb6d81c

下面是Star的汇编指令,与Ldar同理。这里将rax,也就是上一次执行的返回值,赋给栈上。

执行了这一段代码

不难从下面看出,rbp+0x0处的值被修改为rax寄存器内的值

通过上面的分析,此时已经具备了泄漏栈上低4字节数据和栈上8字节数据写的原语。

首先需要解决的是如何控制RIP,通过栈上8字节写的方法,其实已经可以控制rbp了,通过修改字节数组,可以多执行几次leave,这样就可以实现栈迁移,其实这里就可以劫持控制流了。

那么如果获取gadget,也就是泄漏d8的基地值呢,从栈上的值是可以发现一些位于d8的代码片段的。

对于地址随机化,这里的低四位字节是变化很小/基本上不变化,那么现在就是需要泄漏d8基地址的高位字节。因为堆块开头部分还是具有读权限的,所以高位字节可以从堆块开头读到,然后拼接起来,再减去尾部的偏移,就可以拿到d8的基地值,拿到d8的基地值之后,就可以获取gadget,通过这些gadget写rop提权

c3fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2Q4x3V1k6Y4L8$3!0Y4L8r3g2Q4x3X3c8U0N6r3k6Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8U0t1H3x3U0y4Q4x3V1k6I4N6h3q4D9M7#2)9J5c8Y4y4S2L8X3c8T1L8%4S2Q4x3X3c8$3z5r3u0G2P5q4)9J5c8Y4y4G2L8s2g2@1K9h3!0F1

fetch v8
git switch -d d90d4533b05301e2be813a5f90223f4c6c1bf63d
gclient sync
git apply < ./v8.patch
git apply < ./0001-Protect-chunk-headers-on-the-heap.patch
./tools/dev/v8gen.py x64.release
 
vim ./out.gn/x64.release/args.gn
fetch v8
git switch -d d90d4533b05301e2be813a5f90223f4c6c1bf63d
gclient sync
git apply < ./v8.patch
git apply < ./0001-Protect-chunk-headers-on-the-heap.patch
./tools/dev/v8gen.py x64.release
 
vim ./out.gn/x64.release/args.gn
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     f9aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
dcheck_always_on = false
v8_jitless = true
v8_enable_maglev = false
v8_enable_turbofan = false
v8_enable_webassembly = false
v8_expose_memory_corruption_api = true
use_goma = false
v8_code_pointer_sandboxing = true
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     f9aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
dcheck_always_on = false
v8_jitless = true
v8_enable_maglev = false
v8_enable_turbofan = false
v8_enable_webassembly = false
v8_expose_memory_corruption_api = true
use_goma = false
v8_code_pointer_sandboxing = true
gn gen out.gn/x64.release
ninja -C out.gn/x64.release -j 12 d8
gn gen out.gn/x64.release
ninja -C out.gn/x64.release -j 12 d8
#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API
 
namespace {
 
// Sandbox.byteLength
void SandboxGetByteLength(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
  double sandbox_size = GetProcessWideSandbox()->size();
  info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size));
}
 
// new Sandbox.MemoryView(info) -> Sandbox.MemoryView
void SandboxMemoryView(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
  Local<v8::Context> context = isolate->GetCurrentContext();
 
  if (!info.IsConstructCall()) {
    isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'");
    return;
  }
 
  Local<v8::Integer> arg1, arg2;
  if (!info[0]->ToInteger(context).ToLocal(&arg1) ||
      !info[1]->ToInteger(context).ToLocal(&arg2)) {
    isolate->ThrowError("Expects two number arguments (start offset and size)");
    return;
  }
 
  Sandbox* sandbox = GetProcessWideSandbox();
  CHECK_LE(sandbox->size(), kMaxSafeIntegerUint64);
 
  uint64_t offset = arg1->Value();
  uint64_t size = arg2->Value();
  if (offset > sandbox->size() || size > sandbox->size() ||
      (offset + size) > sandbox->size()) {
    isolate->ThrowError(
        "The MemoryView must be entirely contained within the sandbox");
    return;
  }
 
  Factory* factory = reinterpret_cast<Isolate*>(isolate)->factory();
  std::unique_ptr<BackingStore> memory = BackingStore::WrapAllocation(
      reinterpret_cast<void*>(sandbox->base() + offset), size,
      v8::BackingStore::EmptyDeleter, nullptr, SharedFlag::kNotShared);
  if (!memory) {
    isolate->ThrowError("Out of memory: MemoryView backing store");
    return;
  }
  Handle<JSArrayBuffer> buffer = factory->NewJSArrayBuffer(std::move(memory));
  info.GetReturnValue().Set(Utils::ToLocal(buffer));
}
 
// Sandbox.getAddressOf(object) -> Number
void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
 
  if (info.Length() == 0) {
    isolate->ThrowError("First argument must be provided");
    return;
  }
 
  Handle<Object> arg = Utils::OpenHandle(*info[0]);
  if (!arg->IsHeapObject()) {
    isolate->ThrowError("First argument must be a HeapObject");
    return;
  }
 
  // HeapObjects must be allocated inside the pointer compression cage so their
  // address relative to the start of the sandbox can be obtained simply by
  // taking the lowest 32 bits of the absolute address.
  uint32_t address = static_cast<uint32_t>(HeapObject::cast(*arg).address());
  info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}
 
// Sandbox.getSizeOf(object) -> Number
void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
 
  if (info.Length() == 0) {
    isolate->ThrowError("First argument must be provided");
    return;
  }
 
  Handle<Object> arg = Utils::OpenHandle(*info[0]);
  if (!arg->IsHeapObject()) {
    isolate->ThrowError("First argument must be a HeapObject");
    return;
  }
 
  int size = HeapObject::cast(*arg).Size();
  info.GetReturnValue().Set(v8::Integer::New(isolate, size));
}
 
Handle<FunctionTemplateInfo> NewFunctionTemplate(
    Isolate* isolate, FunctionCallback func,
    ConstructorBehavior constructor_behavior) {
  // Use the API functions here as they are more convenient to use.
  v8::Isolate* api_isolate = reinterpret_cast<v8::Isolate*>(isolate);
  Local<FunctionTemplate> function_template =
      FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior,
                            SideEffectType::kHasSideEffect);
  return v8::Utils::OpenHandle(*function_template);
}
 
Handle<JSFunction> CreateFunc(Isolate* isolate, FunctionCallback func,
                              Handle<String> name, bool is_constructor) {
  ConstructorBehavior constructor_behavior = is_constructor
                                                 ? ConstructorBehavior::kAllow
                                                 : ConstructorBehavior::kThrow;
  Handle<FunctionTemplateInfo> function_template =
      NewFunctionTemplate(isolate, func, constructor_behavior);
  return ApiNatives::InstantiateFunction(function_template, name)
      .ToHandleChecked();
}
 
void InstallFunc(Isolate* isolate, Handle<JSObject> holder,
                 FunctionCallback func, const char* name, int num_parameters,
                 bool is_constructor) {
  Factory* factory = isolate->factory();
  Handle<String> function_name = factory->NewStringFromAsciiChecked(name);
  Handle<JSFunction> function =
      CreateFunc(isolate, func, function_name, is_constructor);
  function->shared().set_length(num_parameters);
  JSObject::AddProperty(isolate, holder, function_name, function, NONE);
}
 
void InstallGetter(Isolate* isolate, Handle<JSObject> object,
                   FunctionCallback func, const char* name) {
  Factory* factory = isolate->factory();
  Handle<String> property_name = factory->NewStringFromAsciiChecked(name);
  Handle<JSFunction> getter = CreateFunc(isolate, func, property_name, false);
  Handle<Object> setter = factory->null_value();
  JSObject::DefineOwnAccessorIgnoreAttributes(object, property_name, getter,
                                              setter, FROZEN);
}
 
void InstallFunction(Isolate* isolate, Handle<JSObject> holder,
                     FunctionCallback func, const char* name,
                     int num_parameters) {
  InstallFunc(isolate, holder, func, name, num_parameters, false);
}
 
void InstallConstructor(Isolate* isolate, Handle<JSObject> holder,
                        FunctionCallback func, const char* name,
                        int num_parameters) {
  InstallFunc(isolate, holder, func, name, num_parameters, true);
}
 
// namespace
 
void SandboxTesting::InstallMemoryCorruptionApi(Isolate* isolate) {
  CHECK(GetProcessWideSandbox()->is_initialized());
 
#ifndef V8_EXPOSE_MEMORY_CORRUPTION_API
#error "This function should not be available in any shipping build "          \
       "where it could potentially be abused to facilitate exploitation."
#endif
 
  Factory* factory = isolate->factory();
 
  // Create the special Sandbox object that provides read/write access to the
  // sandbox address space alongside other miscellaneous functionality.
  Handle<JSObject> sandbox =
      factory->NewJSObject(isolate->object_function(), AllocationType::kOld);
 
  InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
  InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
  InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
 
  // Install the Sandbox object as property on the global object.
  Handle<JSGlobalObject> global = isolate->global_object();
  Handle<String> name = factory->NewStringFromAsciiChecked("Sandbox");
  JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
 
#endif  // V8_EXPOSE_MEMORY_CORRUPTION_API
#ifdef V8_EXPOSE_MEMORY_CORRUPTION_API
 
namespace {
 
// Sandbox.byteLength
void SandboxGetByteLength(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
  double sandbox_size = GetProcessWideSandbox()->size();
  info.GetReturnValue().Set(v8::Number::New(isolate, sandbox_size));
}
 
// new Sandbox.MemoryView(info) -> Sandbox.MemoryView
void SandboxMemoryView(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
  Local<v8::Context> context = isolate->GetCurrentContext();
 
  if (!info.IsConstructCall()) {
    isolate->ThrowError("Sandbox.MemoryView must be invoked with 'new'");
    return;
  }
 
  Local<v8::Integer> arg1, arg2;
  if (!info[0]->ToInteger(context).ToLocal(&arg1) ||
      !info[1]->ToInteger(context).ToLocal(&arg2)) {
    isolate->ThrowError("Expects two number arguments (start offset and size)");
    return;
  }
 
  Sandbox* sandbox = GetProcessWideSandbox();
  CHECK_LE(sandbox->size(), kMaxSafeIntegerUint64);
 
  uint64_t offset = arg1->Value();
  uint64_t size = arg2->Value();
  if (offset > sandbox->size() || size > sandbox->size() ||
      (offset + size) > sandbox->size()) {
    isolate->ThrowError(
        "The MemoryView must be entirely contained within the sandbox");
    return;
  }
 
  Factory* factory = reinterpret_cast<Isolate*>(isolate)->factory();
  std::unique_ptr<BackingStore> memory = BackingStore::WrapAllocation(
      reinterpret_cast<void*>(sandbox->base() + offset), size,
      v8::BackingStore::EmptyDeleter, nullptr, SharedFlag::kNotShared);
  if (!memory) {
    isolate->ThrowError("Out of memory: MemoryView backing store");
    return;
  }
  Handle<JSArrayBuffer> buffer = factory->NewJSArrayBuffer(std::move(memory));
  info.GetReturnValue().Set(Utils::ToLocal(buffer));
}
 
// Sandbox.getAddressOf(object) -> Number
void SandboxGetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
 
  if (info.Length() == 0) {
    isolate->ThrowError("First argument must be provided");
    return;
  }
 
  Handle<Object> arg = Utils::OpenHandle(*info[0]);
  if (!arg->IsHeapObject()) {
    isolate->ThrowError("First argument must be a HeapObject");
    return;
  }
 
  // HeapObjects must be allocated inside the pointer compression cage so their
  // address relative to the start of the sandbox can be obtained simply by
  // taking the lowest 32 bits of the absolute address.
  uint32_t address = static_cast<uint32_t>(HeapObject::cast(*arg).address());
  info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}
 
// Sandbox.getSizeOf(object) -> Number
void SandboxGetSizeOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
  DCHECK(ValidateCallbackInfo(info));
  v8::Isolate* isolate = info.GetIsolate();
 
  if (info.Length() == 0) {
    isolate->ThrowError("First argument must be provided");
    return;
  }
 
  Handle<Object> arg = Utils::OpenHandle(*info[0]);
  if (!arg->IsHeapObject()) {
    isolate->ThrowError("First argument must be a HeapObject");
    return;
  }
 
  int size = HeapObject::cast(*arg).Size();
  info.GetReturnValue().Set(v8::Integer::New(isolate, size));
}
 
Handle<FunctionTemplateInfo> NewFunctionTemplate(
    Isolate* isolate, FunctionCallback func,
    ConstructorBehavior constructor_behavior) {
  // Use the API functions here as they are more convenient to use.
  v8::Isolate* api_isolate = reinterpret_cast<v8::Isolate*>(isolate);
  Local<FunctionTemplate> function_template =
      FunctionTemplate::New(api_isolate, func, {}, {}, 0, constructor_behavior,
                            SideEffectType::kHasSideEffect);
  return v8::Utils::OpenHandle(*function_template);
}
 
Handle<JSFunction> CreateFunc(Isolate* isolate, FunctionCallback func,
                              Handle<String> name, bool is_constructor) {
  ConstructorBehavior constructor_behavior = is_constructor
                                                 ? ConstructorBehavior::kAllow
                                                 : ConstructorBehavior::kThrow;
  Handle<FunctionTemplateInfo> function_template =
      NewFunctionTemplate(isolate, func, constructor_behavior);
  return ApiNatives::InstantiateFunction(function_template, name)
      .ToHandleChecked();
}
 
void InstallFunc(Isolate* isolate, Handle<JSObject> holder,
                 FunctionCallback func, const char* name, int num_parameters,
                 bool is_constructor) {
  Factory* factory = isolate->factory();
  Handle<String> function_name = factory->NewStringFromAsciiChecked(name);
  Handle<JSFunction> function =
      CreateFunc(isolate, func, function_name, is_constructor);
  function->shared().set_length(num_parameters);
  JSObject::AddProperty(isolate, holder, function_name, function, NONE);
}
 
void InstallGetter(Isolate* isolate, Handle<JSObject> object,
                   FunctionCallback func, const char* name) {
  Factory* factory = isolate->factory();
  Handle<String> property_name = factory->NewStringFromAsciiChecked(name);
  Handle<JSFunction> getter = CreateFunc(isolate, func, property_name, false);
  Handle<Object> setter = factory->null_value();
  JSObject::DefineOwnAccessorIgnoreAttributes(object, property_name, getter,
                                              setter, FROZEN);
}
 
void InstallFunction(Isolate* isolate, Handle<JSObject> holder,
                     FunctionCallback func, const char* name,
                     int num_parameters) {
  InstallFunc(isolate, holder, func, name, num_parameters, false);
}
 
void InstallConstructor(Isolate* isolate, Handle<JSObject> holder,
                        FunctionCallback func, const char* name,
                        int num_parameters) {
  InstallFunc(isolate, holder, func, name, num_parameters, true);
}
 
// namespace
 
void SandboxTesting::InstallMemoryCorruptionApi(Isolate* isolate) {
  CHECK(GetProcessWideSandbox()->is_initialized());
 
#ifndef V8_EXPOSE_MEMORY_CORRUPTION_API
#error "This function should not be available in any shipping build "          \
       "where it could potentially be abused to facilitate exploitation."
#endif
 
  Factory* factory = isolate->factory();
 
  // Create the special Sandbox object that provides read/write access to the
  // sandbox address space alongside other miscellaneous functionality.
  Handle<JSObject> sandbox =
      factory->NewJSObject(isolate->object_function(), AllocationType::kOld);
 
  InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
  InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
  InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
 
  // Install the Sandbox object as property on the global object.
  Handle<JSGlobalObject> global = isolate->global_object();
  Handle<String> name = factory->NewStringFromAsciiChecked("Sandbox");
  JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
 
#endif  // V8_EXPOSE_MEMORY_CORRUPTION_API
Sandbox.byteLength
new Sandbox.MemoryView(info) -> Sandbox.MemoryView
Sandbox.getAddressOf(object) -> Number
Sandbox.getSizeOf(object) -> Number
Sandbox.byteLength
new Sandbox.MemoryView(info) -> Sandbox.MemoryView
Sandbox.getAddressOf(object) -> Number
Sandbox.getSizeOf(object) -> Number
void SandboxTesting::InstallMemoryCorruptionApi(Isolate* isolate) {
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
#error "This function should not be available in any shipping build "          \
       "where it could potentially be abused to facilitate exploitation."
#endif
 
  CHECK(Sandbox::current()->is_initialized());
 
  // Create the special Sandbox object that provides read/write access to the
  // sandbox address space alongside other miscellaneous functionality.
  Handle<JSObject> sandbox = isolate->factory()->NewJSObject(
      isolate->object_function(), AllocationType::kOld);
 
  InstallGetter(isolate, sandbox, SandboxGetBase, "base");
  InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
  InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
  InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetObjectAt, "getObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxIsValidObjectAt, "isValidObjectAt",
                  1);
  InstallFunction(isolate, sandbox, SandboxIsWritable, "isWritable", 1);
  InstallFunction(isolate, sandbox, SandboxIsWritableObjectAt,
                  "isWritableObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOfObjectAt,
                  "getSizeOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOf,
                  "getInstanceTypeOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOfObjectAt,
                  "getInstanceTypeOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOf,
                  "getInstanceTypeIdOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOfObjectAt,
                  "getInstanceTypeIdOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdFor,
                  "getInstanceTypeIdFor", 1);
  InstallFunction(isolate, sandbox, SandboxGetFieldOffset, "getFieldOffset", 2);
 
  // Install the Sandbox object as property on the global object.
  Handle<JSGlobalObject> global = isolate->global_object();
  Handle<String> name =
      isolate->factory()->NewStringFromAsciiChecked("Sandbox");
  JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
 
#endif  // V8_ENABLE_MEMORY_CORRUPTION_API
void SandboxTesting::InstallMemoryCorruptionApi(Isolate* isolate) {
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
#error "This function should not be available in any shipping build "          \
       "where it could potentially be abused to facilitate exploitation."
#endif
 
  CHECK(Sandbox::current()->is_initialized());
 
  // Create the special Sandbox object that provides read/write access to the
  // sandbox address space alongside other miscellaneous functionality.
  Handle<JSObject> sandbox = isolate->factory()->NewJSObject(
      isolate->object_function(), AllocationType::kOld);
 
  InstallGetter(isolate, sandbox, SandboxGetBase, "base");
  InstallGetter(isolate, sandbox, SandboxGetByteLength, "byteLength");
  InstallConstructor(isolate, sandbox, SandboxMemoryView, "MemoryView", 2);
  InstallFunction(isolate, sandbox, SandboxGetAddressOf, "getAddressOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetObjectAt, "getObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxIsValidObjectAt, "isValidObjectAt",
                  1);
  InstallFunction(isolate, sandbox, SandboxIsWritable, "isWritable", 1);
  InstallFunction(isolate, sandbox, SandboxIsWritableObjectAt,
                  "isWritableObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOf, "getSizeOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetSizeOfObjectAt,
                  "getSizeOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOf,
                  "getInstanceTypeOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeOfObjectAt,
                  "getInstanceTypeOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOf,
                  "getInstanceTypeIdOf", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdOfObjectAt,
                  "getInstanceTypeIdOfObjectAt", 1);
  InstallFunction(isolate, sandbox, SandboxGetInstanceTypeIdFor,
                  "getInstanceTypeIdFor", 1);
  InstallFunction(isolate, sandbox, SandboxGetFieldOffset, "getFieldOffset", 2);
 
  // Install the Sandbox object as property on the global object.
  Handle<JSGlobalObject> global = isolate->global_object();
  Handle<String> name =
      isolate->factory()->NewStringFromAsciiChecked("Sandbox");
  JSObject::AddProperty(isolate, global, name, sandbox, DONT_ENUM);
}
 
#endif  // V8_ENABLE_MEMORY_CORRUPTION_API
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     e3aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 7c57acde43..652aa480dd 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -3336,6 +3336,7 @@ static void AccessIndexedEnumerator(const PropertyCallbackInfo<Array>& info) {}
 
 Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
   Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
+  if (/* DISABLES CODE */ (false)) {
   global_template->Set(Symbol::GetToStringTag(isolate),
                        String::NewFromUtf8Literal(isolate, "global"));
   global_template->Set(isolate, "version",
@@ -3358,8 +3359,10 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
                        FunctionTemplate::New(isolate, ReadLine));
   global_template->Set(isolate, "load",
                        FunctionTemplate::New(isolate, ExecuteFile));
+  }
   global_template->Set(isolate, "setTimeout",
                        FunctionTemplate::New(isolate, SetTimeout));
+  if (/* DISABLES CODE */ (false)) {
   // Some Emscripten-generated code tries to call 'quit', which in turn would
   // call C's exit(). This would lead to memory leaks, because there is no way
   // we can terminate cleanly then, so we need a way to hide 'quit'.
@@ -3390,6 +3393,7 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
     global_template->Set(isolate, "async_hooks",
                          Shell::CreateAsyncHookTemplate(isolate));
   }
+  }
 
   if (options.throw_on_failed_access_check ||
       options.noop_on_failed_access_check) {
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     e3aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 7c57acde43..652aa480dd 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -3336,6 +3336,7 @@ static void AccessIndexedEnumerator(const PropertyCallbackInfo<Array>& info) {}
 
 Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
   Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
+  if (/* DISABLES CODE */ (false)) {
   global_template->Set(Symbol::GetToStringTag(isolate),
                        String::NewFromUtf8Literal(isolate, "global"));
   global_template->Set(isolate, "version",
@@ -3358,8 +3359,10 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
                        FunctionTemplate::New(isolate, ReadLine));
   global_template->Set(isolate, "load",
                        FunctionTemplate::New(isolate, ExecuteFile));
+  }
   global_template->Set(isolate, "setTimeout",
                        FunctionTemplate::New(isolate, SetTimeout));
+  if (/* DISABLES CODE */ (false)) {
   // Some Emscripten-generated code tries to call 'quit', which in turn would
   // call C's exit(). This would lead to memory leaks, because there is no way
   // we can terminate cleanly then, so we need a way to hide 'quit'.
@@ -3390,6 +3393,7 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
     global_template->Set(isolate, "async_hooks",
                          Shell::CreateAsyncHookTemplate(isolate));
   }
+  }
 
   if (options.throw_on_failed_access_check ||
       options.noop_on_failed_access_check) {
Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  if (/* DISABLES CODE */ (false)) {
  global_template->Set(Symbol::GetToStringTag(isolate),
                       String::NewFromUtf8Literal(isolate, "global"));
  global_template->Set(isolate, "version",
                       FunctionTemplate::New(isolate, Version));
 
  global_template->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
  global_template->Set(isolate, "printErr",
                       FunctionTemplate::New(isolate, PrintErr));
  global_template->Set(isolate, "write",
                       FunctionTemplate::New(isolate, WriteStdout));
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "writeFile",
                         FunctionTemplate::New(isolate, WriteFile));
  }
  global_template->Set(isolate, "read",
                       FunctionTemplate::New(isolate, ReadFile));
  global_template->Set(isolate, "readbuffer",
                       FunctionTemplate::New(isolate, ReadBuffer));
  global_template->Set(isolate, "readline",
                       FunctionTemplate::New(isolate, ReadLine));
  global_template->Set(isolate, "load",
                       FunctionTemplate::New(isolate, ExecuteFile));
  }
  global_template->Set(isolate, "setTimeout",
                       FunctionTemplate::New(isolate, SetTimeout));
  if (/* DISABLES CODE */ (false)) {
  // Some Emscripten-generated code tries to call 'quit', which in turn would
  // call C's exit(). This would lead to memory leaks, because there is no way
  // we can terminate cleanly then, so we need a way to hide 'quit'.
  if (!options.omit_quit) {
    global_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit));
  }
  global_template->Set(isolate, "testRunner",
                       Shell::CreateTestRunnerTemplate(isolate));
  global_template->Set(isolate, "Realm", Shell::CreateRealmTemplate(isolate));
  global_template->Set(isolate, "performance",
                       Shell::CreatePerformanceTemplate(isolate));
  global_template->Set(isolate, "Worker", Shell::CreateWorkerTemplate(isolate));
 
  // Prevent fuzzers from creating side effects.
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "os", Shell::CreateOSTemplate(isolate));
  }
  global_template->Set(isolate, "d8", Shell::CreateD8Template(isolate));
 
#ifdef V8_FUZZILLI
  global_template->Set(
      String::NewFromUtf8(isolate, "fuzzilli", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Fuzzilli), PropertyAttribute::DontEnum);
#endif  // V8_FUZZILLI
 
  if (i::v8_flags.expose_async_hooks) {
    global_template->Set(isolate, "async_hooks",
                         Shell::CreateAsyncHookTemplate(isolate));
  }
  }
Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  if (/* DISABLES CODE */ (false)) {
  global_template->Set(Symbol::GetToStringTag(isolate),
                       String::NewFromUtf8Literal(isolate, "global"));
  global_template->Set(isolate, "version",
                       FunctionTemplate::New(isolate, Version));
 
  global_template->Set(isolate, "print", FunctionTemplate::New(isolate, Print));
  global_template->Set(isolate, "printErr",
                       FunctionTemplate::New(isolate, PrintErr));
  global_template->Set(isolate, "write",
                       FunctionTemplate::New(isolate, WriteStdout));
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "writeFile",
                         FunctionTemplate::New(isolate, WriteFile));
  }
  global_template->Set(isolate, "read",
                       FunctionTemplate::New(isolate, ReadFile));
  global_template->Set(isolate, "readbuffer",
                       FunctionTemplate::New(isolate, ReadBuffer));
  global_template->Set(isolate, "readline",
                       FunctionTemplate::New(isolate, ReadLine));
  global_template->Set(isolate, "load",
                       FunctionTemplate::New(isolate, ExecuteFile));
  }
  global_template->Set(isolate, "setTimeout",
                       FunctionTemplate::New(isolate, SetTimeout));
  if (/* DISABLES CODE */ (false)) {
  // Some Emscripten-generated code tries to call 'quit', which in turn would
  // call C's exit(). This would lead to memory leaks, because there is no way
  // we can terminate cleanly then, so we need a way to hide 'quit'.
  if (!options.omit_quit) {
    global_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit));
  }
  global_template->Set(isolate, "testRunner",
                       Shell::CreateTestRunnerTemplate(isolate));
  global_template->Set(isolate, "Realm", Shell::CreateRealmTemplate(isolate));
  global_template->Set(isolate, "performance",
                       Shell::CreatePerformanceTemplate(isolate));
  global_template->Set(isolate, "Worker", Shell::CreateWorkerTemplate(isolate));
 
  // Prevent fuzzers from creating side effects.
  if (!i::v8_flags.fuzzing) {
    global_template->Set(isolate, "os", Shell::CreateOSTemplate(isolate));
  }
  global_template->Set(isolate, "d8", Shell::CreateD8Template(isolate));
 
#ifdef V8_FUZZILLI
  global_template->Set(
      String::NewFromUtf8(isolate, "fuzzilli", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Fuzzilli), PropertyAttribute::DontEnum);
#endif  // V8_FUZZILLI
 
  if (i::v8_flags.expose_async_hooks) {
    global_template->Set(isolate, "async_hooks",
                         Shell::CreateAsyncHookTemplate(isolate));
  }
  }
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     8afK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
diff --git a/src/common/code-memory-access-inl.h b/src/common/code-memory-access-inl.h
index 4b8ac2e5c7..58542dc4e5 100644
--- a/src/common/code-memory-access-inl.h
+++ b/src/common/code-memory-access-inl.h
@@ -17,6 +17,18 @@
 namespace v8 {
 namespace internal {
 
+RwMemoryWriteScope::RwMemoryWriteScope() {
+  SetWritable();
+}
+
+RwMemoryWriteScope::RwMemoryWriteScope(const char *comment) {
+  SetWritable();
+}
+
+RwMemoryWriteScope::~RwMemoryWriteScope() {
+  SetReadOnly();
+}
+
 RwxMemoryWriteScope::RwxMemoryWriteScope(const char* comment) {
   if (!v8_flags.jitless) {
     SetWritable();
diff --git a/src/common/code-memory-access.cc b/src/common/code-memory-access.cc
index be3b9741d2..b8c59c331f 100644
--- a/src/common/code-memory-access.cc
+++ b/src/common/code-memory-access.cc
@@ -4,6 +4,10 @@
 
 #include "src/common/code-memory-access-inl.h"
 #include "src/utils/allocation.h"
+#include "src/common/globals.h"
+#include "src/execution/isolate-inl.h"
+
+#include <sys/mman.h>
 
 namespace v8 {
 namespace internal {
@@ -11,6 +15,68 @@ namespace internal {
 ThreadIsolation::TrustedData ThreadIsolation::trusted_data_;
 ThreadIsolation::UntrustedData ThreadIsolation::untrusted_data_;
 
+thread_local int RwMemoryWriteScope::nesting_level_ = 0;
+
+static void ProtectSpace(Space *space, int prot) {
+  if (space->memory_chunk_list().Empty()) {
+    return;
+  }
+
+  MemoryChunk *c = space->memory_chunk_list().front();
+  while (c) {
+    void *addr = reinterpret_cast<void *>(c->address());
+    // printf("making %p read-only\n", addr);
+    CHECK(mprotect(addr, RoundDown(sizeof(MemoryChunk), 0x1000), prot) == 0);
+    c = c->list_node().next();
+  }
+}
+
+// static
+void RwMemoryWriteScope::SetWritable() {
+  if (nesting_level_ == 0) {
+    int prot = PROT_READ | PROT_WRITE;
+    Isolate *isolate = Isolate::Current();
+    for (int i = FIRST_MUTABLE_SPACE; i < LAST_SPACE; i++) {
+      Space *space = isolate->heap()->space(i);
+      if (space == nullptr) {
+        continue;
+      }
+
+      if (!v8_flags.minor_mc && i == NEW_SPACE) {
+        SemiSpaceNewSpace* semi_space_new_space = SemiSpaceNewSpace::From(static_cast<NewSpace *>(space));
+        ProtectSpace(&semi_space_new_space->from_space(), prot);
+        ProtectSpace(&semi_space_new_space->to_space(), prot);
+      } else {
+        ProtectSpace(space, prot);
+      }
+    }
+  }
+  nesting_level_++;
+}
+
+// static
+void RwMemoryWriteScope::SetReadOnly() {
+  nesting_level_--;
+  if (nesting_level_ == 0) {
+    int prot = PROT_READ;
+    Isolate *isolate = Isolate::Current();
+    for (int i = FIRST_MUTABLE_SPACE; i < LAST_SPACE; i++) {
+      Space *space = isolate->heap()->space(i);
+      if (space == nullptr) {
+        continue;
+      }
+
+      if (!v8_flags.minor_mc && i == NEW_SPACE) {
+        SemiSpaceNewSpace* semi_space_new_space = SemiSpaceNewSpace::From(static_cast<NewSpace *>(space));
+        ProtectSpace(&semi_space_new_space->from_space(), prot);
+        ProtectSpace(&semi_space_new_space->to_space(), prot);
+      } else {
+        ProtectSpace(space, prot);
+      }
+    }
+  }
+}
+
 #if V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
 thread_local int RwxMemoryWriteScope::code_space_write_nesting_level_ = 0;
 #endif  // V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
diff --git a/src/common/code-memory-access.h b/src/common/code-memory-access.h
index e90dcc9a64..4e835c87c7 100644
--- a/src/common/code-memory-access.h
+++ b/src/common/code-memory-access.h
@@ -331,6 +331,22 @@ class V8_NODISCARD RwxMemoryWriteScope {
 #endif  // V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
 };
 
+class V8_NODISCARD RwMemoryWriteScope final {
+ public:
+  V8_INLINE RwMemoryWriteScope();
+  V8_INLINE explicit RwMemoryWriteScope(const char *comment);
+  V8_INLINE ~RwMemoryWriteScope();
+
+  RwMemoryWriteScope(const RwMemoryWriteScope&) = delete;
+  RwMemoryWriteScope& operator=(const RwMemoryWriteScope&) = delete;
+
+ private:
+  static void SetWritable();
+  static void SetReadOnly();
+
+  static thread_local int nesting_level_;
+};
+
 // This class is a no-op version of the RwxMemoryWriteScope class above.
 // It's used as a target type for other scope type definitions when a no-op
 // semantics is required.
@@ -346,7 +362,7 @@ using CodePageMemoryModificationScopeForPerf = RwxMemoryWriteScope;
 #else
 // Without per-thread write permissions, we only use permission switching for
 // debugging and the perf impact of this doesn't matter.
-using CodePageMemoryModificationScopeForPerf = NopRwxMemoryWriteScope;
+using CodePageMemoryModificationScopeForPerf = RwMemoryWriteScope;
 #endif
 
 // Same as the RwxMemoryWriteScope but without inlining the code.
diff --git a/src/execution/isolate.cc b/src/execution/isolate.cc
index c935c8c5ca..2534b7bc2e 100644
--- a/src/execution/isolate.cc
+++ b/src/execution/isolate.cc
@@ -61,6 +61,7 @@
 #include "src/handles/persistent-handles.h"
 #include "src/heap/heap-inl.h"
 #include "src/heap/heap-verifier.h"
+#include "src/heap/heap.h"
 #include "src/heap/local-heap-inl.h"
 #include "src/heap/parked-scope.h"
 #include "src/heap/read-only-heap.h"
@@ -4236,6 +4237,8 @@ void Isolate::VerifyStaticRoots() {
 bool Isolate::Init(SnapshotData* startup_snapshot_data,
                    SnapshotData* read_only_snapshot_data,
                    SnapshotData* shared_heap_snapshot_data, bool can_rehash) {
+  CodePageHeaderModificationScope scope{};
+
   TRACE_ISOLATE(init);
 
 #ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
diff --git a/src/heap/heap.cc b/src/heap/heap.cc
index 4d7c611dfd..d3027dd3b2 100644
--- a/src/heap/heap.cc
+++ b/src/heap/heap.cc
@@ -1748,6 +1748,8 @@ void Heap::CollectGarbage(AllocationSpace space,
 
   DCHECK(AllowGarbageCollection::IsAllowed());
 
+  CodePageHeaderModificationScope hsm{};
+
   const char* collector_reason = nullptr;
   const GarbageCollector collector =
       SelectGarbageCollector(space, gc_reason, &collector_reason);
diff --git a/src/heap/heap.h b/src/heap/heap.h
index 6675b09348..67772961fe 100644
--- a/src/heap/heap.h
+++ b/src/heap/heap.h
@@ -2537,7 +2537,10 @@ class V8_NODISCARD AlwaysAllocateScopeForTesting {
 // scope.
 #if V8_HEAP_USE_PTHREAD_JIT_WRITE_PROTECT
 using CodePageHeaderModificationScope = RwxMemoryWriteScope;
+#elif defined(V8_JITLESS)
+using CodePageHeaderModificationScope = RwMemoryWriteScope;
 #else
+#error "JIT not supported"
 // When write protection of code page headers is not required the scope is
 // a no-op.
 using CodePageHeaderModificationScope = NopRwxMemoryWriteScope;
@@ -2560,6 +2563,7 @@ class V8_NODISCARD CodePageMemoryModificationScope {
   bool scope_active_;
   base::Optional<base::MutexGuard> guard_;
 #endif
+  RwMemoryWriteScope header_scope_;
 
   // Disallow any GCs inside this scope, as a relocation of the underlying
   // object would change the {MemoryChunk} that this scope targets.
diff --git a/src/heap/memory-chunk.cc b/src/heap/memory-chunk.cc
index 0fc34c12a1..479993b219 100644
--- a/src/heap/memory-chunk.cc
+++ b/src/heap/memory-chunk.cc
@@ -10,6 +10,7 @@
 #include "src/common/globals.h"
 #include "src/heap/basic-memory-chunk.h"
 #include "src/heap/code-object-registry.h"
+#include "src/heap/heap.h"
 #include "src/heap/marking-state-inl.h"
 #include "src/heap/memory-allocator.h"
 #include "src/heap/memory-chunk-inl.h"
@@ -223,6 +224,7 @@ void MemoryChunk::ReleaseAllAllocatedMemory() {
 }
 
 SlotSet* MemoryChunk::AllocateSlotSet(RememberedSetType type) {
+  CodePageHeaderModificationScope scope{};
   SlotSet* new_slot_set = SlotSet::Allocate(buckets());
   SlotSet* old_slot_set = base::AsAtomicPointer::AcquireRelease_CompareAndSwap(
       &slot_set_[type], nullptr, new_slot_set);
diff --git a/src/heap/paged-spaces.cc b/src/heap/paged-spaces.cc
index 6083c2a420..5b6d7c965e 100644
--- a/src/heap/paged-spaces.cc
+++ b/src/heap/paged-spaces.cc
@@ -311,6 +311,9 @@ void PagedSpaceBase::SetTopAndLimit(Address top, Address limit, Address end) {
   DCHECK_GE(end, limit);
   DCHECK(top == limit ||
          Page::FromAddress(top) == Page::FromAddress(limit - 1));
+
+  CodePageHeaderModificationScope scope{};
+
   BasicMemoryChunk::UpdateHighWaterMark(allocation_info_.top());
   allocation_info_.Reset(top, limit);
 
@@ -814,6 +817,7 @@ void PagedSpaceBase::UpdateInlineAllocationLimit() {
 // OldSpace implementation
 
 bool PagedSpaceBase::RefillLabMain(int size_in_bytes, AllocationOrigin origin) {
+  CodePageHeaderModificationScope scope("RefillLabMain");
   VMState<GC> state(heap()->isolate());
   RCS_SCOPE(heap()->isolate(),
             RuntimeCallCounterId::kGC_Custom_SlowAllocateRaw);
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     8afK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2M7r3q4U0K9r3g2Q4x3X3g2G2M7X3N6Q4x3V1k6D9K9h3y4W2L8Y4y4W2M7#2)9J5c8V1I4u0b7@1g2z5f1@1g2Q4x3X3b7J5i4K6u0W2x3l9`.`.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
diff --git a/src/common/code-memory-access-inl.h b/src/common/code-memory-access-inl.h
index 4b8ac2e5c7..58542dc4e5 100644
--- a/src/common/code-memory-access-inl.h
+++ b/src/common/code-memory-access-inl.h
@@ -17,6 +17,18 @@
 namespace v8 {
 namespace internal {
 
+RwMemoryWriteScope::RwMemoryWriteScope() {
+  SetWritable();
+}
+
+RwMemoryWriteScope::RwMemoryWriteScope(const char *comment) {
+  SetWritable();
+}
+
+RwMemoryWriteScope::~RwMemoryWriteScope() {
+  SetReadOnly();
+}
+
 RwxMemoryWriteScope::RwxMemoryWriteScope(const char* comment) {
   if (!v8_flags.jitless) {
     SetWritable();
diff --git a/src/common/code-memory-access.cc b/src/common/code-memory-access.cc
index be3b9741d2..b8c59c331f 100644
--- a/src/common/code-memory-access.cc
+++ b/src/common/code-memory-access.cc
@@ -4,6 +4,10 @@
 
 #include "src/common/code-memory-access-inl.h"
 #include "src/utils/allocation.h"
+#include "src/common/globals.h"
+#include "src/execution/isolate-inl.h"
+
+#include <sys/mman.h>
 
 namespace v8 {
 namespace internal {
@@ -11,6 +15,68 @@ namespace internal {
 ThreadIsolation::TrustedData ThreadIsolation::trusted_data_;
 ThreadIsolation::UntrustedData ThreadIsolation::untrusted_data_;
 
+thread_local int RwMemoryWriteScope::nesting_level_ = 0;
+
+static void ProtectSpace(Space *space, int prot) {
+  if (space->memory_chunk_list().Empty()) {
+    return;
+  }
+
+  MemoryChunk *c = space->memory_chunk_list().front();
+  while (c) {
+    void *addr = reinterpret_cast<void *>(c->address());
+    // printf("making %p read-only\n", addr);
+    CHECK(mprotect(addr, RoundDown(sizeof(MemoryChunk), 0x1000), prot) == 0);
+    c = c->list_node().next();
+  }
+}
+
+// static
+void RwMemoryWriteScope::SetWritable() {
+  if (nesting_level_ == 0) {
+    int prot = PROT_READ | PROT_WRITE;
+    Isolate *isolate = Isolate::Current();
+    for (int i = FIRST_MUTABLE_SPACE; i < LAST_SPACE; i++) {
+      Space *space = isolate->heap()->space(i);
+      if (space == nullptr) {
+        continue;
+      }
+
+      if (!v8_flags.minor_mc && i == NEW_SPACE) {
+        SemiSpaceNewSpace* semi_space_new_space = SemiSpaceNewSpace::From(static_cast<NewSpace *>(space));
+        ProtectSpace(&semi_space_new_space->from_space(), prot);
+        ProtectSpace(&semi_space_new_space->to_space(), prot);
+      } else {
+        ProtectSpace(space, prot);
+      }
+    }
+  }
+  nesting_level_++;
+}
+
+// static
+void RwMemoryWriteScope::SetReadOnly() {
+  nesting_level_--;
+  if (nesting_level_ == 0) {
+    int prot = PROT_READ;
+    Isolate *isolate = Isolate::Current();
+    for (int i = FIRST_MUTABLE_SPACE; i < LAST_SPACE; i++) {
+      Space *space = isolate->heap()->space(i);
+      if (space == nullptr) {
+        continue;
+      }
+
+      if (!v8_flags.minor_mc && i == NEW_SPACE) {
+        SemiSpaceNewSpace* semi_space_new_space = SemiSpaceNewSpace::From(static_cast<NewSpace *>(space));
+        ProtectSpace(&semi_space_new_space->from_space(), prot);
+        ProtectSpace(&semi_space_new_space->to_space(), prot);
+      } else {
+        ProtectSpace(space, prot);
+      }
+    }
+  }
+}
+
 #if V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
 thread_local int RwxMemoryWriteScope::code_space_write_nesting_level_ = 0;
 #endif  // V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
diff --git a/src/common/code-memory-access.h b/src/common/code-memory-access.h
index e90dcc9a64..4e835c87c7 100644
--- a/src/common/code-memory-access.h
+++ b/src/common/code-memory-access.h
@@ -331,6 +331,22 @@ class V8_NODISCARD RwxMemoryWriteScope {
 #endif  // V8_HAS_PTHREAD_JIT_WRITE_PROTECT || V8_HAS_PKU_JIT_WRITE_PROTECT
 };
 
+class V8_NODISCARD RwMemoryWriteScope final {
+ public:
+  V8_INLINE RwMemoryWriteScope();
+  V8_INLINE explicit RwMemoryWriteScope(const char *comment);
+  V8_INLINE ~RwMemoryWriteScope();
+
+  RwMemoryWriteScope(const RwMemoryWriteScope&) = delete;
+  RwMemoryWriteScope& operator=(const RwMemoryWriteScope&) = delete;
+
+ private:
+  static void SetWritable();
+  static void SetReadOnly();
+
+  static thread_local int nesting_level_;
+};
+
 // This class is a no-op version of the RwxMemoryWriteScope class above.
 // It's used as a target type for other scope type definitions when a no-op
 // semantics is required.
@@ -346,7 +362,7 @@ using CodePageMemoryModificationScopeForPerf = RwxMemoryWriteScope;

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回