]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Make kprop work for dump files larger than 4GB 1239/head
authorJulien Rische <jrische@redhat.com>
Wed, 19 Jan 2022 18:46:08 +0000 (19:46 +0100)
committerGreg Hudson <ghudson@mit.edu>
Tue, 8 Mar 2022 01:08:34 +0000 (20:08 -0500)
If the dump file size does not fit in 32 bits, encode four zero bytes
(forcing an error for unmodified kpropd) followed by the size in the
next 64 bits.

Add a functional test case, but only run it when an environment
variable is set, as processing a 4GB dump file is too
resource-intensive for make check.

[ghudson@mit.edu: edited comments and commit message; eliminated use
of defined constant in some cases; added test case]

ticket: 9053 (new)

src/kprop/kprop.c
src/kprop/kprop.h
src/kprop/kprop_util.c
src/kprop/kpropd.c
src/tests/t_kprop.py

index 11239ef1870734974d5aa3a974f13ffa34d6b5d0..8f9fd699261e952cf69539e6014bdcaf21963056 100644 (file)
@@ -25,6 +25,7 @@
  */
 
 #include "k5-int.h"
+#include <inttypes.h>
 #include <locale.h>
 #include <sys/file.h>
 #include <signal.h>
@@ -70,11 +71,11 @@ static void open_connection(krb5_context context, char *host, int *fd_out);
 static void kerberos_authenticate(krb5_context context,
                                   krb5_auth_context *auth_context, int fd,
                                   krb5_principal me, krb5_creds **new_creds);
-static int open_database(krb5_context context, char *data_fn, int *size);
+static int open_database(krb5_context context, char *data_fn, off_t *size);
 static void close_database(krb5_context context, int fd);
 static void xmit_database(krb5_context context,
                           krb5_auth_context auth_context, krb5_creds *my_creds,
-                          int fd, int database_fd, int in_database_size);
+                          int fd, int database_fd, off_t in_database_size);
 static void send_error(krb5_context context, krb5_creds *my_creds, int fd,
                        char *err_text, krb5_error_code err_code);
 static void update_last_prop_file(char *hostname, char *file_name);
