/*
 * control_send_repro.c -- standalone reproducer for the control_send()
 * integer-underflow bugs.  No fuzzing framework required.
 *
 * It does exactly what `gpsctl -x <hex> <device>` does (see gpsctl.c:985):
 *   session.device_type->control_send(&session, cooked, cooklen);
 * with a control payload of 1 byte (cooklen == 1).  Every affected driver
 * computes payload = data_len - 2 (Zodiac: len/2 - 1) with no minimum-length
 * check, so cooklen == 1 underflows to ~SIZE_MAX and the driver's *_write()
 * runs a huge memcpy / loop -> out-of-bounds access.
 *
 * Build (AddressSanitizer; a clang that has ASan):
 *   see fuzz/repro_build.sh
 *
 * Run:
 *   ./control_send_repro ubx        # UBX      -> OOB write in ubx_write
 *   ./control_send_repro allystar   # AllyStar -> OOB write in ally_write
 *   ./control_send_repro zodiac     # Zodiac   -> stack overflow in end_write
 *   ./control_send_repro casic      # CASIC    -> guarded write; over-read of msg[1]
 *
 * SPDX-License-Identifier: BSD-2-clause
 */

#include "../include/gpsd_config.h"   // must be before all includes

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../include/gpsd.h"

extern const struct gps_type_t driver_ubx;
extern const struct gps_type_t driver_allystar;
extern const struct gps_type_t driver_casic;
extern const struct gps_type_t driver_zodiac;

int main(int argc, char **argv)
{
    const char *which = (2 <= argc) ? argv[1] : "ubx";
    const struct gps_type_t *drv;

    if (0 == strcmp(which, "ubx")) {
        drv = &driver_ubx;
    } else if (0 == strcmp(which, "allystar")) {
        drv = &driver_allystar;
    } else if (0 == strcmp(which, "casic")) {
        drv = &driver_casic;
    } else if (0 == strcmp(which, "zodiac")) {
        drv = &driver_zodiac;
    } else {
        fprintf(stderr, "usage: %s ubx|allystar|casic|zodiac\n", argv[0]);
        return 2;
    }

    // minimal session, exactly like gpsctl's direct mode
    struct gps_device_t *session =
        (struct gps_device_t *)calloc(1, sizeof(*session));
    static struct gps_context_t ctx;
    memset(&ctx, 0, sizeof(ctx));
    ctx.errout.debug = 0;
    session->context = &ctx;
    // a real (throwaway) fd so the write path is taken (as in gpsctl direct mode)
    int fd = open("/dev/null", O_WRONLY);
    session->gpsdata.gps_fd = (0 > fd) ? -1 : fd;
    session->device_type = drv;

    if (NULL == drv->control_send) {
        fprintf(stderr, "%s has no control_send\n", which);
        return 2;
    }

    /*
     * Mirror gpsctl exactly: a fixed BUFSIZ buffer (gpsctl.c:257 `char
     * cooked[BUFSIZ]`) holding a control payload of length 1 (cooklen == 1).
     * data_len == 1 makes "data_len - 2" (Zodiac: "len/2 - 1") underflow.
     */
    char cooked[BUFSIZ];
    memset(cooked, 0, sizeof(cooked));
    cooked[0] = 0x41;
    size_t cooklen = 1;

    fprintf(stderr, "calling %s control_send with data_len=%zu ...\n",
            which, cooklen);
    (void)drv->control_send(session, cooked, cooklen);
    fprintf(stderr, "returned without crashing (patched?)\n");
    if (0 <= fd) {
        close(fd);
    }

    free(session);
    return 0;
}
