Blame SOURCES/Make-kprop-work-for-dump-files-larger-than-4GB.patch

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