diff --git a/cpp/include/cucim/util/checked_math.h b/cpp/include/cucim/util/checked_math.h new file mode 100644 index 000000000..081b7f3db --- /dev/null +++ b/cpp/include/cucim/util/checked_math.h @@ -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 +#include +#include +#include +#include +#include + +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(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 +inline T checked_mul(T a, T b) +{ + static_assert(std::is_unsigned_v, "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 diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/deflate/deflate.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/deflate/deflate.cpp index 893cb62aa..c65514083 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/deflate/deflate.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/deflate/deflate.cpp @@ -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 */ @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -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(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 @@ -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); } @@ -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; } diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg/libjpeg_turbo.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg/libjpeg_turbo.cpp index bcf1729b6..640615cfa 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg/libjpeg_turbo.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg/libjpeg_turbo.cpp @@ -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 */ @@ -23,6 +23,7 @@ #include #include +#include #include static thread_local char errStr[JMSG_LENGTH_MAX] = "No error"; @@ -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(bytes_read) != size) + THROW_MSG("reading input file", + "Short read: pread returned fewer bytes than requested"); + } } else { @@ -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(width), static_cast(height), + static_cast(tjPixelSize[pixelFormat])); + if ((*dest = (unsigned char*)tjAlloc(alloc_size)) == nullptr) THROW_UNIX("Unable to allocate uncompressed image buffer"); } diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.cpp index 2cf02d283..e05c26932 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.cpp @@ -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 */ @@ -18,7 +18,10 @@ #include "color_conversion.h" +#include + #include +#include #include "color_table.h" @@ -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(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; @@ -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(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; @@ -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(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; @@ -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(width), static_cast(height)); + size_t required = cucim::util::checked_mul(items, static_cast(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]; diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.h b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.h index 7fac8ede0..489199f42 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.h +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.h @@ -1,10 +1,11 @@ /* - * 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 #include #include @@ -12,10 +13,10 @@ 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 diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/libopenjpeg.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/libopenjpeg.cpp index af5ddc118..86b921328 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/libopenjpeg.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/libopenjpeg.cpp @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium * SPDX-FileCopyrightText: Copyright (c) 2002-2014, Professor Benoit Macq * SPDX-FileCopyrightText: Copyright (c) 2010-2011, Kaori Hagihara - * 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 */ @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -145,9 +146,12 @@ bool decode_libopenjpeg(int fd, throw std::runtime_error("Unable to allocate buffer for libopenjpeg!"); } - if (pread(fd, jpeg_buf, size, offset) < 1) + ssize_t bytes_read = pread(fd, jpeg_buf, size, offset); + if (bytes_read < 0 || static_cast(bytes_read) != size) { - throw std::runtime_error("Unable to read file for libopenjpeg!"); + cucim_free(jpeg_buf); + throw std::runtime_error( + fmt::format("Short read for JPEG2000 data: expected {} bytes, got {}", size, bytes_read)); } } else @@ -215,6 +219,52 @@ bool decode_libopenjpeg(int fd, throw std::runtime_error("[Error] Failed to decode image\n"); } } + + // Validate decoded dimensions and component data pointers. + // dest_nbytes is sized from TIFF tile dimensions; the J2K codestream + // may declare larger dimensions in a malformed file. + for (uint32_t c = 0; c < image->numcomps; c++) + { + if (image->comps[c].data == nullptr) + { + throw std::runtime_error( + fmt::format("[Error] J2K component {} has null data after decode", c)); + } + } + { + // Validate component 0 (Y/R) output fits in dest buffer + size_t decoded_pixels = cucim::util::checked_mul( + static_cast(image->comps[0].w), static_cast(image->comps[0].h)); + size_t decoded_bytes = cucim::util::checked_mul(decoded_pixels, static_cast(3)); + if (decoded_bytes > dest_nbytes) + { + throw std::runtime_error( + fmt::format("[Error] J2K decoded dimensions ({}x{}, {} bytes) exceed " + "destination buffer ({} bytes)", + image->comps[0].w, image->comps[0].h, decoded_bytes, dest_nbytes)); + } + + // Validate chroma component dimensions are consistent with luma. + // The SYCC converters index Cb/Cr using luma geometry and subsampling + // assumptions; undersized chroma components cause out-of-bounds reads. + uint32_t luma_w = image->comps[0].w; + uint32_t luma_h = image->comps[0].h; + for (uint32_t c = 1; c < image->numcomps; c++) + { + uint32_t expected_w = (luma_w + image->comps[c].dx - 1) / image->comps[c].dx; + uint32_t expected_h = (luma_h + image->comps[c].dy - 1) / image->comps[c].dy; + if (image->comps[c].w < expected_w || image->comps[c].h < expected_h) + { + throw std::runtime_error( + fmt::format("[Error] J2K component {} dimensions ({}x{}) are smaller than " + "expected ({}x{}) for luma {}x{} with subsampling dx={}, dy={}", + c, image->comps[c].w, image->comps[c].h, + expected_w, expected_h, luma_w, luma_h, + image->comps[c].dx, image->comps[c].dy)); + } + } + } + if (image->color_space != OPJ_CLRSPC_SYCC) { if (color_space == ColorSpace::kSYCC) @@ -240,17 +290,17 @@ bool decode_libopenjpeg(int fd, if ((comp0_dx == 1) && (comp1_dx == 2) && (comp2_dx == 2) && (comp0_dy == 1) && (comp1_dy == 1) && (comp2_dy == 1)) { - fast_sycc422_to_rgb(image, *dest); // horizontal sub-sample only + fast_sycc422_to_rgb(image, *dest, dest_nbytes); // horizontal sub-sample only } else if ((comp0_dx == 1) && (comp1_dx == 2) && (comp2_dx == 2) && (comp0_dy == 1) && (comp1_dy == 2) && (comp2_dy == 2)) { - fast_sycc420_to_rgb(image, *dest); // horizontal and vertical sub-sample + fast_sycc420_to_rgb(image, *dest, dest_nbytes); // horizontal and vertical sub-sample } else if ((comp0_dx == 1) && (comp1_dx == 1) && (comp2_dx == 1) && (comp0_dy == 1) && (comp1_dy == 1) && (comp2_dy == 1)) { - fast_sycc444_to_rgb(image, *dest); // no sub-sample + fast_sycc444_to_rgb(image, *dest, dest_nbytes); // no sub-sample } else { @@ -278,7 +328,7 @@ bool decode_libopenjpeg(int fd, } if (image->comps) { - fast_image_to_rgb(image, *dest); + fast_image_to_rgb(image, *dest, dest_nbytes); } } } diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw.cpp index f6e9d5340..934ab1165 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw.cpp @@ -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 */ @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -52,9 +53,12 @@ bool decode_lzw(int fd, throw std::runtime_error("Unable to allocate buffer for lzw data!"); } - if (pread(fd, lzw_buf, size, offset) < 1) + ssize_t bytes_read = pread(fd, lzw_buf, size, offset); + if (bytes_read < 0 || static_cast(bytes_read) != size) { - throw std::runtime_error("Unable to read file for lzw data!"); + cucim_free(lzw_buf); + throw std::runtime_error( + fmt::format("Short read for LZW data: expected {} bytes, got {}", size, bytes_read)); } } else diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw_libtiff.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw_libtiff.cpp index a4ad18e15..39a403447 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw_libtiff.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw_libtiff.cpp @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: Copyright (c) 1988-1997 Sam Leffler * SPDX-FileCopyrightText: Copyright (c) 1991-1997 Silicon Graphics, Inc. * SPDX-FileCopyrightText: Copyright (c) 1985, 1986 The Regents of the University of California. All rights reserved. - * SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION + * SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION * SPDX-License-Identifier: Apache-2.0 AND LicenseRef-Berkeley-lzw AND libtiff */ @@ -156,8 +156,8 @@ typedef struct unsigned short nbits; /* # of bits/code */ unsigned short maxcode; /* maximum code for lzw_nbits */ unsigned short free_ent; /* next free entry in hash table */ - unsigned long nextdata; /* next bits of i/o */ - long nextbits; /* # of valid bits in lzw_nextdata */ + uint64_t nextdata; /* next bits of i/o */ + tmsize_t nextbits; /* # of valid bits in lzw_nextdata */ int rw_mode; /* preserve rw_mode from init */ } LZWBaseState; @@ -174,7 +174,7 @@ typedef struct typedef uint16_t hcode_t; /* codes fit in 16 bits */ typedef struct { - long hash; + int64_t hash; hcode_t code; } hash_t; @@ -196,8 +196,8 @@ typedef struct LZWBaseState base; /* Decoding specific data */ - long dec_nbitsmask; /* lzw_nbits 1 bits, right adjusted */ - long dec_restart; /* restart count */ + tmsize_t dec_nbitsmask; /* lzw_nbits 1 bits, right adjusted */ + tmsize_t dec_restart; /* restart count */ decodeFunc dec_decode; /* regular or backwards compatible */ code_t* dec_codep; /* current recognized code */ code_t* dec_oldcodep; /* previously recognized code */ @@ -207,10 +207,10 @@ typedef struct /* Encoding specific data */ int enc_oldcode; /* last code encountered */ - long enc_checkpoint; /* point at which to clear table */ - long enc_ratio; /* current compression ratio */ - long enc_incount; /* (input) data bytes encoded */ - long enc_outcount; /* encoded (output) bytes */ + tmsize_t enc_checkpoint; /* point at which to clear table */ + tmsize_t enc_ratio; /* current compression ratio */ + tmsize_t enc_incount; /* (input) data bytes encoded */ + tmsize_t enc_outcount; /* encoded (output) bytes */ uint8_t* enc_rawlimit; /* bound on tif_rawdata buffer */ hash_t* enc_hashtab; /* kept separate for small machines */ } LZWCodecState; @@ -363,30 +363,27 @@ static int LZWDecode(TIFF* tif, uint8_t* op0, tmsize_t occ0, uint16_t s) static const char module[] = "LZWDecode"; LZWCodecState* sp = DecoderState(tif); uint8_t* op = (uint8_t*)op0; - long occ = (long)occ0; + tmsize_t occ = occ0; uint8_t* tp; uint8_t* bp; hcode_t code; int len; - long nbits, nextbits, nbitsmask; - unsigned long nextdata; + tmsize_t nbits, nextbits, nbitsmask; + uint64_t nextdata; code_t *codep, *free_entp, *maxcodep, *oldcodep; (void)s; assert(sp != NULL); assert(sp->dec_codetab != NULL); - /* - Fail if value does not fit in long. - */ - if ((tmsize_t)occ != occ0) + if (occ0 <= 0) return (0); /* * Restart interrupted output operation. */ if (sp->dec_restart) { - long residue; + tmsize_t residue; codep = sp->dec_codep; residue = codep->length - sp->dec_restart; @@ -523,7 +520,7 @@ static int LZWDecode(TIFF* tif, uint8_t* op0, tmsize_t occ0, uint16_t s) } while (codep && codep->length > occ); if (codep) { - sp->dec_restart = (long)occ; + sp->dec_restart = occ; tp = op + occ; do { @@ -570,8 +567,8 @@ static int LZWDecode(TIFF* tif, uint8_t* op0, tmsize_t occ0, uint16_t s) if (occ > 0) { - TIFFErrorExt(tif->tif_clientdata, module, "Not enough data at scanline %" PRIu32 " (short %ld bytes)", - tif->tif_row, occ); + TIFFErrorExt(tif->tif_clientdata, module, "Not enough data at scanline %" PRIu32 " (short %" PRId64 " bytes)", + tif->tif_row, static_cast(occ)); return (0); } return (1); diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/raw/raw.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/raw/raw.cpp index fe122dd92..900554177 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/raw/raw.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/raw/raw.cpp @@ -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 */ @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -52,9 +53,12 @@ bool decode_raw(int fd, throw std::runtime_error("Unable to allocate buffer for raw data!"); } - if (pread(fd, raw_buf, size, offset) < 1) + ssize_t bytes_read = pread(fd, raw_buf, size, offset); + if (bytes_read < 0 || static_cast(bytes_read) != size) { - throw std::runtime_error("Unable to read file for raw data!"); + cucim_free(raw_buf); + throw std::runtime_error( + fmt::format("Short read for raw data: expected {} bytes, got {}", size, bytes_read)); } } else @@ -63,6 +67,16 @@ bool decode_raw(int fd, raw_buf += offset; } + if (size < dest_nbytes) + { + if (fd != -1) + { + cucim_free(raw_buf); + } + throw std::runtime_error( + fmt::format("Raw tile data size ({}) is smaller than expected destination buffer ({})", size, dest_nbytes)); + } + memcpy(*dest, raw_buf, dest_nbytes); if (fd != -1) diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp index e05ef48b6..b0b38850c 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2020-2021, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ #include "ifd.h" #include +#include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #include "cuslide/deflate/deflate.h" @@ -147,7 +149,8 @@ bool IFD::read(const TIFF* tiff, int32_t n_ch = samples_per_pixel_; // number of channels int ndim = 3; - size_t raster_size = w * h * samples_per_pixel_; + size_t raster_size = cucim::util::checked_mul3( + static_cast(w), static_cast(h), static_cast(samples_per_pixel_)); void* raster = nullptr; auto raster_type = cucim::io::DeviceType::kCPU; @@ -343,9 +346,8 @@ bool IFD::read(const TIFF* tiff, TIFFRGBAImage img; if (TIFFRGBAImageBegin(&img, tif, -1, emsg)) { - size_t npixels; - npixels = w * h; - raster_size = npixels * 4; + size_t npixels = cucim::util::checked_mul(static_cast(w), static_cast(h)); + raster_size = cucim::util::checked_mul(npixels, static_cast(4)); if (!raster) { raster = cucim_malloc(raster_size); @@ -534,8 +536,10 @@ size_t IFD::pixel_size_nbytes() const size_t IFD::tile_raster_size_nbytes() const { - const size_t nbytes = tile_width_ * tile_height_ * pixel_size_nbytes(); - return nbytes; + return cucim::util::checked_tile_size( + static_cast(tile_width_), + static_cast(tile_height_), + pixel_size_nbytes()); } bool IFD::is_compression_supported() const @@ -637,6 +641,13 @@ bool IFD::read_region_tiles(const TIFF* tiff, uint64_t ifd_hash_value = ifd->hash_value_; uint32_t dest_pixel_step_y = w * samples_per_pixel; + struct stat file_stat; + uint64_t tiff_file_size = 0; + if (fstat(tiff_file, &file_stat) == 0) + { + tiff_file_size = static_cast(file_stat.st_size); + } + uint32_t nbytes_tw = tw * samples_per_pixel; auto dest_start_ptr = static_cast(raster); @@ -654,9 +665,26 @@ bool IFD::read_region_tiles(const TIFF* tiff, for (uint32_t offset_x = offset_sx; offset_x <= offset_ex; ++offset_x, ++index) { PROF_SCOPED_RANGE(PROF_EVENT_P(ifd_read_region_tiles_iter, index)); + if (index >= ifd->image_piece_offsets_.size() || index >= ifd->image_piece_bytecounts_.size()) + { + throw std::out_of_range( + fmt::format("Tile index {} out of range (max {})", index, ifd->image_piece_offsets_.size())); + } auto tiledata_offset = static_cast(ifd->image_piece_offsets_[index]); auto tiledata_size = static_cast(ifd->image_piece_bytecounts_[index]); + if (tiff_file_size > 0 && tiledata_size > 0) + { + uint64_t tiledata_end; + if (__builtin_add_overflow(tiledata_offset, tiledata_size, &tiledata_end) || + tiledata_end > tiff_file_size) + { + throw std::runtime_error(fmt::format( + "Tile {} data range [{}, +{}) exceeds file size {}", + index, tiledata_offset, tiledata_size, tiff_file_size)); + } + } + // Calculate a simple hash value for the tile index uint64_t index_hash = ifd_hash_value ^ (static_cast(index) | (static_cast(index) << 32)); @@ -934,6 +962,13 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff, int tiff_file = tiff->file_handle_->fd; uint64_t ifd_hash_value = ifd->hash_value_; + struct stat file_stat_boundary; + uint64_t tiff_file_size_boundary = 0; + if (fstat(tiff_file, &file_stat_boundary) == 0) + { + tiff_file_size_boundary = static_cast(file_stat_boundary.st_size); + } + uint32_t dest_pixel_step_y = w * samples_per_pixel; uint32_t nbytes_tw = tw * samples_per_pixel; @@ -962,8 +997,26 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff, if (offset_x >= offset_min_x && offset_x <= offset_max_x && index_y >= start_index_min_y && index_y <= end_index_max_y) { + if (static_cast(index) >= ifd->image_piece_offsets_.size() || + static_cast(index) >= ifd->image_piece_bytecounts_.size()) + { + throw std::out_of_range( + fmt::format("Tile index {} out of range (max {})", index, ifd->image_piece_offsets_.size())); + } tiledata_offset = static_cast(ifd->image_piece_offsets_[index]); tiledata_size = static_cast(ifd->image_piece_bytecounts_[index]); + + if (tiff_file_size_boundary > 0 && tiledata_size > 0) + { + uint64_t tiledata_end; + if (__builtin_add_overflow(tiledata_offset, tiledata_size, &tiledata_end) || + tiledata_end > tiff_file_size_boundary) + { + throw std::runtime_error(fmt::format( + "Tile {} data range [{}, +{}) exceeds file size {}", + index, tiledata_offset, tiledata_size, tiff_file_size_boundary)); + } + } } uint32_t tile_pixel_offset_x = (offset_x == offset_sx) ? pixel_offset_sx : 0; diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp index ba2a45c25..095fd2fb8 100644 --- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp +++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2020-2021, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ #include "tiff.h" #include +#include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include "cuslide/jpeg/libjpeg_turbo.h" #include "cuslide/lzw/lzw.h" @@ -783,7 +785,8 @@ bool TIFF::read_associated_image(const cucim::io::format::ImageMetadataDesc* met width = image_ifd->width_; height = image_ifd->height_; samples_per_pixel = image_ifd->samples_per_pixel_; - raster_size = width * height * samples_per_pixel; + raster_size = cucim::util::checked_raster_size( + static_cast(width), static_cast(height), static_cast(samples_per_pixel)); uint16_t compression_method = image_ifd->compression_; @@ -810,6 +813,13 @@ bool TIFF::read_associated_image(const cucim::io::format::ImageMetadataDesc* met uint32_t strip_nbytes = row_nbytes * rows_per_strip; uint32_t start_row = 0; + struct stat assoc_file_stat; + uint64_t assoc_file_size = 0; + if (fstat(file_handle_->fd, &assoc_file_stat) == 0) + { + assoc_file_size = static_cast(assoc_file_stat.st_size); + } + std::vector& image_piece_offsets = image_ifd->image_piece_offsets_; std::vector& image_piece_bytecounts = image_ifd->image_piece_bytecounts_; for (int64_t piece_index = 0; piece_index < piece_count; ++piece_index) @@ -817,6 +827,18 @@ bool TIFF::read_associated_image(const cucim::io::format::ImageMetadataDesc* met uint64_t offset = image_piece_offsets[piece_index]; uint64_t size = image_piece_bytecounts[piece_index]; + if (assoc_file_size > 0 && size > 0) + { + uint64_t data_end; + if (__builtin_add_overflow(offset, size, &data_end) || data_end > assoc_file_size) + { + cucim_free(raster); + throw std::runtime_error(fmt::format( + "Associated image strip {} data range [{}, +{}) exceeds file size {}", + piece_index, offset, size, assoc_file_size)); + } + } + // If the piece is the last piece, adjust strip_nbytes if (start_row + rows_per_strip >= height) { @@ -883,7 +905,8 @@ bool TIFF::read_associated_image(const cucim::io::format::ImageMetadataDesc* met width = image_width; height = image_height; samples_per_pixel = 3; // NOTE: assumes RGB image - raster_size = image_width * image_height * samples_per_pixel; + raster_size = cucim::util::checked_raster_size( + static_cast(image_width), static_cast(image_height), static_cast(samples_per_pixel)); raster = static_cast(cucim_malloc(raster_size)); // RGB image diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/tiff/ifd.cpp b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/tiff/ifd.cpp index c2dcec862..35426dca8 100644 --- a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/tiff/ifd.cpp +++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/tiff/ifd.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include // nvImageCodec handles ALL decoding (JPEG, JPEG2000, deflate, LZW, raw) @@ -930,8 +931,10 @@ size_t IFD::pixel_size_nbytes() const size_t IFD::tile_raster_size_nbytes() const { - const size_t nbytes = tile_width_ * tile_height_ * pixel_size_nbytes(); - return nbytes; + return cucim::util::checked_tile_size( + static_cast(tile_width_), + static_cast(tile_height_), + pixel_size_nbytes()); } // ============================================================================