summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorJINMEI Tatuya <jinmei@isc.org>2011-04-27 08:17:42 +0200
committerJINMEI Tatuya <jinmei@isc.org>2011-04-27 08:17:42 +0200
commitf4ad75548592b2f3eeae22de5685cacbf5c82bae (patch)
treec191b4599b3b557071fedbca15fb81c60b6e3722 /src/lib
parent[trac812] reject NULL or len=0 data (diff)
downloadkea-f4ad75548592b2f3eeae22de5685cacbf5c82bae.tar.xz
kea-f4ad75548592b2f3eeae22de5685cacbf5c82bae.zip
[trac812] ensure TSIGContext::sign() provides strong exception guarantee
added new test for that. with some other cleanups.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/dns/tests/tsig_unittest.cc36
-rw-r--r--src/lib/dns/tsig.cc51
-rw-r--r--src/lib/dns/tsig.h33
-rw-r--r--src/lib/dns/tsigerror.cc31
-rw-r--r--src/lib/util/unittests/Makefile.am1
-rw-r--r--src/lib/util/unittests/newhook.cc51
-rw-r--r--src/lib/util/unittests/newhook.h76
7 files changed, 223 insertions, 56 deletions
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index 669e38f425..ad2676ade8 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -14,6 +14,7 @@
#include <time.h>
#include <string>
+#include <stdexcept>
#include <vector>
#include <boost/scoped_ptr.hpp>
@@ -24,6 +25,7 @@
#include <util/buffer.h>
#include <util/encode/base64.h>
+#include <util/unittests/newhook.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
@@ -279,6 +281,40 @@ TEST_F(TSIGTest, signBadData) {
EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter);
}
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+// We enable this test only when we enable custom new/delete at build time
+// We could enable/disable the test runtime using the gtest filter, but
+// we'd basically like to minimize the number of disabled tests (they
+// should generally be considered tests that temporarily fail and should
+// be fixed).
+TEST_F(TSIGTest, signExceptionSafety) {
+ // Check sign() provides the strong exception guarantee for the simpler
+ // case (with a key error and empty MAC). The general case is more
+ // complicated and involves more memory allocation, so the test result
+ // won't be reliable.
+
+ tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
+ tsig_ctx.get()),
+ TSIGError::BAD_KEY());
+ // At this point the state should be changed to "CHECKED"
+ ASSERT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ try {
+ int dummydata;
+ isc::util::unittests::force_throw_on_new = true;
+ isc::util::unittests::throw_size_on_new = sizeof(TSIGRecord);
+ tsig_verify_ctx->sign(0, &dummydata, sizeof(dummydata));
+ isc::util::unittests::force_throw_on_new = false;
+ ASSERT_FALSE(true) << "Expected throw on new, but it didn't happen";
+ } catch (const std::bad_alloc&) {
+ isc::util::unittests::force_throw_on_new = false;
+
+ // sign() threw, so the state should still be "CHECKED".
+ EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+ }
+ isc::util::unittests::force_throw_on_new = false;
+}
+#endif // ENABLE_CUSTOM_OPERATOR_NEW
+
// Same test as "sign" but use a different algorithm just to confirm we don't
// naively hardcode constants specific to a particular algorithm.
// Test data generated by
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
index 3a51f03a7c..6e24eb67d6 100644
--- a/src/lib/dns/tsig.cc
+++ b/src/lib/dns/tsig.cc
@@ -19,7 +19,6 @@
#include <cassert> // for the tentative verifyTentative()
#include <vector>
-#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
@@ -67,6 +66,12 @@ gettimeofdayWrapper() {
namespace {
typedef boost::shared_ptr<HMAC> HMACPtr;
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+ return (RRClass::ANY());
+}
struct TSIGContext::TSIGContextImpl {
TSIGContextImpl(const TSIGKey& key) :
@@ -79,12 +84,6 @@ struct TSIGContext::TSIGContextImpl {
TSIGError error_;
uint64_t previous_timesigned_; // only meaningful for response with BADTIME
};
-}
-
-const RRClass&
-TSIGRecord::getClass() {
- return (RRClass::ANY());
-}
TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key))
{
@@ -123,12 +122,12 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
// For errors related to key or MAC, return an unsigned response as
// specified in Section 4.3 of RFC2845.
if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
- impl_->previous_digest_.clear();
- impl_->state_ = SIGNED;
ConstTSIGRecordPtr tsig(new TSIGRecord(
any::TSIG(impl_->key_.getAlgorithmName(),
now, DEFAULT_FUDGE, NULL, 0,
qid, error.getCode(), 0, NULL)));
+ impl_->previous_digest_.clear();
+ impl_->state_ = SIGNED;
return (tsig);
}
@@ -191,16 +190,17 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
const uint16_t otherlen = variables.getLength();
// Get the final digest, update internal state, then finish.
- impl_->previous_digest_ = hmac->sign();
- impl_->state_ = SIGNED;
+ vector<uint8_t> digest = hmac->sign();
ConstTSIGRecordPtr tsig(new TSIGRecord(
any::TSIG(impl_->key_.getAlgorithmName(),
time_signed, DEFAULT_FUDGE,
- impl_->previous_digest_.size(),
- &impl_->previous_digest_[0],
+ digest.size(), &digest[0],
qid, error.getCode(), otherlen,
otherlen == 0 ?
NULL : variables.getData())));
+ // Exception free from now on.
+ impl_->previous_digest_.swap(digest);
+ impl_->state_ = SIGNED;
return (tsig);
}
@@ -222,30 +222,5 @@ TSIGContext::verifyTentative(ConstTSIGRecordPtr tsig, TSIGError error) {
impl_->state_ = CHECKED;
}
-
-namespace {
-const char* const tsigerror_text[] = {
- "BADSIG",
- "BADKEY",
- "BADTIME"
-};
-}
-
-TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) {
- if (code_ > MAX_RCODE_FOR_TSIGERROR) {
- isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode);
- }
-}
-
-string
-TSIGError::toText() const {
- if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
- return (Rcode(code_).toText());
- } else if (code_ <= BAD_TIME_CODE) {
- return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]);
- } else {
- return (boost::lexical_cast<string>(code_));
- }
-}
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
index f743d472bc..55ab41e1b4 100644
--- a/src/lib/dns/tsig.h
+++ b/src/lib/dns/tsig.h
@@ -106,22 +106,22 @@ typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
/// This class supports all these cases.
///
/// A \c TSIGContext object is generally constructed with a TSIG key to be
-/// used for the session, and keeps truck of various kinds of session specific
+/// used for the session, and keeps track of various kinds of session specific
/// information, such as the original digest while waiting for a response or
/// verification error information that is to be used for a subsequent
/// response.
///
/// This class has two main methods, \c sign() and \c verify().
/// The \c sign() method signs given data (which is supposed to be a complete
-/// DNS message to be signed) using the TSIG key and other related information
-/// associated with the \c TSIGContext object.
+/// DNS message without the TSIG itself) using the TSIG key and other
+/// related information associated with the \c TSIGContext object.
/// The \c verify() method verifies a given DNS message that contains a TSIG
/// RR using the key and other internal information.
///
/// In general, a DNS client that wants to send a signed query will construct
/// a \c TSIGContext object with the TSIG key that the client is intending to
-/// use, and sign the query with the context. The client keeps the context,
-/// and verifies the response with it.
+/// use, and sign the query with the context. The client will keeps the
+/// context, and verify the response with it.
///
/// On the other hand, a DNS server will construct a \c TSIGContext object
/// with the information of the TSIG RR included in a query with a set of
@@ -134,7 +134,7 @@ typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
///
/// When multiple messages belong to the same TSIG session, either side
/// (signer or verifier) will keep using the same context. It records
-/// the latest session state (such as the previous digest) so that continues
+/// the latest session state (such as the previous digest) so that repeated
/// calls to \c sign() or \c verify() work correctly in terms of the TSIG
/// protocol.
///
@@ -204,7 +204,8 @@ public:
/// generally expected to be a complete, wire-format DNS message
/// that doesn't contain a TSIG RR, based on the TSIG key and
/// other context information of \c TSIGContext, and returns a
- /// result in the form of an \c rdata::any::TSIG object.
+ /// result in the form of a (pointer object pointing to)
+ /// \c TSIGRecord object.
///
/// The caller of this method will use the returned value to render a
/// complete TSIG RR into the message that has been signed so that it
@@ -220,9 +221,9 @@ public:
/// description), and doesn't inspect it in any way. For example, it
/// doesn't check whether the data length is sane for a valid DNS message.
/// This is also the reason why this method takes the \c qid parameter,
- /// which will be used as the original ID of the resulting \c TSIG object
- /// (RR), even though this value should be stored in the first two octets
- /// (in wire format) of the given data.
+ /// which will be used as the original ID of the resulting
+ /// \c TSIGRecordx object, even though this value should be stored in the
+ /// first two octets (in wire format) of the given data.
///
/// \note This method still checks and rejects empty data (\c NULL pointer
/// data or the specified data length is 0) in order to avoid catastrophic
@@ -230,13 +231,9 @@ public:
/// for HMAC computation, but obviously it doesn't make sense for a DNS
/// message.
///
- /// This method can throw exceptions (see the list), but does not provide
- /// the strong exception guarantee. That is, if an exception is thrown,
- /// the internal state of the \c TSIGContext object can be changed, in
- /// which case it's unlikely that the context can be used for (re)signing
- /// or (re)verifying subsequent messages any more. If the caller wants
- /// to catch the exception and try to recover from it, it must drop the
- /// TSIG session and start a new session with a new context.
+ /// This method provides the strong exception guarantee; unless the method
+ /// returns (without an exception being thrown), the internal state of
+ /// the \c TSIGContext won't be modified.
///
/// \exception InvalidParameter \c data is NULL or \c data_len is 0
/// \exception cryptolink::LibraryError Some unexpected error in the
@@ -244,7 +241,7 @@ public:
/// \exception std::bad_alloc Temporary resource allocation failure
///
/// \param qid The QID to be as the value of the original ID field of
- /// the resulting TSIG
+ /// the resulting TSIG record
/// \param data Points to the wire-format data to be signed
/// \param data_len The length of \c data in bytes
///
diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc
index 42cc5cc373..e63c9ab2dd 100644
--- a/src/lib/dns/tsigerror.cc
+++ b/src/lib/dns/tsigerror.cc
@@ -13,11 +13,42 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <ostream>
+#include <string>
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
#include <dns/tsigerror.h>
namespace isc {
namespace dns {
+namespace {
+const char* const tsigerror_text[] = {
+ "BADSIG",
+ "BADKEY",
+ "BADTIME"
+};
+}
+
+TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) {
+ if (code_ > MAX_RCODE_FOR_TSIGERROR) {
+ isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode);
+ }
+}
+
+std::string
+TSIGError::toText() const {
+ if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+ return (Rcode(code_).toText());
+ } else if (code_ <= BAD_TIME_CODE) {
+ return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]);
+ } else {
+ return (boost::lexical_cast<std::string>(code_));
+ }
+}
+
std::ostream&
operator<<(std::ostream& os, const TSIGError& error) {
return (os << error.toText());
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index b7026478ff..e7cb447f54 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -3,5 +3,6 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
lib_LTLIBRARIES = libutil_unittests.la
libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_SOURCES += newhook.h newhook.cc
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/unittests/newhook.cc b/src/lib/util/unittests/newhook.cc
new file mode 100644
index 0000000000..b9d9fb633b
--- /dev/null
+++ b/src/lib/util/unittests/newhook.cc
@@ -0,0 +1,51 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdlib.h>
+
+#include <new>
+#include <stdexcept>
+
+#include "newhook.h"
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+void*
+operator new(size_t size) throw(std::bad_alloc) {
+ if (isc::util::unittests::force_throw_on_new &&
+ size == isc::util::unittests::throw_size_on_new) {
+ throw std::bad_alloc();
+ }
+ void* p = malloc(size);
+ if (p == NULL) {
+ throw std::bad_alloc();
+ }
+ return (p);
+}
+
+void
+operator delete(void* p) throw() {
+ if (p != NULL) {
+ free (p);
+ }
+}
+#endif
+
+namespace isc {
+namespace util {
+namespace unittests {
+bool force_throw_on_new = false;
+size_t throw_size_on_new = 0;
+}
+}
+}
diff --git a/src/lib/util/unittests/newhook.h b/src/lib/util/unittests/newhook.h
new file mode 100644
index 0000000000..b0dbb64838
--- /dev/null
+++ b/src/lib/util/unittests/newhook.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __UTIL_UNITTESTS_NEWHOOK_H
+#define __UTIL_UNITTESTS_NEWHOOK_H 1
+
+/**
+ * @file newhook.h
+ * @short Enable the use of special operator new that throws for testing.
+ *
+ * This small utility allows a test case to force the global operator new
+ * to throw for a given size to test a case where memory allocation fails
+ * (which normally doesn't happen). To enable the feature, everything must
+ * be built with defining ENABLE_CUSTOM_OPERATOR_NEW beforehand, and set
+ * \c force_throw_on_new to \c true and \c throw_size_on_new to the size
+ * of data that should trigger the exception, immediately before starting
+ * the specific test that needs the exception.
+ *
+ * Example:
+ * \code #include <util/unittests/newhook.h>
+ * ...
+ * TEST(SomeTest, newException) {
+ * isc::util::unittests::force_throw_on_new = true;
+ * isc::util::unittests::throw_size_on_new = sizeof(Foo);
+ * try {
+ * // this will do 'new Foo()' internally and should throw
+ * createFoo();
+ * isc::util::unittests::force_throw_on_new = false;
+ * ASSERT_FALSE(true) << "Expected throw on new";
+ * } catch (const std::bad_alloc&) {
+ * isc::util::unittests::force_throw_on_new = false;
+ * // do some integrity check, etc, if necessary
+ * }
+ * } \endcode
+ *
+ * Replacing the global operator new (and delete) is a dangerous technique,
+ * and triggering an exception solely based on the allocation size is not
+ * reliable, so this feature is disabled by default two-fold: The
+ * ENABLE_CUSTOM_OPERATOR_NEW build time variable, and run-time
+ * \c force_throw_on_new.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Switch to enable the use of special operator new
+///
+/// This is set to \c false by default.
+extern bool force_throw_on_new;
+
+/// The allocation size that triggers an exception in the special operator new
+///
+/// The default value is 0. The value of this variable has no meaning
+/// unless the use of the special operator is enabled at build time and
+/// via \c force_throw_on_new.
+extern size_t throw_size_on_new;
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_NEWHOOK_H
+
+// Local Variables:
+// mode: c++
+// End: