mirror of
https://github.com/qpdf/qpdf.git
synced 2024-10-31 19:02:30 +00:00
New safe type converters in QIntC
This commit is contained in:
parent
bdf29ca33e
commit
a66828caff
@ -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
257
include/qpdf/QIntC.hh
Normal 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
|
@ -17,6 +17,7 @@ BINS_libtests = \
|
||||
numrange \
|
||||
pointer_holder \
|
||||
predictors \
|
||||
qintc \
|
||||
qutil \
|
||||
random \
|
||||
rc4 \
|
||||
|
57
libtests/qintc.cc
Normal file
57
libtests/qintc.cc
Normal 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
18
libtests/qtest/qintc.test
Normal 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);
|
13
libtests/qtest/qintc/qintc.out
Normal file
13
libtests/qtest/qintc/qintc.out
Normal 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
|
Loading…
Reference in New Issue
Block a user