Merge pull request #190 from RomainROCH/fix/buffer-offset-overflow
fix(relinker): reject overflowing buffer offsets
This commit is contained in:
commit
a54cffadc3
|
|
@ -106,6 +106,14 @@ endif()
|
|||
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(buffer_bounds_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
|
||||
)
|
||||
target_include_directories(buffer_bounds_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/io/include)
|
||||
add_test(NAME buffer_bounds COMMAND buffer_bounds_tests)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_executable(file_writer_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/io/tests/FileWriterTests.cpp
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ void WriteU8(std::vector<std::uint8_t>& buf, std::size_t offset, std::uint8_t v)
|
|||
}
|
||||
|
||||
void WriteU16(std::vector<std::uint8_t>& buf, std::size_t offset, std::uint16_t v) {
|
||||
if (offset + 2 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 2)
|
||||
throw std::out_of_range("WriteU16 out of bounds");
|
||||
std::memcpy(buf.data() + offset, &v, 2);
|
||||
}
|
||||
|
||||
void WriteU32(std::vector<std::uint8_t>& buf, std::size_t offset, std::uint32_t v) {
|
||||
if (offset + 4 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 4)
|
||||
throw std::out_of_range("WriteU32 out of bounds");
|
||||
std::memcpy(buf.data() + offset, &v, 4);
|
||||
}
|
||||
|
||||
void WriteU64(std::vector<std::uint8_t>& buf, std::size_t offset, std::uint64_t v) {
|
||||
if (offset + 8 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 8)
|
||||
throw std::out_of_range("WriteU64 out of bounds");
|
||||
std::memcpy(buf.data() + offset, &v, 8);
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) {
|
|||
}
|
||||
|
||||
std::uint16_t ReadU16(const std::vector<std::uint8_t>& buf, std::size_t offset) {
|
||||
if (offset + 2 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 2)
|
||||
throw std::out_of_range("ReadU16 out of bounds");
|
||||
std::uint16_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 2);
|
||||
|
|
@ -80,7 +80,7 @@ std::uint16_t ReadU16(const std::vector<std::uint8_t>& buf, std::size_t offset)
|
|||
}
|
||||
|
||||
std::uint32_t ReadU32(const std::vector<std::uint8_t>& buf, std::size_t offset) {
|
||||
if (offset + 4 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 4)
|
||||
throw std::out_of_range("ReadU32 out of bounds");
|
||||
std::uint32_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 4);
|
||||
|
|
@ -88,7 +88,7 @@ std::uint32_t ReadU32(const std::vector<std::uint8_t>& buf, std::size_t offset)
|
|||
}
|
||||
|
||||
std::uint64_t ReadU64(const std::vector<std::uint8_t>& buf, std::size_t offset) {
|
||||
if (offset + 8 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 8)
|
||||
throw std::out_of_range("ReadU64 out of bounds");
|
||||
std::uint64_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 8);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
namespace Io {
|
||||
|
||||
std::uint16_t ByteReader::ReadU16(const std::vector<std::uint8_t>& buf, std::size_t offset) const {
|
||||
if (offset + 2 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 2)
|
||||
throw std::out_of_range("ByteReader::ReadU16 out of bounds");
|
||||
std::uint16_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 2);
|
||||
|
|
@ -13,7 +13,7 @@ std::uint16_t ByteReader::ReadU16(const std::vector<std::uint8_t>& buf, std::siz
|
|||
}
|
||||
|
||||
std::uint32_t ByteReader::ReadU32(const std::vector<std::uint8_t>& buf, std::size_t offset) const {
|
||||
if (offset + 4 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 4)
|
||||
throw std::out_of_range("ByteReader::ReadU32 out of bounds");
|
||||
std::uint32_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 4);
|
||||
|
|
@ -21,7 +21,7 @@ std::uint32_t ByteReader::ReadU32(const std::vector<std::uint8_t>& buf, std::siz
|
|||
}
|
||||
|
||||
std::uint64_t ByteReader::ReadU64(const std::vector<std::uint8_t>& buf, std::size_t offset) const {
|
||||
if (offset + 8 > buf.size())
|
||||
if (offset > buf.size() || buf.size() - offset < 8)
|
||||
throw std::out_of_range("ByteReader::ReadU64 out of bounds");
|
||||
std::uint64_t v;
|
||||
std::memcpy(&v, buf.data() + offset, 8);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
#include <io/BufferUtils.hpp>
|
||||
#include <io/ByteReader.hpp>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
|
||||
void require(bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
template<typename TOperation>
|
||||
void requireOutOfRange(const TOperation& operation, const std::string& name, std::size_t offset) {
|
||||
try {
|
||||
operation();
|
||||
} catch (const std::out_of_range& error) {
|
||||
require(std::string(error.what()) == name + " out of bounds", name + " changed its out-of-range diagnostic");
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(name + " accepted invalid offset " + std::to_string(offset));
|
||||
}
|
||||
|
||||
template<typename TValue, typename TOperation>
|
||||
void invalidRanges(Bytes& bytes, const TOperation& operation, const std::string& name) {
|
||||
const auto original = bytes;
|
||||
constexpr auto maxOffset = std::numeric_limits<std::size_t>::max();
|
||||
for (const std::size_t offset : {bytes.size() - sizeof(TValue) + 1, bytes.size(), bytes.size() + 1,
|
||||
maxOffset - sizeof(TValue), maxOffset - sizeof(TValue) + 1, maxOffset}) {
|
||||
requireOutOfRange([&] { operation(bytes, offset); }, name, offset);
|
||||
require(bytes == original, name + " modified the buffer on failure");
|
||||
}
|
||||
for (std::size_t size = 0; size < sizeof(TValue); ++size) {
|
||||
Bytes shortBuffer(size, 0xCC);
|
||||
requireOutOfRange([&] { operation(shortBuffer, 0); }, name, 0);
|
||||
require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure");
|
||||
requireOutOfRange([&] { operation(shortBuffer, maxOffset); }, name, maxOffset);
|
||||
require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TValue, typename TRead>
|
||||
void readBounds(const TRead& read, const std::string& name) {
|
||||
const auto value = static_cast<TValue>(0x8877665544332211ull);
|
||||
Bytes bytes(sizeof(value) + 2, 0xCC);
|
||||
std::memcpy(bytes.data() + 1, &value, sizeof(value));
|
||||
require(read(bytes, 1) == value, name + " failed an unaligned read");
|
||||
bytes.pop_back();
|
||||
require(read(bytes, 1) == value, name + " rejected a read ending at the buffer boundary");
|
||||
invalidRanges<TValue>(bytes, read, name);
|
||||
}
|
||||
|
||||
template<typename TValue, typename TWrite>
|
||||
void writeBounds(const TWrite& write, const std::string& name) {
|
||||
const auto value = static_cast<TValue>(0x8877665544332211ull);
|
||||
Bytes bytes(sizeof(value) + 2, 0xCC);
|
||||
auto expected = bytes;
|
||||
std::memcpy(expected.data() + 1, &value, sizeof(value));
|
||||
write(bytes, 1, value);
|
||||
require(bytes == expected, name + " failed an unaligned write");
|
||||
bytes.pop_back();
|
||||
expected.pop_back();
|
||||
bytes.assign(expected.size(), 0xCC);
|
||||
write(bytes, 1, value);
|
||||
require(bytes == expected, name + " rejected a write ending at the buffer boundary");
|
||||
invalidRanges<TValue>(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const Io::ByteReader reader;
|
||||
readBounds<std::uint16_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16");
|
||||
readBounds<std::uint32_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32");
|
||||
readBounds<std::uint64_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64");
|
||||
readBounds<std::uint16_t>(Io::ReadU16, "ReadU16");
|
||||
readBounds<std::uint32_t>(Io::ReadU32, "ReadU32");
|
||||
readBounds<std::uint64_t>(Io::ReadU64, "ReadU64");
|
||||
writeBounds<std::uint8_t>(Io::WriteU8, "WriteU8");
|
||||
writeBounds<std::uint16_t>(Io::WriteU16, "WriteU16");
|
||||
writeBounds<std::uint32_t>(Io::WriteU32, "WriteU32");
|
||||
writeBounds<std::uint64_t>(Io::WriteU64, "WriteU64");
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue