fix struct layout mismatch in sound ioctl time32 fallback conversion
authorRich Felker <dalias@aerifal.cx>
Tue, 19 Oct 2021 20:07:14 +0000 (16:07 -0400)
committerRich Felker <dalias@aerifal.cx>
Tue, 19 Oct 2021 20:07:14 +0000 (16:07 -0400)
the snd_pcm_mmap_control struct used with SNDRV_PCM_IOCTL_SYNC_PTR was
mistakenly defined in the kernel uapi with "before u32" padding both
before and after the first u32 member. our conversion between the
modern struct and the legacy time32 struct was written without
awareness of that mistake, and assumed the time64 version of the
struct was the intended form with padding to match the layout on
64-bit archs. as a result, the struct was not converted correctly when
running on old kernels, with audio glitches as the likely result.

this was discovered thanks to a related bug in the kernel, whereby
32-bit userspace running on a 64-bit kernel also suffered from the
types mismatching. the mistaken layout is now the ABI and can't be
changed -- or at least making a new ioctl to change it would just
result in a worse situation.

our conversion here is changed to treat the snd_pcm_mmap_control
substruct as two separate substructs at locations dependent on
endianness (since the displacement depends on endianness), using the
existing conversion framework.

src/misc/ioctl.c

index 4928281..35804f0 100644 (file)
@@ -6,6 +6,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
+#include <endian.h>
 #include "syscall.h"
 
 #define alignof(t) offsetof(struct { char c; t x; }, x)
@@ -53,7 +54,7 @@ static const struct ioctl_compat_map compat_map[] = {
        { _IOWR('A', 0x23, char[136]), _IOWR('A', 0x23, char[132]), 0, WR, 1, 0 },
        { 0, 0, 4, WR, 1, 0 }, /* snd_pcm_sync_ptr (flags only) */
        { 0, 0, 32, WR, 1, OFFS(8,12,16,24,28) }, /* snd_pcm_mmap_status */
-       { 0, 0, 8, WR, 1, OFFS(0,4) }, /* snd_pcm_mmap_control */
+       { 0, 0, 4, WR, 1, 0 }, /* snd_pcm_mmap_control (each member) */
 
        /* VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_PREPARE_BUF */
        { _IOWR('V',  9, new_misaligned(68)), _IOWR('V',  9, char[68]), 68, WR, 1, OFFS(20, 24) },
@@ -90,7 +91,11 @@ static void convert_ioctl_struct(const struct ioctl_compat_map *map, char *old,
                 * if another exception appears this needs changing. */
                convert_ioctl_struct(map+1, old, new, dir);
                convert_ioctl_struct(map+2, old+4, new+8, dir);
-               convert_ioctl_struct(map+3, old+68, new+72, dir);
+               /* snd_pcm_mmap_control, special-cased due to kernel
+                * type definition having been botched. */
+               int adj = BYTE_ORDER==BIG_ENDIAN ? 4 : 0;
+               convert_ioctl_struct(map+3, old+68, new+72+adj, dir);
+               convert_ioctl_struct(map+3, old+72, new+76+3*adj, dir);
                return;
        }
        for (int i=0; i < map->noffs; i++) {