1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-10-23 20:55:43 +02:00

Fix overly strict bounds check in tiniergltf (#16590)

This makes sure that models exported by Goxel are not falsely rejected. It applies to exporters using strides more broadly.

A workaround is to add padding to the buffer and buffer view.
This commit is contained in:
Lars Müller
2025-10-18 20:00:37 +02:00
committed by GitHub
parent a049174f12
commit 51f19b4329

View File

@@ -31,6 +31,22 @@ static inline void check(bool cond) {
throw std::runtime_error("invalid glTF"); throw std::runtime_error("invalid glTF");
} }
// Unsigned arithmetic helpers with wraparound checks
template<typename T>
static inline T checkedAdd(T a, T b) {
T c = std::numeric_limits<T>::max() - a;
check(b <= c);
return a + b;
}
template<typename T>
static inline T checkedMul(T a, T b) {
T prod = a * b;
check(a == 0 || prod / a == b);
return prod;
}
template <typename T> template <typename T>
static inline void checkIndex(const std::optional<std::vector<T>> &vec, static inline void checkIndex(const std::optional<std::vector<T>> &vec,
const std::optional<std::size_t> &i) { const std::optional<std::size_t> &i) {
@@ -1241,10 +1257,8 @@ struct GlTF {
checkForall(bufferViews, [&](const BufferView &view) { checkForall(bufferViews, [&](const BufferView &view) {
check(buffers.has_value()); check(buffers.has_value());
const Buffer &buf = buffers->at(view.buffer); const Buffer &buf = buffers->at(view.buffer);
// Be careful because of possible integer overflows. // View must fit into the buffer
check(view.byteOffset < buf.byteLength); check(checkedAdd(view.byteOffset, view.byteLength) <= buf.byteLength);
check(view.byteLength <= buf.byteLength);
check(view.byteOffset <= buf.byteLength - view.byteLength);
}); });
const auto checkAccessor = [&](const auto &accessor, const auto checkAccessor = [&](const auto &accessor,
@@ -1252,10 +1266,13 @@ struct GlTF {
const BufferView &view = bufferViews->at(bufferView); const BufferView &view = bufferViews->at(bufferView);
if (view.byteStride.has_value()) if (view.byteStride.has_value())
check(*view.byteStride % accessor.componentSize() == 0); check(*view.byteStride % accessor.componentSize() == 0);
check(byteOffset < view.byteLength);
// Use division to avoid overflows. const std::size_t effective_byte_stride = view.byteStride.value_or(accessor.elementSize());
const auto effective_byte_stride = view.byteStride.value_or(accessor.elementSize()); // Accessor must fit into the buffer view: The last element must be fully in bounds.
check(count <= (view.byteLength - byteOffset) / effective_byte_stride); // Want: (count-1) * effective_byte_stride + accessor.elementSize() + byteOffset <= view.byteLength
// Be careful to avoid wraparound.
check(checkedAdd(checkedMul(count - 1, effective_byte_stride),
checkedAdd(accessor.elementSize(), byteOffset)) <= view.byteLength);
}; };
checkForall(accessors, [&](const Accessor &accessor) { checkForall(accessors, [&](const Accessor &accessor) {
if (accessor.bufferView.has_value()) if (accessor.bufferView.has_value())