1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2020 SUSE 4 */ 5 6/*\ 7 * [Description] 8 * 9 * Test based on Syzkaller reproducer: 10 * https://syzkaller.appspot.com/bug?id=680c24ff647dd7241998e19da52e8136d2fd3523 11 * 12 * The SLIP and SLCAN disciplines can have a race between write_wakeup and 13 * close/hangup. This at least allows the netdev private data (tty->disc_data) 14 * to be set to NULL or possibly freed while a transmit operation is being 15 * added to a workqueue. 16 * 17 * If the race condition exists, then the most likely result of running this 18 * is a null pointer dereference. 19 * 20 * Note that we must set the line discipline to "mouse" first which, for 21 * whatever reason, results in tty_wakeup being called during the close 22 * operation. 23 * 24 * We also test a selection of other line disciplines, but only SLIP and SLCAN 25 * are known to have the problem. 26 * 27 * Fixed by commit from v5.5: 28 * 0ace17d56824 ("can, slip: Protect tty->disc_data in write_wakeup and close with RCU") 29 30 * This is also regression test for commit from v4.5-rc1: 31 * dd42bf119714 ("tty: Prevent ldisc drivers from re-using stale tty fields") 32 */ 33 34#define _GNU_SOURCE 35#include <stdlib.h> 36#include <stdio.h> 37#include <errno.h> 38#include <termios.h> 39#include "lapi/ioctl.h" 40 41#include "tst_test.h" 42#include "tst_safe_stdio.h" 43#include "tst_fuzzy_sync.h" 44 45struct ldisc_info { 46 int n; 47 char *name; 48}; 49 50static struct ldisc_info ldiscs[] = { 51 {2, "mouse"}, 52 53 {1, "SLIP"}, 54 {3, "Async PPP"}, 55 {5, "AX25/KISS"}, 56 {13, "HDLC"}, 57 {14, "Sync PPP"}, 58 {17, "SLCAN"}, 59 {18, "PPS"}, 60 {20, "CAIF"}, 61 {21, "GSM"} 62}; 63 64static struct tst_fzsync_pair fzp; 65static volatile int ptmx; 66 67static void *hangup(void *unused) 68{ 69 int i; 70 71 while (tst_fzsync_run_b(&fzp)) { 72 tst_fzsync_start_race_b(&fzp); 73 for (i = 0; i < 10; i++) { 74 if (tcflush(ptmx, TCIFLUSH)) 75 tst_brk(TBROK | TERRNO, "tcflush(ptmx, TCIFLUSH)"); 76 } 77 tst_fzsync_end_race_b(&fzp); 78 } 79 80 return unused; 81} 82 83static int set_ldisc(int tty, struct ldisc_info *ldisc) 84{ 85 TEST(ioctl(tty, TIOCSETD, &ldisc->n)); 86 87 if (!TST_RET) 88 return 0; 89 90 if (TST_ERR == EINVAL) { 91 tst_res(TCONF | TTERRNO, 92 "You don't appear to have the %s TTY line discipline", 93 ldisc->name); 94 } else { 95 tst_res(TFAIL | TTERRNO, 96 "Failed to set the %s line discipline", ldisc->name); 97 } 98 99 return 1; 100} 101 102static void do_test(unsigned int n) 103{ 104 int pts; 105 char pts_path[PATH_MAX]; 106 struct ldisc_info *ldisc = &ldiscs[n + 1]; 107 108 tst_res(TINFO, "Creating PTY with %s line discipline", ldisc->name); 109 110 tst_fzsync_pair_reset(&fzp, hangup); 111 while (tst_fzsync_run_a(&fzp)) { 112 ptmx = SAFE_OPEN("/dev/ptmx", O_RDONLY); 113 if (grantpt(ptmx)) 114 tst_brk(TBROK | TERRNO, "grantpt(ptmx)"); 115 if (unlockpt(ptmx)) 116 tst_brk(TBROK | TERRNO, "unlockpt(ptmx)"); 117 if (ptsname_r(ptmx, pts_path, sizeof(pts_path))) 118 tst_brk(TBROK | TERRNO, "ptsname_r(ptmx, ...)"); 119 pts = SAFE_OPEN(pts_path, O_RDONLY); 120 121 if (set_ldisc(pts, &ldiscs[0])) 122 tst_brk(TCONF, "Need to set mouse discipline first"); 123 if (set_ldisc(pts, ldisc)) 124 return; 125 126 tst_fzsync_start_race_a(&fzp); 127 ioctl(pts, TIOCVHANGUP); 128 tst_fzsync_end_race_a(&fzp); 129 130 SAFE_CLOSE(pts); 131 SAFE_CLOSE(ptmx); 132 } 133 134 tst_res(TPASS, "Did not crash with %s TTY discipline", ldisc->name); 135} 136 137static void setup(void) 138{ 139 fzp.min_samples = 20; 140 141 tst_fzsync_pair_init(&fzp); 142} 143 144static void cleanup(void) 145{ 146 tst_fzsync_pair_cleanup(&fzp); 147} 148 149static struct tst_test test = { 150 .test = do_test, 151 .tcnt = 9, 152 .setup = setup, 153 .cleanup = cleanup, 154 .needs_root = 1, 155 .max_runtime = 30, 156 .needs_kconfigs = (const char *const[]){ 157 "CONFIG_SERIO_SERPORT", 158 NULL 159 }, 160 .tags = (const struct tst_tag[]) { 161 {"linux-git", "0ace17d568241"}, 162 {"CVE", "2020-14416"}, 163 {"linux-git", "dd42bf1197144"}, 164 {} 165 } 166}; 167