[MG_Remote] validate packed readback bounds before copying GPU replies

This commit is contained in:
2026-09-20 11:56:05 -04:00
parent 70df57bb23
commit 859c0efb01
2 changed files with 71 additions and 26 deletions
+9 -2
View File
@@ -902,6 +902,8 @@ namespace MobileGL::MG_Remote::Client {
}
}
#include "TextureReadbackEmit.inc"
void EmitReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, void* pixels) {
ClientSession& session = RequireSession("ReadPixels");
@@ -968,6 +970,13 @@ namespace MobileGL::MG_Remote::Client {
if (MG_State::pGLContext != nullptr) {
pack = MG_State::pGLContext->GetPixelStoreParameters(/*isUnpack=*/false);
}
ReadbackLayout layout;
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
if (!CheckedReadbackLayout(width, height, 1, bytesPerPixel, pack, false, layout) ||
(pbo && (pboOffset > pbo->GetSize() || layout.End > pbo->GetSize() - pboOffset))) {
RecordReadbackRangeError("ReadPixels");
return;
}
Int32 status = 0;
Uint64 replySize = 0;
@@ -1014,8 +1023,6 @@ namespace MobileGL::MG_Remote::Client {
pack);
}
#include "TextureReadbackEmit.inc"
void EmitPresent() {
ClientSession& session = RequireSession("Present");
BeforeReadOnlyVerb();
@@ -1,9 +1,49 @@
// Included in EmitTables.cpp's client-only anonymous namespace.
// The server returns owned tight pixels. Only the client interprets PACK state
// and application pointers; PBO rows are ordinary buffer uploads after the reply.
struct ReadbackLayout {
Uint64 RowBytes = 0, RowStride = 0, ImageStride = 0, Start = 0, End = 0, Tight = 0;
};
Bool CheckedReadbackLayout(Uint64 width, Uint64 height, Uint64 depth, Uint64 bpp,
const PixelStoreParameters& pack, Bool imagePack, ReadbackLayout& out) {
constexpr Uint64 limit = std::numeric_limits<SizeT>::max();
auto mul = [&](Uint64 a, Uint64 b, Uint64& result) {
if (a && b > limit / a) return false;
result = a * b; return true;
};
auto add = [&](Uint64 a, Uint64 b, Uint64& result) {
if (a > limit || b > limit - a) return false;
result = a + b; return true;
};
if (!width || !height || !depth || !bpp) return false;
const Uint64 align = std::max<Int>(pack.Alignment, 1);
Uint64 rawRow = 0, rounded = 0, skipRows = 0, skipPixels = 0, skipImages = 0;
Uint64 lastImage = 0, lastRow = 0;
if (!mul(width, bpp, out.RowBytes) ||
!mul(pack.RowLength > 0 ? pack.RowLength : width, bpp, rawRow) ||
!add(rawRow, align - 1, rounded) ||
!mul(rounded / align, align, out.RowStride) ||
!mul(imagePack && pack.ImageHeight > 0 ? pack.ImageHeight : height, out.RowStride, out.ImageStride) ||
!mul(pack.SkipRows, out.RowStride, skipRows) || !mul(pack.SkipPixels, bpp, skipPixels) ||
!mul(imagePack ? pack.SkipImages : 0, out.ImageStride, skipImages) ||
!add(skipRows, skipPixels, out.Start) || !add(out.Start, skipImages, out.Start) ||
!mul(depth - 1, out.ImageStride, lastImage) || !mul(height - 1, out.RowStride, lastRow) ||
!add(out.Start, lastImage, out.End) || !add(out.End, lastRow, out.End) ||
!add(out.End, out.RowBytes, out.End) || !mul(out.RowBytes, height, out.Tight) ||
!mul(out.Tight, depth, out.Tight)) return false;
return true;
}
void RecordReadbackRangeError(const char* caller) {
MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Remote/Client", caller,
"Pixel packing exceeds the destination or its addressable range."));
}
void EmitGetTextureImage(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
TextureUploadTarget uploadTarget, GLint level, GLenum format,
GLenum type, GLsizei, void* pixels) {
GLenum type, GLsizei bufSize, void* pixels) {
if (!texture || texture->GetStorageType() != TextureStorageType::Mipmap) return;
auto* mipmap = static_cast<MG_State::GLState::TextureObjectMipmap*>(texture.get());
const Bool wholeCube = texture->GetTarget() == TextureTarget::TextureCubeMap &&
@@ -11,13 +51,26 @@
const auto firstTarget = wholeCube ? TextureUploadTarget::CubeMapPositiveX : uploadTarget;
const IntVec3 size = mipmap->GetMipmapTexelSize(firstTarget, level);
if (size.x() <= 0 || size.y() <= 0 || size.z() <= 0) return;
const Uint64 bpp = ReadbackBytesPerPixel(format, type);
const Uint64 depth = wholeCube ? 6 : static_cast<Uint64>(size.z());
const auto pack = MG_State::pGLContext->GetPixelStoreParameters(false);
const auto target = texture->GetTarget();
const Bool imagePack = wholeCube || target == TextureTarget::Texture3D ||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray;
const auto pbo = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
ReadbackLayout layout;
if (!CheckedReadbackLayout(size.x(), size.y(), depth, bpp, pack, imagePack, layout) ||
(bufSize >= 0 && layout.End > static_cast<Uint64>(bufSize)) ||
(pbo && (pboOffset > pbo->GetSize() || layout.End > pbo->GetSize() - pboOffset))) {
RecordReadbackRangeError("GetTextureImage");
return;
}
const Uint64 tight = layout.Tight;
ClientSession& session = RequireSession("GetTextureImage");
BeforeReadOnlyVerb();
// A by-name texture need not occur in any currently bound sampler.
MG_Pipe::MGPipeTextureEmitterInstance().DrainTextureSubData(*MG_State::pGLContext);
const Uint64 bpp = ReadbackBytesPerPixel(format, type);
const Uint64 depth = wholeCube ? 6 : static_cast<Uint64>(size.z());
const Uint64 tight = static_cast<Uint64>(size.x()) * size.y() * depth * bpp;
MG_Pipe::MGPReadbackInfo info{};
info.Res = MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Texture,
texture->GetLifetimeId());
@@ -40,35 +93,20 @@
RequireReadbackReplyComplete(status, received, info.DstSize);
offset += info.DstSize;
}
const auto pack = MG_State::pGLContext->GetPixelStoreParameters(false);
ApplyReadbackByteSwap(bytes.data(), tight, type, pack);
const Uint64 rowBytes = static_cast<Uint64>(size.x()) * bpp;
const Uint64 alignment = std::max<Int>(pack.Alignment, 1);
const Uint64 rowPixels = pack.RowLength > 0 ? pack.RowLength : size.x();
const Uint64 rowStride = (rowPixels * bpp + alignment - 1) / alignment * alignment;
const auto target = texture->GetTarget();
const Bool imagePack = wholeCube || target == TextureTarget::Texture3D ||
target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray;
const Uint64 imageRows = imagePack && pack.ImageHeight > 0 ? pack.ImageHeight : size.y();
const Uint64 imageStride = imageRows * rowStride;
const Uint64 start = static_cast<Uint64>(pack.SkipRows) * rowStride +
static_cast<Uint64>(pack.SkipPixels) * bpp +
(imagePack ? static_cast<Uint64>(pack.SkipImages) * imageStride : 0);
const auto& pbo = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
for (Uint64 z = 0; z < depth; ++z) {
for (Int y = 0; y < size.y(); ++y) {
const Uint8* source = bytes.data() + (z * size.y() + y) * rowBytes;
const Uint64 offset = start + z * imageStride + y * rowStride;
if (pbo) pbo->UploadSubData({source, static_cast<SizeT>(rowBytes)},
reinterpret_cast<SizeT>(pixels) + offset);
else if (pixels) Memcpy(static_cast<Uint8*>(pixels) + offset, source, rowBytes);
Uint8* source = bytes.data() + (z * size.y() + y) * layout.RowBytes;
const Uint64 offset = layout.Start + z * layout.ImageStride + y * layout.RowStride;
if (pbo) pbo->UploadSubData({source, static_cast<SizeT>(layout.RowBytes)}, pboOffset + offset);
else if (pixels) Memcpy(static_cast<Uint8*>(pixels) + offset, source, layout.RowBytes);
}
}
}
void EmitGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) {
const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
const auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
const auto& texture = unit.GetBindingSlot(textureTarget).GetBoundObject();
EmitGetTextureImage(texture, MG_Util::ConvertGLEnumToTextureUploadTarget(target),
level, format, type, -1, pixels);