diff options
author | Jay Berkenbilt <ejb@ql.org> | 2019-06-20 00:53:22 +0200 |
---|---|---|
committer | Jay Berkenbilt <ejb@ql.org> | 2019-06-21 19:17:21 +0200 |
commit | a66828caff16a4ad64b9d69b5db1c5a5e60418cc (patch) | |
tree | 1f4d5ab4b20668a845d9f7d09b6be5152985c4d3 | |
parent | bdf29ca33e1b9cd73c46a71019e88a964a1dd91f (diff) | |
download | qpdf-a66828caff16a4ad64b9d69b5db1c5a5e60418cc.tar.zst |
New safe type converters in QIntC
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | include/qpdf/QIntC.hh | 257 | ||||
-rw-r--r-- | libtests/build.mk | 1 | ||||
-rw-r--r-- | libtests/qintc.cc | 57 | ||||
-rw-r--r-- | libtests/qtest/qintc.test | 18 | ||||
-rw-r--r-- | libtests/qtest/qintc/qintc.out | 13 |
6 files changed, 351 insertions, 0 deletions
@@ -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 |