fix(relinker): reject invalid and overflowing alignment

This commit is contained in:
Tomás Ortellado 2026-10-05 08:44:55 -03:00
parent 53dc19cf61
commit 9f9e4e28b8
3 changed files with 52 additions and 6 deletions

View File

@ -2,6 +2,8 @@
#define IO_BUFFERUTILS_HPP
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <vector>
#include <string>
@ -27,7 +29,14 @@ std::uint64_t ReadU64(const std::vector<std::uint8_t>& buf, std::size_t offset);
template<typename T>
constexpr T AlignUp(T value, T alignment) {
return (value + alignment - 1) / alignment * alignment;
if (value < 0 || alignment <= 0)
throw std::invalid_argument("AlignUp requires a nonnegative value and a positive alignment");
const T remainder = value % alignment;
if (remainder == 0) return value;
const T padding = alignment - remainder;
if (value > std::numeric_limits<T>::max() - padding)
throw std::overflow_error("Aligned value exceeds integer range");
return value + padding;
}
std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment);

View File

@ -60,11 +60,7 @@ void AppendString(std::vector<std::uint8_t>& buf, const std::string& str) {
}
void AlignBuffer(std::vector<std::uint8_t>& buf, std::size_t alignment) {
const std::size_t remainder = buf.size() % alignment;
if (remainder != 0) {
const std::size_t padding = alignment - remainder;
buf.resize(buf.size() + padding, 0);
}
buf.resize(AlignUp(buf.size(), alignment), 0);
}
std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) {

View File

@ -11,6 +11,8 @@ namespace {
using Bytes = std::vector<std::uint8_t>;
static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32);
void require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
@ -71,12 +73,51 @@ void writeBounds(const TWrite& write, const std::string& name) {
invalidRanges<TValue>(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name);
}
template<typename TError, typename TOperation>
void requireThrows(const TOperation& operation, const std::string& message) {
try {
operation();
} catch (const TError&) {
return;
}
throw std::runtime_error(message);
}
template<typename TValue>
void alignmentBounds() {
constexpr auto maximum = std::numeric_limits<TValue>::max();
require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero");
require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up");
require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment");
require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum");
require(Io::AlignUp(static_cast<TValue>(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum");
require(Io::AlignUp(static_cast<TValue>(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result");
requireThrows<std::overflow_error>([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result");
requireThrows<std::invalid_argument>([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment");
}
}
int main() {
try {
const Io::ByteReader reader;
const Io::ByteWriter writer;
alignmentBounds<std::uint32_t>();
alignmentBounds<std::uint64_t>();
alignmentBounds<std::int32_t>();
alignmentBounds<std::int64_t>();
requireThrows<std::invalid_argument>([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value");
requireThrows<std::invalid_argument>([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment");
require(Io::AlignUp64(std::numeric_limits<std::uint64_t>::max() - 1, std::numeric_limits<std::uint64_t>::max()) == std::numeric_limits<std::uint64_t>::max(), "AlignUp64 overflowed for a representable result");
requireThrows<std::overflow_error>([] { Io::AlignUp64(std::numeric_limits<std::uint64_t>::max(), 16); }, "AlignUp64 accepted an overflowing result");
Bytes aligned{1, 2, 3};
const auto original = aligned;
requireThrows<std::invalid_argument>([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment");
require(aligned == original, "AlignBuffer modified the buffer on failure");
Io::AlignBuffer(aligned, 4);
require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding");
Io::AlignBuffer(aligned, 4);
require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer");
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");