Prevent integer overflow when computing texture sizes and allocating memory for them.

Correctly check for failed allocations when resizing std::vector

PiperOrigin-RevId: 659488680
Change-Id: I7bf4c0b416e96eda2baec32b21be78456604f4b0
This commit is contained in:
Nimrod Gileadi
2024-08-05 02:58:02 -07:00
committed by Copybara-Service
parent ff5d339e48
commit 6a8e28f0e9
2 changed files with 92 additions and 9 deletions
+46 -9
View File
@@ -22,8 +22,10 @@
#include <cstdio>
#include <cstring>
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <new>
#include <optional>
#include <random>
#include <sstream>
@@ -3423,6 +3425,9 @@ void mjCTexture::Builtin2D(void) {
void mjCTexture::BuiltinCube(void) {
unsigned char RGB1[3], RGB2[3], RGBm[3], RGBi[3];
int w = width;
if (w > std::numeric_limits<int>::max() / w) {
throw mjCError(this, "Cube texture width is too large.");
}
int ww = width*width;
// convert fixed colors
@@ -3436,6 +3441,9 @@ void mjCTexture::BuiltinCube(void) {
// gradient
if (builtin == mjBUILTIN_GRADIENT) {
if (ww > std::numeric_limits<int>::max() / 18) {
throw mjCError(this, "Gradient texture width is too large.");
}
for (int r = 0; r < w; r++) {
for (int c = 0; c < w; c++) {
// compute normalized pixel coordinates
@@ -3677,12 +3685,17 @@ void mjCTexture::Load2D(std::string filename, const mjVFS* vfs) {
height = h;
// allocate and copy data
data.assign(nchannel*width*height, 0);
if (data.empty()) {
std::int64_t size = static_cast<std::int64_t>(width)*height;
if (size >= std::numeric_limits<int>::max() / nchannel || size <= 0) {
throw mjCError(this, "Texture too large");
}
try {
data.assign(nchannel*size, 0);
} catch (const std::bad_alloc& e) {
throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)",
(const char*)file_.c_str(), id);
}
memcpy(data.data(), image.data(), nchannel*width*height);
memcpy(data.data(), image.data(), nchannel*size);
image.clear();
}
@@ -3712,12 +3725,20 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
width = height = w;
} else {
width = w/gridsize[1];
if (width >= std::numeric_limits<int>::max()/6) {
throw mjCError(this, "Invalid width of cube texture");
}
height = 6*width;
}
// allocate data
data.assign(3*width*height, 0);
if (data.empty()) {
std::int64_t size = static_cast<std::int64_t>(width)*height;
if (size >= std::numeric_limits<int>::max() / 3 || size <= 0) {
throw mjCError(this, "Cube texture too large");
}
try {
data.assign(3*size, 0);
} catch (const std::bad_alloc& e) {
throw mjCError(this,
"Could not allocate memory for texture '%s' (id %d)",
(const char*)file_.c_str(), id);
@@ -3816,9 +3837,17 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
// first file: set size and allocate data
if (data.empty()) {
width = w;
if (width >= std::numeric_limits<int>::max()/6) {
throw mjCError(this, "Invalid width of builtin texture");
}
height = 6*width;
data.assign(3*width*height, 0);
if (data.empty()) {
std::int64_t size = static_cast<std::int64_t>(width)*height;
if (size >= std::numeric_limits<int>::max() / 3 || size <= 0) {
throw mjCError(this, "PNG texture too large");
}
try {
data.assign(3*size, 0);
} catch (const std::bad_alloc& e) {
throw mjCError(this, "Could not allocate memory for texture");
}
}
@@ -3868,6 +3897,9 @@ void mjCTexture::Compile(const mjVFS* vfs) {
// adjust height of cube texture
if (type != mjTEXTURE_2D) {
if (width >= std::numeric_limits<int>::max()/6) {
throw mjCError(this, "Invalid width of builtin texture");
}
height = 6*width;
} else {
if (height<1) {
@@ -3875,9 +3907,14 @@ void mjCTexture::Compile(const mjVFS* vfs) {
}
}
std::int64_t size = static_cast<std::int64_t>(width)*height;
if (size >= std::numeric_limits<int>::max() / nchannel || size <= 0) {
throw mjCError(this, "Builtin texture too large");
}
// allocate data
data.assign(nchannel*width*height, 0);
if (data.empty()) {
try {
data.assign(nchannel*size, 0);
} catch (const std::bad_alloc& e) {
throw mjCError(this, "Could not allocate memory for texture");
}
+46
View File
@@ -890,6 +890,52 @@ TEST_F(XMLReaderTest, MaterialTextureFailTest) {
"cannot have texture sub-elements"));
}
TEST_F(XMLReaderTest, LargeTextureTest) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<!--
Use a texture width that exceeds the maximum texture size. For cube
textures, the height is ignored and set to width*6. The default number of
channels is 3.
The width in this test is chosen so that 6*width*width*3 is too large to
be represented as an integer.
-->
<texture name="tex" builtin="gradient" width="10923" height="2"/>
</asset>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
mj_deleteModel(model);
}
TEST_F(XMLReaderTest, HugeTextureTest) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<!--
Use a texture width that exceeds the maximum texture size. For cube
textures, the height is ignored and set to width*6. The default number of
channels is 3.
The width in this test is chosen so that 6*width*width*3 is so large that
it overflows and becomes a positive integer.
-->
<texture name="tex" builtin="gradient" width="15447" height="2"/>
</asset>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
mj_deleteModel(model);
}
TEST_F(XMLReaderTest, IncludeAssetsTest) {
static constexpr char xml[] = R"(
<mujoco>