⬢ DragonFlyBSD Kernel Audit
DF-5456 / gen_fixes.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""Author exact git-apply-able fix.diffs for DF-5455 / DF-5456 against the
read-only sys/bus/u4b/serial/umoscom.c (never applied there)."""
import difflib

SRC = "sys/bus/u4b/serial/umoscom.c"
orig_lines = open(SRC).readlines()
orig = "".join(orig_lines)

def emit(path, mod_text, label):
    assert mod_text != orig, label
    d = "".join(difflib.unified_diff(
        orig_lines, mod_text.splitlines(True),
        fromfile="a/sys/bus/u4b/serial/umoscom.c",
        tofile="b/sys/bus/u4b/serial/umoscom.c"))
    open(path, "w").write(d)
    print("wrote", path, "(%d hunks)" % (sum(1 for l in d.splitlines()
          if l.startswith("@@"))))

# ---- DF-5455: initialize the vendor-read buffer -------------------------
a1 = """	struct usb_device_request req;
	uint8_t val;

	req.bmRequestType = UT_READ_VENDOR_DEVICE;"""
assert orig.count(a1) == 1
b1 = """	struct usb_device_request req;
	/*
	 * The usb_error_t result is discarded by every caller, so on a
	 * timeout, stall, cancel or short/zero-length data stage the vendor
	 * read must not return uninitialized kernel stack into the TIOCMGET
	 * modem-status bits (cfg_get_status -> ucom_cfg_status_change ->
	 * sc_msr -> ucom_modem/ucom_totio -> copyout to userspace).
	 */
	uint8_t val = 0;

	req.bmRequestType = UT_READ_VENDOR_DEVICE;"""
emit("findings/poc/DF-5455/fix.diff", orig.replace(a1, b1, 1), "DF-5455")

# ---- DF-5456: bounded cfg timeout + error-propagating cfg_write, aborts --
m = orig

a = "#define\tUMOSCOM_BUFSIZE	       1024\t/* bytes */\n"
assert m.count(a) == 1
b = a + """
/*
 * Bound the synchronous control-request timeout used from the ucom cfg
 * callbacks: these run on the per-adapter ucom taskqueue and block
 * close(2) (usb_proc_mwait) and the USB hub event thread during detach
 * drain (usb_proc_drain), so a 1000 ms timeout per request lets a slow
 * or malicious MCS7703 pin them for many seconds -- the first-open chain
 * alone is 14 sequential requests (~14.2 s).
 */
#define\tUMOSCOM_CFG_TIMEOUT\t250
"""
m = m.replace(a, b, 1)

a = "static void\tumoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t);"
assert m.count(a) == 1
b = "static int\tumoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t);"
m = m.replace(a, b, 1)

a = """	/* Purge FIFOs or odd things happen */
	umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG);

	/* Enable FIFO */
	umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN |
	    UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR |
	    UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK |
	    UMOSCOM_UART_REG);

	/* Enable Interrupt Registers */
	umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG);

	/* Magic */
	umoscom_cfg_write(sc, 0x01, 0x08);

	/* Magic */
	umoscom_cfg_write(sc, 0x00, 0x02);"""
assert m.count(a) == 1
b = """	/* Purge FIFOs or odd things happen */
	if (umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG))
		return;

	/* Enable FIFO */
	if (umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN |
	    UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR |
	    UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK |
	    UMOSCOM_UART_REG))
		return;

	/* Enable Interrupt Registers */
	if (umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG))
		return;

	/* Magic */
	if (umoscom_cfg_write(sc, 0x01, 0x08))
		return;

	/* Magic */
	if (umoscom_cfg_write(sc, 0x00, 0x02))
		return;"""
m = m.replace(a, b, 1)

a = """	umoscom_cfg_write(sc, UMOSCOM_LCR,
	    UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG);
"""
assert m.count(a) == 1
b = """	/*
	 * Abort on the first failed request; whenever we do get past the
	 * divisor writes the final LCR write below still executes, so the
	 * divisor latch never stays open.
	 */
	if (umoscom_cfg_write(sc, UMOSCOM_LCR,
	    UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG)) {
		DPRINTF("divisor latch enable failed, aborting param\\n");
		return;
	}
"""
m = m.replace(a, b, 1)

a = """static void
umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val)
{
	struct usb_device_request req;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UMOSCOM_WRITE;
	USETW(req.wValue, val);
	USETW(req.wIndex, reg);
	USETW(req.wLength, 0);

	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
	    &req, NULL, 0, 1000);
}"""
assert m.count(a) == 1
b = """static int
umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val)
{
	struct usb_device_request req;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UMOSCOM_WRITE;
	USETW(req.wValue, val);
	USETW(req.wIndex, reg);
	USETW(req.wLength, 0);

	return (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
	    &req, NULL, 0, UMOSCOM_CFG_TIMEOUT));
}"""
m = m.replace(a, b, 1)

a = """	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 
	    &req, &val, 0, 1000);"""
assert m.count(a) == 1
b = """	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, 
	    &req, &val, 0, UMOSCOM_CFG_TIMEOUT);"""
m = m.replace(a, b, 1)

# remaining void-call sites of cfg_write (set_dtr/set_rts/set_break/
# cfg_param divisor+final writes) must consume the return value
a = """	umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG);"""
assert m.count(a) == 2
m = m.replace(a, """	if (umoscom_cfg_write(sc, UMOSCOM_MCR,
	    sc->sc_mcr | UMOSCOM_UART_REG))
		DPRINTF("MCR write failed\\n");""", 2)

a = """	umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG);"""
assert m.count(a) == 1
m = m.replace(a, """	if (umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG))
		DPRINTF("LCR break write failed\\n");""", 1)

a = """	sc->sc_lcr = data;
	umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG);"""
assert m.count(a) == 1
m = m.replace(a, """	sc->sc_lcr = data;
	if (umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG))
		DPRINTF("LCR format write failed\\n");""", 1)

a = """	umoscom_cfg_write(sc, UMOSCOM_BAUDLO,
	    (data & 0xFF) | UMOSCOM_UART_REG);

	umoscom_cfg_write(sc, UMOSCOM_BAUDHI,
	    ((data >> 8) & 0xFF) | UMOSCOM_UART_REG);"""
assert m.count(a) == 1
m = m.replace(a, """	if (umoscom_cfg_write(sc, UMOSCOM_BAUDLO,
	    (data & 0xFF) | UMOSCOM_UART_REG))
		DPRINTF("divisor lo write failed\\n");

	if (umoscom_cfg_write(sc, UMOSCOM_BAUDHI,
	    ((data >> 8) & 0xFF) | UMOSCOM_UART_REG))
		DPRINTF("divisor hi write failed\\n");""", 1)

emit("findings/poc/DF-5456/fix.diff", m, "DF-5456")