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