Commit a66828caff16a4ad64b9d69b5db1c5a5e60418cc

Authored by Jay Berkenbilt
1 parent bdf29ca3

New safe type converters in QIntC

ChangeLog
  1 +2019-06-20 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add QIC.hh, containing integer type converters that do range
  4 + checking.
  5 +
1 6 2019-06-18 Jay Berkenbilt <ejb@ql.org>
2 7  
3 8 * Remove previously submitted qpdf_read_memory_fuzzer as it is a
... ...
include/qpdf/QIntC.hh 0 → 100644
  1 +// Copyright (c) 2005-2019 Jay Berkenbilt
  2 +//
  3 +// This file is part of qpdf.
  4 +//
  5 +// Licensed under the Apache License, Version 2.0 (the "License");
  6 +// you may not use this file except in compliance with the License.
  7 +// You may obtain a copy of the License at
  8 +//
  9 +// http://www.apache.org/licenses/LICENSE-2.0
  10 +//
  11 +// Unless required by applicable law or agreed to in writing, software
  12 +// distributed under the License is distributed on an "AS IS" BASIS,
  13 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +// See the License for the specific language governing permissions and
  15 +// limitations under the License.
  16 +//
  17 +// Versions of qpdf prior to version 7 were released under the terms
  18 +// of version 2.0 of the Artistic License. At your option, you may
  19 +// continue to consider qpdf to be licensed under those terms. Please
  20 +// see the manual for additional information.
  21 +
  22 +#ifndef QINTC_HH
  23 +#define QINTC_HH
  24 +
  25 +#include <qpdf/DLL.h>
  26 +#include <qpdf/Types.h>
  27 +#include <stdexcept>
  28 +#include <iostream>
  29 +#include <limits>
  30 +#include <sstream>
  31 +#include <cassert>
  32 +
  33 +// This namespace provides safe integer conversion that detects
  34 +// overflows. It uses short, cryptic names for brevity.
  35 +
  36 +namespace QIntC // QIntC = qpdf Integer Conversion
  37 +{
  38 + // Create templates to get the unsigned version of integer types.
  39 + // With C++11, we could use std::make_unsigned, but qpdf, at least
  40 + // for now, supports pre-c++11 compilers.
  41 + template <typename T>
  42 + class to_u
  43 + {
  44 + };
  45 +
  46 + template <>
  47 + class to_u<char>
  48 + {
  49 + public:
  50 + typedef unsigned char type;
  51 + };
  52 +
  53 + template <>
  54 + class to_u<short>
  55 + {
  56 + public:
  57 + typedef unsigned short type;
  58 + };
  59 +
  60 + template <>
  61 + class to_u<int>
  62 + {
  63 + public:
  64 + typedef unsigned int type;
  65 + };
  66 +
  67 + template <>
  68 + class to_u<long>
  69 + {
  70 + public:
  71 + typedef unsigned long type;
  72 + };
  73 +
  74 + template <>
  75 + class to_u<long long>
  76 + {
  77 + public:
  78 + typedef unsigned long long type;
  79 + };
  80 +
  81 + // Basic IntConverter class, which converts an integer from the
  82 + // From class to one of the To class if it can be done safely and
  83 + // throws a range_error otherwise. This class is specialized for
  84 + // each permutation of signed/unsigned for the From and To
  85 + // classes.
  86 + template <typename From, typename To,
  87 + bool From_signed = std::numeric_limits<From>::is_signed,
  88 + bool To_signed = std::numeric_limits<To>::is_signed>
  89 + class IntConverter
  90 + {
  91 + };
  92 +
  93 + template <typename From, typename To>
  94 + class IntConverter<From, To, false, false>
  95 + {
  96 + public:
  97 + static To convert(From const& i)
  98 + {
  99 + // From and To are both unsigned.
  100 + if (i > std::numeric_limits<To>::max())
  101 + {
  102 + std::ostringstream msg;
  103 + msg << "integer out of range converting " << i
  104 + << " from a "
  105 + << sizeof(From) << "-byte unsigned type to a "
  106 + << sizeof(To) << "-byte unsigned type";
  107 + throw std::range_error(msg.str());
  108 + }
  109 + return static_cast<To>(i);
  110 + }
  111 + };
  112 +
  113 + template <typename From, typename To>
  114 + class IntConverter<From, To, true, true>
  115 + {
  116 + public:
  117 + static To convert(From const& i)
  118 + {
  119 + // From and To are both signed.
  120 + if ((i < std::numeric_limits<To>::min()) ||
  121 + (i > std::numeric_limits<To>::max()))
  122 + {
  123 + std::ostringstream msg;
  124 + msg << "integer out of range converting " << i
  125 + << " from a "
  126 + << sizeof(From) << "-byte signed type to a "
  127 + << sizeof(To) << "-byte signed type";
  128 + throw std::range_error(msg.str());
  129 + }
  130 + return static_cast<To>(i);
  131 + }
  132 + };
  133 +
  134 + template <typename From, typename To>
  135 + class IntConverter<From, To, true, false>
  136 + {
  137 + public:
  138 + static To convert(From const& i)
  139 + {
  140 + // From is signed, and To is unsigned. If i > 0, it's safe to
  141 + // convert it to the corresponding unsigned type and to
  142 + // compare with To's max.
  143 + typename to_u<From>::type ii =
  144 + static_cast<typename to_u<From>::type>(i);
  145 + if ((i < 0) || (ii > std::numeric_limits<To>::max()))
  146 + {
  147 + std::ostringstream msg;
  148 + msg << "integer out of range converting " << i
  149 + << " from a "
  150 + << sizeof(From) << "-byte signed type to a "
  151 + << sizeof(To) << "-byte unsigned type";
  152 + throw std::range_error(msg.str());
  153 + }
  154 + return static_cast<To>(i);
  155 + }
  156 + };
  157 +
  158 + template <typename From, typename To>
  159 + class IntConverter<From, To, false, true>
  160 + {
  161 + public:
  162 + static To convert(From const& i)
  163 + {
  164 + // From is unsigned, and to is signed. Convert To's max to the
  165 + // unsigned version of To and compare i against that.
  166 + typename to_u<To>::type maxval =
  167 + static_cast<typename to_u<To>::type>(
  168 + std::numeric_limits<To>::max());
  169 + if (i > maxval)
  170 + {
  171 + std::ostringstream msg;
  172 + msg << "integer out of range converting " << i
  173 + << " from a "
  174 + << sizeof(From) << "-byte unsigned type to a "
  175 + << sizeof(To) << "-byte signed type";
  176 + throw std::range_error(msg.str());
  177 + }
  178 + return static_cast<To>(i);
  179 + }
  180 + };
  181 +
  182 + // Specific converers. The return type of each function must match
  183 + // the second template prameter to IntConverter.
  184 + template <typename T>
  185 + char to_char(T const& i)
  186 + {
  187 + return IntConverter<T, char>::convert(i);
  188 + }
  189 +
  190 + template <typename T>
  191 + unsigned char to_uchar(T const& i)
  192 + {
  193 + return IntConverter<T, unsigned char>::convert(i);
  194 + }
  195 +
  196 + template <typename T>
  197 + short to_short(T const& i)
  198 + {
  199 + return IntConverter<T, short>::convert(i);
  200 + }
  201 +
  202 + template <typename T>
  203 + unsigned short to_ushort(T const& i)
  204 + {
  205 + return IntConverter<T, unsigned short>::convert(i);
  206 + }
  207 +
  208 + template <typename T>
  209 + int to_int(T const& i)
  210 + {
  211 + return IntConverter<T, int>::convert(i);
  212 + }
  213 +
  214 + template <typename T>
  215 + unsigned int to_uint(T const& i)
  216 + {
  217 + return IntConverter<T, unsigned int>::convert(i);
  218 + }
  219 +
  220 + template <typename T>
  221 + size_t to_size(T const& i)
  222 + {
  223 + return IntConverter<T, size_t>::convert(i);
  224 + }
  225 +
  226 + template <typename T>
  227 + qpdf_offset_t to_offset(T const& i)
  228 + {
  229 + return IntConverter<T, qpdf_offset_t>::convert(i);
  230 + }
  231 +
  232 + template <typename T>
  233 + long to_long(T const& i)
  234 + {
  235 + return IntConverter<T, long >::convert(i);
  236 + }
  237 +
  238 + template <typename T>
  239 + unsigned long to_ulong(T const& i)
  240 + {
  241 + return IntConverter<T, unsigned long >::convert(i);
  242 + }
  243 +
  244 + template <typename T>
  245 + long long to_longlong(T const& i)
  246 + {
  247 + return IntConverter<T, long long>::convert(i);
  248 + }
  249 +
  250 + template <typename T>
  251 + unsigned long long to_ulonglong(T const& i)
  252 + {
  253 + return IntConverter<T, unsigned long long>::convert(i);
  254 + }
  255 +};
  256 +
  257 +#endif // QINTC_HH
