view fuzz/fuzz-common.c @ 1861:2b3a8026a6ce

Add re-exec for server This allows ASLR to re-randomize the address space for every connection, preventing some vulnerabilities from being exploitable by repeated probing. Overhead (memory and time) is yet to be confirmed. At present this is only enabled on Linux. Other BSD platforms with fexecve() would probably also work though have not been tested.
author Matt Johnston <matt@ucc.asn.au>
date Sun, 30 Jan 2022 10:14:56 +0800
parents 19b28d2fbe30
children
line wrap: on
line source

#define FUZZ_NO_REPLACE_STDERR
#define FUZZ_NO_REPLACE_GETPW
#include "includes.h"

#include "includes.h"
#include "dbutil.h"
#include "runopts.h"
#include "crypto_desc.h"
#include "session.h"
#include "dbrandom.h"
#include "bignum.h"
#include "atomicio.h"
#include "fuzz-wrapfd.h"
#include "fuzz.h"

struct dropbear_fuzz_options fuzz;

static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param);
static void load_fixed_hostkeys(void);
static void load_fixed_client_key(void);

// This runs automatically before main, due to contructor attribute in fuzz.h
void fuzz_early_setup(void) {
    /* Set stderr to point to normal stderr by default */
    fuzz.fake_stderr = stderr;
}

void fuzz_common_setup(void) {
	disallow_core();
    fuzz.fuzzing = 1;
    fuzz.wrapfds = 1;
    fuzz.do_jmp = 1;
    fuzz.input = m_malloc(sizeof(buffer));
    _dropbear_log = fuzz_dropbear_log;
    crypto_init();
    fuzz_seed("start", 5);
    /* let any messages get flushed */
    setlinebuf(stdout);
#if DEBUG_TRACE
    if (debug_trace)
    {
        fprintf(stderr, "Dropbear fuzzer: -v specified, not disabling stderr output\n");
    }
    else
#endif
    if (getenv("DROPBEAR_KEEP_STDERR")) {
        fprintf(stderr, "Dropbear fuzzer: DROPBEAR_KEEP_STDERR, not disabling stderr output\n");
    } 
    else 
    {
        fprintf(stderr, "Dropbear fuzzer: Disabling stderr output\n");
        fuzz.fake_stderr = fopen("/dev/null", "w");
        assert(fuzz.fake_stderr);
    }
}

int fuzz_set_input(const uint8_t *Data, size_t Size) {

    fuzz.input->data = (unsigned char*)Data;
    fuzz.input->size = Size;
    fuzz.input->len = Size;
    fuzz.input->pos = 0;

    memset(&ses, 0x0, sizeof(ses));
    memset(&svr_ses, 0x0, sizeof(svr_ses));
    memset(&cli_ses, 0x0, sizeof(cli_ses));
    wrapfd_setup(fuzz.input);
    // printhex("input", fuzz.input->data, fuzz.input->len);

    fuzz_seed(fuzz.input->data, MIN(fuzz.input->len, 16));

    return DROPBEAR_SUCCESS;
}

#if DEBUG_TRACE
static void fuzz_dropbear_log(int UNUSED(priority), const char* format, va_list param) {
    if (debug_trace) {
        char printbuf[1024];
        vsnprintf(printbuf, sizeof(printbuf), format, param);
        fprintf(stderr, "%s\n", printbuf);
    }
}
#else
static void fuzz_dropbear_log(int UNUSED(priority), const char* UNUSED(format), va_list UNUSED(param)) {
    /* No print */
}
#endif /* DEBUG_TRACE */

void fuzz_svr_setup(void) {
    fuzz_common_setup();
    
    _dropbear_exit = svr_dropbear_exit;

    char *argv[] = { 
		"dropbear",
        "-E", 
    };

    int argc = sizeof(argv) / sizeof(*argv);
    svr_getopts(argc, argv);

    load_fixed_hostkeys();
}

void fuzz_svr_hook_preloop() {
    if (fuzz.svr_postauth) {
        ses.authstate.authdone = 1;
        fill_passwd("root");
    }
}

void fuzz_cli_setup(void) {
    fuzz_common_setup();
    
	_dropbear_exit = cli_dropbear_exit;
	_dropbear_log = cli_dropbear_log;

    char *argv[] = { 
		"dbclient",
		"-y",
        "localhost",
        "uptime"
    };

    int argc = sizeof(argv) / sizeof(*argv);
    cli_getopts(argc, argv);

    load_fixed_client_key();
    /* Avoid password prompt */
    setenv(DROPBEAR_PASSWORD_ENV, "password", 1);
}

