Skip to content

Commit 758b5a7

Browse files
authored
refactor: Use spans to solve a number of memory safety issues (#4148)
* exif.cpp: use spans to avoid several possible buffer overruns * paramlist_test.cpp: use spans to be sure we're ok on memory bounds * jpeg: use spans in jpeg icc profile manipulation for memory safety * psd output: switch cmyk conversion to use spans for memory safety I've been cobbling these together bit by bit over the last several weeks, tracking down the memory safety issues identified by the Sonar static analysis. I think that *mostly* they are not real bugs, but the code is confusing enough that it's hard to verify. This refactor makes it safer, and even if the code was previously correct but merely hard to analyze, the bounds checking in span (for debug mode) gives the static analyzer the clues it needs to know this is safe. As further explanation, these situations generally involve something that looks like this, schematically: caller () { T buffer[known_size]; call function(buffer, ...params...); } function(T* buffer, ...params...) { i, j = ...very complex things, hard to analyze... foo = buffer[i]; buffer[j] = bar; // Did we do a buffer overrun??? ¯\_(ツ)_/¯ // This function doesn't necessarily have knowldege of the // true bounds, even if it wanted to check every access. } By rewriting this function as follows: function(span<T> buffer) { // all indexed access to buffer will be bounds checked in debug } we are passing the bounds as well, and at least for debug builds, are also checking bounds with an assertion on every indexed access into buffer, without the function needing a lot of clutter. Fire and forget. --------- Signed-off-by: Larry Gritz <[email protected]>
1 parent 4531742 commit 758b5a7

File tree

7 files changed

+181
-107
lines changed

7 files changed

+181
-107
lines changed

src/include/OpenImageIO/span_util.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ make_span(T (&arg)[N]) // span from C array of known length
6969
return { arg };
7070
}
7171

72+
template<typename T>
73+
inline constexpr span<T>
74+
make_span(T* data, oiio_span_size_type size) // span from ptr + size
75+
{
76+
return { data, size };
77+
}
78+
7279
template<typename T, size_t N>
7380
inline constexpr cspan<T>
7481
make_cspan(T (&arg)[N]) // cspan from C array of known length
@@ -83,6 +90,13 @@ make_cspan(const T& arg) // cspan from a single value
8390
return { &arg, 1 };
8491
}
8592

93+
template<typename T>
94+
inline constexpr cspan<T>
95+
make_cspan(const T* data, oiio_span_size_type size) // cspan from ptr + size
96+
{
97+
return { data, size };
98+
}
99+
86100

87101

88102
/// Try to copy `n` items of type `T` from `src[srcoffset...]` to

