Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

media: dvb-core: Fix use-after-free on race condition at dvb_frontend

If the device node of dvb_frontend is open() and the device is
disconnected, many kinds of UAFs may occur when calling close()
on the device node.

The root cause of this is that wake_up() for dvbdev->wait_queue
is implemented in the dvb_frontend_release() function, but
wait_event() is not implemented in the dvb_frontend_stop() function.

So, implement wait_event() function in dvb_frontend_stop() and
add 'remove_mutex' which prevents race condition for 'fe->exit'.

[mchehab: fix a couple of checkpatch warnings and some mistakes at the error handling logic]

Link: https://lore.kernel.org/linux-media/20221117045925.14297-2-imv4bel@gmail.com
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>

authored by

Hyunwoo Kim and committed by
Mauro Carvalho Chehab
6769a0b7 ae11c0ef

+49 -10
+44 -9
drivers/media/dvb-core/dvb_frontend.c
··· 809 809 810 810 dev_dbg(fe->dvb->device, "%s:\n", __func__); 811 811 812 + mutex_lock(&fe->remove_mutex); 813 + 812 814 if (fe->exit != DVB_FE_DEVICE_REMOVED) 813 815 fe->exit = DVB_FE_NORMAL_EXIT; 814 816 mb(); 815 817 816 - if (!fepriv->thread) 818 + if (!fepriv->thread) { 819 + mutex_unlock(&fe->remove_mutex); 817 820 return; 821 + } 818 822 819 823 kthread_stop(fepriv->thread); 824 + 825 + mutex_unlock(&fe->remove_mutex); 826 + 827 + if (fepriv->dvbdev->users < -1) { 828 + wait_event(fepriv->dvbdev->wait_queue, 829 + fepriv->dvbdev->users == -1); 830 + } 820 831 821 832 sema_init(&fepriv->sem, 1); 822 833 fepriv->state = FESTATE_IDLE; ··· 2772 2761 struct dvb_adapter *adapter = fe->dvb; 2773 2762 int ret; 2774 2763 2764 + mutex_lock(&fe->remove_mutex); 2765 + 2775 2766 dev_dbg(fe->dvb->device, "%s:\n", __func__); 2776 - if (fe->exit == DVB_FE_DEVICE_REMOVED) 2777 - return -ENODEV; 2767 + if (fe->exit == DVB_FE_DEVICE_REMOVED) { 2768 + ret = -ENODEV; 2769 + goto err_remove_mutex; 2770 + } 2778 2771 2779 2772 if (adapter->mfe_shared == 2) { 2780 2773 mutex_lock(&adapter->mfe_lock); ··· 2786 2771 if (adapter->mfe_dvbdev && 2787 2772 !adapter->mfe_dvbdev->writers) { 2788 2773 mutex_unlock(&adapter->mfe_lock); 2789 - return -EBUSY; 2774 + ret = -EBUSY; 2775 + goto err_remove_mutex; 2790 2776 } 2791 2777 adapter->mfe_dvbdev = dvbdev; 2792 2778 } ··· 2810 2794 while (mferetry-- && (mfedev->users != -1 || 2811 2795 mfepriv->thread)) { 2812 2796 if (msleep_interruptible(500)) { 2813 - if (signal_pending(current)) 2814 - return -EINTR; 2797 + if (signal_pending(current)) { 2798 + ret = -EINTR; 2799 + goto err_remove_mutex; 2800 + } 2815 2801 } 2816 2802 } 2817 2803 ··· 2825 2807 if (mfedev->users != -1 || 2826 2808 mfepriv->thread) { 2827 2809 mutex_unlock(&adapter->mfe_lock); 2828 - return -EBUSY; 2810 + ret = -EBUSY; 2811 + goto err_remove_mutex; 2829 2812 } 2830 2813 adapter->mfe_dvbdev = dvbdev; 2831 2814 } ··· 2885 2866 2886 2867 if (adapter->mfe_shared) 2887 2868 mutex_unlock(&adapter->mfe_lock); 2869 + 2870 + mutex_unlock(&fe->remove_mutex); 2888 2871 return ret; 2889 2872 2890 2873 err3: ··· 2908 2887 err0: 2909 2888 if (adapter->mfe_shared) 2910 2889 mutex_unlock(&adapter->mfe_lock); 2890 + 2891 + err_remove_mutex: 2892 + mutex_unlock(&fe->remove_mutex); 2911 2893 return ret; 2912 2894 } 2913 2895 ··· 2920 2896 struct dvb_frontend *fe = dvbdev->priv; 2921 2897 struct dvb_frontend_private *fepriv = fe->frontend_priv; 2922 2898 int ret; 2899 + 2900 + mutex_lock(&fe->remove_mutex); 2923 2901 2924 2902 dev_dbg(fe->dvb->device, "%s:\n", __func__); 2925 2903 ··· 2944 2918 } 2945 2919 mutex_unlock(&fe->dvb->mdev_lock); 2946 2920 #endif 2947 - if (fe->exit != DVB_FE_NO_EXIT) 2948 - wake_up(&dvbdev->wait_queue); 2949 2921 if (fe->ops.ts_bus_ctrl) 2950 2922 fe->ops.ts_bus_ctrl(fe, 0); 2923 + 2924 + if (fe->exit != DVB_FE_NO_EXIT) { 2925 + mutex_unlock(&fe->remove_mutex); 2926 + wake_up(&dvbdev->wait_queue); 2927 + } else { 2928 + mutex_unlock(&fe->remove_mutex); 2929 + } 2930 + 2931 + } else { 2932 + mutex_unlock(&fe->remove_mutex); 2951 2933 } 2952 2934 2953 2935 dvb_frontend_put(fe); ··· 3056 3022 fepriv = fe->frontend_priv; 3057 3023 3058 3024 kref_init(&fe->refcount); 3025 + mutex_init(&fe->remove_mutex); 3059 3026 3060 3027 /* 3061 3028 * After initialization, there need to be two references: one
+5 -1
include/media/dvb_frontend.h
··· 686 686 * @id: Frontend ID 687 687 * @exit: Used to inform the DVB core that the frontend 688 688 * thread should exit (usually, means that the hardware 689 - * got disconnected. 689 + * got disconnected). 690 + * @remove_mutex: mutex that avoids a race condition between a callback 691 + * called when the hardware is disconnected and the 692 + * file_operations of dvb_frontend. 690 693 */ 691 694 692 695 struct dvb_frontend { ··· 707 704 int (*callback)(void *adapter_priv, int component, int cmd, int arg); 708 705 int id; 709 706 unsigned int exit; 707 + struct mutex remove_mutex; 710 708 }; 711 709 712 710 /**