]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 2570] add signature check when reading leapseconds file
authorJuergen Perlinger <perlinger@ntp.org>
Sat, 1 Mar 2014 11:52:12 +0000 (11:52 +0000)
committerJuergen Perlinger <perlinger@ntp.org>
Sat, 1 Mar 2014 11:52:12 +0000 (11:52 +0000)
bk: 5311c9ecpgDfZ41n78yDOR2kLliigg

ChangeLog
libntp/Makefile.am
ntpd/ntp_leapsec.c
ntpd/ntp_leapsec.h
ntpd/ntp_util.c
ports/winnt/vs2008/libntp/libntp.vcproj
tests/ntpd/leapsec.cpp

index 66ba2efde71f4f432a4a0e00746b3ec1df1255f0..1bc233c677c0652704fd2872c5473ad9c6454613 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+* [Bug 2570] refuse to load leapsec file with bad/missing SHA1 hash
 * [Bug 2562] first release of the GPSD client clock (type 46)
 (4.2.7p426) 2014/02/28 Released by Harlan Stenn <stenn@ntp.org>
 * [Bug 2113] Warn about ignored extra args in ntpq.
index d0aa08d7c249d7c3488be39232f8f218aabf8627..069f85722cb5dd125adac46d13ebcb0f9a4cc577 100644 (file)
@@ -34,6 +34,7 @@ libisc_SRCS =                                                         \
        $(srcdir)/../lib/isc/task.c                                     \
        $(srcdir)/../lib/isc/$(LIBISC_PTHREADS_NOTHREADS)/thread.c      \
        $(srcdir)/../lib/isc/unix/time.c                                \
+       $(srcdir)/../lib/isc/sha1.c                                     \
        $(srcdir)/../lib/isc/sockaddr.c                                 \
        $(NULL)
 
index 22e2bd9c78c0614c3da9c7ba56c05d9907bf07b0..2e52d0a836d2448fe441d1e3ec882c8c05ee90d1 100644 (file)
@@ -19,6 +19,8 @@
 #include "ntp_leapsec.h"
 #include "ntp.h"
 
+#include "isc/sha1.h"
+
 /* ---------------------------------------------------------------------
  * GCC is rather sticky with its 'const' attribute. We have to do it more
  * explicit than with a cast if we want to get rid of a CONST qualifier.
@@ -614,13 +616,49 @@ leapsec_reset_frame(void)
 }
 
 /* ------------------------------------------------------------------ */
