mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-02 22:50:20 +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>
|
2019-06-18 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
* Remove previously submitted qpdf_read_memory_fuzzer as it is a
|
* 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 \
|
numrange \
|
||||||
pointer_holder \
|
pointer_holder \
|
||||||
predictors \
|
predictors \
|
||||||
|
qintc \
|
||||||
qutil \
|
qutil \
|
||||||
random \
|
random \
|
||||||
rc4 \
|
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