diff mbox

[v4,05/16] livepatch: ARM/x86: Check displacement of old_addr and new_addr

Message ID 1474043908-12101-6-git-send-email-konrad.wilk@oracle.com (mailing list archive)
State New, archived
Headers show

Commit Message

Konrad Rzeszutek Wilk Sept. 16, 2016, 4:38 p.m. UTC
If the distance is too great we are in trouble - as our relocation
distance can surely be clipped, or still have a valid width - but
cause an overflow of distance.

On various architectures the maximum displacement for a unconditional
branch/jump varies. ARM32 is +/- 32MB, ARM64 is +/- 128MB while x86
for 32-bit relocations is +/- 2G.

Note: On x86 we could use the 64-bit jmpq instruction which
would provide much bigger displacement to do a jump, but we would
still have issues with the new function not being able to reach
any of the old functions (as all the relocations would assume 32-bit
displacement). And "furthermore would require an register or
memory location to load/store the address to." (From Jan).

On ARM the conditional branch supports even a smaller displacement
but fortunatly we are not using that.

Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>

---
Cc: Andrew Cooper <andrew.cooper3@citrix.com>
Cc: Jan Beulich <jbeulich@suse.com>
Cc: Stefano Stabellini <sstabellini@kernel.org>
Cc: Julien Grall <julien.grall@arm.com>

v3: New submission.
v4: s/arch_livepatch_verify_distance/livepatch_verify_distance/
    s/LIVEPATCH_ARCH_RANGE/ARCH_LIVEPATCH_RANGE/
v5: Updated commit description with Jan's comment
    Ditch the casting of long on calculating offset.
---
 docs/misc/livepatch.markdown    | 14 +++++++++++++-
 xen/arch/arm/arm64/livepatch.c  |  1 +
 xen/common/livepatch.c          |  4 ++++
 xen/include/asm-arm/livepatch.h | 11 +++++++++++
 xen/include/asm-x86/livepatch.h |  3 +++
 xen/include/xen/livepatch.h     | 19 +++++++++++++++++--
 6 files changed, 49 insertions(+), 3 deletions(-)

Comments

Jan Beulich Sept. 19, 2016, 9:19 a.m. UTC | #1
>>> On 16.09.16 at 18:38, <konrad.wilk@oracle.com> wrote:
> If the distance is too great we are in trouble - as our relocation

s/great/big/ (or large), as mentioned before?

> @@ -68,7 +69,7 @@ int arch_livepatch_secure(const void *va, unsigned int pages, enum va_type types
>  void arch_livepatch_init(void);
>  
>  #include <public/sysctl.h> /* For struct livepatch_func. */
> -#include <asm/livepatch.h> /* For ARCH_PATCH_INSN_SIZE. */
> +#include <asm/livepatch.h> /* For ARCH_[PATCH_INSN_SIZE, LIVEPATCH_RANGE]. */

Perhaps better to drop the comment - it'll get unwieldy to extend it
the same way for the next addition, and it's kind of expected to
include the per-arch header here.

> @@ -78,6 +79,21 @@ static inline size_t livepatch_insn_len(const struct livepatch_func *func)
>  
>      return ARCH_PATCH_INSN_SIZE;
>  }
> +
> +static inline int livepatch_verify_distance(const struct livepatch_func *func)
> +{
> +    long offset;
> +    long range = (long)ARCH_LIVEPATCH_RANGE;

So you've dropped a few casts, but left this one around. What good
does it do?

Jan
Julien Grall Sept. 19, 2016, 1:12 p.m. UTC | #2
Hello Konrad,

On 16/09/2016 18:38, Konrad Rzeszutek Wilk wrote:
> If the distance is too great we are in trouble - as our relocation
> distance can surely be clipped, or still have a valid width - but
> cause an overflow of distance.
>
> On various architectures the maximum displacement for a unconditional
> branch/jump varies. ARM32 is +/- 32MB, ARM64 is +/- 128MB while x86
> for 32-bit relocations is +/- 2G.
>
> Note: On x86 we could use the 64-bit jmpq instruction which
> would provide much bigger displacement to do a jump, but we would
> still have issues with the new function not being able to reach
> any of the old functions (as all the relocations would assume 32-bit
> displacement). And "furthermore would require an register or
> memory location to load/store the address to." (From Jan).
>
> On ARM the conditional branch supports even a smaller displacement
> but fortunatly we are not using that.

s/fortunatly/fortunately/

>
> Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
>
> ---

[...]

> diff --git a/docs/misc/livepatch.markdown b/docs/misc/livepatch.markdown
> index 9e72897..5baaa0a 100644
> --- a/docs/misc/livepatch.markdown
> +++ b/docs/misc/livepatch.markdown
> @@ -1100,7 +1100,7 @@ and no .data or .bss sections.
>  The hypervisor should verify that the in-place patching would fit within
>  the code or data.
>
> -### Trampoline (e9 opcode)
> +### Trampoline (e9 opcode), x86
>
>  The e9 opcode used for jmpq uses a 32-bit signed displacement. That means
>  we are limited to up to 2GB of virtual address to place the new code
> @@ -1134,3 +1134,15 @@ that in the hypervisor is advised.
>  The tool for generating payloads currently does perform a compile-time
>  check to ensure that the function to be replaced is large enough.
>
> +The hypervisor also checks the displacement during loading of the payload.
> +
> +#### Trampoline (ea opcode), ARM
> +
> +The 0xea000000 instruction (with proper offset) is used for an unconditional
> +branch to the new code.

The opcode/encoding mentioned is wrong for AArch64. Anyway, I am not 
sure why you want to mention the opcode in the documentation. I think it 
would be enough to specify: "unconditional branch instruction (for the 
encoding see the ARM ARM).".

