ChromeV8CVE-2026-0902内存读取漏洞简析&POC纪要

admin 2026-01-27 14:28:45 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了ChromeV8引擎CVE-2026-0902内存读取漏洞。漏洞源于JSON解析时,在GC导致descriptor_array缩水后,未重新检查索引边界,导致越界读取。修复方案是在访问描述符数组前二次检查索引是否越界。文中提供了POC构造思路,建议通过大量属性和长字符串触发GC以复现漏洞。 综合评分: 86 文章分类: 漏洞分析,二进制安全,漏洞POC,代码审计


cover_image

Chrome V8 CVE-2026-0902 内存读取漏洞简析 & POC纪要

原创

鉴帷安全 鉴帷安全

鉴帷安全

2026年1月27日 11:48 中国香港

该篇基于chromuim源码解析。

当前源码基于最新版,需要漏洞环境请回滚。

定位源码:

path:\src\v8\src\json\json-parser.cc

关键入口点

bool&nbsp;JsonParser<Char>::ParseJsonObjectProperties

后续补丁分析:

bool&nbsp;JsonParser<Char>::ParseJsonObjectProperties(&nbsp; &nbsp; JsonContinuation* cont, MessageTemplate first_token_msg,&nbsp; &nbsp; Handle<DescriptorArray> descriptors) {&nbsp; using FastIterableState =&nbsp;DescriptorArray::FastIterableState;&nbsp;&nbsp;if&nbsp;constexpr&nbsp;(fast_iterable_state ==&nbsp;FastIterableState::kJsonSlow) {&nbsp; &nbsp;&nbsp;do&nbsp;{&nbsp; &nbsp; &nbsp;&nbsp;EXPECT_NEXT_RETURN_ON_ERROR(JsonToken::STRING, first_token_msg,&nbsp;false);&nbsp; &nbsp; &nbsp; first_token_msg =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName;&nbsp; &nbsp; &nbsp; JsonString key =&nbsp;ScanJsonPropertyKey(cont);&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; }&nbsp;while&nbsp;(Check<JsonToken::COMMA>());&nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp;&nbsp;DCHECK_GT(descriptors->number_of_descriptors(),&nbsp;0);&nbsp; &nbsp; InternalIndex idx{0};&nbsp; &nbsp;&nbsp;do&nbsp;{&nbsp; &nbsp; &nbsp;&nbsp;EXPECT_NEXT_RETURN_ON_ERROR(JsonToken::STRING, first_token_msg,&nbsp;false);&nbsp; &nbsp; &nbsp; first_token_msg =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName;&nbsp; &nbsp; &nbsp;&nbsp;bool&nbsp;key_match;&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;constexpr&nbsp;(fast_iterable_state ==&nbsp;FastIterableState::kJsonFast) {&nbsp; &nbsp; &nbsp; &nbsp; uint32_t key_length;&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DisallowGarbageCollection no_gc;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Tagged<String> expected_key = Cast<String>(descriptors->GetKey(idx));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Tagged<Map> key_map = expected_key->map();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Fast iterable keys are guaranteed to be 1-byte.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;uint8_t* expected_chars =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;GetFastKeyChars(isolate_, expected_key, key_map, no_gc);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key_length = expected_key->length();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key_match =&nbsp;FastKeyMatch(expected_chars, key_length);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_LIKELY(key_match)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JsonString key =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;JsonString(position(), key_length,&nbsp;false,&nbsp;true,&nbsp;false);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++idx;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cursor_ += key_length +&nbsp;1&nbsp;/* double quote */;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JsonString key =&nbsp;ScanJsonPropertyKey(cont);&nbsp;//CVE-2026-0902&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!key.is_index()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Feedback doesn't match. Finish processing the current property&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// and continue in slow-path if we have more properties.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Check<JsonToken::COMMA>()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;ParseJsonObjectProperties<FastIterableState::kJsonSlow>(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cont, first_token_msg, {});&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;DCHECK_EQ(fast_iterable_state,&nbsp;FastIterableState::kUnknown);&nbsp; &nbsp; &nbsp; &nbsp; JsonString key =&nbsp;ScanJsonPropertyKey(cont);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Indices don't participate in fast iterable key checks.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(key.is_index()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Before accessing the descriptor array, make sure that it wasn't&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// shrunk during a potential GC after the previous range check.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(idx.as_int() >= descriptors->number_of_descriptors())) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break; &nbsp; &nbsp;//Check-After-Effect&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Check if the key is fast iterable.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Some of the checks below are not relevant for the parser, but are&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// requirements for fast iterable keys in general (e.g. for&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// JSON.stringify).&nbsp; &nbsp; &nbsp; &nbsp; Tagged<Name> property_name = descriptors->GetKey(idx);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;bool&nbsp;is_slow = key.has_escape();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Check that the property is enumerable and located in field.&nbsp; &nbsp; &nbsp; &nbsp; PropertyDetails details = descriptors->GetDetails(idx);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(details.IsDontEnum() ||&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; details.location() !=&nbsp;PropertyLocation::kField)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_slow =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Symbol property keys are slow.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(IsSymbol(property_name))) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_slow =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; key_match =&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_LIKELY(!is_slow)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DisallowGarbageCollection no_gc;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Property key is known to be fast so far, so it is guaranteed to&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// be a string.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Tagged<String> expected_key = Cast<String>(property_name);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Tagged<Map> key_map = expected_key->map();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(InstanceTypeChecker::IsTwoByteString(key_map)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Two-byte keys are slow.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_slow =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;uint8_t* expected_chars =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;GetFastKeyChars(isolate_, expected_key, key_map, no_gc);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;uint32_t&nbsp;key_length = expected_key->length();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key_match =&nbsp;FastKeyMatch(expected_chars, key_length, key);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(is_slow)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// The key is not fast iterable. Mark it as slow in the descriptor&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// array.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; descriptors->set_fast_iterable(FastIterableState::kJsonSlow);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Finish parsing the property.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// If key is not fast iterable or doesn't match the feedback, we&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// continue on the slow-path.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(is_slow || !key_match)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Check<JsonToken::COMMA>()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;ParseJsonObjectProperties<FastIterableState::kJsonSlow>(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cont, first_token_msg, {});&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// No more properties to scan, we are done.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; ++idx;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp;while&nbsp;(idx <&nbsp;InternalIndex(descriptors->number_of_descriptors()) &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Check<JsonToken::COMMA>());&nbsp; &nbsp;&nbsp;if&nbsp;constexpr&nbsp;(fast_iterable_state ==&nbsp;FastIterableState::kUnknown) {&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(idx ==&nbsp;InternalIndex(descriptors->number_of_descriptors())) {&nbsp; &nbsp; &nbsp; &nbsp; descriptors->set_fast_iterable_if(FastIterableState::kJsonFast,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FastIterableState::kUnknown);&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// Additional, unknown properties. Scan them slow.&nbsp; &nbsp;&nbsp;if&nbsp;(Check<JsonToken::COMMA>()) {&nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;ParseJsonObjectProperties<FastIterableState::kJsonSlow>(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cont, first_token_msg, descriptors);&nbsp; &nbsp; }&nbsp; }&nbsp;&nbsp;return&nbsp;true;}

核心变更点:

&nbsp; &nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; DCHECK_EQ(fast_iterable_state, FastIterableState::kUnknown);&nbsp; &nbsp; &nbsp; &nbsp; JsonString key = ScanJsonPropertyKey(cont);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Indices don't participate in fast iterable key checks.&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(key.is_index()) {&nbsp;//重要溢出点&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp; &nbsp; &nbsp; &nbsp; }

旧代码中,程序直接通过 idx 去获取属性名和属性详情(descriptors->GetKey(idx))。它假设了 idx 永远在 descriptors 的长度范围之内。在 JSON.parse(或类似的 JSON 属性解析)过程中,如果触发了垃圾回收 (GC),V8 可能会为了节省空间而对对象的 descriptor_array 进行“缩减”(Shrink)。

如果之前做过长度检查,但在访问之前发生了一次 GC,数组长度变短了,之前的检查就失效了。由于 idx 此时指向了一个已经不存在的索引,descriptors->GetKey(idx) 就会产生越界读取 (OOB Read)。

修复补丁:

&nbsp; DCHECK_EQ(fast_iterable_state, FastIterableState::kUnknown);&nbsp;&nbsp;JsonString&nbsp;key&nbsp;=&nbsp;ScanJsonPropertyKey(cont);&nbsp;&nbsp;// Indices don't participate in fast iterable key checks.&nbsp;&nbsp;if&nbsp;(key.is_index()) {&nbsp; &nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(!ParseJsonPropertyValue(key)))&nbsp;return&nbsp;false;&nbsp; &nbsp;&nbsp;continue;&nbsp; }&nbsp;&nbsp;// Before accessing the descriptor array, make sure that it wasn't&nbsp;&nbsp;// shrunk during a potential GC after the previous range check.&nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(idx.as_int() >= descriptors->number_of_descriptors())) {&nbsp; &nbsp;&nbsp;break; &nbsp; &nbsp;//Check-After-Effect //核心点&nbsp; }&nbsp;&nbsp;// Check if the key is fast iterable.&nbsp;&nbsp;// Some of the checks below are not relevant for the parser, but are&nbsp;&nbsp;// requirements for fast iterable keys in general (e.g. for&nbsp;&nbsp;// JSON.stringify).&nbsp; Tagged<Name> property_name = descriptors->GetKey(idx);&nbsp;&nbsp;bool&nbsp;is_slow&nbsp;=&nbsp;key.has_escape();&nbsp;&nbsp;// Check that the property is enumerable and located in field.&nbsp;&nbsp;PropertyDetails&nbsp;details&nbsp;=&nbsp;descriptors->GetDetails(idx);&nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(details.IsDontEnum() ||&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; details.location() != PropertyLocation::kField)) {&nbsp; &nbsp; is_slow =&nbsp;true;&nbsp; }&nbsp;&nbsp;// Symbol property keys are slow.&nbsp;&nbsp;if&nbsp;(V8_UNLIKELY(IsSymbol(property_name))) {&nbsp; &nbsp; is_slow =&nbsp;true;&nbsp; }

代码在每次访问描述符数组前,都强行重新获取一次 number_of_descriptors() 并与 idx 比较。如果发现索引已经超标(由于 GC 导致数组缩水),则立刻停止(break),防止读取非法的内存地址。

来尝试构建一个POC:

const&nbsp;obj = {};for&nbsp;(let&nbsp;i =&nbsp;0; i <&nbsp;1000; i++) {&nbsp; obj['prop'&nbsp;+ i] = i;}// 构造一个复杂的 JSON 字符串// 包含大量长字符串以在解析时触发内存压力let&nbsp;json_str =&nbsp;'{"a":1, '&nbsp;+&nbsp;Array(500).fill(0).map((_, i) =>&nbsp;`"p${i}":"${'A'.repeat(10000)}"`).join(',') +&nbsp;'}';// 触发解析并尝试在解析过程中诱发 GC// 有时可以通过自定义 getter 或大量的字面量解析来触发JSON.parse(json_str);

PS:为了稳定复现,可以在暂时修改源代码,然后编译他

&nbsp;JsonString key =&nbsp;ScanJsonPropertyKey(cont);&nbsp;isolate_->heap()->CollectAllGarbage(Heap::kNoGCFlags,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;GarbageCollectionReason::kTesting);&nbsp;&nbsp;//强制触发GC回收,增加复现稳定性

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:鉴帷安全 鉴帷安全 鉴帷安全《Chrome V8 CVE-2026-0902 内存读取漏洞简析 & POC纪要》

评论:0   参与:  0