fix: TA 收缩缓冲越界读守卫与 BigInt 哨值归一

This commit is contained in:
2026-09-22 21:56:48 +00:00
parent 32e8dce003
commit 6cf0588423
+74 -27
View File
@@ -5,7 +5,9 @@ use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind};
use oxide_types::private_key::{int_key_value, is_int_key, make_well_known_symbol_key, WELL_KNOWN_SYMBOL_SPECIES}; use oxide_types::private_key::{int_key_value, is_int_key, make_well_known_symbol_key, WELL_KNOWN_SYMBOL_SPECIES};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use crate::array_buffer::{array_buffer_payload, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; use crate::array_buffer::{
array_buffer_payload, default_array_buffer_proto, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH,
};
use oxide_runtime_api::{NativeResult, VmHost}; use oxide_runtime_api::{NativeResult, VmHost};
@@ -141,7 +143,8 @@ fn create_typed_array<H: VmHost>(
/// (规范上忽略 species,toReversed/toSorted/with 的交付语义)。 /// (规范上忽略 species,toReversed/toSorted/with 的交付语义)。
fn create_same_type_typed_array<H: VmHost>(vm: &mut H, kind: TypedArrayKind, length: usize) -> *mut JsObject { fn create_same_type_typed_array<H: VmHost>(vm: &mut H, kind: TypedArrayKind, length: usize) -> *mut JsObject {
let bpe = kind.bytes_per_element(); let bpe = kind.bytes_per_element();
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; length * bpe])); let buffer =
JsValue::from_js_object(new_array_buffer(vm, vec![0; length * bpe], 0, default_array_buffer_proto(vm)));
create_typed_array(vm, kind, buffer, 0, length) create_typed_array(vm, kind, buffer, 0, length)
} }
@@ -460,6 +463,11 @@ fn write_typed_array_element<H: VmHost>(
/// 读 TypedArray 元素并转为对应 JS 值:BigInt 类型读为 BigInt 值(i64/u64 位模式 /// 读 TypedArray 元素并转为对应 JS 值:BigInt 类型读为 BigInt 值(i64/u64 位模式
/// 原样搬运,无精度损失),数值类型按位模式读为 Number。 /// 原样搬运,无精度损失),数值类型按位模式读为 Number。
fn read_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { fn read_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue {
// 底层缓冲可能已被 resize 收缩:元素字节区越界时按 undefined 读
// (live 视图越界语义),不切片 panic。
if offset + kind.bytes_per_element() > bytes.len() {
return JsValue::undefined();
}
// 安全读取:先校验 slice 长度匹配目标类型字节数,避免 try_into panic。 // 安全读取:先校验 slice 长度匹配目标类型字节数,避免 try_into panic。
macro_rules! read_bytes { macro_rules! read_bytes {
($slice:expr, $ty:ty) => {{ ($slice:expr, $ty:ty) => {{
@@ -546,6 +554,11 @@ fn element_error_text<H: VmHost>(vm: &mut H, err: JsValue) -> String {
/// 把已按元素类型转换的值(数值 kind 为 Number、BigInt kind 为 BigInt)按位模式 /// 把已按元素类型转换的值(数值 kind 为 Number、BigInt kind 为 BigInt)按位模式
/// 截断写入底层 buffer。转换由调用方 [`ta_element_value`] 完成,本函数无副作用。 /// 截断写入底层 buffer。转换由调用方 [`ta_element_value`] 完成,本函数无副作用。
fn write_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: JsValue) { fn write_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: JsValue) {
// 底层缓冲可能已被 resize 收缩:元素字节区越界时静默不写
// (live 视图越界语义),不切片 panic。
if offset + kind.bytes_per_element() > bytes.len() {
return;
}
let n = oxide_runtime_api::to_number(value); let n = oxide_runtime_api::to_number(value);
match kind { match kind {
TypedArrayKind::Int8 => bytes[offset] = n as i32 as u8 as i8 as u8, TypedArrayKind::Int8 => bytes[offset] = n as i32 as u8 as i8 as u8,
@@ -559,8 +572,10 @@ fn write_element<H: VmHost>(vm: &mut H, kind: TypedArrayKind, bytes: &mut [u8],
TypedArrayKind::Float64 => bytes[offset..offset + 8].copy_from_slice(&n.to_ne_bytes()), TypedArrayKind::Float64 => bytes[offset..offset + 8].copy_from_slice(&n.to_ne_bytes()),
TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => { TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
// 取低 64 位位模式:与 2^64-1 掩码后恒非负且可转 u64,BigInt64 按位 // 取低 64 位位模式:与 2^64-1 掩码后恒非负且可转 u64,BigInt64 按位
// 模式重解释为 i64(二进制补码)。 // 模式重解释为 i64(二进制补码)。非 BigInt 值按 ToBigInt 语义落 0
let v = vm.bigint_value(value); // (越界读哨值回写路径,防对非 BigInt 取位模式)。
static ZERO_BI: std::sync::LazyLock<BigInt> = std::sync::LazyLock::new(|| BigInt::from(0_i64));
let v: &BigInt = if value.is_bigint() { vm.bigint_value(value) } else { &ZERO_BI };
let low = (v & (BigInt::from(u64::MAX))) let low = (v & (BigInt::from(u64::MAX)))
.to_u64() .to_u64()
.expect("与 u64::MAX 掩码后恒在 u64 范围"); .expect("与 u64::MAX 掩码后恒在 u64 范围");
@@ -688,7 +703,9 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
return NativeResult::Err(range_error(vm, "TypedArray byteOffset out of bounds")); return NativeResult::Err(range_error(vm, "TypedArray byteOffset out of bounds"));
} }
let remaining = buffer_len - byte_offset; let remaining = buffer_len - byte_offset;
let length = if args.len() > 3 { // 规范以 "length is not undefined" 分支:显式 undefined 与省略同义,
// 取整缓冲剩余长度(live 全长),不落入 ToIndex(undefined)=0。
let length = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
native_try!(to_index(vm, vm.reg(args[3]), "TypedArray length out of bounds")) native_try!(to_index(vm, vm.reg(args[3]), "TypedArray length out of bounds"))
} else { } else {
remaining / bpe remaining / bpe
@@ -703,7 +720,8 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
} else { } else {
let values = native_try!(collect_array_like(vm, first, true)); let values = native_try!(collect_array_like(vm, first, true));
let byte_len = values.len().saturating_mul(bpe); let byte_len = values.len().saturating_mul(bpe);
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); let buffer =
JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len], 0, default_array_buffer_proto(vm)));
let payload_ptr = native_try!(array_buffer_payload(vm, buffer)); let payload_ptr = native_try!(array_buffer_payload(vm, buffer));
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer_ref) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else { let Some(buffer_ref) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else {
@@ -728,7 +746,8 @@ fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) ->
if byte_len > MAX_ARRAY_BUFFER_LENGTH { if byte_len > MAX_ARRAY_BUFFER_LENGTH {
return NativeResult::Err(range_error(vm, "invalid TypedArray length")); return NativeResult::Err(range_error(vm, "invalid TypedArray length"));
} }
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len])); let buffer =
JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len], 0, default_array_buffer_proto(vm)));
(buffer, 0, len) (buffer, 0, len)
}; };
@@ -939,20 +958,26 @@ pub fn typed_array_subarray<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
} else { } else {
0 0
}; };
let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { let end_undefined = args.len() <= 2 || vm.reg(args[2]).is_undefined();
let end = if !end_undefined {
native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) native_try!(normalize_index(vm, vm.reg(args[2]), view.length))
} else { } else {
view.length view.length
}; };
let count = end.max(start).saturating_sub(start); let count = end.max(start).saturating_sub(start);
let byte_offset = view.byte_offset + start * view.kind.bytes_per_element(); let byte_offset = view.byte_offset + start * view.kind.bytes_per_element();
let result = native_try!(typed_array_species_create( // end 未给且底层 buffer 可缩放:构造参数省略 count,子视图随 buffer
vm, // 伸缩(定长 buffer 恒三参,count 定死区间)。
this_val, let resizable = match array_buffer_payload(vm, view.buffer) {
view.kind, Ok(p) => unsafe { &*p }.max_byte_length != 0,
vec![view.buffer, JsValue::int(byte_offset as i32), JsValue::int(count as i32)], Err(_) => false,
None, };
)); let ctor_args = if end_undefined && resizable {
vec![view.buffer, JsValue::int(byte_offset as i32)]
} else {
vec![view.buffer, JsValue::int(byte_offset as i32), JsValue::int(count as i32)]
};
let result = native_try!(typed_array_species_create(vm, this_val, view.kind, ctor_args, None));
NativeResult::Ok(result) NativeResult::Ok(result)
} }
@@ -1110,14 +1135,30 @@ pub fn typed_array_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 越界返回 undefined。 /// 越界返回 undefined。
fn ta_read<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize) -> Result<JsValue, JsValue> { fn ta_read<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize) -> Result<JsValue, JsValue> {
if index >= view.length { if index >= view.length {
return Ok(JsValue::undefined()); return Ok(ta_oob_neutral(vm, view.kind));
} }
let payload_ptr = array_buffer_payload(vm, view.buffer)?; let payload_ptr = array_buffer_payload(vm, view.buffer)?;
// SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); return Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
}; };
Ok(read_element(vm, view.kind, buffer, absolute_byte_offset(view, index))) let val = read_element(vm, view.kind, buffer, absolute_byte_offset(view, index));
// 底层缓冲被 resize 收缩后越界读返回 undefined;BigInt 类型归一成 0n
// (ToBigInt(undefined) 语义),使原地方法的比较/回写拿到合法 BigInt。
if is_bigint_kind(view.kind) && val.is_undefined() {
return Ok(vm.new_bigint(BigInt::from(0_i64)));
}
Ok(val)
}
/// 越界索引的中性元素值:BigInt 类型给 0n(后续按 BigInt 消费),数值类型
/// 给 undefined(后续 to_number 归 0)。
fn ta_oob_neutral<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> JsValue {
if is_bigint_kind(kind) {
vm.new_bigint(BigInt::from(0_i64))
} else {
JsValue::undefined()
}
} }
/// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。 /// 把已转换的值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。
@@ -2096,11 +2137,10 @@ pub fn uint8array_to_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
}; };
NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_base64( // 视图范围按当前缓冲长度收口(缓冲可被 resize 收缩)。
&buffer[view.byte_offset..view.byte_offset + view.length], let start = view.byte_offset.min(buffer.len());
alphabet, let end = (view.byte_offset + view.length).min(buffer.len());
omit_padding, NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_base64(&buffer[start..end], alphabet, omit_padding)))
)))
} }
/// `%Uint8Array%.prototype.toHex()`:ValidateUint8Array 后编码小写两位 hex。 /// `%Uint8Array%.prototype.toHex()`:ValidateUint8Array 后编码小写两位 hex。
@@ -2112,9 +2152,10 @@ pub fn uint8array_to_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else { let Some(buffer) = unsafe { &*payload_ptr }.data.as_deref() else {
return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid"));
}; };
NativeResult::Ok( // 视图范围按当前缓冲长度收口(缓冲可被 resize 收缩)。
vm.new_string_owned(crate::ta_codec::encode_hex(&buffer[view.byte_offset..view.byte_offset + view.length])), let start = view.byte_offset.min(buffer.len());
) let end = (view.byte_offset + view.length).min(buffer.len());
NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_hex(&buffer[start..end])))
} }
/// `%Uint8Array%.prototype.setFromBase64(string, options)`:解码写入视图, /// `%Uint8Array%.prototype.setFromBase64(string, options)`:解码写入视图,
@@ -2131,7 +2172,10 @@ pub fn uint8array_set_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeR
}; };
let start = view.byte_offset; let start = view.byte_offset;
let (read, written) = native_try!(ta_decode_base64(vm, string, options, Some(view.length), &mut |idx, b| { let (read, written) = native_try!(ta_decode_base64(vm, string, options, Some(view.length), &mut |idx, b| {
// 缓冲可被 resize 收缩:目标字节越界时静默不写(live 视图越界语义)。
if start + idx < buffer.len() {
buffer[start + idx] = b; buffer[start + idx] = b;
}
})); }));
NativeResult::Ok(ta_read_written_result(vm, read, written)) NativeResult::Ok(ta_read_written_result(vm, read, written))
} }
@@ -2153,7 +2197,10 @@ pub fn uint8array_set_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
let start = view.byte_offset; let start = view.byte_offset;
let units = vm.string_units(string).into_owned(); let units = vm.string_units(string).into_owned();
let (read, written) = match crate::ta_codec::decode_hex(&units, Some(view.length), &mut |idx, b| { let (read, written) = match crate::ta_codec::decode_hex(&units, Some(view.length), &mut |idx, b| {
// 缓冲可被 resize 收缩:目标字节越界时静默不写(live 视图越界语义)。
if start + idx < buffer.len() {
buffer[start + idx] = b; buffer[start + idx] = b;
}
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")), Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")),
@@ -2169,7 +2216,7 @@ pub fn uint8array_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
let mut bytes = Vec::new(); let mut bytes = Vec::new();
native_try!(ta_decode_base64(vm, string, options, None, &mut |_, b| bytes.push(b))); native_try!(ta_decode_base64(vm, string, options, None, &mut |_, b| bytes.push(b)));
let len = bytes.len(); let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes)); let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes, 0, default_array_buffer_proto(vm)));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len))) NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
} }
@@ -2186,7 +2233,7 @@ pub fn uint8array_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")), Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")),
} }
let len = bytes.len(); let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes)); let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes, 0, default_array_buffer_proto(vm)));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len))) NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
} }