*
  *     0x210           fpsimd_context
  *      0x10           esr_context
+ *      0x20           extra_context (optional)
  *      0x10           terminator (null _aarch64_ctx)
  *
- *     0xdd0           (reserved for future allocation)
+ *     0xdb0           (reserved for future allocation)
  *
  * New records that can exceed this space need to be opt-in for userspace, so
  * that an expanded signal frame is not generated unexpectedly.  The mechanism
        __u64 esr;
 };
 
+/*
+ * extra_context: describes extra space in the signal frame for
+ * additional structures that don't fit in sigcontext.__reserved[].
+ *
+ * Note:
+ *
+ * 1) fpsimd_context, esr_context and extra_context must be placed in
+ * sigcontext.__reserved[] if present.  They cannot be placed in the
+ * extra space.  Any other record can be placed either in the extra
+ * space or in sigcontext.__reserved[], unless otherwise specified in
+ * this file.
+ *
+ * 2) There must not be more than one extra_context.
+ *
+ * 3) If extra_context is present, it must be followed immediately in
+ * sigcontext.__reserved[] by the terminating null _aarch64_ctx.
+ *
+ * 4) The extra space to which datap points must start at the first
+ * 16-byte aligned address immediately after the terminating null
+ * _aarch64_ctx that follows the extra_context structure in
+ * __reserved[].  The extra space may overrun the end of __reserved[],
+ * as indicated by a sufficiently large value for the size field.
+ *
+ * 5) The extra space must itself be terminated with a null
+ * _aarch64_ctx.
+ */
+#define EXTRA_MAGIC    0x45585401
+
+struct extra_context {
+       struct _aarch64_ctx head;
+       __u64 datap; /* 16-byte aligned pointer to extra space cast to __u64 */
+       __u32 size; /* size in bytes of the extra space */
+       __u32 __reserved[3];
+};
+
 #endif /* _UAPI__ASM_SIGCONTEXT_H */
 
 #include <linux/freezer.h>
 #include <linux/stddef.h>
 #include <linux/uaccess.h>
+#include <linux/sizes.h>
 #include <linux/string.h>
 #include <linux/tracehook.h>
 #include <linux/ratelimit.h>
 
        unsigned long fpsimd_offset;
        unsigned long esr_offset;
+       unsigned long extra_offset;
        unsigned long end_offset;
 };
 
+#define BASE_SIGFRAME_SIZE round_up(sizeof(struct rt_sigframe), 16)
+#define TERMINATOR_SIZE round_up(sizeof(struct _aarch64_ctx), 16)
+#define EXTRA_CONTEXT_SIZE round_up(sizeof(struct extra_context), 16)
+
 static void init_user_layout(struct rt_sigframe_user_layout *user)
 {
+       const size_t reserved_size =
+               sizeof(user->sigframe->uc.uc_mcontext.__reserved);
+
        memset(user, 0, sizeof(*user));
        user->size = offsetof(struct rt_sigframe, uc.uc_mcontext.__reserved);
 
-       user->limit = user->size +
-               sizeof(user->sigframe->uc.uc_mcontext.__reserved) -
-               round_up(sizeof(struct _aarch64_ctx), 16);
-               /* ^ reserve space for terminator */
+       user->limit = user->size + reserved_size;
+
+       user->limit -= TERMINATOR_SIZE;
+       user->limit -= EXTRA_CONTEXT_SIZE;
+       /* Reserve space for extension and terminator ^ */
 }
 
 static size_t sigframe_size(struct rt_sigframe_user_layout const *user)
        return round_up(max(user->size, sizeof(struct rt_sigframe)), 16);
 }
 
+/*
+ * Sanity limit on the approximate maximum size of signal frame we'll
+ * try to generate.  Stack alignment padding and the frame record are
+ * not taken into account.  This limit is not a guarantee and is
+ * NOT ABI.
+ */
+#define SIGFRAME_MAXSZ SZ_64K
+
+static int __sigframe_alloc(struct rt_sigframe_user_layout *user,
+                           unsigned long *offset, size_t size, bool extend)
+{
+       size_t padded_size = round_up(size, 16);
+
+       if (padded_size > user->limit - user->size &&
+           !user->extra_offset &&
+           extend) {
+               int ret;
+
+               user->limit += EXTRA_CONTEXT_SIZE;
+               ret = __sigframe_alloc(user, &user->extra_offset,
+                                      sizeof(struct extra_context), false);
+               if (ret) {
+                       user->limit -= EXTRA_CONTEXT_SIZE;
+                       return ret;
+               }
+
+               /* Reserve space for the __reserved[] terminator */
+               user->size += TERMINATOR_SIZE;
+
+               /*
+                * Allow expansion up to SIGFRAME_MAXSZ, ensuring space for
+                * the terminator:
+                */
+               user->limit = SIGFRAME_MAXSZ - TERMINATOR_SIZE;
+       }
+
+       /* Still not enough space?  Bad luck! */
+       if (padded_size > user->limit - user->size)
+               return -ENOMEM;
+
+       *offset = user->size;
+       user->size += padded_size;
+
+       return 0;
+}
+
 /*
  * Allocate space for an optional record of <size> bytes in the user
  * signal frame.  The offset from the signal frame base address to the
 static int sigframe_alloc(struct rt_sigframe_user_layout *user,
                          unsigned long *offset, size_t size)
 {
-       size_t padded_size = round_up(size, 16);
+       return __sigframe_alloc(user, offset, size, true);
+}
 
-       *offset = user->size;
-       user->size += padded_size;
+/* Allocate the null terminator record and prevent further allocations */
+static int sigframe_alloc_end(struct rt_sigframe_user_layout *user)
+{
+       int ret;
+
+       /* Un-reserve the space reserved for the terminator: */
+       user->limit += TERMINATOR_SIZE;
 
+       ret = sigframe_alloc(user, &user->end_offset,
+                            sizeof(struct _aarch64_ctx));
+       if (ret)
+               return ret;
+
+       /* Prevent further allocation: */
+       user->limit = user->size;
        return 0;
 }
 
        char __user *base = (char __user *)&sc->__reserved;
        size_t offset = 0;
        size_t limit = sizeof(sc->__reserved);
