73a275
From 5d541f1f0b468b1c976acf8ec2359bd0c8c73be7 Mon Sep 17 00:00:00 2001
73a275
From: Julien Rische <jrische@redhat.com>
73a275
Date: Wed, 19 Jan 2022 19:46:08 +0100
73a275
Subject: [PATCH] Make kprop work for dump files larger than 4GB
73a275
73a275
If the dump file size does not fit in 32 bits, encode four zero bytes
73a275
(forcing an error for unmodified kpropd) followed by the size in the
73a275
next 64 bits.
73a275
73a275
Add a functional test case, but only run it when an environment
73a275
variable is set, as processing a 4GB dump file is too
73a275
resource-intensive for make check.
73a275
73a275
[ghudson@mit.edu: edited comments and commit message; eliminated use
73a275
of defined constant in some cases; added test case]
73a275
73a275
ticket: 9053 (new)
73a275
---
73a275
 src/kprop/kprop.c      | 37 +++++++++++++++++++++----------------
73a275
 src/kprop/kprop.h      | 12 ++++++++++++
73a275
 src/kprop/kprop_util.c | 42 ++++++++++++++++++++++++++++++++++++++++++
73a275
 src/kprop/kpropd.c     | 33 +++++++++++++++++++++------------
73a275
 src/tests/t_kprop.py   | 34 ++++++++++++++++++++++++++++++++++
73a275
 5 files changed, 130 insertions(+), 28 deletions(-)
73a275
73a275
diff --git a/src/kprop/kprop.c b/src/kprop/kprop.c
73a275
index 0b53aae7e..5adb4d31f 100644
73a275
--- a/src/kprop/kprop.c
73a275
+++ b/src/kprop/kprop.c
73a275
@@ -25,6 +25,7 @@
73a275
  */
73a275
 
73a275
 #include "k5-int.h"
73a275
+#include <inttypes.h>
73a275
 #include <locale.h>
73a275
 #include <sys/file.h>
73a275
 #include <signal.h>
73a275
@@ -71,11 +72,11 @@ static void open_connection(krb5_context context, char *host, int *fd_out);
73a275
 static void kerberos_authenticate(krb5_context context,
73a275
                                   krb5_auth_context *auth_context, int fd,
73a275
                                   krb5_principal me, krb5_creds **new_creds);
73a275
-static int open_database(krb5_context context, char *data_fn, int *size);
73a275
+static int open_database(krb5_context context, char *data_fn, off_t *size);
73a275
 static void close_database(krb5_context context, int fd);
73a275
 static void xmit_database(krb5_context context,
73a275
                           krb5_auth_context auth_context, krb5_creds *my_creds,
73a275
-                          int fd, int database_fd, int in_database_size);
73a275
+                          int fd, int database_fd, off_t in_database_size);
73a275
 static void send_error(krb5_context context, krb5_creds *my_creds, int fd,
73a275
                        char *err_text, krb5_error_code err_code);
73a275
 static void update_last_prop_file(char *hostname, char *file_name);
73a275
@@ -90,7 +91,8 @@ static void usage()
73a275
 int
73a275
 main(int argc, char **argv)
