New safe type converters in QIntC

This commit is contained in:
Jay Berkenbilt 2019-06-19 18:53:22 -04:00
parent bdf29ca33e
commit a66828caff
6 changed files with 351 additions and 0 deletions

View File

@ -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

257
include/qpdf/QIntC.hh Normal file
View File

@ -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

View File

@ -17,6 +17,7 @@ BINS_libtests = \
numrange \
pointer_holder \
predictors \
qintc \
qutil \
random \
rc4 \

57
libtests/qintc.cc Normal file
View File

@ -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;
}

18
libtests/qtest/qintc.test Normal file
View File

@ -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);

View File

@ -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