diff options
author | Herton R. Krzesinski <herton@redhat.com> | 2016-01-14 14:56:58 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-02-07 02:45:46 -0500 |
commit | 1f55c718c290616889c04946864a13ef30f64929 (patch) | |
tree | d578b6dee5f2eb8ebe8f73ccba2b79a31252c52c | |
parent | 2831c89f42dcde440cfdccb9fee9f42d54bbc1ef (diff) |
pty: make sure super_block is still valid in final /dev/tty close
Considering current pty code and multiple devpts instances, it's possible
to umount a devpts file system while a program still has /dev/tty opened
pointing to a previosuly closed pty pair in that instance. In the case all
ptmx and pts/N files are closed, umount can be done. If the program closes
/dev/tty after umount is done, devpts_kill_index will use now an invalid
super_block, which was already destroyed in the umount operation after
running ->kill_sb. This is another "use after free" type of issue, but now
related to the allocated super_block instance.
To avoid the problem (warning at ida_remove and potential crashes) for
this specific case, I added two functions in devpts which grabs additional
references to the super_block, which pty code now uses so it makes sure
the super block structure is still valid until pty shutdown is done.
I also moved the additional inode references to the same functions, which
also covered similar case with inode being freed before /dev/tty final
close/shutdown.
Signed-off-by: Herton R. Krzesinski <herton@redhat.com>
Cc: stable@vger.kernel.org # 2.6.29+
Reviewed-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/tty/pty.c | 9 | ||||
-rw-r--r-- | fs/devpts/inode.c | 20 | ||||
-rw-r--r-- | include/linux/devpts_fs.h | 4 |
3 files changed, 30 insertions, 3 deletions
diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 3b5cde833ee5..2348fa613707 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c | |||
@@ -688,7 +688,7 @@ static void pty_unix98_shutdown(struct tty_struct *tty) | |||
688 | else | 688 | else |
689 | ptmx_inode = tty->link->driver_data; | 689 | ptmx_inode = tty->link->driver_data; |
690 | devpts_kill_index(ptmx_inode, tty->index); | 690 | devpts_kill_index(ptmx_inode, tty->index); |
691 | iput(ptmx_inode); /* drop reference we acquired at ptmx_open */ | 691 | devpts_del_ref(ptmx_inode); |
692 | } | 692 | } |
693 | 693 | ||
694 | static const struct tty_operations ptm_unix98_ops = { | 694 | static const struct tty_operations ptm_unix98_ops = { |
@@ -785,9 +785,12 @@ static int ptmx_open(struct inode *inode, struct file *filp) | |||
785 | * still have /dev/tty opened pointing to the master/slave pair (ptmx | 785 | * still have /dev/tty opened pointing to the master/slave pair (ptmx |
786 | * is closed/released before /dev/tty), we must make sure that the inode | 786 | * is closed/released before /dev/tty), we must make sure that the inode |
787 | * is still valid when we call the final pty_unix98_shutdown, thus we | 787 | * is still valid when we call the final pty_unix98_shutdown, thus we |
788 | * hold an additional reference to the ptmx inode | 788 | * hold an additional reference to the ptmx inode. For the same /dev/tty |
789 | * last close case, we also need to make sure the super_block isn't | ||
790 | * destroyed (devpts instance unmounted), before /dev/tty is closed and | ||
791 | * on its release devpts_kill_index is called. | ||
789 | */ | 792 | */ |
790 | ihold(inode); | 793 | devpts_add_ref(inode); |
791 | 794 | ||
792 | tty_add_file(tty, filp); | 795 | tty_add_file(tty, filp); |
793 | 796 | ||
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 1f107fd51328..655f21f99160 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c | |||
@@ -575,6 +575,26 @@ void devpts_kill_index(struct inode *ptmx_inode, int idx) | |||
575 | mutex_unlock(&allocated_ptys_lock); | 575 | mutex_unlock(&allocated_ptys_lock); |
576 | } | 576 | } |
577 | 577 | ||
578 | /* | ||
579 | * pty code needs to hold extra references in case of last /dev/tty close | ||
580 | */ | ||
581 | |||
582 | void devpts_add_ref(struct inode *ptmx_inode) | ||
583 | { | ||
584 | struct super_block *sb = pts_sb_from_inode(ptmx_inode); | ||
585 | |||
586 | atomic_inc(&sb->s_active); | ||
587 | ihold(ptmx_inode); | ||
588 | } | ||
589 | |||
590 | void devpts_del_ref(struct inode *ptmx_inode) | ||
591 | { | ||
592 | struct super_block *sb = pts_sb_from_inode(ptmx_inode); | ||
593 | |||
594 | iput(ptmx_inode); | ||
595 | deactivate_super(sb); | ||
596 | } | ||
597 | |||
578 | /** | 598 | /** |
579 | * devpts_pty_new -- create a new inode in /dev/pts/ | 599 | * devpts_pty_new -- create a new inode in /dev/pts/ |
580 | * @ptmx_inode: inode of the master | 600 | * @ptmx_inode: inode of the master |
diff --git a/include/linux/devpts_fs.h b/include/linux/devpts_fs.h index 251a2090a554..e0ee0b3000b2 100644 --- a/include/linux/devpts_fs.h +++ b/include/linux/devpts_fs.h | |||
@@ -19,6 +19,8 @@ | |||
19 | 19 | ||
20 | int devpts_new_index(struct inode *ptmx_inode); | 20 | int devpts_new_index(struct inode *ptmx_inode); |
21 | void devpts_kill_index(struct inode *ptmx_inode, int idx); | 21 | void devpts_kill_index(struct inode *ptmx_inode, int idx); |
22 | void devpts_add_ref(struct inode *ptmx_inode); | ||
23 | void devpts_del_ref(struct inode *ptmx_inode); | ||
22 | /* mknod in devpts */ | 24 | /* mknod in devpts */ |
23 | struct inode *devpts_pty_new(struct inode *ptmx_inode, dev_t device, int index, | 25 | struct inode *devpts_pty_new(struct inode *ptmx_inode, dev_t device, int index, |
24 | void *priv); | 26 | void *priv); |
@@ -32,6 +34,8 @@ void devpts_pty_kill(struct inode *inode); | |||
32 | /* Dummy stubs in the no-pty case */ | 34 | /* Dummy stubs in the no-pty case */ |
33 | static inline int devpts_new_index(struct inode *ptmx_inode) { return -EINVAL; } | 35 | static inline int devpts_new_index(struct inode *ptmx_inode) { return -EINVAL; } |
34 | static inline void devpts_kill_index(struct inode *ptmx_inode, int idx) { } | 36 | static inline void devpts_kill_index(struct inode *ptmx_inode, int idx) { } |
37 | static inline void devpts_add_ref(struct inode *ptmx_inode) { } | ||
38 | static inline void devpts_del_ref(struct inode *ptmx_inode) { } | ||
35 | static inline struct inode *devpts_pty_new(struct inode *ptmx_inode, | 39 | static inline struct inode *devpts_pty_new(struct inode *ptmx_inode, |
36 | dev_t device, int index, void *priv) | 40 | dev_t device, int index, void *priv) |
37 | { | 41 | { |