kadm5: Add test_marshall program

The lib/kadm5/test_marshall program allows one to construct and check
encodings for various struct types for which we have
{kadm5,krb5}_{ret,store}_<type>() functions.

Currently supported are:

 - krb5_keyblock
 - krb5_principal
 - krb5_times
 - krb5_address
 - krb5_addresses
 - krb5_authdata
 - krb5_creds
 - krb5_key_data
 - krb5_tl_data
 - kadm5_principal_ent_rec

With this we'll be able to a) construct test vectors, b) use those to
drive fuzzing with AFL or other fuzzers.
This commit is contained in:
Nicolas Williams
2022-12-21 17:57:41 -06:00
parent 8b6926f4c0
commit 07ce06e7b2
2 changed files with 461 additions and 1 deletions

View File

@@ -15,7 +15,7 @@ libkadm5srv_la_LDFLAGS += $(LDFLAGS_VERSION_SCRIPT)$(srcdir)/version-script.map
endif
sbin_PROGRAMS = iprop-log
check_PROGRAMS = default_keys
check_PROGRAMS = default_keys test_marshall
noinst_PROGRAMS = test_pw_quality
noinst_LTLIBRARIES = sample_passwd_check.la sample_hook.la
@@ -37,6 +37,9 @@ libexec_PROGRAMS = ipropd-master ipropd-slave
default_keys_SOURCES = default_keys.c
default_keys_CPPFLAGS = -I$(srcdir)/../krb5
test_marshall_SOURCES = marshall.c
test_marshall_CPPFLAGS = -I$(srcdir)/../krb5 -DTEST
kadm5includedir = $(includedir)/kadm5
buildkadm5include = $(buildinclude)/kadm5

View File

