Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions cpp/include/cucim/util/checked_math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef CUCIM_UTIL_CHECKED_MATH_H
#define CUCIM_UTIL_CHECKED_MATH_H

#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <string>
#include <type_traits>

namespace cucim::util
{

constexpr size_t kDefaultMaxTileBytes = 2ULL * 1024 * 1024 * 1024; // 2 GiB
constexpr size_t kDefaultMaxRasterBytes = 16ULL * 1024 * 1024 * 1024; // 16 GiB

namespace detail
{

inline size_t parse_env_size(const char* env_var, size_t default_value)
{
const char* val = std::getenv(env_var);
if (val == nullptr || val[0] == '\0')
{
return default_value;
}
char* end = nullptr;
unsigned long long parsed = std::strtoull(val, &end, 10);
if (end == val || parsed == 0)
{
return default_value;
}
return static_cast<size_t>(parsed);
}

} // namespace detail

inline size_t max_tile_bytes()
{
static const size_t value = detail::parse_env_size("CUCIM_MAX_TILE_BYTES", kDefaultMaxTileBytes);
return value;
}

inline size_t max_raster_bytes()
{
static const size_t value = detail::parse_env_size("CUCIM_MAX_RASTER_BYTES", kDefaultMaxRasterBytes);
return value;
}

template <typename T>
inline T checked_mul(T a, T b)
{
static_assert(std::is_unsigned_v<T>, "checked_mul requires unsigned types");
T result;
if (__builtin_mul_overflow(a, b, &result))
{
throw std::overflow_error("Integer overflow in buffer size calculation");
}
return result;
}

inline size_t checked_mul3(size_t a, size_t b, size_t c)
{
return checked_mul(checked_mul(a, b), c);
}

inline size_t checked_tile_size(size_t width, size_t height, size_t pixel_bytes)
{
size_t result = checked_mul3(width, height, pixel_bytes);
size_t limit = max_tile_bytes();
if (result > limit)
{
throw std::overflow_error("Tile size exceeds maximum allowed (" +
std::to_string(result) + " > " +
std::to_string(limit) + " bytes). "
"Override with CUCIM_MAX_TILE_BYTES env var.");
}
return result;
}

inline size_t checked_raster_size(size_t width, size_t height, size_t pixel_bytes)
{
size_t result = checked_mul3(width, height, pixel_bytes);
size_t limit = max_raster_bytes();
if (result > limit)
{
throw std::overflow_error("Raster size exceeds maximum allowed (" +
std::to_string(result) + " > " +
std::to_string(limit) + " bytes). "
"Override with CUCIM_MAX_RASTER_BYTES env var.");
}
return result;
}

} // namespace cucim::util

#endif // CUCIM_UTIL_CHECKED_MATH_H
35 changes: 31 additions & 4 deletions cpp/plugins/cucim.kit.cuslide/src/cuslide/deflate/deflate.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION
* SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -13,6 +13,7 @@
#include <stdexcept>
#include <unistd.h>

#include <fmt/format.h>
#include <cucim/memory/memory_manager.h>
#include <cucim/profiler/nvtx3.h>

Expand Down Expand Up @@ -63,9 +64,12 @@ bool decode_deflate(int fd,
throw std::runtime_error("Unable to allocate buffer for libdeflate!");
}

if (pread(fd, deflate_buf, size, offset) < 1)
ssize_t bytes_read = pread(fd, deflate_buf, size, offset);
if (bytes_read < 0 || static_cast<uint64_t>(bytes_read) != size)
{
throw std::runtime_error("Unable to read file for libdeflate!");
cucim_free(deflate_buf);
throw std::runtime_error(
fmt::format("Short read for deflate data: expected {} bytes, got {}", size, bytes_read));
}
}
else
Expand All @@ -75,9 +79,10 @@ bool decode_deflate(int fd,
}

