hello devs,

upstream commit 99b5c5bb9a5435a5ae5d46445ac0f2bf6aa5ee52 removed the use
of set_fs in get_kctl_0dB_offset under the assumption that the only runtime
value of kctl->tlv.c was snd_hda_mixer_amp_tlv. alas, recently, the KERNEXEC
and UDEREF features in PaX reported a violation of this assumption as it
turns out that _snd_ctl_add_slave sets this callback to slave_tlv_cmd instead.

it seems to me that there're several other possible values for this callback
so it's not clear why it was assumed that get_kctl_0dB_offset would only be
called with only one of them in practice, but nevertheless, it happens here
so clearly something is wrong. the backtrace that shows how things go wrong
in this case:

 PAX: suspicious general protection fault: 0000 [#1] PREEMPT SMP
 Modules linked in:
 CPU: 4 PID: 31 Comm: kworker/4:0 Not tainted 4.13.5-i386-pax #17
 Workqueue: events azx_probe_work
 task: eb61c880 task.stack: eb62a000
 EIP: 0060:[<009bbd2d>] init_slave_0dB+0x2d/0xa0
 EFLAGS: 00210202 CPU: 4
 EAX: 00991fd0 EBX: e9883a80 ECX: e9883a80 EDX: eb62bd74
 ESI: eb62bd74 EDI: e9098800 EBP: eb62bd08 ESP: eb62bcec
  DS: 0068 ES: 0068 FS: 00d8 GS: 0068 SS: 0068
 CR0: 80050033 CR2: 00000000 CR3: 03b2d100 CR4: 000406f0 shadow CR4: 000406f0
 Call Trace:
  [<009bb469>] map_slaves+0xb9/0xe0
  [<009bce0e>] __snd_hda_add_vmaster+0xde/0x110
  [<009c82f0>] snd_hda_gen_build_controls+0x1a0/0x1b0
  [<009d0a4d>] cx_auto_build_controls+0xd/0x70
  [<009bdf76>] snd_hda_codec_build_controls+0x186/0x1d0
  [<009b92ad>] hda_codec_driver_probe+0x6d/0xf0
  [<006a3be9>] driver_probe_device+0x289/0x420
  [<006a3ed6>] __device_attach_driver+0x76/0x100
  [<006a1edf>] bus_for_each_drv+0x3f/0x70
  [<006a3813>] __device_attach+0xa3/0x110
  [<006a3f9d>] device_initial_probe+0xd/0x10
  [<006a2d6f>] bus_probe_device+0x6f/0x80
  [<006a100f>] device_add+0x2cf/0x590
  [<009d8d8c>] snd_hdac_device_register+0xc/0x40
  [<009b90b4>] snd_hda_codec_configure+0x34/0x140
  [<009c2875>] azx_codec_configure+0x25/0x50
  [<009d8081>] azx_probe_continue+0x621/0x9e0
  [<009d84bd>] azx_probe_work+0xd/0x10
  [<0006fff2>] process_one_work+0x122/0x2a0
  [<000701a9>] worker_thread+0x39/0x390
  [<00074df2>] kthread+0xe2/0x110
  [<00cee619>] ret_from_fork+0x19/0x30
 Code: e5 57 89 c7 56 89 d6 53 89 cb 83 ec 10 8b 41 6c a9 00 00 00 10 74 09 81 
79 58 90 bc 9b 00 74 4e a8 10 74 38 8b 43 58 85 c0 74 31 <83> 38 01 75 2c 8b 48 
0c 81 e1 ff ff fe ff 74 21 8b 16 39 d1 
74
 EIP: [<009bbd2d>] init_slave_0dB+0x2d/0xa0 SS:ESP: 0068:eb62bcec

what KERNEXEC on i386 does is that it executes kernel code in its own 0-based
code segment hence the 'low' code addresses. due to the current logic that
checks for SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK in get_kctl_0dB_offset, this
callback address is instead treated as a data pointer (as apparently
SNDRV_CTL_ELEM_ACCESS_TLV_READ is also set) and since it's not a valid kernel
address, it causes a GPF under the UDEREF PaX feature (without UDEREF it'd
have been an oops since such low addresses are not mapped for kernel threads).

on vanilla kernels all this is a silent read of kernel code bytes that are
then interpreted as the tlv[] array content, which is probably not what you
want either.

as for fixing this, first the above mentioned assumption should be re-evaluated.
if it's considered correct then there is some logic bug in my case (i can help
debug it if you tell me what to do) otherwise the current pattern of

   if (SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK && callback == snd_hda_mixer_amp_tlv) 
{
     call wrapped function underneath snd_hda_mixer_amp_tlv
   } else if (SNDRV_CTL_ELEM_ACCESS_TLV_READ) {
     treat unrecognized callback address as data ptr
   }

should be changed to

   if (SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK( {
     if (callback == snd_hda_mixer_amp_tlv) {
       call wrapped function underneath snd_hda_mixer_amp_tlv
     } else if (callback == others) {
       handle others, WARN/BUG/etc
     }
   } else if (SNDRV_CTL_ELEM_ACCESS_TLV_READ) {
     no longer treat unrecognized callback address as data ptr
   }

and all other callbacks with userland access should be refactored the same
way as snd_hda_mixer_amp_tlv was. i'd also suggest that you at least do the
above suggested if/else pattern change in order to prevent the misuse of
unexpected callbacks in the future.

cheers,
  PaX Team

Reply via email to