WAVM: Incorrect bounds check when translating a reference type can results in buffer overrun
出问题的代码如下:
libraries/chain/include/eosio/chain/webassembly/wavm.hpp
/**
* Specialization for transcribing a reference type in the native method signature
* This type transcribes into an int32 pointer checks the validity of that memory
* range before dispatching to the native method
*
* @tparam Ret - the return type of the native method
* @tparam Inputs - the remaining native parameters to transcribe
* @tparam Translated - the list of transcribed wasm parameters
*/
template<typename T, typename Ret, typename... Inputs, typename ...Translated>
struct intrinsic_invoker_impl<Ret, std::tuple<T &, Inputs...>, std::tuple<Translated...>> {
using next_step = intrinsic_invoker_impl<Ret, std::tuple<Inputs...>, std::tuple<Translated..., I32>>;
using then_type = Ret (*)(running_instance_context &, T &, Inputs..., Translated...);
template<then_type Then, typename U=T>
static auto translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr) -> std::enable_if_t<std::is_const<U>::value, Ret> {
// references cannot be created for null pointers
FC_ASSERT((U32)ptr != 0);
MemoryInstance* mem = ctx.memory;
if(!mem || (U32)ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
Runtime::causeException(Exception::Cause::accessViolation);
T &base = *(T*)(getMemoryBaseAddress(mem)+(U32)ptr);
if ( reinterpret_cast<uintptr_t>(&base) % alignof(T) != 0 ) {
wlog( "misaligned const reference" );
std::remove_const_t<T> copy;
T* copy_ptr = ©
memcpy( (void*)copy_ptr, (void*)&base, sizeof(T) );
return Then(ctx, *copy_ptr, rest..., translated...);
}
return Then(ctx, base, rest..., translated...);
}
template<then_type Then, typename U=T>
static auto translate_one(running_instance_context& ctx, Inputs... rest, Translated... translated, I32 ptr) -> std::enable_if_t<!std::is_const<U>::value, Ret> {
// references cannot be created for null pointers
FC_ASSERT((U32)ptr != 0);
MemoryInstance* mem = ctx.memory;
if(!mem || (U32)ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
Runtime::causeException(Exception::Cause::accessViolation);
T &base = *(T*)(getMemoryBaseAddress(mem)+(U32)ptr);
if ( reinterpret_cast<uintptr_t>(&base) % alignof(T) != 0 ) {
wlog( "misaligned reference" );
std::remove_const_t<T> copy;
T* copy_ptr = ©
memcpy( (void*)copy_ptr, (void*)&base, sizeof(T) );
Ret ret = Then(ctx, *copy_ptr, rest..., translated...);
memcpy( (void*)&base, (void*)copy_ptr, sizeof(T) );
return ret;
}
return Then(ctx, base, rest..., translated...);
}
template<then_type Then>
static const auto fn() {
return next_step::template fn<translate_one<Then>>();
}
};
这是一个调用包含reference类型native函数的结构,例如
void printi128( const int128_t* value );
这个api就会用这个结构进行调用。
出问题的代码如下,下面是已经修复的代码:
if(!mem || (U32)ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
原来的代码如下:
if(!mem || ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
即没有加(U32)将int转换成unsigned int,而sizeof(T)是一个unsigned long,在64位平台上占用8个字节。所以当ptr和sizeof(T)相加时,编译器会将ptr扩展为8个字节,但是扩展的方式是和ptr的有signed和unsigned有关的,例如int类型的-1扩展成8字节的unsigned long时,实际上在内存中的表示就从0xffffffff变成0xffffffffffffffff,而如果是先将int类型的-1转成unsigned int,在这里是(U32)ptr,这样编译器在对其进行扩展的时候就会变成0x00000000ffffffff,即高位补0,所以(U32)ptr+sizeof(T)和ptr+sizeof(T)的结果是不一样的。
这样,当没有加U32强制转换时,一个负的ptr就有可能绕过检测,而调用下面的代码:
T &base = *(T*)(getMemoryBaseAddress(mem)+ptr);
现己修复成
T &base = *(T*)(getMemoryBaseAddress(mem)+(U32)ptr);
这样会造成覆盖不在wasm空间之内的内存,造成程序的数据的破坏。