size_t out_size;
enum libdeflate_result decompress_result;
{
PROF_SCOPED_RANGE(PROF_EVENT(libdeflate_zlib_decompress));
libdeflate_zlib_decompress(
decompress_result = libdeflate_zlib_decompress(
d, deflate_buf, size /*in_nbytes*/, *dest, dest_nbytes /*out_nbytes_avail*/, &out_size);
}

Expand All @@ -90,6 +95,28 @@ bool decode_deflate(int fd,
PROF_SCOPED_RANGE(PROF_EVENT(libdeflate_free_decompressor));
libdeflate_free_decompressor(d);
}

if (decompress_result != LIBDEFLATE_SUCCESS)
{
const char* reason = "unknown error";
switch (decompress_result)
{
case LIBDEFLATE_BAD_DATA:
reason = "corrupt or invalid compressed data";
break;
case LIBDEFLATE_SHORT_OUTPUT:
reason = "decompressed size is less than expected";
break;
case LIBDEFLATE_INSUFFICIENT_SPACE:
reason = "output buffer too small for decompressed data";
break;
default:
break;
}
throw std::runtime_error(
fmt::format("Deflate decompression failed: {}", reason));
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* SPDX-FileCopyrightText: Copyright (C) 2009-2020 D. R. Commander.
* SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION
* SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause AND IJG-short AND Zlib
*/

Expand All @@ -23,6 +23,7 @@
#include <unistd.h>

#include <cucim/profiler/nvtx3.h>
#include <cucim/util/checked_math.h>
#include <turbojpeg.h>

static thread_local char errStr[JMSG_LENGTH_MAX] = "No error";
Expand Down Expand Up @@ -147,8 +148,12 @@ bool decode_libjpeg(int fd,
THROW_UNIX("allocating JPEG buffer");
}

if (pread(fd, jpeg_buf, size, offset) < 1)
THROW_UNIX("reading input file");
{
ssize_t bytes_read = pread(fd, jpeg_buf, size, offset);
if (bytes_read < 0 || static_cast<uint64_t>(bytes_read) != size)
THROW_MSG("reading input file",
"Short read: pread returned fewer bytes than requested");
}
}
else
{
Expand Down Expand Up @@ -186,7 +191,10 @@ bool decode_libjpeg(int fd,
if (*dest == nullptr)
{
PROF_SCOPED_RANGE(PROF_EVENT(decoder_libjpeg_turbo_tjAlloc));
if ((*dest = (unsigned char*)tjAlloc(width * height * tjPixelSize[pixelFormat])) == nullptr)
size_t alloc_size = cucim::util::checked_tile_size(
static_cast<size_t>(width), static_cast<size_t>(height),
static_cast<size_t>(tjPixelSize[pixelFormat]));
if ((*dest = (unsigned char*)tjAlloc(alloc_size)) == nullptr)
THROW_UNIX("Unable to allocate uncompressed image buffer");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* SPDX-FileCopyrightText: Copyright (c) 2003-2007, Francois-Olivier Devaux
* SPDX-FileCopyrightText: Copyright (c) 2003-2014, Antonin Descampe
* SPDX-FileCopyrightText: Copyright (c) 2005, Herve Drolon, FreeImage Team
* SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION
* SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0 AND BSD-2-Clause
*/

Expand All @@ -18,7 +18,10 @@

#include "color_conversion.h"

#include <stdexcept>

#include <cucim/profiler/nvtx3.h>
#include <cucim/util/checked_math.h>

#include "color_table.h"

Expand All @@ -30,12 +33,17 @@ static inline uint8_t clamp(int32_t x)
return (x < 0) ? 0 : ((x > 255) ? 255 : x);
}

void fast_sycc422_to_rgb(opj_image_t* image, uint8_t* dest)
void fast_sycc422_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes)
{
PROF_SCOPED_RANGE(PROF_EVENT(jpeg2k_fast_sycc422_to_rgb));
const opj_image_comp_t* comps = image->comps;
const size_t maxw = (size_t)comps[0].w;
const size_t maxh = (size_t)comps[0].h;
size_t required = cucim::util::checked_mul3(maxw, maxh, static_cast<size_t>(3));
if (required > dest_nbytes)
{
throw std::overflow_error("J2K codestream dimensions exceed destination buffer size");
}
const int* y = image->comps[0].data;
const int* cb = image->comps[1].data;
const int* cr = image->comps[2].data;
Expand Down Expand Up @@ -111,12 +119,17 @@ void fast_sycc422_to_rgb(opj_image_t* image, uint8_t* dest)
}
}

void fast_sycc420_to_rgb(opj_image_t* image, uint8_t* dest)
void fast_sycc420_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes)
{
PROF_SCOPED_RANGE(PROF_EVENT(jpeg2k_fast_sycc420_to_rgb));
const opj_image_comp_t* comps = image->comps;
const size_t maxw = (size_t)comps[0].w;
const size_t maxh = (size_t)comps[0].h;
size_t required = cucim::util::checked_mul3(maxw, maxh, static_cast<size_t>(3));
if (required > dest_nbytes)
{
throw std::overflow_error("J2K codestream dimensions exceed destination buffer size");
}
const int* y = image->comps[0].data;
const int* cb = image->comps[1].data;
const int* cr = image->comps[2].data;
Expand Down Expand Up @@ -307,13 +320,18 @@ void fast_sycc420_to_rgb(opj_image_t* image, uint8_t* dest)
}
}

