diff options
author | Chao Bi <chao.bi@intel.com> | 2013-10-17 03:08:27 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-11-25 11:52:53 -0500 |
commit | c284ee2cf12b55fa8496b2d098bf0938688f1c1c (patch) | |
tree | 5f553255c2b1dd33f6dcab7434ea2d7673bbb71b | |
parent | f3014127ada32f51ce0baf0bee4c6324a601ef59 (diff) |
n_gsm: race between ld close and gsmtty open
ttyA has ld associated to n_gsm, when ttyA is closing, it triggers
to release gsmttyB's ld data dlci[B], then race would happen if gsmttyB
is opening in parallel.
Here are race cases we found recently in test:
CASE #1
====================================================================
releasing dlci[B] race with gsmtty_install(gsmttyB), then panic
in gsmtty_open(gsmttyB), as below:
tty_release(ttyA) tty_open(gsmttyB)
| |
----- gsmtty_install(gsmttyB)
| |
----- gsm_dlci_alloc(gsmttyB) => alloc dlci[B]
tty_ldisc_release(ttyA) -----
| |
gsm_dlci_release(dlci[B]) -----
| |
gsm_dlci_free(dlci[B]) -----
| |
----- gsmtty_open(gsmttyB)
gsmtty_open()
{
struct gsm_dlci *dlci = tty->driver_data; => here it uses dlci[B]
...
}
In gsmtty_open(gsmttyA), it uses dlci[B] which was release, so hit a panic.
=====================================================================
CASE #2
=====================================================================
releasing dlci[0] race with gsmtty_install(gsmttyB), then panic
in gsmtty_open(), as below:
tty_release(ttyA) tty_open(gsmttyB)
| |
----- gsmtty_install(gsmttyB)
| |
----- gsm_dlci_alloc(gsmttyB) => alloc dlci[B]
| |
----- gsmtty_open(gsmttyB) fail
| |
----- tty_release(gsmttyB)
| |
----- gsmtty_close(gsmttyB)
| |
----- gsmtty_detach_dlci(dlci[B])
| |
----- dlci_put(dlci[B])
| |
tty_ldisc_release(ttyA) -----
| |
gsm_dlci_release(dlci[0]) -----
| |
gsm_dlci_free(dlci[0]) -----
| |
----- dlci_put(dlci[0])
In gsmtty_detach_dlci(dlci[B]), it tries to use dlci[0] which was released,
then hit panic.
=====================================================================
IMHO, n_gsm tty operations would refer released ldisc, as long as
gsm_dlci_release() has chance to release ldisc data when some gsmtty operations
are not completed..
This patch is try to avoid it by:
1) in n_gsm driver, use a global gsm spin lock to avoid gsm_dlci_release() run in
parallel with gsmtty_install();
2) Increase dlci's ref count in gsmtty_install() instead of in gsmtty_open(), the
purpose is to prevent gsm_dlci_release() releasing dlci after gsmtty_install()
allocats dlci but before gsmtty_open increases dlci's ref count;
3) Decrease dlci's ref count in gsmtty_remove(), which is a tty framework api, and
this is the opposite process of step 2).
Signed-off-by: Chao Bi <chao.bi@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/tty/n_gsm.c | 37 |
1 files changed, 27 insertions, 10 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index c0f76da55304..069bfd654485 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c | |||
@@ -2054,9 +2054,11 @@ void gsm_cleanup_mux(struct gsm_mux *gsm) | |||
2054 | dlci->state == DLCI_CLOSED); | 2054 | dlci->state == DLCI_CLOSED); |
2055 | } | 2055 | } |
2056 | /* Free up any link layer users */ | 2056 | /* Free up any link layer users */ |
2057 | spin_lock(&gsm->lock); | ||
2057 | for (i = 0; i < NUM_DLCI; i++) | 2058 | for (i = 0; i < NUM_DLCI; i++) |
2058 | if (gsm->dlci[i]) | 2059 | if (gsm->dlci[i]) |
2059 | gsm_dlci_release(gsm->dlci[i]); | 2060 | gsm_dlci_release(gsm->dlci[i]); |
2061 | spin_unlock(&gsm->lock); | ||
2060 | /* Now wipe the queues */ | 2062 | /* Now wipe the queues */ |
2061 | list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list) | 2063 | list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list) |
2062 | kfree(txq); | 2064 | kfree(txq); |
@@ -2909,23 +2911,33 @@ static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty) | |||
2909 | This is ok from a locking | 2911 | This is ok from a locking |
2910 | perspective as we don't have to worry about this | 2912 | perspective as we don't have to worry about this |
2911 | if DLCI0 is lost */ | 2913 | if DLCI0 is lost */ |
2912 | if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) | 2914 | spin_lock(&gsm->lock); |
2915 | if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) { | ||
2916 | spin_unlock(&gsm->lock); | ||
2913 | return -EL2NSYNC; | 2917 | return -EL2NSYNC; |
2918 | } | ||
2914 | dlci = gsm->dlci[line]; | 2919 | dlci = gsm->dlci[line]; |
2915 | if (dlci == NULL) { | 2920 | if (dlci == NULL) { |
2916 | alloc = true; | 2921 | alloc = true; |
2917 | dlci = gsm_dlci_alloc(gsm, line); | 2922 | dlci = gsm_dlci_alloc(gsm, line); |
2918 | } | 2923 | } |
2919 | if (dlci == NULL) | 2924 | if (dlci == NULL) { |
2925 | spin_unlock(&gsm->lock); | ||
2920 | return -ENOMEM; | 2926 | return -ENOMEM; |
2927 | } | ||
2921 | ret = tty_port_install(&dlci->port, driver, tty); | 2928 | ret = tty_port_install(&dlci->port, driver, tty); |
2922 | if (ret) { | 2929 | if (ret) { |
2923 | if (alloc) | 2930 | if (alloc) |
2924 | dlci_put(dlci); | 2931 | dlci_put(dlci); |
2932 | spin_unlock(&gsm->lock); | ||
2925 | return ret; | 2933 | return ret; |
2926 | } | 2934 | } |
2927 | 2935 | ||
2936 | dlci_get(dlci); | ||
2937 | dlci_get(gsm->dlci[0]); | ||
2938 | mux_get(gsm); | ||
2928 | tty->driver_data = dlci; | 2939 | tty->driver_data = dlci; |
2940 | spin_unlock(&gsm->lock); | ||
2929 | 2941 | ||
2930 | return 0; | 2942 | return 0; |
2931 | } | 2943 | } |
@@ -2936,9 +2948,6 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) | |||
2936 | struct tty_port *port = &dlci->port; | 2948 | struct tty_port *port = &dlci->port; |
2937 | 2949 | ||
2938 | port->count++; | 2950 | port->count++; |
2939 | dlci_get(dlci); | ||
2940 | dlci_get(dlci->gsm->dlci[0]); | ||
2941 | mux_get(dlci->gsm); | ||
2942 | tty_port_tty_set(port, tty); | 2951 | tty_port_tty_set(port, tty); |
2943 | 2952 | ||
2944 | dlci->modem_rx = 0; | 2953 | dlci->modem_rx = 0; |
@@ -2965,7 +2974,7 @@ static void gsmtty_close(struct tty_struct *tty, struct file *filp) | |||
2965 | mutex_unlock(&dlci->mutex); | 2974 | mutex_unlock(&dlci->mutex); |
2966 | gsm = dlci->gsm; | 2975 | gsm = dlci->gsm; |
2967 | if (tty_port_close_start(&dlci->port, tty, filp) == 0) | 2976 | if (tty_port_close_start(&dlci->port, tty, filp) == 0) |
2968 | goto out; | 2977 | return; |
2969 | gsm_dlci_begin_close(dlci); | 2978 | gsm_dlci_begin_close(dlci); |
2970 | if (test_bit(ASYNCB_INITIALIZED, &dlci->port.flags)) { | 2979 | if (test_bit(ASYNCB_INITIALIZED, &dlci->port.flags)) { |
2971 | if (C_HUPCL(tty)) | 2980 | if (C_HUPCL(tty)) |
@@ -2973,10 +2982,7 @@ static void gsmtty_close(struct tty_struct *tty, struct file *filp) | |||
2973 | } | 2982 | } |
2974 | tty_port_close_end(&dlci->port, tty); | 2983 | tty_port_close_end(&dlci->port, tty); |
2975 | tty_port_tty_set(&dlci->port, NULL); | 2984 | tty_port_tty_set(&dlci->port, NULL); |
2976 | out: | 2985 | return; |
2977 | dlci_put(dlci); | ||
2978 | dlci_put(gsm->dlci[0]); | ||
2979 | mux_put(gsm); | ||
2980 | } | 2986 | } |
2981 | 2987 | ||
2982 | static void gsmtty_hangup(struct tty_struct *tty) | 2988 | static void gsmtty_hangup(struct tty_struct *tty) |
@@ -3153,6 +3159,16 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) | |||
3153 | return gsmtty_modem_update(dlci, encode); | 3159 | return gsmtty_modem_update(dlci, encode); |
3154 | } | 3160 | } |
3155 | 3161 | ||
3162 | static void gsmtty_remove(struct tty_driver *driver, struct tty_struct *tty) | ||
3163 | { | ||
3164 | struct gsm_dlci *dlci = tty->driver_data; | ||
3165 | struct gsm_mux *gsm = dlci->gsm; | ||
3166 | |||
3167 | dlci_put(dlci); | ||
3168 | dlci_put(gsm->dlci[0]); | ||
3169 | mux_put(gsm); | ||
3170 | driver->ttys[tty->index] = NULL; | ||
3171 | } | ||
3156 | 3172 | ||
3157 | /* Virtual ttys for the demux */ | 3173 | /* Virtual ttys for the demux */ |
3158 | static const struct tty_operations gsmtty_ops = { | 3174 | static const struct tty_operations gsmtty_ops = { |
@@ -3172,6 +3188,7 @@ static const struct tty_operations gsmtty_ops = { | |||
3172 | .tiocmget = gsmtty_tiocmget, | 3188 | .tiocmget = gsmtty_tiocmget, |
3173 | .tiocmset = gsmtty_tiocmset, | 3189 | .tiocmset = gsmtty_tiocmset, |
3174 | .break_ctl = gsmtty_break_ctl, | 3190 | .break_ctl = gsmtty_break_ctl, |
3191 | .remove = gsmtty_remove, | ||
3175 | }; | 3192 | }; |
3176 | 3193 | ||
3177 | 3194 | ||