... ...
libtests/build.mk
... ... @@ -17,6 +17,7 @@ BINS_libtests = \
17 17 numrange \
18 18 pointer_holder \
19 19 predictors \
  20 + qintc \
20 21 qutil \
21 22 random \
22 23 rc4 \
... ...
libtests/qintc.cc 0 → 100644
  1 +#include <qpdf/QIntC.hh>
  2 +#include <stdint.h>
  3 +#include <cassert>
  4 +
  5 +#define try_convert(exp_pass, fn, i) \
  6 + try_convert_real(#fn "(" #i ")", exp_pass, fn, i)
  7 +
  8 +template <typename From, typename To>
  9 +static void try_convert_real(
  10 + char const* description, bool exp_pass,
  11 + To (*fn)(From const&), From const& i)
  12 +{
  13 + bool passed = false;
  14 + try
  15 + {
  16 + To result = fn(i);
  17 + passed = true;
  18 + std::cout << description << ": " << i << " " << result;
  19 + }
  20 + catch (std::range_error& e)
  21 + {
  22 + std::cout << description << ": " << e.what();
  23 + passed = false;
  24 + }
  25 + std::cout << ((passed == exp_pass) ? " PASSED" : " FAILED") << std::endl;
  26 +}
  27 +
  28 +int main()
  29 +{
  30 + uint32_t u1 = 3141592653U; // Too big for signed type
  31 + int32_t i1 = -1153374643; // Same bit pattern as u1
  32 + uint64_t ul1 = 1099511627776LL; // Too big for 32-bit
  33 + uint64_t ul2 = 12345; // Fits into 32-bit
  34 + int32_t i2 = 81; // Fits in char and uchar
  35 + char c1 = '\xf7'; // Signed vaule when char
  36 +
  37 + // Verify i1 and u1 have same bit pattern
  38 + assert(static_cast<uint32_t>(i1) == u1);
  39 + // Verify that we can unsafely convert between char and unsigned char
  40 + assert(c1 == static_cast<char>(static_cast<unsigned char>(c1)));
  41 +
  42 + try_convert(true, QIntC::to_int<int32_t>, i1);
  43 + try_convert(true, QIntC::to_uint<uint32_t>, u1);
  44 + try_convert(false, QIntC::to_int<uint32_t>, u1);
  45 + try_convert(false, QIntC::to_uint<int32_t>, i1);
  46 + try_convert(false, QIntC::to_int<uint64_t>, ul1);
  47 + try_convert(true, QIntC::to_int<uint64_t>, ul2);
  48 + try_convert(true, QIntC::to_uint<uint64_t>, ul2);
  49 + try_convert(true, QIntC::to_offset<uint32_t>, u1);
  50 + try_convert(true, QIntC::to_offset<int32_t>, i1);
  51 + try_convert(false, QIntC::to_size<int32_t>, i1);
  52 + try_convert(true, QIntC::to_char<int32_t>, i2);
  53 + try_convert(true, QIntC::to_uchar<int32_t>, i2);
  54 + try_convert(false, QIntC::to_uchar<char>, c1);
  55 +
  56 + return 0;
  57 +}
... ...
libtests/qtest/qintc.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +BEGIN { $^W = 1; }
  4 +use strict;
  5 +
  6 +chdir("qintc") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +my $td = new TestDriver('qintc');
  11 +
  12 +$td->runtest("QINTC",
  13 + {$td->COMMAND => "qintc"},
  14 + {$td->FILE => "qintc.out",
  15 + $td->EXIT_STATUS => 0},
  16 + $td->NORMALIZE_NEWLINES | $td->RM_WS_ONLY_LINES);
  17 +
  18 +$td->report(1);
... ...
libtests/qtest/qintc/qintc.out 0 → 100644
  1 +QIntC::to_int<int32_t>(i1): -1153374643 -1153374643 PASSED
  2 +QIntC::to_uint<uint32_t>(u1): 3141592653 3141592653 PASSED
  3 +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
  4 +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
  5 +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
  6 +QIntC::to_int<uint64_t>(ul2): 12345 12345 PASSED
  7 +QIntC::to_uint<uint64_t>(ul2): 12345 12345 PASSED
  8 +QIntC::to_offset<uint32_t>(u1): 3141592653 3141592653 PASSED
  9 +QIntC::to_offset<int32_t>(i1): -1153374643 -1153374643 PASSED
  10 +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
  11 +QIntC::to_char<int32_t>(i2): 81 Q PASSED
  12 +QIntC::to_uchar<int32_t>(i2): 81 Q PASSED
  13 +QIntC::to_uchar<char>(c1): integer out of range converting ÷ from a 1-byte signed type to a 1-byte unsigned type PASSED
... ...