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>>();
}
};
這是一個調(diào)用包含reference類型native函數(shù)的結(jié)構(gòu),例如
void printi128( const int128_t* value );
這個api就會用這個結(jié)構(gòu)進行調(diào)用。
出問題的代碼如下,下面是已經(jīng)修復的代碼:
if(!mem || (U32)ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
原來的代碼如下:
if(!mem || ptr+sizeof(T) >= IR::numBytesPerPage*Runtime::getMemoryNumPages(mem))
即沒有加(U32)將int轉(zhuǎn)換成unsigned int,而sizeof(T)是一個unsigned long,在64位平臺上占用8個字節(jié)。所以當ptr和sizeof(T)相加時,編譯器會將ptr擴展為8個字節(jié),但是擴展的方式是和ptr的有signed和unsigned有關的,例如int類型的-1擴展成8字節(jié)的unsigned long時,實際上在內(nèi)存中的表示就從0xffffffff變成0xffffffffffffffff,而如果是先將int類型的-1轉(zhuǎn)成unsigned int,在這里是(U32)ptr,這樣編譯器在對其進行擴展的時候就會變成0x00000000ffffffff,即高位補0,所以(U32)ptr+sizeof(T)和ptr+sizeof(T)的結(jié)果是不一樣的。
這樣,當沒有加U32強制轉(zhuǎn)換時,一個負的ptr就有可能繞過檢測,而調(diào)用下面的代碼:
T &base = *(T*)(getMemoryBaseAddress(mem)+ptr);
現(xiàn)己修復成
T &base = *(T*)(getMemoryBaseAddress(mem)+(U32)ptr);
這樣會造成覆蓋不在wasm空間之內(nèi)的內(nèi)存,造成程序的數(shù)據(jù)的破壞。