+/* load a file from a FILE pointer. Note: If hcheck is true, load
+ * only after successful signature check. The stream must be seekable
+ * or this will fail.
+ */
 int/*BOOL*/
 leapsec_load_file(
        FILE * ifp   ,
-       int    blimit)
+       int    blimit,
+       int    hcheck)
 {
        leap_table_t * pt;
+       int            rcheck;
+
+       hcheck = (hcheck != 0);
+       rcheck = leapsec_validate((leapsec_reader)getc, ifp);
+       switch (rcheck)
+       {
+       case LSVALID_GOODHASH:
+               msyslog(LOG_NOTICE, "%s",
+                       "leapsecond file: good hash signature");
+               break;
+
+       case LSVALID_NOHASH:
+               msyslog(LOG_INFO, "%s",
+                       "leapsecond file: no hash signature");
+               break;
+       case LSVALID_BADHASH:
+               msyslog(LOG_ERR, "%s",
+                       "leapsecond file: signature mismatch");
+               break;
+       case LSVALID_BADFORMAT:
+               msyslog(LOG_ERR, "%s",
+                       "leapsecond file: malformed hash signature");
+               break;
+       default:
+               msyslog(LOG_ERR, "leapsecond file: unknown error code %d",
+                       rcheck);
+               break;
+       }
+       if (rcheck < hcheck)
+               return 0;
 
+       rewind(ifp);
        pt = leapsec_get_table(TRUE);
        return leapsec_load(pt, (leapsec_reader)getc, ifp, blimit)
            && leapsec_set_table(pt);
@@ -998,4 +1036,106 @@ betweenu32(
        return rc;
 }
 
+/* =====================================================================
+ * validation stuff
+ */
+
+typedef struct {
+       unsigned char hv[ISC_SHA1_DIGESTLENGTH];
+} sha1_digest;
+
+/* [internal] parse a digest line to get the hash signature
+ * I would have preferred the CTYPE 'isblank()' function, but alas,
+ * it's not in MSVC bevore Studio2013; it also seems to be added
+ * with the C99 standard (not being sure about that) and not all
+ * target compilers implement this. Using a direct character
+ * compare is the way out; the NIST leapsec file is 7bit ASCII
+ * and the locale should not matter much here.
+ */
+static int/*BOOL*/
+do_leap_hash(
+       sha1_digest * mac,
+       char const  * cp )
+{
+       int idx, dv;
+
+       memset(mac, 0, sizeof(*mac));
+       for (idx = 0;  *cp && idx < 2*sizeof(*mac);  ++cp) {
+               if (isdigit(*cp))
+                       dv = *cp - '0';
+               else if (isxdigit(*cp))
+                       dv = (toupper(*cp) - 'A' + 10);
+               else if ('\t' == *cp || ' ' == *cp) /*isblank(*cp))*/
+                       continue;
+               else
+                       break;
+
+               mac->hv[idx/2] = (unsigned char)(
+                       (mac->hv[idx/2] * 16) + dv);
+               ++idx;
+       }
+       return (idx == 2*sizeof(*mac));
+}
+
+/* [internal] add the digits of a data line to the hash, stopping at the
+ * next hash ('#') character.
+ */
+static void
+do_hash_data(
+       isc_sha1_t * mdctx,
+       char const * cp   )
+{
+       unsigned char  text[32]; // must be power of two!
+       unsigned int   tlen =  0;
+       unsigned char  ch;
+
+       while ('\0' != (ch = *cp++) && '#' != ch)
+               if (isdigit(ch)) {
+                       text[tlen++] = ch;
+                       tlen &= (sizeof(text)-1);
+                       if (0 == tlen)
+                               isc_sha1_update(
+                                       mdctx, text, sizeof(text));
+               }
+       
+       if (0 < tlen)
+               isc_sha1_update(mdctx, text, tlen);
+}
+
+/* given a reader and a reader arg, calculate and validate the the hash
+ * signature of a NIST leap second file.
+ */
+int
+leapsec_validate(
+       leapsec_reader func,
+       void *         farg)
+{
+       isc_sha1_t     mdctx;
+       sha1_digest    rdig, ldig; /* remote / local digests */
+       char           line[50];
+       int            hlseen = -1;
+
+       isc_sha1_init(&mdctx);
+       while (get_line(func, farg, line, sizeof(line))) {
+               if (!strncmp(line, "#h", 2))
+                       hlseen = do_leap_hash(&rdig, line+2);
+               else if (!strncmp(line, "#@", 2))
+                       do_hash_data(&mdctx, line+2);
+               else if (!strncmp(line, "#$", 2))
+                       do_hash_data(&mdctx, line+2);
+               else if (isdigit(line[0]))
+                       do_hash_data(&mdctx, line);
+       }
+       isc_sha1_final(&mdctx, ldig.hv);
+       isc_sha1_invalidate(&mdctx);
+
+       if (0 > hlseen)
+               return LSVALID_NOHASH;
+       if (0 == hlseen)
+               return LSVALID_BADFORMAT;
+       if (0 != memcmp(&rdig, &ldig, sizeof(sha1_digest)))
+               return LSVALID_BADHASH;
+       return LSVALID_GOODHASH;
+}
+
 /* -*- that's all folks! -*- */
index f2442c224bca757a96a4cc941ce56b3f1c33d4ac..014028a7b442c83e5fca3e4b9a898c92d1508cd5 100644 (file)
@@ -26,6 +26,19 @@ typedef int  (*leapsec_reader)(void*);
 struct leap_table;
 typedef struct leap_table leap_table_t;
 
+/* Validate a stream containing a leap second file in the NIST / NTPD
+ * format that can also be loaded via 'leapsec_load()'. This uses
+ * the SHA1 hash and preprocessing as described in the NIST leapsecond
+ * file.
+ */
+#define LSVALID_GOODHASH       1       /* valid signature         */
+#define LSVALID_NOHASH         0       /* no signature in file    */
+#define LSVALID_BADHASH               -1       /* signature mismatch      */
+#define LSVALID_BADFORMAT      -2      /* signature not parseable */
+
+extern int leapsec_validate(leapsec_reader, void*);
+
+
 /* Set/get electric mode
  * Electric mode is defined as the operation mode where the system clock
  * automagically manages the leap second, so we don't have to care about
@@ -123,7 +136,7 @@ extern void leapsec_dump(const leap_table_t*, leapsec_dumper func, void *farg);
 /* Read a leap second file. This is a convenience wrapper around the
  * generic load function, 'leapsec_load()'.
  */
-extern int/*BOOL*/ leapsec_load_file(FILE * fp, int blimit);
+extern int/*BOOL*/ leapsec_load_file(FILE * fp, int blimit, int checked);
 
 /* Get the current leap data signature. This consists of the last
  * ransition, the table expiration, and the total TAI difference at the
index 185f4b0e7c41173e5007db9a8368e0306fb29803..5613f1b96592c3eb97e7bf4b25588c907fe649ff 100644 (file)
@@ -471,6 +471,10 @@ stats_config(
 
        /*
         * Read leapseconds file.
+        *
+        * Note: Currently a leap file without SHA1 signature is
+        * accepted, but if there is a signature line, the signature
+        * must be valid or the file is rejected.
         */
        case STATS_LEAP_FILE:
                if (!value || (len = strlen(value)) == 0)
@@ -489,7 +493,7 @@ stats_config(
                        msyslog(LOG_ERR,
                            "leapseconds: stat(%s) failed: %m",
                            leapseconds_file);
-               } else if (!leapsec_load_file(fp, TRUE)) {
+               } else if (!leapsec_load_file(fp, TRUE, FALSE)) {
                        msyslog(LOG_ERR,
                                "format error leapseconds file %s",
                                leapseconds_file);
@@ -866,6 +870,10 @@ record_timing_stats(
  *     -1 if there was a problem,
  *      0 if the leapfile has expired or less than 24hrs remaining TTL
  *     >0 # of full days until the leapfile expires
+ *
+ * Note: This loads a new leapfile on the fly. Currently a leap file
+ * without SHA1 signature is accepted, but if there is a signature line,
+ * the signature must be valid or the file is rejected.
  */
 int
 check_leap_file(
@@ -894,7 +902,7 @@ check_leap_file(
                } else if (  (sp1->st_mtime != sp2->st_mtime)
                          || (sp1->st_ctime != sp2->st_ctime)) {
                        leapseconds_file_sb1 = leapseconds_file_sb2;
-                       if (!leapsec_load_file(fp, TRUE)) {
+                       if (!leapsec_load_file(fp, TRUE, FALSE)) {
                                msyslog(LOG_ERR,
                                        "format error leapseconds file %s",
                                        leapseconds_file);
index f9f57507d4d26b425af3a7c29d092ec989652b73..536c16e0254198b579cff80f2eea7dfe61a4ee2a 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="Windows-1252"?>
 <VisualStudioProject
        ProjectType="Visual C++"
-       Version="9.00"
+       Version="9,00"
        Name="libntp"
        ProjectGUID="{400FBFCB-462E-40D0-B06B-3B74E3FFFD00}"
        TargetFrameworkVersion="0"
                                RelativePath="..\..\libntp\SetSystemTime.c"
                                >
                        </File>
+                       <File
+                               RelativePath="..\..\..\..\lib\isc\sha1.c"
+                               >
+                       </File>
                        <File
                                RelativePath="..\..\..\..\libntp\snprintf.c"
                                >
                                >
                        </File>
                        <File
-                               RelativePath="..\..\include\sys\time.h"
+                               RelativePath="..\..\..\..\lib\isc\win32\include\isc\time.h"
                                >
                        </File>
                        <File
                                >
                        </File>
                        <File
-                               RelativePath="..\..\..\..\lib\isc\win32\include\isc\time.h"
+                               RelativePath="..\..\include\sys\time.h"
                                >
                        </File>
                        <File
index 34c12709839811bdeeab69a9b401abd6822a8b9f..f8e1a13e343ce6c91fc654926f8f3d614ac5c584 100644 (file)
@@ -102,6 +102,65 @@ static const char leap3 [] =
     "3550089600        33      # 1 Jul 2012\n"
     "#\n";
 
+// short table with good hash
+static const char leap_ghash [] =
+    "#\n"
+    "#@        3610569600\n"
+    "#$        3610566000\n"
+    "#\n"
+    "2272060800 10     # 1 Jan 1972\n"
+    "2287785600        11      # 1 Jul 1972\n"
+    "2303683200        12      # 1 Jan 1973\n"
+    "2335219200        13      # 1 Jan 1974\n"
+    "2366755200        14      # 1 Jan 1975\n"
+    "2398291200        15      # 1 Jan 1976\n"
+    "2429913600        16      # 1 Jan 1977\n"
+    "2461449600        17      # 1 Jan 1978\n"
+    "2492985600        18      # 1 Jan 1979\n"
+    "2524521600        19      # 1 Jan 1980\n"
+    "#\n"
+    "#h 4b304e10 95642b3f c10b91f9 90791725 25f280d0\n"
+    "#\n";
+
+// short table with bad hash
+static const char leap_bhash [] =
+    "#\n"
+    "#@        3610569600\n"
+    "#$        3610566000\n"
+    "#\n"
+    "2272060800 10     # 1 Jan 1972\n"
+    "2287785600        11      # 1 Jul 1972\n"
+    "2303683200        12      # 1 Jan 1973\n"
+    "2335219200        13      # 1 Jan 1974\n"
+    "2366755200        14      # 1 Jan 1975\n"
+    "2398291200        15      # 1 Jan 1976\n"
+    "2429913600        16      # 1 Jan 1977\n"
+    "2461449600        17      # 1 Jan 1978\n"
+    "2492985600        18      # 1 Jan 1979\n"
+    "2524521600        19      # 1 Jan 1980\n"
+    "#\n"
+    "#h        dc2e6b0b 5aade95d a0587abd 4e0dacb4 e4d5049e\n"
+    "#\n";
+
+// short table with malformed hash
+static const char leap_mhash [] =
+    "#\n"
+    "#@        3610569600\n"
+    "#$        3610566000\n"
+    "#\n"
+    "2272060800 10     # 1 Jan 1972\n"
+    "2287785600        11      # 1 Jul 1972\n"
+    "2303683200        12      # 1 Jan 1973\n"
+    "2335219200        13      # 1 Jan 1974\n"
+    "2366755200        14      # 1 Jan 1975\n"
+    "2398291200        15      # 1 Jan 1976\n"
+    "2429913600        16      # 1 Jan 1977\n"
+    "2461449600        17      # 1 Jan 1978\n"
+    "2492985600        18      # 1 Jan 1979\n"
+    "2524521600        19      # 1 Jan 1980\n"
+    "#\n"
+    "#h f2349a02 788b9534 a8f2e141 f2029Q6d 4064a7ee\n"
+    "#\n";
 
 static uint32_t lsec2009 = 3439756800u; // 1 Jan 2009, 00:00:00 utc
 static uint32_t lsec2012 = 3550089600u; // 1 Jul 2012, 00:00:00 utc
@@ -194,6 +253,43 @@ void leapsecTest::TearDown()
     ntpcal_set_timefunc(NULL);
 }
 
+// =====================================================================
+// VALIDATION TESTS
+// =====================================================================
+
+// ----------------------------------------------------------------------
+TEST_F(leapsecTest, ValidateGood) {
+       const char *cp = leap_ghash;
+       int         rc = leapsec_validate(stringreader, &cp);
+       EXPECT_EQ(LSVALID_GOODHASH, rc);
+}
+
+// ----------------------------------------------------------------------
+TEST_F(leapsecTest, ValidateNoHash) {
+       const char *cp = leap2;
+       int         rc = leapsec_validate(stringreader, &cp);
+       EXPECT_EQ(LSVALID_NOHASH, rc);
+}
+
+// ----------------------------------------------------------------------
+TEST_F(leapsecTest, ValidateBad) {
+       const char *cp = leap_bhash;
+       int         rc = leapsec_validate(stringreader, &cp);
+       EXPECT_EQ(LSVALID_BADHASH, rc);
+}
+
+// ----------------------------------------------------------------------
+TEST_F(leapsecTest, ValidateMalformed) {
+       const char *cp = leap_mhash;
+       int         rc = leapsec_validate(stringreader, &cp);
+       EXPECT_EQ(LSVALID_BADFORMAT, rc);
+}
+
+// =====================================================================
+// BASIC FUNCTIONS
+// =====================================================================
+
+// ----------------------------------------------------------------------
 // test number parser
 TEST_F(leapsecTest, ParseVUI64) {
        vint64 act, exp;
@@ -222,6 +318,7 @@ TEST_F(leapsecTest, ParseVUI64) {
        EXPECT_EQ(*ep, '\0');
 }
 
+// ----------------------------------------------------------------------
 // test table selection
 TEST_F(leapsecTest, tableSelect) {
        leap_table_t *pt1, *pt2, *pt3, *pt4;
@@ -256,6 +353,7 @@ TEST_F(leapsecTest, tableSelect) {
        EXPECT_NE(pt2, pt3);
 }
 
+// ----------------------------------------------------------------------
 // load file & check expiration
 TEST_F(leapsecTest, loadFileExpire) {
        const char *cp = leap1;
@@ -271,6 +369,7 @@ TEST_F(leapsecTest, loadFileExpire) {
        EXPECT_EQ(1, rc);
 }
 
+// ----------------------------------------------------------------------
 // load file & check time-to-live
 TEST_F(leapsecTest, loadFileTTL) {
        const char *cp = leap1;
@@ -298,6 +397,7 @@ TEST_F(leapsecTest, loadFileTTL) {
        EXPECT_EQ(-1, rc);      
 }
 
+// ----------------------------------------------------------------------
 // ad-hoc jump: leap second at 2009.01.01 -60days
 TEST_F(leapsecTest, ls2009faraway) {
        int            rc;
@@ -314,6 +414,7 @@ TEST_F(leapsecTest, ls2009faraway) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // ad-hoc jump: leap second at 2009.01.01 -1week
 TEST_F(leapsecTest, ls2009weekaway) {
        int            rc;
@@ -330,6 +431,7 @@ TEST_F(leapsecTest, ls2009weekaway) {
        EXPECT_EQ(LSPROX_SCHEDULE, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // ad-hoc jump: leap second at 2009.01.01 -1hr
 TEST_F(leapsecTest, ls2009houraway) {
        int            rc;
@@ -346,6 +448,7 @@ TEST_F(leapsecTest, ls2009houraway) {
        EXPECT_EQ(LSPROX_ANNOUNCE, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // ad-hoc jump: leap second at 2009.01.01 -1sec
 TEST_F(leapsecTest, ls2009secaway) {
        int            rc;
@@ -362,6 +465,7 @@ TEST_F(leapsecTest, ls2009secaway) {
        EXPECT_EQ(LSPROX_ALERT, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // ad-hoc jump to leap second at 2009.01.01
 TEST_F(leapsecTest, ls2009onspot) {
        int            rc;
@@ -378,6 +482,7 @@ TEST_F(leapsecTest, ls2009onspot) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // test handling of the leap second at 2009.01.01 without table
 TEST_F(leapsecTest, ls2009nodata) {
        int            rc;
@@ -394,6 +499,7 @@ TEST_F(leapsecTest, ls2009nodata) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // test handling of the leap second at 2009.01.01 with culled data
 TEST_F(leapsecTest, ls2009limdata) {
        int            rc;
@@ -410,6 +516,7 @@ TEST_F(leapsecTest, ls2009limdata) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // add dynamic leap second (like from peer/clock)
 TEST_F(leapsecTest, addDynamic) {
        int            rc;
@@ -440,6 +547,7 @@ TEST_F(leapsecTest, addDynamic) {
        //leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
 }
 
+// ----------------------------------------------------------------------
 // add fixed leap seconds (like from network packet)
 TEST_F(leapsecTest, addFixed) {
        int            rc;
@@ -492,6 +600,7 @@ TEST_F(leapsecTest, addFixed) {
 // SEQUENCE TESTS
 // =====================================================================
 
+// ----------------------------------------------------------------------
 // leap second insert at 2009.01.01, electric mode
 TEST_F(leapsecTest, ls2009seqInsElectric) {
        int            rc;
@@ -533,6 +642,7 @@ TEST_F(leapsecTest, ls2009seqInsElectric) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // leap second insert at 2009.01.01, dumb mode
 TEST_F(leapsecTest, ls2009seqInsDumb) {
        int            rc;
@@ -580,6 +690,7 @@ TEST_F(leapsecTest, ls2009seqInsDumb) {
 }
 
 
+// ----------------------------------------------------------------------
 // fake leap second remove at 2009.01.01, electric mode
 TEST_F(leapsecTest, ls2009seqDelElectric) {
        int            rc;
@@ -621,6 +732,7 @@ TEST_F(leapsecTest, ls2009seqDelElectric) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // fake leap second remove at 2009.01.01. dumb mode
 TEST_F(leapsecTest, ls2009seqDelDumb) {
        int            rc;
@@ -662,6 +774,7 @@ TEST_F(leapsecTest, ls2009seqDelDumb) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // leap second insert at 2012.07.01, electric mode
 TEST_F(leapsecTest, ls2012seqInsElectric) {
        int            rc;
@@ -702,6 +815,7 @@ TEST_F(leapsecTest, ls2012seqInsElectric) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
+// ----------------------------------------------------------------------
 // leap second insert at 2012.07.01, dumb mode
 TEST_F(leapsecTest, ls2012seqInsDumb) {
        int            rc;
@@ -753,4 +867,3 @@ TEST_F(leapsecTest, ls2012seqInsDumb) {
        EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
 }
 
-