Commit message: speakup: Do not let the line discipline be used several times

Speakup has only one speakup_tty variable to store the tty it is managing. This
makes sense since its codebase currently assumes that there is only one user who
controls the screen reading.

That however means that we have to forbid using the line discipline several
times, otherwise the second closure would try to free a NULL ldisc_data, leading to

general protection fault: 0000 [#1] SMP KASAN PTI
RIP: 0010:spk_ttyio_ldisc_close+0x2c/0x60
Call Trace:
 tty_ldisc_release+0xa2/0x340
 tty_release_struct+0x17/0xd0
 tty_release+0x9d9/0xcc0
 __fput+0x231/0x740
 task_work_run+0x12c/0x1a0
 do_exit+0x9b5/0x2230
 ? release_task+0x1240/0x1240
 ? __do_page_fault+0x562/0xa30
 do_group_exit+0xd5/0x2a0
 __x64_sys_exit_group+0x35/0x40
 do_syscall_64+0x89/0x2b0
 ? page_fault+0x8/0x30
 entry_SYSCALL_64_after_hwframe+0x44/0xa9

Cc: stable@vger.kernel.org
Reported-by: 秦世松 <qinshisong1205@gmail.com>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Tested-by: Shisong Qin <qinshisong1205@gmail.com>
Link: https://lore.kernel.org/r/20201110183541.fzgnlwhjpgqzjeth@function
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
CVE: CVE-2020-28941
CWE: CWE-362
Vulnerability description: An issue was discovered in drivers/accessibility/speakup/spk_ttyio.c in the Linux kernel through 5.9.9. Local attackers on systems with the speakup driver could cause a local denial of service attack, aka CID-d41227544427. This occurs because of an invalid free when the line discipline is used more than once.

Project: linux
File: spk_ttyio.c

static int spk_ttyio_out(struct spk_synth *in_synth, const char ch)
{
	mutex_lock(&speakup_tty_mutex);
	if (in_synth->alive && speakup_tty && speakup_tty->ops->write) {
		int ret = speakup_tty->ops->write(speakup_tty, &ch, 1);

		mutex_unlock(&speakup_tty_mutex);
		if (ret == 0)
			/* No room */
			return 0;
		if (ret < 0) {
			pr_warn("%s: I/O error, deactivating speakup\n",
				in_synth->long_name);
			/* No synth any more, so nobody will restart TTYs,
			 * and we thus need to do it ourselves.  Now that there
			 * is no synth we can let application flood anyway
			 */
			in_synth->alive = 0;
			speakup_start_ttys();
			return 0;
		}
		return 1;
	}

	mutex_unlock(&speakup_tty_mutex);
	return 0;
}

Project: linux
File: spk_ttyio.c

static unsigned char spk_ttyio_in(void)
{
	return ttyio_in(SPK_SYNTH_TIMEOUT);
}

Project: linux
File: spk_ttyio.c

static int get_dev_to_use(struct spk_synth *synth, dev_t *dev_no)
{
	/* use ser only when dev is not specified */
	if (strcmp(synth->dev_name, SYNTH_DEFAULT_DEV) ||
	    synth->ser == SYNTH_DEFAULT_SER)
		return tty_dev_name_to_number(synth->dev_name, dev_no);

	return ser_to_dev(synth->ser, dev_no);
}

Project: linux
File: spk_ttyio.c

int spk_ttyio_synth_probe(struct spk_synth *synth)
{
	int rv = spk_ttyio_initialise_ldisc(synth);

	if (rv)
		return rv;

	synth->alive = 1;
	spk_ttyio_synth = synth;

	return 0;
}

Project: linux
File: spk_ttyio.c

static unsigned char spk_ttyio_in_nowait(void)
{
	u8 rv = ttyio_in(0);

	return (rv == 0xff) ? 0 : rv;
}

Project: linux
File: spk_ttyio.c

static int check_tty(struct tty_struct *tty)
{
	if (!tty) {
		pr_warn("%s: I/O error, deactivating speakup\n",
			spk_ttyio_synth->long_name);
		/* No synth any more, so nobody will restart TTYs, and we thus
		 * need to do it ourselves.  Now that there is no synth we can
		 * let application flood anyway
		 */
		spk_ttyio_synth->alive = 0;
		speakup_start_ttys();
		return 1;
	}

	return 0;
}

Project: linux
File: spk_ttyio.c

static int spk_ttyio_receive_buf2(struct tty_struct *tty,
				  const unsigned char *cp, char *fp, int count)
{
	struct spk_ldisc_data *ldisc_data = tty->disc_data;

	if (spk_ttyio_synth->read_buff_add) {
		int i;

		for (i = 0; i < count; i++)
			spk_ttyio_synth->read_buff_add(cp[i]);

		return count;
	}

	if (!ldisc_data->buf_free)
		/* ttyio_in will tty_schedule_flip */
		return 0;

	/* Make sure the consumer has read buf before we have seen
	 * buf_free == true and overwrite buf
	 */
	mb();

	ldisc_data->buf = cp[0];
	ldisc_data->buf_free = false;
	complete(&ldisc_data->completion);

	return 1;
}

Project: linux
File: spk_ttyio.c

static int spk_ttyio_out_unicode(struct spk_synth *in_synth, u16 ch)
{
	int ret;

	if (ch < 0x80) {
		ret = spk_ttyio_out(in_synth, ch);
	} else if (ch < 0x800) {
		ret  = spk_ttyio_out(in_synth, 0xc0 | (ch >> 6));
		ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));
	} else {
		ret  = spk_ttyio_out(in_synth, 0xe0 | (ch >> 12));
		ret &= spk_ttyio_out(in_synth, 0x80 | ((ch >> 6) & 0x3f));
		ret &= spk_ttyio_out(in_synth, 0x80 | (ch & 0x3f));
	}
	return ret;
}

