[PATCH libaio] Fix io_pgetevents() syscall wrapper on 32-bit userland on 64-bit kernels
authorGuillem Jover <guillem@hadrons.org>
Fri, 16 Aug 2019 00:33:46 +0000 (02:33 +0200)
committerGuillem Jover <guillem@debian.org>
Thu, 24 Dec 2020 14:41:17 +0000 (14:41 +0000)
The kernel compat syscall in the kernel got introduced with a broken
layout, which requires a pointer to the actual sigset_t variable but
with the size of the running kernel, not the size of the compat code.

This means that when the wrapper sends the expected compat (32-bit)
pointer, the kernel reads a 64-bit pointer, eating with it also the
sigset size member. And then proceeds to fail the comparison of the
sigset_t size and returns EINVAL.

This really needs to be fixed in the kernel, as there's no apparent
user of the broken compat layout (from codesearch.debian.org, nor a
quick github.com search). But we have to workaround it in libaio so
that we can use kernels that have not yet been fixed.

We do that, by trying the non-broken layout (that would be used with
a 32-bit userland on a 32-bit kernel), and if that fails with -EINVAL
we retry with a structure padded to what the kernel expects.

Signed-off-by: Guillem Jover <guillem@hadrons.org>
Gbp-Pq: Name 0001-Fix-io_pgetevents-syscall-wrapper-on-32-bit-userland.patch

src/io_pgetevents.c
src/libaio.h

index e6b061468897ebacf9b339266ffe35e8908361e9..b2515f260bbf1db76e840c93ac172fa6f485f39f 100644 (file)
@@ -33,17 +33,41 @@ int io_pgetevents(io_context_t ctx, long min_nr, long nr,
                struct io_event *events, struct timespec *timeout,
                sigset_t *sigmask)
 {
-       struct {
-               unsigned long ss;
-               unsigned long ss_len;
-       } data;
+       struct io_sigset aio_sigset;
+#ifndef __LP64__
+       struct io_sigset_compat aio_sigset_compat = { 0 };
+#endif
+       int ret;
 
        if (aio_ring_is_empty(ctx, timeout))
                return 0;
 
-       data.ss = (unsigned long)sigmask;
-       data.ss_len = _NSIG / 8;
-       return __io_pgetevents(ctx, min_nr, nr, events, timeout, &data);
+       aio_sigset.ss = (unsigned long)sigmask;
+       aio_sigset.ss_len = _NSIG / 8;
+       ret = __io_pgetevents(ctx, min_nr, nr, events, timeout, &aio_sigset);
+
+#ifndef __LP64__
+       /*
+        * The compat kernel syscall got introduced with an broken layout for
+        * its sigset argument, expecting it to contain a pointer for the
+        * non-compat pointer size.
+        *
+        * To cope with this on unfixed kernels, in case we are built as a
+        * 32-bit library (which could run on a kernel with compat code) and
+        * when the syscall returns EINVAL due to the kernel not finding the
+        * sigset size member when unpacking the structure, we retry with
+        * the fixed up compat layout, which requires the padding to be
+        * zero-filled, otherwise the 64-bit pointer will contain garbage.
+        */
+       if (ret != -EINVAL)
+               return ret;
+
+       aio_sigset_compat.ss = (unsigned long)sigmask;
+       aio_sigset_compat.ss_len = _NSIG / 8;
+       ret = __io_pgetevents(ctx, min_nr, nr, events, timeout, &aio_sigset_compat);
+#endif
+
+       return ret;
 }
 #else
 int io_pgetevents(io_context_t ctx, long min_nr, long nr,
index 9695a53700c1fcbf0860cccc145bd2bfe7c9ce7c..db2c43889f1bba589a240b88bfef5f833c5c0e09 100644 (file)
@@ -152,6 +152,16 @@ struct io_event {
        PADDEDul(res2, __pad4);
 };
 
+struct io_sigset {
+       unsigned long ss;
+       unsigned long ss_len;
+};
+
+struct io_sigset_compat {
+       PADDEDptr(unsigned long ss, __ss_pad);
+       unsigned long ss_len;
+};
+
 #undef PADDED
 #undef PADDEDptr
 #undef PADDEDul