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