+       bool have_extra_context = false;
+       char const __user *const sfp = (char const __user *)sf;
 
        user->fpsimd = NULL;
 
        while (1) {
                int err = 0;
                u32 magic, size;
+               char const __user *userp;
+               struct extra_context const __user *extra;
+               u64 extra_datap;
+               u32 extra_size;
+               struct _aarch64_ctx const __user *end;
+               u32 end_magic, end_size;
 
                if (limit - offset < sizeof(*head))
                        goto invalid;
                        /* ignore */
                        break;
 
+               case EXTRA_MAGIC:
+                       if (have_extra_context)
+                               goto invalid;
+
+                       if (size < sizeof(*extra))
+                               goto invalid;
+
+                       userp = (char const __user *)head;
+
+                       extra = (struct extra_context const __user *)userp;
+                       userp += size;
+
+                       __get_user_error(extra_datap, &extra->datap, err);
+                       __get_user_error(extra_size, &extra->size, err);
+                       if (err)
+                               return err;
+
+                       /* Check for the dummy terminator in __reserved[]: */
+
+                       if (limit - offset - size < TERMINATOR_SIZE)
+                               goto invalid;
+
+                       end = (struct _aarch64_ctx const __user *)userp;
+                       userp += TERMINATOR_SIZE;
+
+                       __get_user_error(end_magic, &end->magic, err);
+                       __get_user_error(end_size, &end->size, err);
+                       if (err)
+                               return err;
+
+                       if (end_magic || end_size)
+                               goto invalid;
+
+                       /* Prevent looping/repeated parsing of extra_context */
+                       have_extra_context = true;
+
+                       base = (__force void __user *)extra_datap;
+                       if (!IS_ALIGNED((unsigned long)base, 16))
+                               goto invalid;
+
+                       if (!IS_ALIGNED(extra_size, 16))
+                               goto invalid;
+
+                       if (base != userp)
+                               goto invalid;
+
+                       /* Reject "unreasonably large" frames: */
+                       if (extra_size > sfp + SIGFRAME_MAXSZ - userp)
+                               goto invalid;
+
+                       /*
+                        * Ignore trailing terminator in __reserved[]
+                        * and start parsing extra data:
+                        */
+                       offset = 0;
+                       limit = extra_size;
+                       continue;
+
                default:
                        goto invalid;
                }
                        return err;
        }
 
-       /*
-        * Allocate space for the terminator record.
-        * HACK: here we undo the reservation of space for the end record.
-        * This bodge should be replaced with a cleaner approach later on.
-        */
-       user->limit = offsetof(struct rt_sigframe, uc.uc_mcontext.__reserved) +
-               sizeof(user->sigframe->uc.uc_mcontext.__reserved);
-
-       err = sigframe_alloc(user, &user->end_offset,
-                            sizeof(struct _aarch64_ctx));
-       return err;
+       return sigframe_alloc_end(user);
 }
 
 
                __put_user_error(current->thread.fault_code, &esr_ctx->esr, err);
        }
 
+       if (err == 0 && user->extra_offset) {
+               char __user *sfp = (char __user *)user->sigframe;
+               char __user *userp =
+                       apply_user_offset(user, user->extra_offset);
+
+               struct extra_context __user *extra;
+               struct _aarch64_ctx __user *end;
+               u64 extra_datap;
+               u32 extra_size;
+
+               extra = (struct extra_context __user *)userp;
+               userp += EXTRA_CONTEXT_SIZE;
+
+               end = (struct _aarch64_ctx __user *)userp;
+               userp += TERMINATOR_SIZE;
+
+               /*
+                * extra_datap is just written to the signal frame.
+                * The value gets cast back to a void __user *
+                * during sigreturn.
+                */
+               extra_datap = (__force u64)userp;
+               extra_size = sfp + round_up(user->size, 16) - userp;
+
+               __put_user_error(EXTRA_MAGIC, &extra->head.magic, err);
+               __put_user_error(EXTRA_CONTEXT_SIZE, &extra->head.size, err);
+               __put_user_error(extra_datap, &extra->datap, err);
+               __put_user_error(extra_size, &extra->size, err);
+
+               /* Add the terminator */
+               __put_user_error(0, &end->magic, err);
+               __put_user_error(0, &end->size, err);
+       }
+
        /* set the "end" magic */
        if (err == 0) {
                struct _aarch64_ctx __user *end =