console close race fix resend
PATCH 1/1 console close race fix resend
Marcelo,
[ Resend of previous patch, now against pre8. Note it (a) is a bug fix and (b) was in Alan's tree ]
The attached is a fix originally by Andrew Morton and discovered by the preempt-kernel patch. It is in Alan's tree but was never merged into Linus's.
There is a race between con_close and con_flush_chars. n_tty_receive_buf writes to the tty queue and then flushes it via con_flush_chars. If the console closes in between these operations, con_flush_char barfs.
Please, for all that is righteous, apply.
Robert Love
drivers/char/console.c +7 -1
@@ -2356,8 +2356,14 @@return ;pm_access (pm_con); + +/* +* If we raced with con_close(), `vt' may be null. +* Hence this bandaid. - akpm +*/ acquire_console_sem (); -set_cursor (vt->vc_num); +if (vt) +set_cursor (vt->vc_num);release_console_sem (); }
Quoted 4 lines · On 2001.12.10 17:51 Robert Love wrote:
Marcelo,
[ Resend of previous patch, now against pre8. Note it (a) is a bug fix and (b) was in Alan's tree ]
and (c) appears to still have a race... You should extract the value from the structure inside the lock, otherwise you will still race with con_close (though perhaps a smaller race) but since the call to acquire_console_sem() can sleep, the vt handle you have may be stale.
Quoted 1 line
Please, for all that is righteous, apply.
please fix it better first...
(unless I am mistaken).
-gordo
Quoted 5 lines · Gordon Oliver
and (c) appears to still have a race... You should extract the value from the structure inside the lock, otherwise you will still race with
con_close(though perhaps a smaller race) but since the call toacquire_console_sem()can sleep, the vt handle you have may be stale.
Ehh, I don't think so. Here is the whole patched function:
static void con_flush_chars(struct tty_struct *tty)
{
struct vt_struct *vt = (struct vt_struct *)tty->driver_data;
if (in_interrupt()) /* from flush_to_ldisc */
return;
pm_access(pm_con);
acquire_console_sem();
if (vt)
set_cursor(vt->vc_num);
release_console_sem();
}When we check vt, it isn't stale. vt is a _pointer_ to the data so that first reference against it is guaranteed to grab the correct value. The only possible race is between the if and the set_cursor, but that isn't an issue because we acquired the console semaphore. There is no race here.
Quoted 4 lines
Quoted 1 line
Please, for all that is righteous, apply.
please fix it better first...
(unless I am mistaken).
Thus, unless I am mistaken, it is fine. Please, apply.
Robert Love
Quoted 12 lines · Robert Love
Quoted 5 lines · Gordon Oliver
and (c) appears to still have a race... You should extract the value from the structure inside the lock, otherwise you will still race with
con_close(though perhaps a smaller race) but since the call toacquire_console_sem()can sleep, the vt handle you have may be stale.Ehh, I don't think so. Here is the whole patched function:
static void
con_flush_chars(struct tty_struct *tty)
Quoted 11 lines
{
struct vt_struct *vt = (struct vt_struct *)tty->driver_data;
if (in_interrupt()) /* from flush_to_ldisc */
return;
pm_access(pm_con);
acquire_console_sem();
if (vt)
set_cursor(vt->vc_num);
release_console_sem();
}It could be improved - we really should test tty->driver_data inside lock_kernel(), and after the possible sleep.
How does this look (and how does it test?)
drivers/char/console.c +7 -3
@@ -100,6 +100,7 @@#include <linux/tqueue.h> #include <linux/bootmem.h> #include <linux/pm.h> +#include <linux/smp_lock.h> #include <asm/io.h> #include <asm/system.h>
@@ -2350,15 +2351,18 @@ static void con_start(struct tty_structstatic void con_flush_chars (struct tty_struct *tty) { -struct vt_struct *vt = (struct vt_struct *)tty->driver_data; +struct vt_struct *vt;if (in_interrupt ())/* from flush_to_ldisc */ return ; -pm_access (pm_con); +lock_kernel ();/* versus con_close() */ acquire_console_sem (); -set_cursor (vt->vc_num); + vt = (struct vt_struct *)tty->driver_data; +if (vt) +set_cursor (vt->vc_num);release_console_sem (); +unlock_kernel (); }/*
OK, I talked to Andrew off-list and we agree his fix is more correct. It should be applied. I tested it (I have a consistent method of reproducing the race under preempt-kernel) and it works.
I rediffed it against pre8 because I had trouble applying his, oddly. Ignore previous patch. Marcelo, please apply. Seriously, this time.
Robert Love
drivers/char/console.c +11 -2
@@ -100,6 +100,7 @@#include <linux/tqueue.h> #include <linux/bootmem.h> #include <linux/pm.h> +#include <linux/smp_lock.h> #include <asm/io.h> #include <asm/system.h>
@@ -2348,17 +2349,25 @@set_leds (); } +/* +* we can race here against con_close, so we grab the bkl +* and check the pointer before calling set_cursor +*/ static void con_flush_chars (struct tty_struct *tty) { -struct vt_struct *vt = (struct vt_struct *)tty->driver_data; +struct vt_struct *vt;if (in_interrupt ())/* from flush_to_ldisc */ return ;pm_access (pm_con); +lock_kernel ();acquire_console_sem (); -set_cursor (vt->vc_num); + vt = (struct vt_struct *)tty->driver_data; +if (vt) +set_cursor (vt->vc_num);release_console_sem (); +unlock_kernel (); }/*
Quoted 14 lines · On 2001.12.10 22:05 Robert Love wrote:
Ehh, I don't think so. Here is the whole patched function:
static void
con_flush_chars(struct tty_struct *tty){ struct vt_struct *vt = (struct vt_struct *)tty->driver_data; if (in_interrupt()) /* from flush_to_ldisc */ return; pm_access(pm_con); acquire_console_sem(); if (vt) set_cursor(vt->vc_num); release_console_sem(); }
Quoted 5 lines
When we check vt, it isn't stale. vt is a _pointer_ to the data so that first reference against it is guaranteed to grab the correct value. The only possible race is between the if and the
set_cursor, but that isn't an issue because we acquired the console semaphore. There is no race here.
I like the patch that Andrew Morton sent in reply to this better. Note that in the event that the above code does the following sequence it will cause a stale pointer to be used:
con_flush_chars con_close vt = <> tty->driver_data = NULL acquire_console_sem() set_cursor() release_console_sem()
Now it _might_ be ok to act on a stale vt pointer, but it sure feels like thin ice. I'm not sure that there is any danger of the vt data being modified in a way that would break this, but since the tty no longer has a reference it is bad practice.
What the earlier patch did is created some very subtle semantics for a small window of a race. It fixed the blaring bug (the OOPS) but left a possible one that would be harder to find....
-gordo