Skip to content

Commit 7206998

Browse files
committed
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: bbbc7e8 ("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>
1 parent 80bd64a commit 7206998

4 files changed

Lines changed: 37 additions & 19 deletions

File tree

include/sound/hda_codec.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#ifndef __SOUND_HDA_CODEC_H
99
#define __SOUND_HDA_CODEC_H
1010

11-
#include <linux/kref.h>
11+
#include <linux/refcount.h>
1212
#include <linux/mod_devicetable.h>
1313
#include <sound/info.h>
1414
#include <sound/control.h>
@@ -166,8 +166,8 @@ struct hda_pcm {
166166
bool own_chmap; /* codec driver provides own channel maps */
167167
/* private: */
168168
struct hda_codec *codec;
169-
struct kref kref;
170169
struct list_head list;
170+
unsigned int disconnected:1;
171171
};
172172

173173
/* codec information */
@@ -187,6 +187,8 @@ struct hda_codec {
187187

188188
/* PCM to create, set by patch_ops.build_pcms callback */
189189
struct list_head pcm_list_head;
190+
refcount_t pcm_ref;
191+
wait_queue_head_t remove_sleep;
190192

191193
/* codec specific info */
192194
void *spec;
@@ -420,7 +422,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec);
420422

421423
static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm)
422424
{
423-
kref_get(&pcm->kref);
425+
refcount_inc(&pcm->codec->pcm_ref);
424426
}
425427
void snd_hda_codec_pcm_put(struct hda_pcm *pcm);
426428

sound/pci/hda/hda_bind.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ static int hda_codec_driver_remove(struct device *dev)
156156
return codec->bus->core.ext_ops->hdev_detach(&codec->core);
157157
}
158158

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+
159164
if (codec->patch_ops.free)
160165
codec->patch_ops.free(codec);
161166
snd_hda_codec_cleanup_for_unbind(codec);

sound/pci/hda/hda_codec.c

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -703,20 +703,10 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
703703
/*
704704
* PCM device
705705
*/
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-
717706
void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
718707
{
719-
kref_put(&pcm->kref, release_pcm);
708+
if (refcount_dec_and_test(&pcm->codec->pcm_ref))
709+
wake_up(&pcm->codec->remove_sleep);
720710
}
721711
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);
722712

@@ -731,7 +721,6 @@ struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
731721
return NULL;
732722

733723
pcm->codec = codec;
734-
kref_init(&pcm->kref);
735724
va_start(args, fmt);
736725
pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
737726
va_end(args);
@@ -741,22 +730,39 @@ struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
741730
}
742731

743732
list_add_tail(&pcm->list, &codec->pcm_list_head);
733+
refcount_inc(&codec->pcm_ref);
744734
return pcm;
745735
}
746736
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
747737

748738
/*
749739
* codec destructor
750740
*/
741+
void snd_hda_codec_disconnect_pcms(struct hda_codec *codec)
742+
{
743+
struct hda_pcm *pcm;
744+
745+
list_for_each_entry(pcm, &codec->pcm_list_head, list) {
746+
if (pcm->disconnected)
747+
continue;
748+
if (pcm->pcm)
749+
snd_device_disconnect(codec->card, pcm->pcm);
750+
snd_hda_codec_pcm_put(pcm);
751+
pcm->disconnected = 1;
752+
}
753+
}
754+
751755
static void codec_release_pcms(struct hda_codec *codec)
752756
{
753757
struct hda_pcm *pcm, *n;
754758

755759
list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
756-
list_del_init(&pcm->list);
760+
list_del(&pcm->list);
757761
if (pcm->pcm)
758-
snd_device_disconnect(codec->card, pcm->pcm);
759-
snd_hda_codec_pcm_put(pcm);
762+
snd_device_free(pcm->codec->card, pcm->pcm);
763+
clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
764+
kfree(pcm->name);
765+
kfree(pcm);
760766
}
761767
}
762768

@@ -769,6 +775,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
769775
codec->registered = 0;
770776
}
771777

778+
snd_hda_codec_disconnect_pcms(codec);
772779
cancel_delayed_work_sync(&codec->jackpoll_work);
773780
if (!codec->in_freeing)
774781
snd_hda_ctls_clear(codec);
@@ -792,6 +799,7 @@ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
792799
remove_conn_list(codec);
793800
snd_hdac_regmap_exit(&codec->core);
794801
codec->configured = 0;
802+
refcount_set(&codec->pcm_ref, 1); /* reset refcount */
795803
}
796804
EXPORT_SYMBOL_GPL(snd_hda_codec_cleanup_for_unbind);
797805

@@ -958,6 +966,8 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card,
958966
snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
959967
INIT_LIST_HEAD(&codec->conn_list);
960968
INIT_LIST_HEAD(&codec->pcm_list_head);
969+
refcount_set(&codec->pcm_ref, 1);
970+
init_waitqueue_head(&codec->remove_sleep);
961971

962972
INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
963973
codec->depop_delay = -1;

sound/pci/hda/hda_local.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
137137
int snd_hda_codec_reset(struct hda_codec *codec);
138138
void snd_hda_codec_register(struct hda_codec *codec);
139139
void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec);
140+
void snd_hda_codec_disconnect_pcms(struct hda_codec *codec);
140141

141142
#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
142143

0 commit comments

Comments
 (0)