Prevent 32-bit numeric overflow on image allocation.
We assume the pixels of an image can be addressed with a signed 32-bit
offset, including any padding. For a 3D image it's possible to exceed
this without exceeding the per-dimension limits. Lowering the per-
dimension limit so the allocation is always less than 2 GiB makes them
unreasonably small, so instead we must check the total size.
Use 1 GiB as the soft limit in OpenGL.
Bug chromium:835299
Change-Id: I9c5184002c1710e3923b549f8c21e7f6a516e1c7
Reviewed-on: https://swiftshader-review.googlesource.com/18869
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/OpenGL/common/Image.cpp b/src/OpenGL/common/Image.cpp
index 6f38322..601876e 100644
--- a/src/OpenGL/common/Image.cpp
+++ b/src/OpenGL/common/Image.cpp
@@ -773,6 +773,10 @@
namespace egl
{
+ // We assume the data can be indexed with a signed 32-bit offset, including any padding,
+ // so we must keep the image size reasonable. 1 GiB ought to be enough for anybody.
+ enum { IMPLEMENTATION_MAX_IMAGE_SIZE_BYTES = 0x40000000 };
+
enum TransferType
{
Bytes,
@@ -1192,24 +1196,49 @@
Image *Image::create(Texture *parentTexture, GLsizei width, GLsizei height, GLint internalformat)
{
+ if(size(width, height, 1, 0, 1, internalformat) > IMPLEMENTATION_MAX_IMAGE_SIZE_BYTES)
+ {
+ return nullptr;
+ }
+
return new ImageImplementation(parentTexture, width, height, internalformat);
}
Image *Image::create(Texture *parentTexture, GLsizei width, GLsizei height, GLsizei depth, int border, GLint internalformat)
{
+ if(size(width, height, depth, border, 1, internalformat) > IMPLEMENTATION_MAX_IMAGE_SIZE_BYTES)
+ {
+ return nullptr;
+ }
+
return new ImageImplementation(parentTexture, width, height, depth, border, internalformat);
}
Image *Image::create(GLsizei width, GLsizei height, GLint internalformat, int pitchP)
{
+ if(size(pitchP, height, 1, 0, 1, internalformat) > IMPLEMENTATION_MAX_IMAGE_SIZE_BYTES)
+ {
+ return nullptr;
+ }
+
return new ImageImplementation(width, height, internalformat, pitchP);
}
Image *Image::create(GLsizei width, GLsizei height, GLint internalformat, int multiSampleDepth, bool lockable)
{
+ if(size(width, height, 1, 0, multiSampleDepth, internalformat) > IMPLEMENTATION_MAX_IMAGE_SIZE_BYTES)
+ {
+ return nullptr;
+ }
+
return new ImageImplementation(width, height, internalformat, multiSampleDepth, lockable);
}
+ size_t Image::size(int width, int height, int depth, int border, int samples, GLint internalformat)
+ {
+ return sw::Surface::size(width, height, depth, border, samples, gl::SelectInternalFormat(internalformat));
+ }
+
int ClientBuffer::getWidth() const
{
return width;
diff --git a/src/OpenGL/common/Image.hpp b/src/OpenGL/common/Image.hpp
index cb77a4c..4fed893 100644
--- a/src/OpenGL/common/Image.hpp
+++ b/src/OpenGL/common/Image.hpp
@@ -145,6 +145,8 @@
// Back buffer from client buffer
static Image *create(const egl::ClientBuffer& clientBuffer);
+ static size_t size(int width, int height, int depth, int border, int samples, GLint internalformat);
+
GLsizei getWidth() const
{
return width;
diff --git a/src/Renderer/Surface.cpp b/src/Renderer/Surface.cpp
index 788ca63..83412a3 100644
--- a/src/Renderer/Surface.cpp
+++ b/src/Renderer/Surface.cpp
@@ -2648,14 +2648,23 @@
size_t Surface::size(int width, int height, int depth, int border, int samples, Format format)
{
+ samples = max(1, samples);
+
switch(format)
{
default:
- // FIXME: Unpacking byte4 to short4 in the sampler currently involves reading 8 bytes,
- // and stencil operations also read 8 bytes per four 8-bit stencil values,
- // so we have to allocate 4 extra bytes to avoid buffer overruns.
- return (size_t)sliceB(width, height, border, format, true) * depth * samples + 4;
+ {
+ uint64_t size = (uint64_t)sliceB(width, height, border, format, true) * depth * samples;
+ // FIXME: Unpacking byte4 to short4 in the sampler currently involves reading 8 bytes,
+ // and stencil operations also read 8 bytes per four 8-bit stencil values,
+ // so we have to allocate 4 extra bytes to avoid buffer overruns.
+ size += 4;
+
+ // We can only sample buffers smaller than 2 GiB.
+ // Force an out-of-memory if larger, or let the caller report an error.
+ return size < 0x80000000u ? (size_t)size : std::numeric_limits<size_t>::max();
+ }
case FORMAT_YV12_BT601:
case FORMAT_YV12_BT709:
case FORMAT_YV12_JFIF:
diff --git a/tests/unittests/unittests.cpp b/tests/unittests/unittests.cpp
index 3132258..f263eda 100644
--- a/tests/unittests/unittests.cpp
+++ b/tests/unittests/unittests.cpp
@@ -204,7 +204,8 @@
EXPECT_EQ((EGLBoolean)EGL_TRUE, success);
}
- struct ProgramHandles {
+ struct ProgramHandles
+ {
GLuint program;
GLuint vsShader;
GLuint fsShader;
@@ -287,6 +288,7 @@
EGLConfig getConfig() const { return config; }
EGLSurface getSurface() const { return surface; }
EGLContext getContext() const { return context; }
+
private:
EGLDisplay display;
EGLConfig config;
@@ -425,6 +427,29 @@
Uninitialize();
}
+// Test conditions that should result in a GL_OUT_OF_MEMORY and not crash
+TEST_F(SwiftShaderTest, OutOfMemory)
+{
+ // Image sizes are assumed to fit in a 32-bit signed integer by the renderer,
+ // so test that we can't create a 2+ GiB image.
+ {
+ Initialize(3, false);
+
+ GLuint tex = 1;
+ glBindTexture(GL_TEXTURE_3D, tex);
+
+ const int width = 0xC2;
+ const int height = 0x541;
+ const int depth = 0x404;
+ glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA32F, width, height, depth, 0, GL_RGBA, GL_FLOAT, nullptr);
+ EXPECT_GLENUM_EQ(GL_OUT_OF_MEMORY, glGetError());
+
+ // The spec states that the GL is in an undefined state when GL_OUT_OF_MEMORY
+ // is returned, and the context must be recreated before attempting more rendering.
+ Uninitialize();
+ }
+}
+
// Note: GL_ARB_texture_rectangle is part of gl2extchromium.h in the Chromium repo
// GL_ARB_texture_rectangle
#ifndef GL_ARB_texture_rectangle
diff --git a/tests/unittests/unittests.vcxproj.user b/tests/unittests/unittests.vcxproj.user
index 567b191..075c68f 100644
--- a/tests/unittests/unittests.vcxproj.user
+++ b/tests/unittests/unittests.vcxproj.user
@@ -3,7 +3,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerEnvironment>PATH=$(SolutionDir)lib\$(Configuration)_$(Platform)</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
- <LocalDebuggerCommandArguments>--gtest_break_on_failure</LocalDebuggerCommandArguments>
+ <LocalDebuggerCommandArguments>--gtest_break_on_failure --gtest_filter=*</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LocalDebuggerEnvironment>PATH=$(SolutionDir)lib\$(Configuration)_$(Platform)</LocalDebuggerEnvironment>
@@ -12,7 +12,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LocalDebuggerEnvironment>PATH=$(SolutionDir)lib\$(Configuration)_$(Platform)</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
- <LocalDebuggerCommandArguments>--gtest_break_on_failure</LocalDebuggerCommandArguments>
+ <LocalDebuggerCommandArguments>--gtest_break_on_failure --gtest_filter=*</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LocalDebuggerEnvironment>PATH=$(SolutionDir)lib\$(Configuration)_$(Platform)</LocalDebuggerEnvironment>