diff mbox series

tty: vt: check for atomic context in con_write()

Message ID 9cd9d3eb-418f-44cc-afcf-7283d51252d6@I-love.SAKURA.ne.jp
State New
Headers show
Series tty: vt: check for atomic context in con_write() | expand

Commit Message

Tetsuo Handa Jan. 20, 2024, 10:34 a.m. UTC
syzbot is reporting sleep in atomic context, for gsmld_write() is calling
con_write() with spinlock held and IRQs disabled.

Since include/linux/tty_ldisc.h says that "struct tty_ldisc_ops"->write
(e.g. gsmld_write()) is allowed to sleep and include/linux/tty_driver.h
says that "struct tty_operations"->write (e.g. con_write()) is not
allowed to sleep, we should handle this problem on the con_write() side.

It seems that "Andrew Morton: console locking merge" in 2.4.10-pre11 added
in_interrupt() check to do_con_write()/con_put_char()/con_flush_chars()
in order to handle exceptional caller.

Since include/linux/preempt.h says that in_atomic() cannot know about held
spinlocks in non-preemptible kernels, but gsmld_write() is calling
con_write() with IRQs disabled, we can add irqs_disabled() check to
do_con_write()/con_flush_chars() in order to handle this case. Though,
I'm not sure whether returning the bytes to write is appropriate behavior
when do_con_write() can't work...

Reported-by: syzbot+06fa1063cca8163ea541@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=06fa1063cca8163ea541
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
 drivers/tty/vt/vt.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Comments

Tetsuo Handa Jan. 22, 2024, 2:08 p.m. UTC | #1
On 2024/01/22 15:48, Jiri Slaby wrote:
> On 20. 01. 24, 11:34, Tetsuo Handa wrote:
>> syzbot is reporting sleep in atomic context, for gsmld_write() is calling
>> con_write() with spinlock held and IRQs disabled.
> 
> gsm should never be bound to a console in the first place.
> 
> Noone has sent a patch to deny that yet.
> 
> Follow:
> https://lore.kernel.org/all/49453ebd-b321-4f34-a1a5-d828d8881010@kernel.org/
> 
> And feel free to patch that ;).
> 
> thanks,

OK. Here is a deny-listing based filter using device number of sysfs entry.
(We don't want to compare with the function address of con_write().
Thus, this patch is comparing with device major/minor numbers.)

----------
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 4036566febcb..6f9730dce5aa 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -3630,6 +3630,10 @@ static int gsmld_open(struct tty_struct *tty)
 	if (tty->ops->write == NULL)
 		return -EINVAL;
 
+	/* Can't be attached to virtual consoles. */
+	if (tty->dev && MAJOR(tty->dev->devt) == 4 && MINOR(tty->dev->devt) < 64)
+		return -EINVAL;
+
 	/* Attach our ldisc data */
 	gsm = gsm_alloc_mux();
 	if (gsm == NULL)
----------

Is it possible to use allow-listing based filtering?
(Attaching on /dev/tty (major=5, minor=0) causes current ssh session
to be closed. Unexpectedly loosing connection might be a problem for
fuzz testing...)

----------
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/tty.h>

