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:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user