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

ALSA: pcm: Don't embed device

So far we use the embedded struct device for each PCM substreams in
struct snd_pcm. This may result in UAF when the delayed kobj release
is used; each corresponding struct device is still accessed at the
(delayed) device release, while the snd_pcm object may be already
gone.

As a workaround, detach the struct device from the snd_pcm object by
allocating via the new snd_device_alloc() helper.

A caveat is that we store the PCM substream pointer to drvdata since
the device resume and others require the access to it.

This patch is based on the fix Curtis posted initially. In this
patch, the changes are split and use the new helper function instead.

Link: https://lore.kernel.org/r/20230801171928.1460120-1-cujomalainey@chromium.org
Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Signed-off-by: Curtis Malainey <cujomalainey@chromium.org>
Tested-by: Curtis Malainey <cujomalainey@chromium.org>
Link: https://lore.kernel.org/r/20230816160252.23396-4-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>

+17 -13
+1 -1
include/sound/pcm.h
··· 510 510 #endif 511 511 #endif 512 512 struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */ 513 - struct device dev; 513 + struct device *dev; 514 514 }; 515 515 516 516 struct snd_pcm {
+2 -2
sound/aoa/soundbus/i2sbus/pcm.c
··· 972 972 goto out_put_ci_module; 973 973 snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 974 974 &i2sbus_playback_ops); 975 - dev->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].dev.parent = 975 + dev->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]->dev.parent = 976 976 &dev->ofdev.dev; 977 977 i2sdev->out.created = 1; 978 978 } ··· 989 989 goto out_put_ci_module; 990 990 snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 991 991 &i2sbus_record_ops); 992 - dev->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].dev.parent = 992 + dev->pcm->streams[SNDRV_PCM_STREAM_CAPTURE]->dev.parent = 993 993 &dev->ofdev.dev; 994 994 i2sdev->in.created = 1; 995 995 }
+13 -9
sound/core/pcm.c
··· 604 604 #ifdef CONFIG_PM_SLEEP 605 605 static int do_pcm_suspend(struct device *dev) 606 606 { 607 - struct snd_pcm_str *pstr = container_of(dev, struct snd_pcm_str, dev); 607 + struct snd_pcm_str *pstr = dev_get_drvdata(dev); 608 608 609 609 if (!pstr->pcm->no_device_suspend) 610 610 snd_pcm_suspend_all(pstr->pcm); ··· 650 650 if (!substream_count) 651 651 return 0; 652 652 653 - snd_device_initialize(&pstr->dev, pcm->card); 654 - pstr->dev.groups = pcm_dev_attr_groups; 655 - pstr->dev.type = &pcm_dev_type; 656 - dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, 653 + err = snd_device_alloc(&pstr->dev, pcm->card); 654 + if (err < 0) 655 + return err; 656 + dev_set_name(pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, 657 657 stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); 658 + pstr->dev->groups = pcm_dev_attr_groups; 659 + pstr->dev->type = &pcm_dev_type; 660 + dev_set_drvdata(pstr->dev, pstr); 658 661 659 662 if (!pcm->internal) { 660 663 err = snd_pcm_stream_proc_init(pstr); ··· 848 845 #endif 849 846 free_chmap(pstr); 850 847 if (pstr->substream_count) 851 - put_device(&pstr->dev); 848 + put_device(pstr->dev); 852 849 } 853 850 854 851 #if IS_ENABLED(CONFIG_SND_PCM_OSS) ··· 1018 1015 static ssize_t pcm_class_show(struct device *dev, 1019 1016 struct device_attribute *attr, char *buf) 1020 1017 { 1021 - struct snd_pcm_str *pstr = container_of(dev, struct snd_pcm_str, dev); 1018 + struct snd_pcm_str *pstr = dev_get_drvdata(dev); 1022 1019 struct snd_pcm *pcm = pstr->pcm; 1023 1020 const char *str; 1024 1021 static const char *strs[SNDRV_PCM_CLASS_LAST + 1] = { ··· 1079 1076 /* register pcm */ 1080 1077 err = snd_register_device(devtype, pcm->card, pcm->device, 1081 1078 &snd_pcm_f_ops[cidx], pcm, 1082 - &pcm->streams[cidx].dev); 1079 + pcm->streams[cidx].dev); 1083 1080 if (err < 0) { 1084 1081 list_del_init(&pcm->list); 1085 1082 goto unlock; ··· 1126 1123 1127 1124 pcm_call_notify(pcm, n_disconnect); 1128 1125 for (cidx = 0; cidx < 2; cidx++) { 1129 - snd_unregister_device(&pcm->streams[cidx].dev); 1126 + if (pcm->streams[cidx].dev) 1127 + snd_unregister_device(pcm->streams[cidx].dev); 1130 1128 free_chmap(&pcm->streams[cidx]); 1131 1129 } 1132 1130 mutex_unlock(&pcm->open_mutex);
+1 -1
sound/usb/media.c
··· 35 35 { 36 36 struct media_device *mdev; 37 37 struct media_ctl *mctl; 38 - struct device *pcm_dev = &pcm->streams[stream].dev; 38 + struct device *pcm_dev = pcm->streams[stream].dev; 39 39 u32 intf_type; 40 40 int ret = 0; 41 41 u16 mixer_pad;