Skip to content

Commit

Permalink
Make kprop work for dump files larger than 4GB
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
jrisc authored and greghudson committed Mar 8, 2022
1 parent 922592d commit 2188041
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 28 deletions.
37 changes: 21 additions & 16 deletions src/kprop/kprop.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include "k5-int.h"
#include <inttypes.h>
#include <locale.h>
#include <sys/file.h>
#include <signal.h>
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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!"));
Expand Down Expand Up @@ -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);
}
Expand Down
12 changes: 12 additions & 0 deletions src/kprop/kprop.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand All @@ -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);
42 changes: 42 additions & 0 deletions src/kprop/kprop_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
33 changes: 21 additions & 12 deletions src/kprop/kpropd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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");
Expand Down
34 changes: 34 additions & 0 deletions src/tests/t_kprop.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,39 @@ def check_output(kpropd):
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')

0 comments on commit 2188041

Please sign in to comment.