src/jpeg.imageio/jpeg_pvt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ extern "C" {
3636
OIIO_PLUGIN_NAMESPACE_BEGIN
3737

3838

39-
#define MAX_DATA_BYTES_IN_MARKER 65519L
39+
#define MAX_DATA_BYTES_IN_MARKER 65519UL
4040
#define ICC_HEADER_SIZE 14
4141
#define ICC_PROFILE_ATTR "ICCProfile"
4242

src/jpeg.imageio/jpegoutput.cpp

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <OpenImageIO/filesystem.h>
1010
#include <OpenImageIO/fmath.h>
1111
#include <OpenImageIO/imageio.h>
12+
#include <OpenImageIO/span_util.h>
1213
#include <OpenImageIO/tiffutils.h>
1314

1415
#include "jpeg_pvt.h"
@@ -280,36 +281,33 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec,
280281
m_spec.set_format(TypeDesc::UINT8); // JPG is only 8 bit
281282

282283
// Write ICC profile, if we have anything
283-
const ParamValue* icc_profile_parameter = m_spec.find_attribute(
284-
ICC_PROFILE_ATTR);
285-
if (icc_profile_parameter != NULL) {
286-
unsigned char* icc_profile
287-
= (unsigned char*)icc_profile_parameter->data();
288-
unsigned int icc_profile_length = icc_profile_parameter->type().size();
289-
if (icc_profile && icc_profile_length) {
284+
if (auto icc_profile_parameter = m_spec.find_attribute(ICC_PROFILE_ATTR)) {
285+
cspan<unsigned char> icc_profile((unsigned char*)
286+
icc_profile_parameter->data(),
287+
icc_profile_parameter->type().size());
288+
if (icc_profile.size() && icc_profile.data()) {
290289
/* Calculate the number of markers we'll need, rounding up of course */
291-
int num_markers = icc_profile_length / MAX_DATA_BYTES_IN_MARKER;
292-
if ((unsigned int)(num_markers * MAX_DATA_BYTES_IN_MARKER)
293-
!= icc_profile_length)
290+
size_t num_markers = icc_profile.size() / MAX_DATA_BYTES_IN_MARKER;
291+
if (num_markers * MAX_DATA_BYTES_IN_MARKER
292+
!= std::size(icc_profile))
294293
num_markers++;
295-
int curr_marker = 1; /* per spec, count starts at 1*/
296-
size_t profile_size = MAX_DATA_BYTES_IN_MARKER + ICC_HEADER_SIZE;
297-
std::vector<JOCTET> profile(profile_size);
294+
int curr_marker = 1; /* per spec, count starts at 1*/
295+
std::vector<JOCTET> profile(MAX_DATA_BYTES_IN_MARKER
296+
+ ICC_HEADER_SIZE);
297+
size_t icc_profile_length = icc_profile.size();
298298
while (icc_profile_length > 0) {
299299
// length of profile to put in this marker
300-
unsigned int length
301-
= std::min(icc_profile_length,
302-
(unsigned int)MAX_DATA_BYTES_IN_MARKER);
300+
size_t length = std::min(icc_profile_length,
301+
size_t(MAX_DATA_BYTES_IN_MARKER));
303302
icc_profile_length -= length;
304303
// Write the JPEG marker header (APP2 code and marker length)
305304
strcpy((char*)profile.data(), "ICC_PROFILE"); // NOSONAR
306305
profile[11] = 0;
307306
profile[12] = curr_marker;
308307
profile[13] = (JOCTET)num_markers;
309-
OIIO_ASSERT(profile_size >= ICC_HEADER_SIZE + length);
310-
memcpy(profile.data() + ICC_HEADER_SIZE,
311-
icc_profile + length * (curr_marker - 1),
312-
length); //NOSONAR
308+
OIIO_ASSERT(profile.size() >= ICC_HEADER_SIZE + length);
309+
spancpy(make_span(profile), ICC_HEADER_SIZE, icc_profile,
310+
length * (curr_marker - 1), length);
313311
jpeg_write_marker(&m_cinfo, JPEG_APP0 + 2, profile.data(),
314312
ICC_HEADER_SIZE + length);
315313
curr_marker++;

src/libOpenImageIO/exif.cpp

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -639,85 +639,117 @@ add_exif_item_to_spec(ImageSpec& spec, const char* name,
639639
int offset_adjustment = 0)
640640
{
641641
OIIO_ASSERT(dirp);
642-
const uint8_t* dataptr = (const uint8_t*)pvt::dataptr(*dirp, buf,
643-
offset_adjustment);
644-
if (!dataptr)
645-
return;
646642
TypeDesc type = tiff_datatype_to_typedesc(*dirp);
643+
size_t count = dirp->tdir_count;
647644
if (dirp->tdir_type == TIFF_SHORT) {
648-
std::vector<uint16_t> d((const uint16_t*)dataptr,
649-
(const uint16_t*)dataptr + dirp->tdir_count);
650-
if (swab)
651-
swap_endian(d.data(), d.size());
652-
spec.attribute(name, type, d.data());
645+
cspan<uint8_t> dspan
646+
= pvt::dataspan<uint16_t>(*dirp, buf, offset_adjustment, count);
647+
if (dspan.empty())
648+
return;
649+
if (swab) {
650+
// In the byte swap case, copy it into a vector because the
651+
// upstream source isn't mutable.
652+
std::vector<uint16_t> dswab((const uint16_t*)dspan.begin(),
653+
(const uint16_t*)dspan.end());
654+
swap_endian(dswab.data(), dswab.size());
655+
spec.attribute(name, type, dswab.data());
656+
} else {
657+
spec.attribute(name, type, dspan.data());
658+
}
653659
return;
654660
}
655661
if (dirp->tdir_type == TIFF_LONG) {
656-
std::vector<uint32_t> d((const uint32_t*)dataptr,
657-
(const uint32_t*)dataptr + dirp->tdir_count);
658-
if (swab)
659-
swap_endian(d.data(), d.size());
660-
spec.attribute(name, type, d.data());
662+
cspan<uint8_t> dspan
663+
= pvt::dataspan<uint32_t>(*dirp, buf, offset_adjustment, count);
664+
if (dspan.empty())
665+
return;
666+
if (swab) {
667+
// In the byte swap case, copy it into a vector because the
668+
// upstream source isn't mutable.
669+
std::vector<uint32_t> dswab((const uint32_t*)dspan.begin(),
670+
(const uint32_t*)dspan.end());
671+
swap_endian(dswab.data(), dswab.size());
672+
spec.attribute(name, type, dswab.data());
673+
} else {
674+
spec.attribute(name, type, dspan.data());
675+
}
661676
return;
662677
}
663678
if (dirp->tdir_type == TIFF_RATIONAL) {
664-
int n = dirp->tdir_count; // How many
665-
float* f = OIIO_ALLOCA(float, n);
666-
for (int i = 0; i < n; ++i) {
679+
cspan<uint8_t> dspan
680+
= pvt::dataspan<uint32_t>(*dirp, buf, offset_adjustment, 2 * count);
681+
if (dspan.empty())
682+
return;
683+
float* f = OIIO_ALLOCA(float, count);
684+
for (size_t i = 0; i < count; ++i) {
685+
// Because the values in the blob aren't 32-bit-aligned, memcpy
686+
// them into ints to do the swapping.
667687
unsigned int num, den;
668-
memcpy(&num, dataptr + (2 * i) * sizeof(unsigned int),
669-
sizeof(unsigned int));
670-
memcpy(&den, dataptr + (2 * i + 1) * sizeof(unsigned int),
688+
memcpy(&num, dspan.data() + (2 * i) * sizeof(unsigned int),
689+
sizeof(unsigned int)); //NOSONAR
690+
memcpy(&den, dspan.data() + (2 * i + 1) * sizeof(unsigned int),
671691
sizeof(unsigned int)); //NOSONAR
672692
if (swab) {
673-
swap_endian(&num);
674-
swap_endian(&den);
693+
num = byteswap(num);
694+
den = byteswap(den);
675695
}
676696
f[i] = (float)((double)num / (double)den);
677697
}
678698
if (dirp->tdir_count == 1)
679-
spec.attribute(name, *f);
699+
spec.attribute(name, f[0]);
680700
else
681-
spec.attribute(name, TypeDesc(TypeDesc::FLOAT, n), f);
701+
spec.attribute(name, TypeDesc(TypeDesc::FLOAT, int(count)), f);
682702
return;
683703
}
684704
if (dirp->tdir_type == TIFF_SRATIONAL) {
685-
int n = dirp->tdir_count; // How many
686-
float* f = OIIO_ALLOCA(float, n);
687-
for (int i = 0; i < n; ++i) {
705+
cspan<uint8_t> dspan
706+
= pvt::dataspan<int32_t>(*dirp, buf, offset_adjustment, 2 * count);
707+
if (dspan.empty())
708+
return;
709+
float* f = OIIO_ALLOCA(float, count);
710+
for (size_t i = 0; i < count; ++i) {
711+
// Because the values in the blob aren't 32-bit-aligned, memcpy
712+
// them into ints to do the swapping.
688713
int num, den;
689-
memcpy(&num, dataptr + (2 * i) * sizeof(int), sizeof(int));
690-
memcpy(&den, dataptr + (2 * i + 1) * sizeof(int),
714+
memcpy(&num, dspan.data() + (2 * i) * sizeof(int),
715+
sizeof(int)); //NOSONAR
716+
memcpy(&den, dspan.data() + (2 * i + 1) * sizeof(int),
691717
sizeof(int)); //NOSONAR
692718
if (swab) {
693-
swap_endian(&num);
694-
swap_endian(&den);
719+
num = byteswap(num);
720+
den = byteswap(den);
695721
}
696722
f[i] = (float)((double)num / (double)den);
697723
}
698724
if (dirp->tdir_count == 1)
699-
spec.attribute(name, *f);
725+
spec.attribute(name, f[0]);
700726
else
701-
spec.attribute(name, TypeDesc(TypeDesc::FLOAT, n), f);
727+
spec.attribute(name, TypeDesc(TypeDesc::FLOAT, int(count)), f);
702728
return;
703729
}
704730
if (dirp->tdir_type == TIFF_ASCII) {
705-
int len = tiff_data_size(*dirp);
706-
const char* ptr = (const char*)dataptr;
707-
while (len && ptr[len - 1] == 0) // Don't grab the terminating null
708-
--len;
709-
std::string str(ptr, len);
731+
size_t len = tiff_data_size(*dirp);
732+
cspan<uint8_t> dspan = pvt::dataspan<char>(*dirp, buf,
733+
offset_adjustment, len);
734+
if (dspan.empty())
735+
return;
736+
// Don't grab the terminating null
737+
while (dspan.size() && dspan.back() == 0)
738+
dspan = dspan.subspan(0, dspan.size() - 1);
739+
std::string str(dspan.begin(), dspan.end());
710740
if (strlen(str.c_str()) < str.length()) // Stray \0 in the middle
711741
str = std::string(str.c_str());
712742
spec.attribute(name, str);
713743
return;
714744
}
715-
if (dirp->tdir_type == TIFF_BYTE && dirp->tdir_count == 1) {
745+
if (dirp->tdir_type == TIFF_BYTE && count == 1) {
716746
// Not sure how to handle "bytes" generally, but certainly for just
717747
// one, add it as an int.
718-
unsigned char d;
719-
d = *dataptr; // byte stored in offset itself
720-
spec.attribute(name, (int)d);
748+
cspan<uint8_t> dspan = pvt::dataspan<uint8_t>(*dirp, buf,
749+
offset_adjustment, count);
750+
if (dspan.empty())
751+
return;
752+
spec.attribute(name, (int)dspan[0]);
721753
return;
722754
}
723755

src/libOpenImageIO/exif.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ dataptr(const TIFFDirEntry& td, cspan<uint8_t> data, int offset_adjustment)
4444

4545

4646

47+
// Return a span that bounds offset data within the dir entry.
48+
// No matter what the type T, we still return a cspan<uint8_t> because it's
49+
// unaligned data somewhere in the middle of a byte array. We would have
50+
// alignment errors if we try to access it as some other type if it's not
51+
// properly aligned.
52+
template<typename T>
53+
inline cspan<uint8_t>
54+
dataspan(const TIFFDirEntry& td, cspan<uint8_t> data, int offset_adjustment,
55+
size_t count)
56+
{
57+
size_t len = tiff_data_size(td);
58+
OIIO_DASSERT(len == sizeof(T) * count);
59+
if (len <= 4)
60+
return { (const uint8_t*)&td.tdir_offset, oiio_span_size_type(len) };
61+
else {
62+
int offset = td.tdir_offset + offset_adjustment;
63+
if (offset < 0 || size_t(offset) + len > std::size(data))
64+
return {}; // out of bounds! return empty span
65+
return { data.data() + offset, oiio_span_size_type(len) };
66+
}
67+
}
68+
69+
70+
4771
struct LabelIndex {
4872
int value;
4973
const char* label;

0 commit comments

Comments
 (0)