> This means we are limited on ARM32 to +/- 32MB
> +displacement and on ARM64 to +/- 128MB displacement.
> +
> +The new code is placed in the 8M - 10M virtual address space while the
> +Xen code is in 2M - 4M. That gives us enough space.
> +
> +The hypervisor also checks the displacement during loading of the payload.

Regards,
diff mbox

Patch

diff --git a/docs/misc/livepatch.markdown b/docs/misc/livepatch.markdown
index 9e72897..5baaa0a 100644
--- a/docs/misc/livepatch.markdown
+++ b/docs/misc/livepatch.markdown
@@ -1100,7 +1100,7 @@  and no .data or .bss sections.
 The hypervisor should verify that the in-place patching would fit within
 the code or data.
 
-### Trampoline (e9 opcode)
+### Trampoline (e9 opcode), x86
 
 The e9 opcode used for jmpq uses a 32-bit signed displacement. That means
 we are limited to up to 2GB of virtual address to place the new code
@@ -1134,3 +1134,15 @@  that in the hypervisor is advised.
 The tool for generating payloads currently does perform a compile-time
 check to ensure that the function to be replaced is large enough.
 
+The hypervisor also checks the displacement during loading of the payload.
+
+#### Trampoline (ea opcode), ARM
+
+The 0xea000000 instruction (with proper offset) is used for an unconditional
+branch to the new code. This means we are limited on ARM32 to +/- 32MB
+displacement and on ARM64 to +/- 128MB displacement.
+
+The new code is placed in the 8M - 10M virtual address space while the
+Xen code is in 2M - 4M. That gives us enough space.
+
+The hypervisor also checks the displacement during loading of the payload.
diff --git a/xen/arch/arm/arm64/livepatch.c b/xen/arch/arm/arm64/livepatch.c
index 49eb69b..7d593b2 100644
--- a/xen/arch/arm/arm64/livepatch.c
+++ b/xen/arch/arm/arm64/livepatch.c
@@ -40,6 +40,7 @@  void arch_livepatch_apply_jmp(struct livepatch_func *func)
     else
         insn = aarch64_insn_gen_nop();
 
+    /* Verified in livepatch_verify_distance. */
     ASSERT(insn != AARCH64_BREAK_FAULT);
 
     new_ptr = func->old_addr - (void *)_start + vmap_of_xen_text;
diff --git a/xen/common/livepatch.c b/xen/common/livepatch.c
index a4ce8c7..38260b9 100644
--- a/xen/common/livepatch.c
+++ b/xen/common/livepatch.c
@@ -540,6 +540,10 @@  static int prepare_payload(struct payload *payload,
         rc = resolve_old_address(f, elf);
         if ( rc )
             return rc;
+
+        rc = livepatch_verify_distance(f);
+        if ( rc )
+            return rc;
     }
 
     sec = livepatch_elf_sec_by_name(elf, ".livepatch.hooks.load");