Project: linux
File: spk_ttyio.c

static void spk_ttyio_ldisc_close(struct tty_struct *tty)
{
	mutex_lock(&speakup_tty_mutex);
	kfree(speakup_tty->disc_data);
	speakup_tty = NULL;
	mutex_unlock(&speakup_tty_mutex);
}

Project: linux
File: spk_ttyio.c

static int ser_to_dev(int ser, dev_t *dev_no)
{
	if (ser < 0 || ser > (255 - 64)) {
		pr_err("speakup: Invalid ser param. Must be between 0 and 191 inclusive.\n");
		return -EINVAL;
	}

	*dev_no = MKDEV(4, (64 + ser));
	return 0;
}

Project: linux
File: spk_ttyio.c

const char *spk_ttyio_synth_immediate(struct spk_synth *synth, const char *buff)
{
	u_char ch;

	while ((ch = *buff)) {
		if (ch == '\n')
			ch = synth->procspeech;
		if (tty_write_room(speakup_tty) < 1 ||
		    !synth->io_ops->synth_out(synth, ch))
			return buff;
		buff++;
	}
	return NULL;
}

Project: linux
File: spk_ttyio.c

static void spk_ttyio_tiocmset(unsigned int set, unsigned int clear)
{
	mutex_lock(&speakup_tty_mutex);
	if (check_tty(speakup_tty)) {
		mutex_unlock(&speakup_tty_mutex);
		return;
	}

	if (speakup_tty->ops->tiocmset)
		speakup_tty->ops->tiocmset(speakup_tty, set, clear);
	mutex_unlock(&speakup_tty_mutex);
}

Project: linux
File: spk_ttyio.c

static void spk_ttyio_send_xchar(char ch)
{
	mutex_lock(&speakup_tty_mutex);
	if (check_tty(speakup_tty)) {
		mutex_unlock(&speakup_tty_mutex);
		return;
	}

	if (speakup_tty->ops->send_xchar)
		speakup_tty->ops->send_xchar(speakup_tty, ch);
	mutex_unlock(&speakup_tty_mutex);
}

Project: linux
File: spk_ttyio.c

static inline void get_termios(struct tty_struct *tty,
			       struct ktermios *out_termios)
{
	down_read(&tty->termios_rwsem);
	*out_termios = tty->termios;
	up_read(&tty->termios_rwsem);
}

Project: linux
File: spk_ttyio.c

static int spk_ttyio_wait_for_xmitr(struct spk_synth *in_synth)
{
	return 1;
}

Project: linux
File: spk_ttyio.c

static void spk_ttyio_flush_buffer(void)
{
	mutex_lock(&speakup_tty_mutex);
	if (check_tty(speakup_tty)) {
		mutex_unlock(&speakup_tty_mutex);
		return;
	}

	if (speakup_tty->ops->flush_buffer)
		speakup_tty->ops->flush_buffer(speakup_tty);

	mutex_unlock(&speakup_tty_mutex);
}

Project: linux
File: spk_ttyio.c

void spk_ttyio_release(void)
{
	if (!speakup_tty)
		return;

	tty_lock(speakup_tty);

	if (speakup_tty->ops->close)
		speakup_tty->ops->close(speakup_tty, NULL);

	tty_ldisc_flush(speakup_tty);
	tty_unlock(speakup_tty);
	tty_kclose(speakup_tty);
}

Project: linux
File: spk_ttyio.c

static unsigned char ttyio_in(int timeout)
{
	struct spk_ldisc_data *ldisc_data = speakup_tty->disc_data;
	char rv;

	if (wait_for_completion_timeout(&ldisc_data->completion,
					usecs_to_jiffies(timeout)) == 0) {
		if (timeout)
			pr_warn("spk_ttyio: timeout (%d)  while waiting for input\n",
				timeout);
		return 0xff;
	}

	rv = ldisc_data->buf;
	/* Make sure we have read buf before we set buf_free to let
	 * the producer overwrite it
	 */
	mb();
	ldisc_data->buf_free = true;
	/* Let TTY push more characters */
	tty_schedule_flip(speakup_tty->port);

	return rv;
}

Project: linux
File: spk_ttyio.c

void spk_ttyio_unregister_ldisc(void)
{
	if (tty_unregister_ldisc(N_SPEAKUP))
		pr_warn("speakup: Couldn't unregister ldisc\n");
}

Project: linux
File: spk_ttyio.c

void spk_ttyio_register_ldisc(void)
{
	if (tty_register_ldisc(N_SPEAKUP, &spk_ttyio_ldisc_ops))
		pr_warn("speakup: Error registering line discipline. Most synths won't work.\n");
}