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

ALSA: hda: Fix potential deadlock at codec unbinding

When a codec is unbound dynamically via sysfs while its stream is in
use, we may face a potential deadlock at the proc remove or a UAF.
This happens since the hda_pcm is managed by a linked list, as it
handles the hda_pcm object release via kref.

When a PCM is opened at the unbinding time, the release of hda_pcm
gets delayed and it ends up with the close of the PCM stream releasing
the associated hda_pcm object of its own. The hda_pcm destructor
contains the PCM device release that includes the removal of procfs
entries. And, this removal has the sync of the close of all in-use
files -- which would never finish because it's called from the PCM
file descriptor itself, i.e. it's trying to shoot its foot.

For addressing the deadlock above, this patch changes the way to
manage and release the hda_pcm object. The kref of hda_pcm is
dropped, and instead a simple refcount is introduced in hda_codec for
keeping the track of the active PCM streams, and at each PCM open and
close, this refcount is adjusted accordingly. At unbinding, the
driver calls snd_device_disconnect() for each PCM stream, then
synchronizes with the refcount finish, and finally releases the object
resources.

Fixes: bbbc7e8502c9 ("ALSA: hda - Allocate hda_pcm objects dynamically")
Link: https://lore.kernel.org/r/20211116072459.18930-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>

+37 -19
+5 -3
include/sound/hda_codec.h
··· 8 8 #ifndef __SOUND_HDA_CODEC_H 9 9 #define __SOUND_HDA_CODEC_H 10 10 11 - #include <linux/kref.h> 11 + #include <linux/refcount.h> 12 12 #include <linux/mod_devicetable.h> 13 13 #include <sound/info.h> 14 14 #include <sound/control.h> ··· 166 166 bool own_chmap; /* codec driver provides own channel maps */ 167 167 /* private: */ 168 168 struct hda_codec *codec; 169 - struct kref kref; 170 169 struct list_head list; 170 + unsigned int disconnected:1; 171 171 }; 172 172 173 173 /* codec information */ ··· 187 187 188 188 /* PCM to create, set by patch_ops.build_pcms callback */ 189 189 struct list_head pcm_list_head; 190 + refcount_t pcm_ref; 191 + wait_queue_head_t remove_sleep; 190 192 191 193 /* codec specific info */ 192 194 void *spec; ··· 422 420 423 421 static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm) 424 422 { 425 - kref_get(&pcm->kref); 423 + refcount_inc(&pcm->codec->pcm_ref); 426 424 } 427 425 void snd_hda_codec_pcm_put(struct hda_pcm *pcm); 428 426
+5
sound/pci/hda/hda_bind.c
··· 156 156 return codec->bus->core.ext_ops->hdev_detach(&codec->core); 157 157 } 158 158 159 + refcount_dec(&codec->pcm_ref); 160 + snd_hda_codec_disconnect_pcms(codec); 161 + wait_event(codec->remove_sleep, !refcount_read(&codec->pcm_ref)); 162 + snd_power_sync_ref(codec->bus->card); 163 + 159 164 if (codec->patch_ops.free) 160 165 codec->patch_ops.free(codec); 161 166 snd_hda_codec_cleanup_for_unbind(codec);
+26 -16
sound/pci/hda/hda_codec.c
··· 703 703 /* 704 704 * PCM device 705 705 */ 706 - static void release_pcm(struct kref *kref) 707 - { 708 - struct hda_pcm *pcm = container_of(kref, struct hda_pcm, kref); 709 - 710 - if (pcm->pcm) 711 - snd_device_free(pcm->codec->card, pcm->pcm); 712 - clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits); 713 - kfree(pcm->name); 714 - kfree(pcm); 715 - } 716 - 717 706 void snd_hda_codec_pcm_put(struct hda_pcm *pcm) 718 707 { 719 - kref_put(&pcm->kref, release_pcm); 708 + if (refcount_dec_and_test(&pcm->codec->pcm_ref)) 709 + wake_up(&pcm->codec->remove_sleep); 720 710 } 721 711 EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put); 722 712 ··· 721 731 return NULL; 722 732 723 733 pcm->codec = codec; 724 - kref_init(&pcm->kref); 725 734 va_start(args, fmt); 726 735 pcm->name = kvasprintf(GFP_KERNEL, fmt, args); 727 736 va_end(args); ··· 730 741 } 731 742 732 743 list_add_tail(&pcm->list, &codec->pcm_list_head); 744 + refcount_inc(&codec->pcm_ref); 733 745 return pcm; 734 746 } 735 747 EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new); ··· 738 748 /* 739 749 * codec destructor 740 750 */ 751 + void snd_hda_codec_disconnect_pcms(struct hda_codec *codec) 752 + { 753 + struct hda_pcm *pcm; 754 + 755 + list_for_each_entry(pcm, &codec->pcm_list_head, list) { 756 + if (pcm->disconnected) 757 + continue; 758 + if (pcm->pcm) 759 + snd_device_disconnect(codec->card, pcm->pcm); 760 + snd_hda_codec_pcm_put(pcm); 761 + pcm->disconnected = 1; 762 + } 763 + } 764 + 741 765 static void codec_release_pcms(struct hda_codec *codec) 742 766 { 743 767 struct hda_pcm *pcm, *n; 744 768 745 769 list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) { 746 - list_del_init(&pcm->list); 770 + list_del(&pcm->list); 747 771 if (pcm->pcm) 748 - snd_device_disconnect(codec->card, pcm->pcm); 749 - snd_hda_codec_pcm_put(pcm); 772 + snd_device_free(pcm->codec->card, pcm->pcm); 773 + clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits); 774 + kfree(pcm->name); 775 + kfree(pcm); 750 776 } 751 777 } 752 778 ··· 775 769 codec->registered = 0; 776 770 } 777 771 772 + snd_hda_codec_disconnect_pcms(codec); 778 773 cancel_delayed_work_sync(&codec->jackpoll_work); 779 774 if (!codec->in_freeing) 780 775 snd_hda_ctls_clear(codec); ··· 799 792 remove_conn_list(codec); 800 793 snd_hdac_regmap_exit(&codec->core); 801 794 codec->configured = 0; 795 + refcount_set(&codec->pcm_ref, 1); /* reset refcount */ 802 796 } 803 797 EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind); 804 798 ··· 966 958 snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); 967 959 INIT_LIST_HEAD(&codec->conn_list); 968 960 INIT_LIST_HEAD(&codec->pcm_list_head); 961 + refcount_set(&codec->pcm_ref, 1); 962 + init_waitqueue_head(&codec->remove_sleep); 969 963 970 964 INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work); 971 965 codec->depop_delay = -1;
+1
sound/pci/hda/hda_local.h
··· 137 137 int snd_hda_codec_reset(struct hda_codec *codec); 138 138 void snd_hda_codec_register(struct hda_codec *codec); 139 139 void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec); 140 + void snd_hda_codec_disconnect_pcms(struct hda_codec *codec); 140 141 141 142 #define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core) 142 143