diff --git a/xen/include/asm-arm/livepatch.h b/xen/include/asm-arm/livepatch.h
index 929c7d9..482d74f 100644
--- a/xen/include/asm-arm/livepatch.h
+++ b/xen/include/asm-arm/livepatch.h
@@ -6,6 +6,8 @@ 
 #ifndef __XEN_ARM_LIVEPATCH_H__
 #define __XEN_ARM_LIVEPATCH_H__
 
+#include <xen/sizes.h> /* For SZ_* macros. */
+
 /* On ARM32,64 instructions are always 4 bytes long. */
 #define ARCH_PATCH_INSN_SIZE 4
 
@@ -15,6 +17,15 @@ 
  */
 extern void *vmap_of_xen_text;
 
+/* These ranges are only for unconditional branches. */
+#ifdef CONFIG_ARM_32
+/* ARM32: A4.3 IN ARM DDI 0406C.j -  we are using only ARM instructions in Xen.*/
+#define ARCH_LIVEPATCH_RANGE SZ_32M
+#else
+/* ARM64: C1.3.2 in ARM DDI 0487A.j */
+#define ARCH_LIVEPATCH_RANGE SZ_128M
+#endif
+
 #endif /* __XEN_ARM_LIVEPATCH_H__ */
 
 /*
diff --git a/xen/include/asm-x86/livepatch.h b/xen/include/asm-x86/livepatch.h
index 5e04aa1..7dfc2e7 100644
--- a/xen/include/asm-x86/livepatch.h
+++ b/xen/include/asm-x86/livepatch.h
@@ -6,7 +6,10 @@ 
 #ifndef __XEN_X86_LIVEPATCH_H__
 #define __XEN_X86_LIVEPATCH_H__
 
+#include <xen/sizes.h> /* For SZ_* macros. */
+
 #define ARCH_PATCH_INSN_SIZE 5
+#define ARCH_LIVEPATCH_RANGE SZ_2G
 
 #endif /* __XEN_X86_LIVEPATCH_H__ */
 
diff --git a/xen/include/xen/livepatch.h b/xen/include/xen/livepatch.h
index b714fbc..6ea92b5 100644
--- a/xen/include/xen/livepatch.h
+++ b/xen/include/xen/livepatch.h
@@ -12,6 +12,7 @@  struct livepatch_elf_sym;
 struct xen_sysctl_livepatch_op;
 
 #include <xen/elfstructs.h>
+#include <xen/errno.h> /* For -ENOSYS or -EOVERFLOW */
 #ifdef CONFIG_LIVEPATCH
 
 /*
@@ -68,7 +69,7 @@  int arch_livepatch_secure(const void *va, unsigned int pages, enum va_type types
 void arch_livepatch_init(void);
 
 #include <public/sysctl.h> /* For struct livepatch_func. */
-#include <asm/livepatch.h> /* For ARCH_PATCH_INSN_SIZE. */
+#include <asm/livepatch.h> /* For ARCH_[PATCH_INSN_SIZE, LIVEPATCH_RANGE]. */
 int arch_livepatch_verify_func(const struct livepatch_func *func);
 
 static inline size_t livepatch_insn_len(const struct livepatch_func *func)
@@ -78,6 +79,21 @@  static inline size_t livepatch_insn_len(const struct livepatch_func *func)
 
     return ARCH_PATCH_INSN_SIZE;
 }
+
+static inline int livepatch_verify_distance(const struct livepatch_func *func)
+{
+    long offset;
+    long range = (long)ARCH_LIVEPATCH_RANGE;
+
+    if ( !func->new_addr ) /* Ignore NOPs. */
+        return 0;
+
+    offset = func->old_addr - func->new_addr;
+    if ( offset < -range || offset >= range )
+        return -EOVERFLOW;
+
+    return 0;
+}
 /*
  * These functions are called around the critical region patching live code,
  * for an architecture to take make appropratie global state adjustments.
@@ -102,7 +118,6 @@  void arch_livepatch_unmask(void);
 #define init_or_livepatch_data        __initdata
 #define init_or_livepatch             __init
 
-#include <xen/errno.h> /* For -ENOSYS */
 static inline int livepatch_op(struct xen_sysctl_livepatch_op *op)
 {
     return -ENOSYS;