xref: /aosp_15_r20/external/ltp/testcases/kernel/pty/pty03.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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 
45 struct ldisc_info {
46 	int n;
47 	char *name;
48 };
49 
50 static 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 
64 static struct tst_fzsync_pair fzp;
65 static volatile int ptmx;
66 
hangup(void * unused)67 static 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 
set_ldisc(int tty,struct ldisc_info * ldisc)83 static 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 
do_test(unsigned int n)102 static 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 
setup(void)137 static void setup(void)
138 {
139 	fzp.min_samples = 20;
140 
141 	tst_fzsync_pair_init(&fzp);
142 }
143 
cleanup(void)144 static void cleanup(void)
145 {
146 	tst_fzsync_pair_cleanup(&fzp);
147 }
148 
149 static 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