@@ -89,7 +90,8 @@ static void usage()
 int
 main(int argc, char **argv)
 {
-    int fd, database_fd, database_size;
+    int fd, database_fd;
+    off_t database_size;
     krb5_error_code retval;
     krb5_context context;
     krb5_creds *my_creds;
@@ -331,7 +333,7 @@ kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context,
  * in the size of the database file.
  */
 static int
-open_database(krb5_context context, char *data_fn, int *size)
+open_database(krb5_context context, char *data_fn, off_t *size)
 {
     struct stat stbuf, stbuf_ok;
     char *data_ok_fn;
@@ -405,19 +407,18 @@ close_database(krb5_context context, int fd)
 static void
 xmit_database(krb5_context context, krb5_auth_context auth_context,
               krb5_creds *my_creds, int fd, int database_fd,
-              int in_database_size)
+              off_t in_database_size)
 {
     krb5_int32 n;
     krb5_data inbuf, outbuf;
-    char buf[KPROP_BUFSIZ];
+    char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
     krb5_error_code retval;
     krb5_error *error;
-    krb5_ui_4 database_size = in_database_size, send_size, sent_size;
+    uint64_t database_size = in_database_size, send_size, sent_size;
 
     /* Send over the size. */
-    send_size = htonl(database_size);
-    inbuf.data = (char *)&send_size;
-    inbuf.length = sizeof(send_size); /* must be 4, really */
+    inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
+    encode_database_size(database_size, &inbuf);
     /* KPROP_CKSUMTYPE */
     retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL);
     if (retval) {
@@ -452,7 +453,7 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
         retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL);
         if (retval) {
             snprintf(buf, sizeof(buf),
-                     "while encoding database block starting at %d",
+                     "while encoding database block starting at %"PRIu64,
                      sent_size);
             com_err(progname, retval, "%s", buf);
             send_error(context, my_creds, fd, buf, retval);
@@ -463,14 +464,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
         if (retval) {
             krb5_free_data_contents(context, &outbuf);
             com_err(progname, retval,
-                    _("while sending database block starting at %d"),
+                    _("while sending database block starting at %"PRIu64),
                     sent_size);
             exit(1);
         }
         krb5_free_data_contents(context, &outbuf);
         sent_size += n;
         if (debug)
-            printf("%d bytes sent.\n", sent_size);
+            printf("%"PRIu64" bytes sent.\n", sent_size);
     }
     if (sent_size != database_size) {
         com_err(progname, 0, _("Premature EOF found for database file!"));
@@ -525,10 +526,14 @@ xmit_database(krb5_context context, krb5_auth_context auth_context,
         exit(1);
     }
 
-    memcpy(&send_size, outbuf.data, sizeof(send_size));
-    send_size = ntohl(send_size);
+    retval = decode_database_size(&outbuf, &send_size);
+    if (retval) {
+        com_err(progname, retval, _("malformed sent database size message"));
+        exit(1);
+    }
     if (send_size != database_size) {
-        com_err(progname, 0, _("Kpropd sent database size %d, expecting %d"),
+        com_err(progname, 0, _("Kpropd sent database size %"PRIu64
+                               ", expecting %"PRIu64),
                 send_size, database_size);
         exit(1);
     }
index 75331cc8a0c25d032e225900f44f770b70ed36d4..3a319b5354fb17860988f7e706babce2ad3ee699 100644 (file)
@@ -32,6 +32,7 @@
 #define KPROP_PROT_VERSION "kprop5_01"
 
 #define KPROP_BUFSIZ 32768
+#define KPROP_DBSIZE_MAX_BUFSIZ 12  /* max length of an encoded DB size */
 
 /* pathnames are in osconf.h, included via k5-int.h */
 
@@ -41,3 +42,14 @@ int sockaddr2krbaddr(krb5_context context, int family, struct sockaddr *sa,
 krb5_error_code
 sn2princ_realm(krb5_context context, const char *hostname, const char *sname,
                const char *realm, krb5_principal *princ_out);
+
+/*
+ * Encode size in four bytes (for backward compatibility) if it fits; otherwise
+ * use the larger encoding.  buf must be allocated with at least
+ * KPROP_DBSIZE_MAX_BUFSIZ bytes.
+ */
+void encode_database_size(uint64_t size, krb5_data *buf);
+
+/* Decode a database size.  Return KRB5KRB_ERR_GENERIC if buf has an invalid
+ * length or did not encode a 32-bit size compactly. */
+krb5_error_code decode_database_size(const krb5_data *buf, uint64_t *size_out);
index c2b2e87648f6c9bbd79f27b84d93b4c50d3ed450..c0aa2c89bec6fb1dd1f6eb81bf1d4b1031cee38b 100644 (file)
@@ -92,3 +92,45 @@ sn2princ_realm(krb5_context context, const char *hostname, const char *sname,
     *princ_out = princ;
     return 0;
 }
+
+void
+encode_database_size(uint64_t size, krb5_data *buf)
+{
+    assert(buf->length >= 12);
+    if (size > 0 && size <= UINT32_MAX) {
+        /* Encode in 32 bits for backward compatibility. */
+        store_32_be(size, buf->data);
+        buf->length = 4;
+    } else {
+        /* Set the first 32 bits to 0 and encode in the following 64 bits. */
+        store_32_be(0, buf->data);
+        store_64_be(size, buf->data + 4);
+        buf->length = 12;
+    }
+}
+
+krb5_error_code
+decode_database_size(const krb5_data *buf, uint64_t *size_out)
+{
+    uint64_t size;
+
+    if (buf->length == 12) {
+        /* A 12-byte buffer must have the first four bytes zeroed. */
+        if (load_32_be(buf->data) != 0)
+            return KRB5KRB_ERR_GENERIC;
+
+        /* The size is stored in the next 64 bits.  Values from 1..2^32-1 must
+         * be encoded in four bytes. */
+        size = load_64_be(buf->data + 4);
+        if (size > 0 && size <= UINT32_MAX)
+            return KRB5KRB_ERR_GENERIC;
+    } else if (buf->length == 4) {
+        size = load_32_be(buf->data);
+    } else {
+        /* Invalid buffer size. */
+        return KRB5KRB_ERR_GENERIC;
+    }
+
+    *size_out = size;
+    return 0;
+}
index 46bb9d1f72ba2705b695a1555c31704fd7b5bd59..f2341d720f0347915e049fc3d9fd9952a74fea81 100644 (file)
@@ -55,6 +55,7 @@
 #include "com_err.h"
 #include "fake-addrinfo.h"
 
+#include <inttypes.h>
 #include <locale.h>
 #include <ctype.h>
 #include <sys/file.h>
@@ -1345,9 +1346,10 @@ static void
 recv_database(krb5_context context, int fd, int database_fd,
               krb5_data *confmsg)
 {
-    krb5_ui_4 database_size, received_size;
+    uint64_t database_size, received_size;
     int n;
     char buf[1024];
+    char dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
     krb5_data inbuf, outbuf;
     krb5_error_code retval;
 
@@ -1369,10 +1371,17 @@ recv_database(krb5_context context, int fd, int database_fd,
                 _("while decoding database size from client"));
         exit(1);
     }
-    memcpy(&database_size, outbuf.data, sizeof(database_size));
+
+    retval = decode_database_size(&outbuf, &database_size);
+    if (retval) {
+        send_error(context, fd, retval, "malformed database size message");
+        com_err(progname, retval,
+                _("malformed database size message from client"));
+        exit(1);
+    }
+
     krb5_free_data_contents(context, &inbuf);
     krb5_free_data_contents(context, &outbuf);
-    database_size = ntohl(database_size);
 
     /* Initialize the initial vector. */
     retval = krb5_auth_con_initivector(context, auth_context);
@@ -1392,7 +1401,7 @@ recv_database(krb5_context context, int fd, int database_fd,
         retval = krb5_read_message(context, &fd, &inbuf);
         if (retval) {
             snprintf(buf, sizeof(buf),
-                     "while reading database block starting at offset %d",
+                     "while reading database block starting at offset %"PRIu64,
                      received_size);
             com_err(progname, retval, "%s", buf);
             send_error(context, fd, retval, buf);
@@ -1403,8 +1412,8 @@ recv_database(krb5_context context, int fd, int database_fd,
         retval = krb5_rd_priv(context, auth_context, &inbuf, &outbuf, NULL);
         if (retval) {
             snprintf(buf, sizeof(buf),
-                     "while decoding database block starting at offset %d",
-                     received_size);
+                     "while decoding database block starting at offset %"
+                     PRIu64, received_size);
             com_err(progname, retval, "%s", buf);
             send_error(context, fd, retval, buf);
             krb5_free_data_contents(context, &inbuf);
@@ -1414,13 +1423,13 @@ recv_database(krb5_context context, int fd, int database_fd,
         krb5_free_data_contents(context, &inbuf);
         if (n < 0) {
             snprintf(buf, sizeof(buf),
-                     "while writing database block starting at offset %d",
+                     "while writing database block starting at offset %"PRIu64,
                      received_size);
             send_error(context, fd, errno, buf);
         } else if ((unsigned int)n != outbuf.length) {
             snprintf(buf, sizeof(buf),
                      "incomplete write while writing database block starting "
-                     "at \noffset %d (%d written, %d expected)",
+                     "at \noffset %"PRIu64" (%d written, %d expected)",
                      received_size, n, outbuf.length);
             send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
         }
@@ -1431,7 +1440,8 @@ recv_database(krb5_context context, int fd, int database_fd,
     /* OK, we've seen the entire file.  Did we get too many bytes? */
     if (received_size > database_size) {
         snprintf(buf, sizeof(buf),
-                 "Received %d bytes, expected %d bytes for database file",
+                 "Received %"PRIu64" bytes, expected %"PRIu64
+                 " bytes for database file",
                  received_size, database_size);
         send_error(context, fd, KRB5KRB_ERR_GENERIC, buf);
     }
@@ -1441,9 +1451,8 @@ recv_database(krb5_context context, int fd, int database_fd,
 
     /* Create message acknowledging number of bytes received, but
      * don't send it until kdb5_util returns successfully. */
-    database_size = htonl(database_size);
-    inbuf.data = (char *)&database_size;
-    inbuf.length = sizeof(database_size);
+    inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
+    encode_database_size(database_size, &inbuf);
     retval = krb5_mk_safe(context,auth_context,&inbuf,confmsg,NULL);
     if (retval) {
         com_err(progname, retval, "while encoding # of received bytes");
index d96f7c5602e49ef8427c52f3382c7fb6c2ae177f..4421a7c353c8904d9bb7913bffc5272b1ae1292e 100755 (executable)
@@ -87,5 +87,39 @@ realm.run([kdb5_util, 'dump', dumpfile])
 realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname])
 check_output(kpropd)
 realm.run([kadminl, 'listprincs'], replica3, expected_msg='wakawaka')
+stop_daemon(kpropd)
+
+# This test is too resource-intensive to be included in "make check"
+# by default, but it can be enabled in the environment to test the
+# propagation of databases large enough to require a 12-byte encoding
+# of the database size.
+if 'KPROP_LARGE_DB_TEST' in os.environ:
+    output('Generating >4GB dumpfile\n')
+    with open(dumpfile, 'w') as f:
+        f.write('kdb5_util load_dump version 6\n')
+        f.write('princ\t38\t15\t3\t1\t0\tK/M@KRBTEST.COM\t64\t86400\t0\t0\t0'
+                '\t0\t0\t0\t8\t2\t0100\t9\t8\t0100010000000000\t2\t28'
+                '\tb93e105164625f6372656174696f6e404b5242544553542e434f4d00'
+                '\t1\t1\t18\t62\t2000408c027c250e8cc3b81476414f2214d57c1ce'
+                '38891e29792e87258247c73547df4d5756266931dd6686b62270e6568'
+                '95a31ec66bfe913b4f15226227\t-1;\n')
+        for i in range(1, 20000000):
+            f.write('princ\t38\t21\t1\t1\t0\tp%08d@KRBTEST.COM' % i)
+            f.write('\t0\t86400\t0\t0\t0\t0\t0\t0\t2\t27'
+                    '\td73e1051757365722f61646d696e404b5242544553542e434f4d00'
+                    '\t1\t1\t17\t46'
+                    '\t10009c8ab7b3f89ccf3ca3ad98352a461b7f4f1b0c49'
+                    '5605117591d9ad52ba4da0adef7a902126973ed2bdc3ffbf\t-1;\n')
+    assert os.path.getsize(dumpfile) > 4 * 1024 * 1024 * 1024
+    with open(dumpfile + '.dump_ok', 'w') as f:
+        f.write('\0')
+    conf_large = {'dbmodules': {'db': {'database_name': '$testdir/db.large'}},
+                  'realms': {'$realm': {'iprop_resync_timeout': '3600'}}}
+    large = realm.special_env('large', True, kdc_conf=conf_large)
+    kpropd = realm.start_kpropd(large, ['-d'])
+    realm.run([kprop, '-f', dumpfile, '-P', str(realm.kprop_port()), hostname])
+    check_output(kpropd)
+    realm.run([kadminl, 'getprinc', 'p19999999'], env=large,
+              expected_msg='Principal: p19999999')
 
 success('kprop tests')