e4ad1accb28d0ed8cea6f12395d58686ad344ca7 is broken

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The commit:

ASoC: pcm: free path list before exiting from error conditions

is broken and must not be applied to stable kernels, and needs reverting
for 3.14.

Let's look at the three places this has been added:

                paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list);
                if (paths < 0) {
+                       dpcm_path_put(&list);

                paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list);
                if (paths < 0) {
+                       dpcm_path_put(&list);

        if (dpcm_path_get(fe, stream, &list) <= 0) {
+               dpcm_path_put(&list);

and ask the basic question - under what circumstances does dpcm_path_get()
return a value less than zero?

Well, one case is this path:

int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
        int stream, struct snd_soc_dapm_widget_list **list_)
{
        list = kzalloc(sizeof(struct snd_soc_dapm_widget_list) +
                        sizeof(struct snd_soc_dapm_widget *), GFP_KERNEL);
        if (list == NULL)
                return -ENOMEM;

and the result is that `list' in each of the three cases above is
uninitialised.

Another case is:

        /* get number of valid DAI paths and their widgets */
        paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, &list);
        
        dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths,
                        stream ? "capture" : "playback");

        *list_ = list;
        return paths;
}

if snd_soc_dapm_dai_get_connected_widgets() returns negative, or in
the case of dpcm_fe_dai_open(), it returns zero.  Can this return
negative?  Well, snd_soc_dapm_dai_get_connected_widgets() selects
between calling is_connected_output_ep() and is_connected_input_ep(),
returning its value.

>From what I can see, these two functions do not return negative numbers
ever.  They may return zero.

So, in the case of the first two additions, paths will only be negative
_if_ we failed to allocate the memory for `list' which means `list' does
*not* need freeing.

In the third case, let's look closer at the function:

        struct snd_soc_dapm_widget_list *list;

        if (dpcm_path_get(fe, stream, &list) <= 0) {
                dpcm_path_put(&list);
                dev_dbg(fe->dev, "ASoC: %s no valid %s route\n",
                        fe->dai_link->name, stream ? "capture" : "playback");
        }

Note that this block does not exit, it continues on:

        /* calculate valid and active FE <-> BE dpcms */
        dpcm_process_paths(fe, stream, &list, 1);
        
and dpcm_process_paths dereferences &list, and ultimately dereferences
`list' itself.  There's two bugs here - first, in the case of
dpcm_path_get() returning negative, if we survive dpcm_path_put() then
we're dereferencing an uninitialised pointer in this function.  Secondly,
if dpcm_path_get() returned zero, we end up dereferencing memory which
has been freed.

The above function continues, and eventually we get here:

        dpcm_clear_pending_state(fe, stream);
        dpcm_path_put(&list);

which ends up trying a second time to put this list.

Therefore, this commit is 100% wrong in every regard, and it needs to be
reverted.  However, in analysing this, there is the matter of the
unchecked failure of dpcm_path_get() when it returns negative, leaving
`list' completely uninitialised.

I noticed this because this commit leads to an oops in dpcm_add_paths()
as the free'd memory changes state beneath this function resulting in
a hard to debug oops.

Unable to handle kernel NULL pointer dereference at virtual address 00000004
pgd = d062c000
[00000004] *pgd=10609831, *pte=00000000, *ppte=00000000
Internal error: Oops: 17 [#1] PREEMPT ARM
Modules linked in: fuse bnep rfcomm bluetooth 6lowpan_iphc ext3 jbd ext2 snd_soc_spdif_tx m25p80 mtd orion_wdt snd_soc_kirkwood snd_soc_kirkwood_spdif
CPU: 0 PID: 1564 Comm: pulseaudio Not tainted 3.14.0+ #872
task: d05c2400 ti: d0628000 task.ti: d0628000
PC is at dpcm_add_paths.isra.7+0x148/0x164
LR is at dpcm_get_be+0x88/0x108
pc : [<c032c240>]    lr : [<c032abe8>]    psr: 600f0013
sp : d0629d18  ip : 00000000  fp : d5b1a000
r10: 00000000  r9 : d588c010  r8 : d5a99148
r7 : 00000001  r6 : d588c010  r5 : 00000000  r4 : d5a99140
r3 : 00000000  r2 : 00000000  r1 : c05485a0  r0 : d5bad800
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 10c5387d  Table: 1062c019  DAC: 00000015
Process pulseaudio (pid: 1564, stack limit = 0xd0628250)
...
Code: eaffffec e5983000 e5960000 e59f1014 (e5932004) 

The oopsing instruction is from:

                        dev_err(fe->dev, "ASoC: no BE found for %s\n",
                                        list->widgets[i]->name);   

trying to load ->name, but list->widgets[1] has magically turned into
a NULL pointer mid-function.

-- 
FTTC broadband for 0.8mile line: now at 9.7Mbps down 460kbps up... slowly
improving, and getting towards what was expected from it.
--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]