Skip to content

Commit a91d661

Browse files
committed
ALSA: hda - Fix incorrect TLV callback check introduced during set_fs() removal
The commit 99b5c5b ("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: 99b5c5b ("ALSA: hda - Remove the use of set_fs()") Reported-by: PaX Team <[email protected]> Cc: <[email protected]> # v4.13 Signed-off-by: Takashi Iwai <[email protected]>
1 parent 6bf88a3 commit a91d661

File tree

3 files changed

+89
-42
lines changed

3 files changed

+89
-42
lines changed

include/sound/control.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kctl,
248248
void *private_data);
249249
void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only);
250250
#define snd_ctl_sync_vmaster_hook(kctl) snd_ctl_sync_vmaster(kctl, true)
251+
int snd_ctl_apply_vmaster_slaves(struct snd_kcontrol *kctl,
252+
int (*func)(struct snd_kcontrol *, void *),
253+
void *arg);
251254

252255
/*
253256
* Helper functions for jack-detection controls

sound/core/vmaster.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,34 @@ void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only)
484484
master->hook(master->hook_private_data, master->val);
485485
}
486486
EXPORT_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+
*/
497+
int 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+
}
517+
EXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_slaves);

sound/pci/hda/hda_codec.c

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,36 +1803,6 @@ static int check_slave_present(struct hda_codec *codec,
18031803
return 1;
18041804
}
18051805

1806-
/* guess the value corresponding to 0dB */
1807-
static 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-
18361806
/* call kctl->put with the given value(s) */
18371807
static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
18381808
{
@@ -1847,19 +1817,58 @@ static int put_kctl_with_value(struct snd_kcontrol *kctl, int val)
18471817
return 0;
18481818
}
18491819

1850-
/* initialize the slave volume with 0dB */
1851-
static int init_slave_0dB(struct hda_codec *codec,
1852-
void *data, struct snd_kcontrol *slave)
1820+
struct slave_init_arg {
1821+
struct hda_codec *codec;
1822+
int step;
1823+
};
1824+
1825+
/* initialize the slave volume with 0dB via snd_ctl_apply_vmaster_slaves() */
1826+
static int init_slave_0dB(struct snd_kcontrol *kctl, void *_arg)
18531827
{
1854-
int offset = get_kctl_0dB_offset(codec, slave, data);
1855-
if (offset > 0)
1856-
put_kctl_with_value(slave, offset);
1828+
struct slave_init_arg *arg = _arg;
1829+
int _tlv[4];
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+
18571867
return 0;
18581868
}
18591869

1860-
/* unmute the slave */
1861-
static int init_slave_unmute(struct hda_codec *codec,
1862-
void *data, struct snd_kcontrol *slave)
1870+
/* unmute the slave via snd_ctl_apply_vmaster_slaves() */
1871+
static int init_slave_unmute(struct snd_kcontrol *slave, void *_arg)
18631872
{
18641873
return put_kctl_with_value(slave, 1);
18651874
}
@@ -1919,9 +1928,13 @@ int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
19191928
/* init with master mute & zero volume */
19201929
put_kctl_with_value(kctl, 0);
19211930
if (init_slave_vol) {
1922-
int step = 0;
1923-
map_slaves(codec, slaves, suffix,
1924-
tlv ? init_slave_0dB : init_slave_unmute, &step);
1931+
struct slave_init_arg arg = {
1932+
.codec = codec,
1933+
.step = 0,
1934+
};
1935+
snd_ctl_apply_vmaster_slaves(kctl,
1936+
tlv ? init_slave_0dB : init_slave_unmute,
1937+
&arg);
19251938
}
19261939

19271940
if (ctl_ret)

0 commit comments

Comments
 (0)