aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--include/qpdf/QIntC.hh257
-rw-r--r--libtests/build.mk1
-rw-r--r--libtests/qintc.cc57
-rw-r--r--libtests/qtest/qintc.test18
-rw-r--r--libtests/qtest/qintc/qintc.out13
6 files changed, 351 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 3917e87a..d88fad35 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2019-06-20 Jay Berkenbilt <ejb@ql.org>
+
+ * Add QIC.hh, containing integer type converters that do range
+ checking.
+
2019-06-18 Jay Berkenbilt <ejb@ql.org>
* Remove previously submitted qpdf_read_memory_fuzzer as it is a
diff --git a/include/qpdf/QIntC.hh b/include/qpdf/QIntC.hh
new file mode 100644
index 00000000..dac85065
--- /dev/null
+++ b/include/qpdf/QIntC.hh
@@ -0,0 +1,257 @@
+// Copyright (c) 2005-2019 Jay Berkenbilt
+//
+// This file is part of qpdf.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Versions of qpdf prior to version 7 were released under the terms
+// of version 2.0 of the Artistic License. At your option, you may
+// continue to consider qpdf to be licensed under those terms. Please
+// see the manual for additional information.
+
+#ifndef QINTC_HH
+#define QINTC_HH
+
+#include <qpdf/DLL.h>
+#include <qpdf/Types.h>
+#include <stdexcept>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <cassert>
+
+// This namespace provides safe integer conversion that detects
+// overflows. It uses short, cryptic names for brevity.
+
+namespace QIntC // QIntC = qpdf Integer Conversion
+{
+ // Create templates to get the unsigned version of integer types.
+ // With C++11, we could use std::make_unsigned, but qpdf, at least
+ // for now, supports pre-c++11 compilers.
+ template <typename T>
+ class to_u
+ {
+ };
+
+ template <>
+ class to_u<char>
+ {
+ public:
+ typedef unsigned char type;
+ };
+
+ template <>
+ class to_u<short>
+ {
+ public:
+ typedef unsigned short type;
+ };
+
+ template <>
+ class to_u<int>
+ {
+ public:
+ typedef unsigned int type;
+ };
+
+ template <>
+ class to_u<long>
+ {
+ public:
+ typedef unsigned long type;
+ };
+
+ template <>
+ class to_u<long long>
+ {
+ public:
+ typedef unsigned long long type;
+ };
+
+ // Basic IntConverter class, which converts an integer from the
+ // From class to one of the To class if it can be done safely and
+ // throws a range_error otherwise. This class is specialized for
+ // each permutation of signed/unsigned for the From and To
+ // classes.
+ template <typename From, typename To,
+ bool From_signed = std::numeric_limits<From>::is_signed,
+ bool To_signed = std::numeric_limits<To>::is_signed>
+ class IntConverter
+ {
+ };
+
+ template <typename From, typename To>
+ class IntConverter<From, To, false, false>
+ {
+ public:
+ static To convert(From const& i)
+ {
+ // From and To are both unsigned.
+ if (i > std::numeric_limits<To>::max())
+ {
+ std::ostringstream msg;
+ msg << "integer out of range converting " << i
+ << " from a "
+ << sizeof(From) << "-byte unsigned type to a "
+ << sizeof(To) << "-byte unsigned type";
+ throw std::range_error(msg.str());
+ }
+ return static_cast<To>(i);
+ }
+ };
+
+ template <typename From, typename To>
+ class IntConverter<From, To, true, true>
+ {
+ public:
+ static To convert(From const& i)
+ {
+ // From and To are both signed.
+ if ((i < std::numeric_limits<To>::min()) ||
+ (i > std::numeric_limits<To>::max()))
+ {
+ std::ostringstream msg;
+ msg << "integer out of range converting " << i
+ << " from a "
+ << sizeof(From) << "-byte signed type to a "
+ << sizeof(To) << "-byte signed type";
+ throw std::range_error(msg.str());
+ }
+ return static_cast<To>(i);
+ }
+ };
+
+ template <typename From, typename To>
+ class IntConverter<From, To, true, false>
+ {
+ public:
+ static To convert(From const& i)
+ {
+ // From is signed, and To is unsigned. If i > 0, it's safe to
+ // convert it to the corresponding unsigned type and to
+ // compare with To's max.
+ typename to_u<From>::type ii =
+ static_cast<typename to_u<From>::type>(i);
+ if ((i < 0) || (ii > std::numeric_limits<To>::max()))
+ {
+ std::ostringstream msg;
+ msg << "integer out of range converting " << i
+ << " from a "
+ << sizeof(From) << "-byte signed type to a "
+ << sizeof(To) << "-byte unsigned type";
+ throw std::range_error(msg.str());
+ }
+ return static_cast<To>(i);
+ }
+ };
+
+ template <typename From, typename To>
+ class IntConverter<From, To, false, true>
+ {
+ public:
+ static To convert(From const& i)
+ {
+ // From is unsigned, and to is signed. Convert To's max to the
+ // unsigned version of To and compare i against that.
+ typename to_u<To>::type maxval =
+ static_cast<typename to_u<To>::type>(
+ std::numeric_limits<To>::max());
+ if (i > maxval)
+ {
+ std::ostringstream msg;
+ msg << "integer out of range converting " << i
+ << " from a "
+ << sizeof(From) << "-byte unsigned type to a "
+ << sizeof(To) << "-byte signed type";
+ throw std::range_error(msg.str());
+ }
+ return static_cast<To>(i);
+ }
+ };
+
+ // Specific converers. The return type of each function must match
+ // the second template prameter to IntConverter.
+ template <typename T>
+ char to_char(T const& i)
+ {
+ return IntConverter<T, char>::convert(i);
+ }
+
+ template <typename T>
+ unsigned char to_uchar(T const& i)
+ {
+ return IntConverter<T, unsigned char>::convert(i);
+ }
+
+ template <typename T>
+ short to_short(T const& i)
+ {
+ return IntConverter<T, short>::convert(i);
+ }
+
+ template <typename T>
+ unsigned short to_ushort(T const& i)
+ {
+ return IntConverter<T, unsigned short>::convert(i);
+ }
+
+ template <typename T>
+ int to_int(T const& i)
+ {
+ return IntConverter<T, int>::convert(i);
+ }
+
+ template <typename T>
+ unsigned int to_uint(T const& i)
+ {
+ return IntConverter<T, unsigned int>::convert(i);
+ }
+
+ template <typename T>
+ size_t to_size(T const& i)
+ {
+ return IntConverter<T, size_t>::convert(i);
+ }
+
+ template <typename T>
+ qpdf_offset_t to_offset(T const& i)
+ {
+ return IntConverter<T, qpdf_offset_t>::convert(i);
+ }
+
+ template <typename T>
+ long to_long(T const& i)
+ {
+ return IntConverter<T, long >::convert(i);
+ }
+
+ template <typename T>
+ unsigned long to_ulong(T const& i)
+ {
+ return IntConverter<T, unsigned long >::convert(i);
+ }
+
+ template <typename T>
+ long long to_longlong(T const& i)
+ {
+ return IntConverter<T, long long>::convert(i);
+ }
+
+ template <typename T>
+ unsigned long long to_ulonglong(T const& i)
+ {
+ return IntConverter<T, unsigned long long>::convert(i);
+ }
+};
+
+#endif // QINTC_HH
diff --git a/libtests/build.mk b/libtests/build.mk
index 5c1ee56a..4313b630 100644
--- a/libtests/build.mk
+++ b/libtests/build.mk
@@ -17,6 +17,7 @@ BINS_libtests = \
numrange \
pointer_holder \
predictors \
+ qintc \
qutil \
random \
rc4 \
diff --git a/libtests/qintc.cc b/libtests/qintc.cc
new file mode 100644
index 00000000..79fc6f53
--- /dev/null
+++ b/libtests/qintc.cc
@@ -0,0 +1,57 @@
+#include <qpdf/QIntC.hh>
+#include <stdint.h>
+#include <cassert>
+
+#define try_convert(exp_pass, fn, i) \
+ try_convert_real(#fn "(" #i ")", exp_pass, fn, i)
+
+template <typename From, typename To>
+static void try_convert_real(
+ char const* description, bool exp_pass,
+ To (*fn)(From const&), From const& i)
+{
+ bool passed = false;
+ try
+ {
+ To result = fn(i);
+ passed = true;
+ std::cout << description << ": " << i << " " << result;
+ }
+ catch (std::range_error& e)
+ {
+ std::cout << description << ": " << e.what();
+ passed = false;
+ }
+ std::cout << ((passed == exp_pass) ? " PASSED" : " FAILED") << std::endl;
+}
+
+int main()
+{
+ uint32_t u1 = 3141592653U; // Too big for signed type
+ int32_t i1 = -1153374643; // Same bit pattern as u1
+ uint64_t ul1 = 1099511627776LL; // Too big for 32-bit
+ uint64_t ul2 = 12345; // Fits into 32-bit
+ int32_t i2 = 81; // Fits in char and uchar
+ char c1 = '\xf7'; // Signed vaule when char
+
+ // Verify i1 and u1 have same bit pattern
+ assert(static_cast<uint32_t>(i1) == u1);
+ // Verify that we can unsafely convert between char and unsigned char
+ assert(c1 == static_cast<char>(static_cast<unsigned char>(c1)));
+
+ try_convert(true, QIntC::to_int<int32_t>, i1);
+ try_convert(true, QIntC::to_uint<uint32_t>, u1);
+ try_convert(false, QIntC::to_int<uint32_t>, u1);
+ try_convert(false, QIntC::to_uint<int32_t>, i1);
+ try_convert(false, QIntC::to_int<uint64_t>, ul1);
+ try_convert(true, QIntC::to_int<uint64_t>, ul2);
+ try_convert(true, QIntC::to_uint<uint64_t>, ul2);
+ try_convert(true, QIntC::to_offset<uint32_t>, u1);
+ try_convert(true, QIntC::to_offset<int32_t>, i1);
+ try_convert(false, QIntC::to_size<int32_t>, i1);
+ try_convert(true, QIntC::to_char<int32_t>, i2);
+ try_convert(true, QIntC::to_uchar<int32_t>, i2);
+ try_convert(false, QIntC::to_uchar<char>, c1);
+
+ return 0;
+}
diff --git a/libtests/qtest/qintc.test b/libtests/qtest/qintc.test
new file mode 100644
index 00000000..4b2dc3a5
--- /dev/null
+++ b/libtests/qtest/qintc.test
@@ -0,0 +1,18 @@
+#!/usr/bin/env perl
+require 5.008;
+BEGIN { $^W = 1; }
+use strict;
+
+chdir("qintc") or die "chdir testdir failed: $!\n";
+
+require TestDriver;
+
+my $td = new TestDriver('qintc');
+
+$td->runtest("QINTC",
+ {$td->COMMAND => "qintc"},
+ {$td->FILE => "qintc.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES | $td->RM_WS_ONLY_LINES);
+
+$td->report(1);
diff --git a/libtests/qtest/qintc/qintc.out b/libtests/qtest/qintc/qintc.out
new file mode 100644
index 00000000..9b6d35d9
--- /dev/null
+++ b/libtests/qtest/qintc/qintc.out
@@ -0,0 +1,13 @@
+QIntC::to_int<int32_t>(i1): -1153374643 -1153374643 PASSED
+QIntC::to_uint<uint32_t>(u1): 3141592653 3141592653 PASSED
+QIntC::to_int<uint32_t>(u1): integer out of range converting 3141592653 from a 4-byte unsigned type to a 4-byte signed type PASSED
+QIntC::to_uint<int32_t>(i1): integer out of range converting -1153374643 from a 4-byte signed type to a 4-byte unsigned type PASSED
+QIntC::to_int<uint64_t>(ul1): integer out of range converting 1099511627776 from a 8-byte unsigned type to a 4-byte signed type PASSED
+QIntC::to_int<uint64_t>(ul2): 12345 12345 PASSED
+QIntC::to_uint<uint64_t>(ul2): 12345 12345 PASSED
+QIntC::to_offset<uint32_t>(u1): 3141592653 3141592653 PASSED
+QIntC::to_offset<int32_t>(i1): -1153374643 -1153374643 PASSED
+QIntC::to_size<int32_t>(i1): integer out of range converting -1153374643 from a 4-byte signed type to a 8-byte unsigned type PASSED
+QIntC::to_char<int32_t>(i2): 81 Q PASSED
+QIntC::to_uchar<int32_t>(i2): 81 Q PASSED
+QIntC::to_uchar<char>(c1): integer out of range converting ÷ from a 1-byte signed type to a 1-byte unsigned type PASSED