#include "fuzz-hostkeys.c"   

static void load_fixed_client_key(void) {

    buffer *b = buf_new(3000);
    sign_key *key;
    enum signkey_type keytype;

    key = new_sign_key();
    keytype = DROPBEAR_SIGNKEY_ANY;
    buf_putbytes(b, keyed25519, keyed25519_len);
    buf_setpos(b, 0);
    if (buf_get_priv_key(b, key, &keytype) == DROPBEAR_FAILURE) {
        dropbear_exit("failed fixed ed25519 hostkey");
    }
    list_append(cli_opts.privkeys, key);

    buf_free(b);
}

static void load_fixed_hostkeys(void) {

    buffer *b = buf_new(3000);
    enum signkey_type type;

    TRACE(("load fixed hostkeys"))

    svr_opts.hostkey = new_sign_key();

    buf_setlen(b, 0);
    buf_putbytes(b, keyr, keyr_len);
    buf_setpos(b, 0);
    type = DROPBEAR_SIGNKEY_RSA;
    if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) {
        dropbear_exit("failed fixed rsa hostkey");
    }

    buf_setlen(b, 0);
    buf_putbytes(b, keyd, keyd_len);
    buf_setpos(b, 0);
    type = DROPBEAR_SIGNKEY_DSS;
    if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) {
        dropbear_exit("failed fixed dss hostkey");
    }

    buf_setlen(b, 0);
    buf_putbytes(b, keye, keye_len);
    buf_setpos(b, 0);
    type = DROPBEAR_SIGNKEY_ECDSA_NISTP256;
    if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) {
        dropbear_exit("failed fixed ecdsa hostkey");
    }

    buf_setlen(b, 0);
    buf_putbytes(b, keyed25519, keyed25519_len);
    buf_setpos(b, 0);
    type = DROPBEAR_SIGNKEY_ED25519;
    if (buf_get_priv_key(b, svr_opts.hostkey, &type) == DROPBEAR_FAILURE) {
        dropbear_exit("failed fixed ed25519 hostkey");
    }

    buf_free(b);
}

void fuzz_kex_fakealgos(void) {
    ses.newkeys->recv.crypt_mode = &dropbear_mode_none;
    ses.newkeys->recv.algo_mac = &dropbear_nohash;
}

void fuzz_get_socket_address(int UNUSED(fd), char **local_host, char **local_port,
                        char **remote_host, char **remote_port, int UNUSED(host_lookup)) {
    if (local_host) {
        *local_host = m_strdup("fuzzlocalhost");
    }
    if (local_port) {
        *local_port = m_strdup("1234");
    }
    if (remote_host) {
        *remote_host = m_strdup("fuzzremotehost");
    }
    if (remote_port) {
        *remote_port = m_strdup("9876");
    }
}

/* cut down version of svr_send_msg_kexdh_reply() that skips slow maths. Still populates structures */
void fuzz_fake_send_kexdh_reply(void) {
    assert(!ses.dh_K);
    m_mp_alloc_init_multi(&ses.dh_K, NULL);
    mp_set_ul(ses.dh_K, 12345678uL);
    finish_kexhashbuf();
}

/* fake version of spawn_command() */
int fuzz_spawn_command(int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid) {
    *ret_writefd = wrapfd_new_dummy();
    *ret_readfd = wrapfd_new_dummy();
    if (ret_errfd) {
        *ret_errfd = wrapfd_new_dummy();
    }
    if (*ret_writefd == -1 || *ret_readfd == -1 || (ret_errfd && *ret_errfd == -1)) {
        m_close(*ret_writefd);
        m_close(*ret_readfd);
        if (ret_errfd) {
            m_close(*ret_errfd);
        }
        return DROPBEAR_FAILURE;
    } else {
        *ret_pid = 999;
        return DROPBEAR_SUCCESS;

    }
}

/* Fake dropbear_listen, always returns failure for now.
TODO make it sometimes return success with wrapfd_new_dummy() sockets.
Making the listeners fake a new incoming connection will be harder. */
/* Listen on address:port. 
 * Special cases are address of "" listening on everything,
 * and address of NULL listening on localhost only.
 * Returns the number of sockets bound on success, or -1 on failure. On
 * failure, if errstring wasn't NULL, it'll be a newly malloced error
 * string.*/
