arm64: signal: Allow expansion of the signal frame
authorDave Martin <Dave.Martin@arm.com>
Tue, 20 Jun 2017 17:23:39 +0000 (18:23 +0100)
committerWill Deacon <will.deacon@arm.com>
Fri, 23 Jun 2017 17:20:18 +0000 (18:20 +0100)
This patch defines an extra_context signal frame record that can be
used to describe an expanded signal frame, and modifies the context
block allocator and signal frame setup and parsing code to create,
populate, parse and decode this block as necessary.

To avoid abuse by userspace, parse_user_sigframe() attempts to
ensure that:

 * no more than one extra_context is accepted;
 * the extra context data is a sensible size, and properly placed
   and aligned.

The extra_context data is required to start at the first 16-byte
aligned address immediately after the dummy terminator record
following extra_context in rt_sigframe.__reserved[] (as ensured
during signal delivery).  This serves as a sanity-check that the
signal frame has not been moved or copied without taking the extra
data into account.

Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
[will: add __force annotation when casting extra_datap to __user pointer]
Signed-off-by: Will Deacon <will.deacon@arm.com>
arch/arm64/include/uapi/asm/sigcontext.h
arch/arm64/kernel/signal.c

index 1328a2c143716e8cb2bcd427dc90f05ce4cdd3f3..f0a76b9fcd6e3528b3dabf938e18238f8355527c 100644 (file)
@@ -41,9 +41,10 @@ struct sigcontext {
  *
  *     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
@@ -80,4 +81,39 @@ struct esr_context {
        __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 */
index fa787e6ac7c2423f78cf85509699717170be03db..089c3747995dc40b726fbe9f53450ddb5c350f95 100644 (file)
@@ -25,6 +25,7 @@
 #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>
@@ -60,18 +61,27 @@ struct rt_sigframe_user_layout {
 
        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)
@@ -79,6 +89,52 @@ 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
@@ -87,11 +143,24 @@ static size_t sigframe_size(struct rt_sigframe_user_layout const *user)
 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;
 }
 
@@ -162,6 +231,8 @@ static int parse_user_sigframe(struct user_ctxs *user,
        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;
 
@@ -171,6 +242,12 @@ static int parse_user_sigframe(struct user_ctxs *user,
        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;
@@ -208,6 +285,64 @@ static int parse_user_sigframe(struct user_ctxs *user,
                        /* 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;
                }
@@ -318,17 +453,7 @@ static int setup_sigframe_layout(struct rt_sigframe_user_layout *user)
                        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);
 }
 
 
@@ -369,6 +494,40 @@ static int setup_sigframe(struct rt_sigframe_user_layout *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 =