@@ -472,3 +472,460 @@ _kadm5_unmarshal_params(krb5_context context,
return ret;
}
#ifdef TEST
#include <getarg.h>
#include <krb5-protos.h>
#include <hex.h>
static int version_flag;
static int help_flag;
static int verbose_flag;
static int in_text_flag = 0;
static int in_binary_flag = 0;
static int out_hex_flag = 0;
static int out_binary_flag = 0;
static int must_round_trip_flag = 0;
static int byteorder_packed_flag = 0;
static char *byteorder_string_in_string;
static char *byteorder_string_out_string;
static struct getargs args[] = {
{ "version", '\0', arg_flag, &version_flag,
"Version", NULL },
{ "help", '\0', arg_flag, &help_flag,
"Show this message", NULL },
{ "verbose", 'v', arg_flag, &verbose_flag, NULL, NULL },
{ "in-text", '\0', arg_flag, &in_text_flag,
"Input is a text \"recipe\"", NULL },
{ "in-binary", '\0', arg_flag, &in_binary_flag,
"Input is binary", NULL },
{ "out-hex", '\0', arg_flag, &out_hex_flag,
"Output hex", NULL },
{ "out-binary", '\0', arg_flag, &out_binary_flag,
"Output binary", NULL },
{ "must-round-trip", '\0', arg_flag, &must_round_trip_flag,
"Check that encoding and decoding round-trip", NULL },
{ "byte-order-out", '\0', arg_string, &byteorder_string_out_string,
"Output byte order", "host, network, be, or le" },
{ "byte-order-in", '\0', arg_string, &byteorder_string_in_string,
"Input byte order", "host, network, be, or le" },
{ "byte-order-packed", '\0', arg_flag, &byteorder_packed_flag, NULL, NULL },
};
#define DO_TYPE1(t, r, s) \
if (strcmp(type, #t) == 0) { \
t v; \
ret = r(in, &v); \
if (ret == 0) \
ret = s(out, v); \
return ret; \
}
#define DO_TYPE2(t, r, s) \
if (strcmp(type, #t) == 0) { \
t v; \
ret = r(in, &v); \
if (ret == 0) \
ret = s(out, &v); \
return ret; \
}
static krb5_error_code
reencode(const char *type, krb5_storage *in, krb5_storage *out)
{
krb5_error_code ret;
krb5_storage_seek(in, 0, SEEK_SET);
/*
* TODO: When --verbose print a visual representation of the value.
*
* We have functionality in lib/krb5 for that for krb5_principal and
* krb5_address, but not any of the others. Adding krb5_print_*()
* and kadm5_print_*() functions just for this program to use seems
* annoying.
*/
DO_TYPE1(krb5_keyblock, krb5_ret_keyblock, krb5_store_keyblock);
DO_TYPE1(krb5_principal, krb5_ret_principal, krb5_store_principal);
DO_TYPE1(krb5_times, krb5_ret_times, krb5_store_times);
DO_TYPE1(krb5_address, krb5_ret_address, krb5_store_address);
DO_TYPE1(krb5_addresses, krb5_ret_addrs, krb5_store_addrs);
DO_TYPE1(krb5_authdata, krb5_ret_authdata, krb5_store_authdata);
DO_TYPE2(krb5_creds, krb5_ret_creds, krb5_store_creds);
DO_TYPE2(krb5_key_data, kadm5_ret_key_data, kadm5_store_key_data);
DO_TYPE2(krb5_tl_data, kadm5_ret_tl_data, kadm5_store_tl_data);
DO_TYPE2(kadm5_principal_ent_rec, kadm5_ret_principal_ent,
kadm5_store_principal_ent);
return ENOTSUP;
}
static krb5_error_code
eval_recipe1(krb5_storage *sp, const char *typ, const char *val)
{
krb5_error_code ret;
uint64_t vu = 0;
int64_t vi = 0;
int consumed = 0;
if (strncmp(typ, "int", sizeof("int") - 1) == 0) {
if (sscanf(val, "%"PRIi64"%n", &vi, &consumed) != 1)
return EINVAL;
if (consumed < 1)
return EINVAL;
while (isspace(val[consumed]))
consumed++;
if (val[consumed] != '\0')
return EINVAL;
} else if (strncmp(typ, "uint", sizeof("uint") - 1) == 0) {
/* There's no equally-useful equivalent of %i for unsigned */
if (val[0] == '0') {
if (val[1] == 'x') {
if (sscanf(val, "%"PRIx64"%n", &vu, &consumed) != 1)
return EINVAL;
} else {
if (sscanf(val, "%"PRIo64"%n", &vu, &consumed) != 1)
return EINVAL;
}
} else {
if (sscanf(val, "%"PRIu64"%n", &vu, &consumed) != 1)
return EINVAL;
}
if (consumed < 1)
return EINVAL;
while (isspace(val[consumed]))
consumed++;
if (val[consumed] != '\0')
return EINVAL;
vi = (int64_t)vu;
}
#define DO_INTn(n) \
if (strcmp(typ, "int" #n) == 0) { \
if (n < 64 && vi < INT ## n ## _MIN) \
return EOVERFLOW; \
if (n < 64 && vi > INT ## n ## _MAX) \
return EOVERFLOW; \
return krb5_store_int ## n (sp, vi); \
}
DO_INTn(8);
DO_INTn(16);
DO_INTn(32);
DO_INTn(64);
#define DO_UINTn(n) \
if (strcmp(typ, "uint" #n) == 0) { \
if (n < 64 && vu > INT ## n ## _MAX) \
return EOVERFLOW; \
return krb5_store_int ## n (sp, vi); \
}
DO_UINTn(8);
DO_UINTn(16);
DO_UINTn(32);
DO_UINTn(64);
if (strcmp(typ, "string") == 0)
return krb5_store_string(sp, val);
if (strcmp(typ, "stringz") == 0)
return krb5_store_stringz(sp, val);
if (strcmp(typ, "stringnl") == 0)
return krb5_store_stringnl(sp, val);
if (strcmp(typ, "data") == 0) {
ssize_t dsz = strlen(val);
krb5_data d;
/*
* 'data' as in 'krb5_data'.
*
* krb5_store_data() stores the length then the data.
*/
if (krb5_data_alloc(&d, dsz))
return ENOMEM;
dsz = hex_decode(val, d.data, d.length);
if (dsz < 0)
return EINVAL;
d.length = dsz;
ret = krb5_store_data(sp, d);
krb5_data_free(&d);
return ret;
}
if (strcmp(typ, "rawdata") == 0) {
ssize_t dsz = strlen(val);
void *d;
/* Store the data w/o a length prefix */
d = malloc(dsz);
if (d == NULL)
return ENOMEM;
dsz = hex_decode(val, d, dsz);
if (dsz < 0)
return EINVAL;
ret = krb5_store_datalen(sp, d, dsz);
free(d);
return ret;
}
return ENOTSUP;
}
static krb5_storage *
eval_recipe(char *r, int spflags)
{
krb5_error_code ret;
krb5_storage *sp;
unsigned int lineno = 0;
char *nxt = NULL;
char *p;
sp = krb5_storage_emem();
if (sp == NULL)
errx(1, "Out of memory");
krb5_storage_set_flags(sp, spflags);
for (p = r; p && *p; p = nxt) {
char *typ;
char *val;
lineno++;
/* Terminate p at \n */
nxt = p;
do {
nxt = strpbrk(nxt, "\r\n");
if (nxt && *nxt == '\r') {
if (*(++nxt) != '\n')
continue;
}
if (nxt && *nxt == '\n') {
*(nxt++) = '\0';
break;
}
} while (nxt);
while (isspace(*p))
p++;
if (*p == '#') {
p = nxt;
continue;
}
if (*p == '\0')
continue;
typ = p;
val = strpbrk(p, " \t");
if (val) {
*(val++) = '\0';
while (isspace(*val))
val++;
}
ret = eval_recipe1(sp, typ, val);
if (ret)
krb5_err(NULL, 1, ret, "Error at line %u", lineno);
}
return sp;
}
static void
usage(int code)
{
if (code)
dup2(STDERR_FILENO, STDOUT_FILENO);
arg_printusage(args, sizeof(args) / sizeof(args[0]), "test_marshall",
"Usage: test_marshal [options] TYPE-NAME INPUT-FILE "
"[OUTPUT-FILE]\n"
"\tText inputs must be of the form:\n\n"
"\t\tsimpletype literalvalue\n\n"
"\twhere {simpletype} is one of:\n\n"
"\t\tint8\n"
"\t\tint16\n"
"\t\tint32\n"
"\t\tint64\n"
"\t\tuint8\n"
"\t\tuint16\n"
"\t\tuint32\n"
"\t\tuint64\n"
"\t\tstring\n"
"\t\tstringz\n"
"\t\tstringnl\n"
"\t\tdata\n"
"\t\trawdata\n\n"
"\tand {literalvalue} is as appropriate for the {simpletype}:\n\n"
"\t - For int types the value can be decimal, octal, or hexadecimal.\n"
"\t - For string types the string ends at the end of the line.\n"
"\t - For {data} the value is hex and will be encoded as a 32-bit\n"
"\t length then the raw binary data.\n"
"\t - For {rawdata} the value is hex and will be encoded as just the\n"
"\t raw binary data.\n\n"
"\tThe {TYPE} must be one of: krb5_keyblock, krb5_principal,\n"
"\tkrb5_times, krb5_address, krb5_addresses, krb5_authdata,\n"
"\tkrb5_creds, krb5_key_data, krb5_tl_data, or\n"
"\tkadm5_principal_ent_rec.\n\n"
"Options:\n");
exit(code);
}
static krb5_flags
byteorder_flags(const char *s)
{
if (s == NULL)
return KRB5_STORAGE_BYTEORDER_HOST;
if (strcasecmp(s, "host") == 0)
return KRB5_STORAGE_BYTEORDER_HOST;
if (strcasecmp(s, "network") == 0)
return KRB5_STORAGE_BYTEORDER_BE;
if (strcasecmp(s, "be") == 0)
return KRB5_STORAGE_BYTEORDER_BE;
if (strcasecmp(s, "le") == 0)
return KRB5_STORAGE_BYTEORDER_LE;
return 0;
}
/*
* This program is intended to make fuzzing of krb5_ret_*() and kadm5_ret_*()
* possible.
*
* Inputs are either binary encodings or simplistic textual representations of
* XDR-ish data structures normally coded with {kadm5,krb5}_{ret,store}_*()
* functions.
*
* A textual representation of these structures looks like:
*
* type value
* ..
*
* where type is one of char, int32, etc., and where value is an appropriate
* literal for type.
*/
int
main(int argc, char **argv)
{
krb5_error_code ret = 0;
krb5_storage *insp = NULL;
krb5_storage *outsp = NULL;
krb5_flags spflags_in = 0;
krb5_flags spflags_out = 0;
krb5_data i, o;
size_t insz = 0;
char *intxt = NULL;
void *inbin = NULL;
int optidx = 0;
if (getarg(args, sizeof(args)/sizeof(args[0]), argc, argv, &optidx))
usage(1);
if (help_flag)
usage(0);
argc -= optidx;
argv += optidx;
if (argc < 1)
errx(1, "Missing type name argument");
if (argc < 2)
errx(1, "Missing input file argument");
if (argc > 3)
errx(1, "Too many arguments");
if ((in_text_flag && in_binary_flag) ||
(!in_text_flag && !in_binary_flag))
errx(1, "One and only one of --in-text and --in-binary must be given");
if (out_hex_flag && out_binary_flag)
errx(1, "At most one of --out-text and --out-binary must be given");
if (!out_hex_flag && !out_binary_flag) {
if (isatty(STDOUT_FILENO)) {
warnx("Will output hex because stdout is a terminal");
out_hex_flag = 1;
} else {
warnx("Will output binary");
out_binary_flag = 1;
}
}
if (byteorder_packed_flag) {
spflags_in |= KRB5_STORAGE_BYTEORDER_PACKED;
spflags_out |= KRB5_STORAGE_BYTEORDER_PACKED;
}
spflags_in |= byteorder_flags(byteorder_string_in_string);
spflags_out |= byteorder_flags(byteorder_string_out_string);
/* Read the input */
if (in_text_flag)
errno = rk_undumptext(argv[1], &intxt, NULL);
else
errno = rk_undumpdata(argv[1], &inbin, &insz);
if (errno)
err(1, "Could not read %s", argv[1]);
/* If the input is a recipe, evaluate it */
if (intxt)
insp = eval_recipe(intxt, spflags_in);
else
insp = krb5_storage_from_mem(inbin, insz);
if (insp == NULL)
errx(1, "Out of memory");
krb5_storage_set_flags(insp, spflags_in);
outsp = krb5_storage_emem();
if (outsp == NULL)
errx(1, "Out of memory");
krb5_storage_set_flags(outsp, spflags_out);
ret = reencode(argv[0], insp, outsp);
if (ret)
krb5_err(NULL, 1, ret, "Could not decode and re-encode");
ret = krb5_storage_to_data(insp, &i);
if (ret == 0)
ret = krb5_storage_to_data(outsp, &o);
if (ret)
krb5_err(NULL, 1, ret, "Could not check round-tripping");
if (i.length != o.length || memcmp(i.data, o.data, i.length) != 0) {
if (must_round_trip_flag) {
char *hexstr = NULL;
if (hex_encode(inbin, insz, &hexstr) == -1)
err(1, "Encoding does not round-trip");
if (fprintf(stderr, "%s\n", hexstr) < 0)
err(1, "Could not output encoding");
free(hexstr);
errx(1, "Encoding does not round-trip");
} else {
warnx("Encoding does not round-trip");
}
} else if (verbose_flag) {
fprintf(stderr, "Encoding round-trips!\n");
}
if (out_hex_flag) {
char *hexstr = NULL;
if (hex_encode(i.data, i.length, &hexstr) == -1)
err(1, "Could not hex-encode output");
if (argv[2]) {
FILE *f;
f = fopen(argv[2], "w");
if (f == NULL)
err(1, "Could not open %s for writing", argv[2]);
if (fprintf(f, "%s\n", hexstr) < 0 || fclose(f))
err(1, "Could write to %s", argv[2]);
} else {
if (printf("%s\n", hexstr) < 0)
err(1, "Could not write to stdout");
}
free(hexstr);
} else {
if (argv[2]) {
rk_dumpdata(argv[2], i.data, i.length);
} else {
if (fwrite(i.data, i.length, 1, stdout) != 1 ||
fflush(stdout) != 0)
err(1, "Could not output encoding");
}
}
krb5_data_free(&o);
krb5_data_free(&i);
krb5_storage_free(insp);
krb5_storage_free(outsp);
return ret;
}
#endif