// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2020 SUSE */ /*\ * [Description] * * Test based on Syzkaller reproducer: * https://syzkaller.appspot.com/bug?id=680c24ff647dd7241998e19da52e8136d2fd3523 * * The SLIP and SLCAN disciplines can have a race between write_wakeup and * close/hangup. This at least allows the netdev private data (tty->disc_data) * to be set to NULL or possibly freed while a transmit operation is being * added to a workqueue. * * If the race condition exists, then the most likely result of running this * is a null pointer dereference. * * Note that we must set the line discipline to "mouse" first which, for * whatever reason, results in tty_wakeup being called during the close * operation. * * We also test a selection of other line disciplines, but only SLIP and SLCAN * are known to have the problem. * * Fixed by commit from v5.5: * 0ace17d56824 ("can, slip: Protect tty->disc_data in write_wakeup and close with RCU") * This is also regression test for commit from v4.5-rc1: * dd42bf119714 ("tty: Prevent ldisc drivers from re-using stale tty fields") */ #define _GNU_SOURCE #include #include #include #include #include "lapi/ioctl.h" #include "tst_test.h" #include "tst_safe_stdio.h" #include "tst_fuzzy_sync.h" struct ldisc_info { int n; char *name; }; static struct ldisc_info ldiscs[] = { {2, "mouse"}, {1, "SLIP"}, {3, "Async PPP"}, {5, "AX25/KISS"}, {13, "HDLC"}, {14, "Sync PPP"}, {17, "SLCAN"}, {18, "PPS"}, {20, "CAIF"}, {21, "GSM"} }; static struct tst_fzsync_pair fzp; static volatile int ptmx; static void *hangup(void *unused) { int i; while (tst_fzsync_run_b(&fzp)) { tst_fzsync_start_race_b(&fzp); for (i = 0; i < 10; i++) { if (tcflush(ptmx, TCIFLUSH)) tst_brk(TBROK | TERRNO, "tcflush(ptmx, TCIFLUSH)"); } tst_fzsync_end_race_b(&fzp); } return unused; } static int set_ldisc(int tty, struct ldisc_info *ldisc) { TEST(ioctl(tty, TIOCSETD, &ldisc->n)); if (!TST_RET) return 0; if (TST_ERR == EINVAL) { tst_res(TCONF | TTERRNO, "You don't appear to have the %s TTY line discipline", ldisc->name); } else { tst_res(TFAIL | TTERRNO, "Failed to set the %s line discipline", ldisc->name); } return 1; } static void do_test(unsigned int n) { int pts; char pts_path[PATH_MAX]; struct ldisc_info *ldisc = &ldiscs[n + 1]; tst_res(TINFO, "Creating PTY with %s line discipline", ldisc->name); tst_fzsync_pair_reset(&fzp, hangup); while (tst_fzsync_run_a(&fzp)) { ptmx = SAFE_OPEN("/dev/ptmx", O_RDONLY); if (grantpt(ptmx)) tst_brk(TBROK | TERRNO, "grantpt(ptmx)"); if (unlockpt(ptmx)) tst_brk(TBROK | TERRNO, "unlockpt(ptmx)"); if (ptsname_r(ptmx, pts_path, sizeof(pts_path))) tst_brk(TBROK | TERRNO, "ptsname_r(ptmx, ...)"); pts = SAFE_OPEN(pts_path, O_RDONLY); if (set_ldisc(pts, &ldiscs[0])) tst_brk(TCONF, "Need to set mouse discipline first"); if (set_ldisc(pts, ldisc)) return; tst_fzsync_start_race_a(&fzp); ioctl(pts, TIOCVHANGUP); tst_fzsync_end_race_a(&fzp); SAFE_CLOSE(pts); SAFE_CLOSE(ptmx); } tst_res(TPASS, "Did not crash with %s TTY discipline", ldisc->name); } static void setup(void) { fzp.min_samples = 20; tst_fzsync_pair_init(&fzp); } static void cleanup(void) { tst_fzsync_pair_cleanup(&fzp); } static struct tst_test test = { .test = do_test, .tcnt = 9, .setup = setup, .cleanup = cleanup, .needs_root = 1, .max_runtime = 30, .needs_kconfigs = (const char *const[]){ "CONFIG_SERIO_SERPORT", NULL }, .tags = (const struct tst_tag[]) { {"linux-git", "0ace17d568241"}, {"CVE", "2020-14416"}, {"linux-git", "dd42bf1197144"}, {} } };