int main(int argc, char *argv[]) {
        int ldisc = N_GSM0710;

        return ioctl(open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY), TIOCSETD, &ldisc) == 0;
}
----------
Greg Kroah-Hartman Jan. 24, 2024, 1:14 p.m. UTC | #2
On Wed, Jan 24, 2024 at 07:06:04PM +0900, Tetsuo Handa wrote:
> syzbot is reporting sleep in atomic context, for gsmld_write() is calling
> con_write() with spinlock held and IRQs disabled.
> 
> Since n_gsm is designed to be used for serial port, reject attaching to
> virtual consoles and PTY devices, by checking tty's device major/minor
> numbers at gsmld_open().
> 
> Link: https://www.kernel.org/doc/html/v6.7/driver-api/tty/n_gsm.html
> Reported-by: syzbot+06fa1063cca8163ea541@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=06fa1063cca8163ea541
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
>  drivers/tty/n_gsm.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
> 
> diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
> index 4036566febcb..14581483af78 100644
> --- a/drivers/tty/n_gsm.c
> +++ b/drivers/tty/n_gsm.c
> @@ -3623,6 +3623,7 @@ static void gsmld_close(struct tty_struct *tty)
>  static int gsmld_open(struct tty_struct *tty)
>  {
>  	struct gsm_mux *gsm;
> +	int major;
>  
>  	if (!capable(CAP_NET_ADMIN))
>  		return -EPERM;
> @@ -3630,6 +3631,17 @@ static int gsmld_open(struct tty_struct *tty)
>  	if (tty->ops->write == NULL)
>  		return -EINVAL;
>  
> +	major = tty->driver->major;
> +	/* Reject Virtual consoles */
> +	if (major == 4 && tty->driver->minor_start == 1)
> +		return -EINVAL;
> +	/* Reject Unix98 PTY masters/slaves */
> +	if (major >= 128 && major <= 143)
> +		return -EINVAL;
> +	/* Reject BSD PTY masters/slaves */
> +	if (major >= 2 && major <= 3)
> +		return -EINVAL;

That is a lot of hard-coded magic numbers, why aren't these defined
anywhere?

But really, this is only for fuzz testers, why can't they just not ever
bind this to a console?  Why do we have to have these checks in the
kernel to prevent userspace from doing dumb things that no real
userspace will ever do?

thanks,

greg k-h
Tetsuo Handa Feb. 3, 2024, 1:45 p.m. UTC | #3
On 2024/01/24 22:14, Greg Kroah-Hartman wrote:
>> @@ -3630,6 +3631,17 @@ static int gsmld_open(struct tty_struct *tty)
>>  	if (tty->ops->write == NULL)
>>  		return -EINVAL;
>>  
>> +	major = tty->driver->major;
>> +	/* Reject Virtual consoles */
>> +	if (major == 4 && tty->driver->minor_start == 1)
>> +		return -EINVAL;
>> +	/* Reject Unix98 PTY masters/slaves */
>> +	if (major >= 128 && major <= 143)
>> +		return -EINVAL;
>> +	/* Reject BSD PTY masters/slaves */
>> +	if (major >= 2 && major <= 3)
>> +		return -EINVAL;
> 
> That is a lot of hard-coded magic numbers, why aren't these defined
> anywhere?

Well, include/uapi/linux/major.h defines

  #define TTY_MAJOR 4
  #define UNIX98_PTY_MASTER_MAJOR 128
  #define UNIX98_PTY_MAJOR_COUNT 8
  #define UNIX98_PTY_SLAVE_MAJOR (UNIX98_PTY_MASTER_MAJOR+UNIX98_PTY_MAJOR_COUNT)
  #define PTY_MASTER_MAJOR 2
  #define PTY_SLAVE_MAJOR 3

but does not define end of UNIX98_PTY_SLAVE_MAJOR range, and
no file defines start of minor number for virtual consoles.

Does fixing this bug worth updating include/uapi/linux/major.h and adding
include/uapi/linux/minor.h ? Since these numbers won't change, I feel that
a line of comment is sufficient.

> 
> But really, this is only for fuzz testers, why can't they just not ever
> bind this to a console?  Why do we have to have these checks in the
> kernel to prevent userspace from doing dumb things that no real
> userspace will ever do?

Fuzz testing is for testing unexpected arguments. This bug is nothing but
missing validation that should be fixed on the kernel side rather than
trying to tune fuzzers.

> 
> thanks,
> 
> greg k-h
diff mbox series

Patch

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 156efda7c80d..0d3d602ae147 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -2856,7 +2856,7 @@  static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
 	struct vt_notifier_param param;
 	bool rescan;
 
-	if (in_interrupt())
+	if (in_interrupt() || irqs_disabled())
 		return count;
 
 	console_lock();
@@ -3314,7 +3314,7 @@  static void con_flush_chars(struct tty_struct *tty)
 {
 	struct vc_data *vc;
 
-	if (in_interrupt())	/* from flush_to_ldisc */
+	if (in_interrupt() || irqs_disabled()) /* from flush_to_ldisc */
 		return;
 
 	/* if we race with con_close(), vt may be null */