table: prevent integer overflow in pit::FaceRecord

refs #4979

Change-Id: Iacd5abdfc8ae4530364e3fa590778a234140b7ed
diff --git a/daemon/fw/forwarder.cpp b/daemon/fw/forwarder.cpp
index 67b8f52..55a9062 100644
--- a/daemon/fw/forwarder.cpp
+++ b/daemon/fw/forwarder.cpp
@@ -574,7 +574,7 @@
 Forwarder::setExpiryTimer(const shared_ptr<pit::Entry>& pitEntry, time::milliseconds duration)
 {
   BOOST_ASSERT(pitEntry);
-  BOOST_ASSERT(duration >= 0_ms);
+  duration = std::max(duration, 0_ms);
 
   pitEntry->expiryTimer.cancel();
   pitEntry->expiryTimer = getScheduler().schedule(duration, [=] { onInterestFinalize(pitEntry); });
diff --git a/daemon/table/pit-face-record.cpp b/daemon/table/pit-face-record.cpp
index d58ec96..639f424 100644
--- a/daemon/table/pit-face-record.cpp
+++ b/daemon/table/pit-face-record.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -28,6 +28,9 @@
 namespace nfd {
 namespace pit {
 
+// Impose a maximum lifetime to prevent integer overflow when calculating m_expiry.
+static const time::milliseconds MAX_LIFETIME{10_days};
+
 void
 FaceRecord::update(const Interest& interest)
 {
@@ -35,9 +38,7 @@
   m_lastRenewed = time::steady_clock::now();
 
   auto lifetime = interest.getInterestLifetime();
-  if (lifetime < 0_ms) {
-    lifetime = ndn::DEFAULT_INTEREST_LIFETIME;
-  }
+  lifetime = std::min(lifetime, MAX_LIFETIME);
   m_expiry = m_lastRenewed + lifetime;
 }
 
diff --git a/tests/daemon/table/pit-entry.t.cpp b/tests/daemon/table/pit-entry.t.cpp
index 8bdb5fe..00993ee 100644
--- a/tests/daemon/table/pit-entry.t.cpp
+++ b/tests/daemon/table/pit-entry.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -29,6 +29,10 @@
 #include "tests/daemon/global-io-fixture.hpp"
 #include "tests/daemon/face/dummy-face.hpp"
 
+#include <boost/test/data/test_case.hpp>
+
+namespace bdata = boost::unit_test::data;
+
 namespace nfd {
 namespace pit {
 namespace tests {
@@ -166,17 +170,42 @@
   BOOST_CHECK(entry.getOutRecord(*face2) == entry.out_end());
 }
 
-BOOST_AUTO_TEST_CASE(Lifetime)
+const time::milliseconds lifetimes[] = {
+  -1_ms, // unset
+  1_ms,
+  ndn::DEFAULT_INTEREST_LIFETIME,
+  8624_ms,
+  86400_s,
+  time::milliseconds(std::numeric_limits<time::milliseconds::rep>::max()),
+};
+
+BOOST_DATA_TEST_CASE(Lifetime, bdata::make(lifetimes), lifetime)
 {
   auto interest = makeInterest("/7oIEurbgy6");
+  if (lifetime >= 0_ms) {
+    interest->setInterestLifetime(lifetime);
+  }
+
+  auto expectedLifetime = lifetime;
+  if (lifetime < 0_ms) {
+    expectedLifetime = ndn::DEFAULT_INTEREST_LIFETIME;
+  }
+  else if (lifetime > 10_days) {
+    expectedLifetime = 10_days;
+  }
+
   auto face = make_shared<DummyFace>();
   Entry entry(*interest);
 
   auto inIt = entry.insertOrUpdateInRecord(*face, *interest);
-  BOOST_CHECK_GT(inIt->getExpiry(), time::steady_clock::now());
+  auto expiryFromNow = inIt->getExpiry() - time::steady_clock::now();
+  BOOST_CHECK_GT(expiryFromNow, 0_ms);
+  BOOST_CHECK_LT(time::abs(expiryFromNow - expectedLifetime), 100_ms);
 
   auto outIt = entry.insertOrUpdateOutRecord(*face, *interest);
-  BOOST_CHECK_GT(outIt->getExpiry(), time::steady_clock::now());
+  expiryFromNow = outIt->getExpiry() - time::steady_clock::now();
+  BOOST_CHECK_GT(expiryFromNow, 0_ms);
+  BOOST_CHECK_LT(time::abs(expiryFromNow - expectedLifetime), 100_ms);
 }
 
 BOOST_AUTO_TEST_CASE(OutRecordNack)