int fuzz_dropbear_listen(const char* UNUSED(address), const char* UNUSED(port),
        int *UNUSED(socks), unsigned int UNUSED(sockcount), char **errstring, int *UNUSED(maxfd)) {
    if (errstring) {
        *errstring = m_strdup("fuzzing can't listen (yet)");
    }
    return -1;
}

int fuzz_run_server(const uint8_t *Data, size_t Size, int skip_kexmaths, int postauth) {
    static int once = 0;
    if (!once) {
        fuzz_svr_setup();
        fuzz.skip_kexmaths = skip_kexmaths;
        once = 1;
    }

    fuzz.svr_postauth = postauth;

    if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) {
        return 0;
    }

    uint32_t wrapseed;
    genrandom((void*)&wrapseed, sizeof(wrapseed));
    wrapfd_setseed(wrapseed);

    int fakesock = wrapfd_new_fuzzinput();

    m_malloc_set_epoch(1);
    fuzz.do_jmp = 1;
    if (setjmp(fuzz.jmp) == 0) {
        svr_session(fakesock, fakesock);
        m_malloc_free_epoch(1, 0);
    } else {
        fuzz.do_jmp = 0;
        m_malloc_free_epoch(1, 1);
        TRACE(("dropbear_exit longjmped"))
        /* dropbear_exit jumped here */
    }

    return 0;
}

int fuzz_run_client(const uint8_t *Data, size_t Size, int skip_kexmaths) {
    static int once = 0;
    if (!once) {
        fuzz_cli_setup();
        fuzz.skip_kexmaths = skip_kexmaths;
        once = 1;
    }

    if (fuzz_set_input(Data, Size) == DROPBEAR_FAILURE) {
        return 0;
    }

    // Allow to proceed sooner
    ses.kexstate.donefirstkex = 1;

    uint32_t wrapseed;
    genrandom((void*)&wrapseed, sizeof(wrapseed));
    wrapfd_setseed(wrapseed);

    int fakesock = wrapfd_new_fuzzinput();

    m_malloc_set_epoch(1);
    fuzz.do_jmp = 1;
    if (setjmp(fuzz.jmp) == 0) {
        cli_session(fakesock, fakesock, NULL, 0);
        m_malloc_free_epoch(1, 0);
    } else {
        fuzz.do_jmp = 0;
        m_malloc_free_epoch(1, 1);
        TRACE(("dropbear_exit longjmped"))
        /* dropbear_exit jumped here */
    }

    return 0;
}

const void* fuzz_get_algo(const algo_type *algos, const char* name) {
    const algo_type *t;
    for (t = algos; t->name; t++) {
        if (strcmp(t->name, name) == 0) {
            return t->data;
        }
    }
    assert(0);
}

void fuzz_dump(const unsigned char* data, size_t len) {
    if (fuzz.dumping) {
        TRACE(("dump %zu", len))
        assert(atomicio(vwrite, fuzz.recv_dumpfd, (void*)data, len) == len);
    }
}

static struct passwd pwd_root = {
    .pw_name = "root",
    .pw_passwd = "!",
    .pw_uid = 0,
    .pw_gid = 0,
    .pw_dir = "/root",
    .pw_shell = "/bin/sh",
};

static struct passwd pwd_other = {
    .pw_name = "other",
    .pw_passwd = "!",
    .pw_uid = 100,
    .pw_gid = 100,
    .pw_dir = "/home/other",
    .pw_shell = "/bin/sh",
};


/* oss-fuzz runs fuzzers under minijail, without /etc/passwd.
We provide sufficient values for the fuzzers to run */
struct passwd* fuzz_getpwnam(const char *login) {
    if (!fuzz.fuzzing) {
        return getpwnam(login);
    }
    if (strcmp(login, pwd_other.pw_name) == 0) {
        return &pwd_other;
    }
    if (strcmp(login, pwd_root.pw_name) == 0) {
        return &pwd_root;
    }
    return NULL;
}

struct passwd* fuzz_getpwuid(uid_t uid) {
    if (!fuzz.fuzzing) {
        return getpwuid(uid);
    }
    if (uid == pwd_other.pw_uid) {
        return &pwd_other;
    }
    if (uid == pwd_root.pw_uid) {
        return &pwd_root;
    }
    return NULL;
}