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

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