aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2017-10-16 05:39:28 -0400
committerTakashi Iwai <tiwai@suse.de>2017-10-18 06:27:00 -0400
commita91d66129fb9bcead12af3ed2008d6ddbf179509 (patch)
treea11e4e5447a4fa58a1fac1e0a5e3f31128df52fd
parent6bf88a343db2b3c160edf9b82a74966b31cc80bd (diff)
ALSA: hda - Fix incorrect TLV callback check introduced during set_fs() removal
The commit 99b5c5bb9a54 ("ALSA: hda - Remove the use of set_fs()") converted the get_kctl_0dB_offset() call for killing set_fs() usage in HD-audio codec code. The conversion assumed that the TLV callback used in HD-audio code is only snd_hda_mixer_amp() and applies the TLV calculation locally. Although this assumption is correct, and all slave kctls are actually with that callback, the current code is still utterly buggy; it doesn't hit this condition and falls back to the next check. It's because the function gets called after adding slave kctls to vmaster. By assigning a slave kctl, the slave kctl object is faked inside vmaster code, and the whole kctl ops are overridden. Thus the callback op points to a different value from what we've assumed. More badly, as reported by the KERNEXEC and UDEREF features of PaX, the code flow turns into the unexpected pitfall. The next fallback check is SNDRV_CTL_ELEM_ACCESS_TLV_READ access bit, and this always hits for each kctl with TLV. Then it evaluates the callback function pointer wrongly as if it were a TLV array. Although currently its side-effect is fairly limited, this incorrect reference may lead to an unpleasant result. For addressing the regression, this patch introduces a new helper to vmaster code, snd_ctl_apply_vmaster_slaves(). This works similarly like the existing map_slaves() in hda_codec.c: it loops over the slave list of the given master, and applies the given function to each slave. Then the initializer function receives the right kctl object and we can compare the correct pointer instead of the faked one. Also, for catching the similar breakage in future, give an error message when the unexpected TLV callback is found and bail out immediately. Fixes: 99b5c5bb9a54 ("ALSA: hda - Remove the use of set_fs()") Reported-by: PaX Team <pageexec@freemail.hu> Cc: <stable@vger.kernel.org> # v4.13 Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--include/sound/control.h3
-rw-r--r--sound/core/vmaster.c31
-rw-r--r--sound/pci/hda/hda_codec.c97
3 files changed, 89 insertions, 42 deletions
diff --git a/include/sound/control.h b/include/sound/control.h
index bd7246de58e7..a1f1152bc687 100644
--- a/include/sound/control.h
+++ b/include/sound/control.h
@@ -248,6 +248,9 @@ int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kctl,
248 void *private_data); 248 void *private_data);
249void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only); 249void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only);
250#define snd_ctl_sync_vmaster_hook(kctl) snd_ctl_sync_vmaster(kctl, true) 250#define snd_ctl_sync_vmaster_hook(kctl) snd_ctl_sync_vmaster(kctl, true)
251int snd_ctl_apply_vmaster_slaves(struct snd_kcontrol *kctl,
252 int (*func)(struct snd_kcontrol *, void *),
253 void *arg);
251 254
252/* 255/*
253 * Helper functions for jack-detection controls 256 * Helper functions for jack-detection controls
diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c
index 6c58e6f73a01..e43af18d4383 100644
--- a/sound/core/vmaster.c
+++ b/sound/core/vmaster.c
@@ -484,3 +484,34 @@ void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only)
484 master->hook(master->hook_private_data, master->val); 484 master->hook(master->hook_private_data, master->val);
485} 485}
486EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); 486EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster);
487
488/**
489 * snd_ctl_apply_vmaster_slaves - Apply function to each vmaster slave
490 * @kctl: vmaster kctl element
491 * @func: function to apply
492 * @arg: optional function argument
493 *
494 * Apply the function @func to each slave kctl of the given vmaster kctl.
495 * Returns 0 if successful, or a negative error code.
496 */
497int snd_ctl_apply_vmaster_slaves(struct snd_kcontrol *kctl,
498 int (*func)(struct snd_kcontrol *, void *),
499 void *arg)
500{
501 struct link_master *master;
502 struct link_slave *slave;
503 int err;
504
505 master = snd_kcontrol_chip(kctl);
506 err = master_init(master);
507 if (err < 0)
508 return err;
509 list_for_each_entry(slave, &master->slaves, list) {
510 err = func(&slave->slave, arg);
511 if (err < 0)
512 return err;
513 }
514
515 return 0;
516}
517EXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_slaves);
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index b6cf9684c2ec..a0989d231fd0 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -1803,36 +1803,6 @@ static int check_slave_present(struct hda_codec *codec,
1803 return 1; 1803 return 1;
1804} 1804}
1805 1805
1806/* guess the value corresponding to 0dB */
1807static int get_kctl_0dB_offset(struct hda_codec *codec,
1808 struct snd_kcontrol *kctl, int *step_to_check)
1809{
1810 int _tlv[4];
1811 const int *tlv = NULL;
1812 int val = -1;
1813
1814 if ((kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) &&
1815 kctl->tlv.c == snd_hda_mixer_amp_tlv) {
1816 get_ctl_amp_tlv(kctl, _tlv);
1817 tlv = _tlv;
1818 } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)
1819 tlv = kctl->tlv.p;
1820 if (tlv && tlv[0] == SNDRV_CTL_TLVT_DB_SCALE) {
1821 int step = tlv[3];
1822 step &= ~TLV_DB_SCALE_MUTE;
1823 if (!step)
1824 return -1;
1825 if (*step_to_check && *step_to_check != step) {
1826 codec_err(codec, "Mismatching dB step for vmaster slave (%d!=%d)\n",
1827 *step_to_check, step);
1828 return -1;
1829 }
1830 *step_to_check = step;
1831 val = -tlv[2] / step;
1832 }
1833 return val;
1834}
1835
1836/* call kctl->put with the given value(s) */ 1806/* call kctl->put with the given value(s) */
1837static int put_kctl_with_value(struct snd_kcontrol *kctl, int val) 1807static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
1838{ 1808{
@@ -1847,19 +1817,58 @@ static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
1847 return 0; 1817 return 0;
1848} 1818}
1849 1819
1850/* initialize the slave volume with 0dB */ 1820struct slave_init_arg {
1851static int init_slave_0dB(struct hda_codec *codec, 1821 struct hda_codec *codec;
1852 void *data, struct snd_kcontrol *slave) 1822 int step;
1823};
1824
1825/* initialize the slave volume with 0dB via snd_ctl_apply_vmaster_slaves() */
1826static int init_slave_0dB(struct snd_kcontrol *kctl, void *_arg)
1853{ 1827{
1854 int offset = get_kctl_0dB_offset(codec, slave, data); 1828 struct slave_init_arg *arg = _arg;
1855 if (offset > 0) 1829 int _tlv[4];
1856 put_kctl_with_value(slave, offset); 1830 const int *tlv = NULL;
1831 int step;
1832 int val;
1833
1834 if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
1835 if (kctl->tlv.c != snd_hda_mixer_amp_tlv) {
1836 codec_err(arg->codec,
1837 "Unexpected TLV callback for slave %s:%d\n",
1838 kctl->id.name, kctl->id.index);
1839 return 0; /* ignore */
1840 }
1841 get_ctl_amp_tlv(kctl, _tlv);
1842 tlv = _tlv;
1843 } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)
1844 tlv = kctl->tlv.p;
1845
1846 if (!tlv || tlv[0] != SNDRV_CTL_TLVT_DB_SCALE)
1847 return 0;
1848
1849 step = tlv[3];
1850 step &= ~TLV_DB_SCALE_MUTE;
1851 if (!step)
1852 return 0;
1853 if (arg->step && arg->step != step) {
1854 codec_err(arg->codec,
1855 "Mismatching dB step for vmaster slave (%d!=%d)\n",
1856 arg->step, step);
1857 return 0;
1858 }
1859
1860 arg->step = step;
1861 val = -tlv[2] / step;
1862 if (val > 0) {
1863 put_kctl_with_value(kctl, val);
1864 return val;
1865 }
1866
1857 return 0; 1867 return 0;
1858} 1868}
1859 1869
1860/* unmute the slave */ 1870/* unmute the slave via snd_ctl_apply_vmaster_slaves() */
1861static int init_slave_unmute(struct hda_codec *codec, 1871static int init_slave_unmute(struct snd_kcontrol *slave, void *_arg)
1862 void *data, struct snd_kcontrol *slave)
1863{ 1872{
1864 return put_kctl_with_value(slave, 1); 1873 return put_kctl_with_value(slave, 1);
1865} 1874}
@@ -1919,9 +1928,13 @@ int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
1919 /* init with master mute & zero volume */ 1928 /* init with master mute & zero volume */
1920 put_kctl_with_value(kctl, 0); 1929 put_kctl_with_value(kctl, 0);
1921 if (init_slave_vol) { 1930 if (init_slave_vol) {
1922 int step = 0; 1931 struct slave_init_arg arg = {
1923 map_slaves(codec, slaves, suffix, 1932 .codec = codec,
1924 tlv ? init_slave_0dB : init_slave_unmute, &step); 1933 .step = 0,
1934 };
1935 snd_ctl_apply_vmaster_slaves(kctl,
1936 tlv ? init_slave_0dB : init_slave_unmute,
1937 &arg);
1925 } 1938 }
1926 1939
1927 if (ctl_ret) 1940 if (ctl_ret)