Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// 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 <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <termios.h>
#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"},
{}
}
};