void fast_sycc444_to_rgb(opj_image_t* image, uint8_t* dest)
void fast_sycc444_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes)
{
PROF_SCOPED_RANGE(PROF_EVENT(jpeg2k_fast_sycc444_to_rgb));
const opj_image_comp_t* comps = image->comps;
const size_t maxw = (size_t)comps[0].w;
const size_t maxh = (size_t)comps[0].h;
const size_t max = maxw * maxh;
const size_t max = cucim::util::checked_mul(maxw, maxh);
size_t required = cucim::util::checked_mul(max, static_cast<size_t>(3));
if (required > dest_nbytes)
{
throw std::overflow_error("J2K codestream dimensions exceed destination buffer size");
}
const int* y = image->comps[0].data;
const int* cb = image->comps[1].data;
const int* cr = image->comps[2].data;
Expand All @@ -339,19 +357,24 @@ void fast_sycc444_to_rgb(opj_image_t* image, uint8_t* dest)
}
}

void fast_image_to_rgb(opj_image_t* image, uint8_t* dest)
void fast_image_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes)
{
PROF_SCOPED_RANGE(PROF_EVENT(jpeg2k_fast_image_to_rgb));
opj_image_comp_t* comps = image->comps;
uint32_t width = comps[0].w;
uint32_t height = comps[0].h;
uint32_t items = width * height;
size_t items = cucim::util::checked_mul(static_cast<size_t>(width), static_cast<size_t>(height));
size_t required = cucim::util::checked_mul(items, static_cast<size_t>(3));
if (required > dest_nbytes)
{
throw std::overflow_error("J2K image dimensions exceed destination buffer size");
}

uint8_t* buf = dest;
int32_t* comp0 = comps[0].data;
int32_t* comp1 = comps[1].data;
int32_t* comp2 = comps[2].data;
for (uint32_t i = 0; i < items; ++i)
for (size_t i = 0; i < items; ++i)
{
*(buf++) = comp0[i];
*(buf++) = comp1[i];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION
* SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef CUSLIDE_JPEG2K_COLOR_CONVERSION_H
#define CUSLIDE_JPEG2K_COLOR_CONVERSION_H

#include <cstddef>
#include <cstdint>

#include <openjpeg.h>

namespace cuslide::jpeg2k
{

void fast_sycc420_to_rgb(opj_image_t* image, uint8_t* dest);
void fast_sycc422_to_rgb(opj_image_t* image, uint8_t* dest);
void fast_sycc444_to_rgb(opj_image_t* image, uint8_t* dest);
void fast_image_to_rgb(opj_image_t* image, uint8_t* dest);
void fast_sycc420_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes);
void fast_sycc422_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes);
void fast_sycc444_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes);
void fast_image_to_rgb(opj_image_t* image, uint8_t* dest, size_t dest_nbytes);

} // namespace cuslide::jpeg2k

Expand Down
Loading
Loading