73a275
 {
73a275
-    int fd, database_fd, database_size;
73a275
+    int fd, database_fd;
73a275
+    off_t database_size;
73a275
     krb5_error_code retval;
73a275
     krb5_context context;
73a275
     krb5_creds *my_creds;
73a275
@@ -339,7 +341,7 @@ kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context,
73a275
  * in the size of the database file.
73a275
  */
73a275
 static int
73a275
-open_database(krb5_context context, char *data_fn, int *size)
73a275
+open_database(krb5_context context, char *data_fn, off_t *size)
73a275
 {
73a275
     struct stat stbuf, stbuf_ok;
73a275
     char *data_ok_fn;
73a275
@@ -413,19 +415,18 @@ close_database(krb5_context context, int fd)
73a275
 static void
73a275
 xmit_database(krb5_context context, krb5_auth_context auth_context,
73a275
               krb5_creds *my_creds, int fd, int database_fd,
73a275
-              int in_database_size)
73a275
+              off_t in_database_size)
73a275
 {
73a275
     krb5_int32 n;
73a275
     krb5_data inbuf, outbuf;
73a275
-    char buf[KPROP_BUFSIZ];
73a275
+    char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
73a275
     krb5_error_code retval;
73a275
     krb5_error *error;
73a275
-    krb5_ui_4 database_size = in_database_size, send_size, sent_size;
73a275
+    uint64_t database_size = in_database_size, send_size, sent_size;
73a275
 
73a275
     /* Send over the size. */
73a275
-    send_size = htonl(database_size);
73a275
-    inbuf.data = (char *)&send_size;
73a275
-    inbuf.length = sizeof(send_size); /* must be 4, really */
73a275
+    inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
73a275
+    encode_database_size(database_size, &inbuf);
73a275
     /* KPROP_CKSUMTYPE */
73a275
     retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL);
73a275
     if (retval) {
73a275
@@ -460,7 +461,7 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
73a275
         retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL);
73a275
         if (retval) {
73a275
             snprintf(buf, sizeof(buf),
73a275
-                     "while encoding database block starting at %d",
73a275
+                     "while encoding database block starting at %"PRIu64,
73a275
                      sent_size);
73a275
             com_err(progname, retval, "%s", buf);
73a275
             send_error(context, my_creds, fd, buf, retval);
73a275
@@ -471,14 +472,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
73a275
         if (retval) {
73a275
             krb5_free_data_contents(context, &outbuf);
73a275
             com_err(progname, retval,
73a275
-                    _("while sending database block starting at %d"),
73a275
+                    _("while sending database block starting at %"PRIu64),
73a275
                     sent_size);
73a275
             exit(1);
73a275
         }
73a275
         krb5_free_data_contents(context, &outbuf);
73a275
         sent_size += n;
73a275
         if (debug)
73a275
-            printf("%d bytes sent.\n", sent_size);
73a275
+            printf("%"PRIu64" bytes sent.\n", sent_size);
73a275
     }
73a275
     if (sent_size != database_size) {
73a275
         com_err(progname, 0, _("Premature EOF found for database file!"));
73a275
@@ -533,10 +534,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
73a275
         exit(1);
73a275
     }
73a275
 
73a275
-    memcpy(&send_size, outbuf.data, sizeof(send_size));
73a275
-    send_size = ntohl(send_size);
73a275
+    retval = decode_database_size(&outbuf, &send_size);
73a275
+    if (retval) {
73a275
+        com_err(progname, retval, _("malformed sent database size message"));
73a275
+        exit(1);
73a275
+    }
73a275
     if (send_size != database_size) {
73a275
-        com_err(progname, 0, _("Kpropd sent database size %d, expecting %d"),
73a275
+        com_err(progname, 0, _("Kpropd sent database size %"PRIu64
73a275
+                               ", expecting %"PRIu64),
73a275
                 send_size, database_size);
73a275
         exit(1);
73a275
     }
73a275
diff --git a/src/kprop/kprop.h b/src/kprop/kprop.h
73a275
index 75331cc8a..3a319b535 100644
73a275
--- a/src/kprop/kprop.h
73a275
+++ b/src/kprop/kprop.h
73a275
@@ -32,6 +32,7 @@
73a275
 #define KPROP_PROT_VERSION "kprop5_01"
73a275
 
73a275
 #define KPROP_BUFSIZ 32768
73a275
+#define KPROP_DBSIZE_MAX_BUFSIZ 12  /* max length of an encoded DB size */
73a275
 
73a275
 /* pathnames are in osconf.h, included via k5-int.h */
73a275
 
73a275
@@ -41,3 +42,14 @@ int sockaddr2krbaddr(krb5_context context, int family, struct sockaddr *sa,
73a275
 krb5_error_code
73a275
 sn2princ_realm(krb5_context context, const char *hostname, const char *sname,
73a275
                const char *realm, krb5_principal *princ_out);
73a275
+
73a275
+/*
73a275
+ * Encode size in four bytes (for backward compatibility) if it fits; otherwise
73a275
+ * use the larger encoding.  buf must be allocated with at least
73a275
+ * KPROP_DBSIZE_MAX_BUFSIZ bytes.
73a275
+ */
73a275
+void encode_database_size(uint64_t size, krb5_data *buf);
73a275
+
73a275
+/* Decode a database size.  Return KRB5KRB_ERR_GENERIC if buf has an invalid
73a275
+ * length or did not encode a 32-bit size compactly. */
73a275
+krb5_error_code decode_database_size(const krb5_data *buf, uint64_t *size_out);
73a275
diff --git a/src/kprop/kprop_util.c b/src/kprop/kprop_util.c
73a275
index c32d174b9..9d6b25389 100644
73a275
--- a/src/kprop/kprop_util.c
73a275
+++ b/src/kprop/kprop_util.c
73a275
@@ -96,3 +96,45 @@ sn2princ_realm(krb5_context context, const char *hostname, const char *sname,
73a275
         (*princ_out)->type = KRB5_NT_SRV_HST;
73a275
     return ret;
73a275
 }
73a275
+
73a275
+void
73a275
+encode_database_size(uint64_t size, krb5_data *buf)
73a275
+{
73a275
+    assert(buf->length >= 12);
73a275
+    if (size > 0 && size <= UINT32_MAX) {
73a275
+        /* Encode in 32 bits for backward compatibility. */
73a275
+        store_32_be(size, buf->data);
73a275
+        buf->length = 4;
73a275
+    } else {
73a275
+        /* Set the first 32 bits to 0 and encode in the following 64 bits. */
73a275
+        store_32_be(0, buf->data);
73a275
+        store_64_be(size, buf->data + 4);
73a275
+        buf->length = 12;
73a275
+    }
73a275
+}
73a275
+
73a275
+krb5_error_code
73a275
+decode_database_size(const krb5_data *buf, uint64_t *size_out)
73a275
+{
73a275
+    uint64_t size;
73a275
+
73a275
+    if (buf->length == 12) {
73a275
+        /* A 12-byte buffer must have the first four bytes zeroed. */
73a275
+        if (load_32_be(buf->data) != 0)
73a275
+            return KRB5KRB_ERR_GENERIC;
73a275
+
73a275
+        /* The size is stored in the next 64 bits.  Values from 1..2^32-1 must
73a275
+         * be encoded in four bytes. */
73a275
+        size = load_64_be(buf->data + 4);
73a275
+        if (size > 0 && size <= UINT32_MAX)
73a275
+            return KRB5KRB_ERR_GENERIC;
73a275
+    } else if (buf->length == 4) {
73a275
+        size = load_32_be(buf->data);
73a275
+    } else {
73a275
+        /* Invalid buffer size. */
73a275
+        return KRB5KRB_ERR_GENERIC;
73a275
+    }
73a275
+
73a275
+    *size_out = size;
73a275
+    return 0;
73a275
+}
73a275
diff --git a/src/kprop/kpropd.c b/src/kprop/kpropd.c
73a275
index 356e3e0e6..a83a86866 100644
73a275
--- a/src/kprop/kpropd.c
73a275
+++ b/src/kprop/kpropd.c
73a275
@@ -55,6 +55,7 @@
73a275
 #include "com_err.h"
73a275
 #include "fake-addrinfo.h"
73a275
 
73a275
+#include <inttypes.h>
73a275
 #include <locale.h>
73a275
 #include <ctype.h>
73a275
 #include <sys/file.h>
73a275
@@ -1354,9 +1355,10 @@ static void
73a275
 recv_database(krb5_context context, int fd, int database_fd,
73a275
               krb5_data *confmsg)
73a275
 {
73a275
-    krb5_ui_4 database_size, received_size;
73a275
+    uint64_t database_size, received_size;
73a275
     int n;
73a275
     char buf[1024];
73a275
+    char dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
73a275
     krb5_data inbuf, outbuf;
73a275
     krb5_error_code retval;
73a275
 
73a275
@@ -1378,10 +1380,17 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
                 _("while decoding database size from client"));
73a275
         exit(1);
73a275
     }
73a275
-    memcpy(&database_size, outbuf.data, sizeof(database_size));
73a275
+
73a275
+    retval = decode_database_size(&outbuf, &database_size);
73a275
+    if (retval) {
73a275
+        send_error(context, fd, retval, "malformed database size message");
73a275
+        com_err(progname, retval,
73a275
+                _("malformed database size message from client"));
73a275
+        exit(1);
73a275
+    }
73a275
+
73a275
     krb5_free_data_contents(context, &inbuf);
73a275
     krb5_free_data_contents(context, &outbuf);
73a275
-    database_size = ntohl(database_size);
73a275
 
73a275
     /* Initialize the initial vector. */
73a275
     retval = krb5_auth_con_initivector(context, auth_context);
73a275
@@ -1401,7 +1410,7 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
         retval = krb5_read_message(context, &fd, &inbuf);
73a275
         if (retval) {
73a275
             snprintf(buf, sizeof(buf),
73a275
-                     "while reading database block starting at offset %d",
73a275
+                     "while reading database block starting at offset %"PRIu64,
73a275
                      received_size);
73a275
             com_err(progname, retval, "%s", buf);
73a275
             send_error(context, fd, retval, buf);
73a275
@@ -1412,8 +1421,8 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
         retval = krb5_rd_priv(context, auth_context, &inbuf, &outbuf, NULL);
73a275
         if (retval) {
73a275
             snprintf(buf, sizeof(buf),
73a275
-                     "while decoding database block starting at offset %d",
73a275
-                     received_size);
73a275
+                     "while decoding database block starting at offset %"
73a275
+                     PRIu64, received_size);
73a275
             com_err(progname, retval, "%s", buf);
73a275
             send_error(context, fd, retval, buf);
73a275
             krb5_free_data_contents(context, &inbuf);
73a275
@@ -1424,13 +1433,13 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
         krb5_free_data_contents(context, &outbuf);
73a275
         if (n < 0) {
73a275
             snprintf(buf, sizeof(buf),
73a275
-                     "while writing database block starting at offset %d",
73a275
+                     "while writing database block starting at offset %"PRIu64,
73a275
                      received_size);
73a275
             send_error(context, fd, errno, buf);
73a275
         } else if ((unsigned int)n != outbuf.length) {
73a275
             snprintf(buf, sizeof(buf),
73a275
                      "incomplete write while writing database block starting "
73a275
-                     "at \noffset %d (%d written, %d expected)",
73a275
+                     "at \noffset %"PRIu64" (%d written, %d expected)",
73a275
                      received_size, n, outbuf.length);
73a275
             send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
73a275
         }
73a275
@@ -1440,7 +1449,8 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
     /* OK, we've seen the entire file.  Did we get too many bytes? */
73a275
     if (received_size > database_size) {
73a275
         snprintf(buf, sizeof(buf),
73a275
-                 "Received %d bytes, expected %d bytes for database file",
73a275
+                 "Received %"PRIu64" bytes, expected %"PRIu64
73a275
+                 " bytes for database file",
73a275
                  received_size, database_size);
73a275
         send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
73a275
     }
73a275
@@ -1450,9 +1460,8 @@ recv_database(krb5_context context, int fd, int database_fd,
73a275
 
73a275
     /* Create message acknowledging number of bytes received, but
73a275
      * don't send it until kdb5_util returns successfully. */
73a275
-    database_size = htonl(database_size);
73a275
-    inbuf.data = (char *)&database_size;
73a275
-    inbuf.length = sizeof(database_size);
73a275
+    inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
73a275
+    encode_database_size(database_size, &inbuf);
73a275
     retval = krb5_mk_safe(context,auth_context,&inbuf,confmsg,NULL);
73a275
     if (retval) {
73a275
         com_err(progname, retval, "while encoding # of receieved bytes");
73a275
diff --git a/src/tests/t_kprop.py b/src/tests/t_kprop.py
73a275
index c33e4fea2..f8ffd653a 100755
73a275
--- a/src/tests/t_kprop.py
73a275
+++ b/src/tests/t_kprop.py
73a275
@@ -87,5 +87,39 @@ realm.run([kdb5_util, 'dump', dumpfile])
73a275
 realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname])
73a275
 check_output(kpropd)
73a275
 realm.run([kadminl, 'listprincs'], replica3, expected_msg='wakawaka')
73a275
+stop_daemon(kpropd)
73a275
+
73a275
+# This test is too resource-intensive to be included in "make check"
73a275
+# by default, but it can be enabled in the environment to test the
73a275
+# propagation of databases large enough to require a 12-byte encoding
73a275
+# of the database size.
73a275
+if 'KPROP_LARGE_DB_TEST' in os.environ:
73a275
+    output('Generating >4GB dumpfile\n')
73a275
+    with open(dumpfile, 'w') as f:
73a275
+        f.write('kdb5_util load_dump version 6\n')
73a275
+        f.write('princ\t38\t15\t3\t1\t0\tK/M@KRBTEST.COM\t64\t86400\t0\t0\t0'
73a275
+                '\t0\t0\t0\t8\t2\t0100\t9\t8\t0100010000000000\t2\t28'
73a275
+                '\tb93e105164625f6372656174696f6e404b5242544553542e434f4d00'
73a275
+                '\t1\t1\t18\t62\t2000408c027c250e8cc3b81476414f2214d57c1ce'
73a275
+                '38891e29792e87258247c73547df4d5756266931dd6686b62270e6568'
73a275
+                '95a31ec66bfe913b4f15226227\t-1;\n')
73a275
+        for i in range(1, 20000000):
73a275
+            f.write('princ\t38\t21\t1\t1\t0\tp%08d@KRBTEST.COM' % i)
73a275
+            f.write('\t0\t86400\t0\t0\t0\t0\t0\t0\t2\t27'
73a275
+                    '\td73e1051757365722f61646d696e404b5242544553542e434f4d00'
73a275
+                    '\t1\t1\t17\t46'
73a275
+                    '\t10009c8ab7b3f89ccf3ca3ad98352a461b7f4f1b0c49'
73a275
+                    '5605117591d9ad52ba4da0adef7a902126973ed2bdc3ffbf\t-1;\n')
73a275
+    assert os.path.getsize(dumpfile) > 4 * 1024 * 1024 * 1024
73a275
+    with open(dumpfile + '.dump_ok', 'w') as f:
73a275
+        f.write('\0')
73a275
+    conf_large = {'dbmodules': {'db': {'database_name': '$testdir/db.large'}},
73a275
+                  'realms': {'$realm': {'iprop_resync_timeout': '3600'}}}
73a275
+    large = realm.special_env('large', True, kdc_conf=conf_large)
73a275
+    kpropd = realm.start_kpropd(large, ['-d'])
73a275
+    realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname])
73a275
+    check_output(kpropd)
73a275
+    realm.run([kadminl, 'getprinc', 'p19999999'], env=large,
73a275
+              expected_msg='Principal: p19999999')
73a275
 
73a275
 success('kprop tests')
73a275
-- 
73a275
2.35.1
73a275