diff mbox

[v5,06/16] livepatch: ARM/x86: Check displacement of old_addr and new_addr

Message ID 1474479154-20991-7-git-send-email-konrad.wilk@oracle.com (mailing list archive)
State New, archived
Headers show

Commit Message

Konrad Rzeszutek Wilk Sept. 21, 2016, 5:32 p.m. UTC
If the distance is too big 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 fortunately 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.
    Move most of the docs in "livepatch: Initial ARM64 support."
    Drop the cast on assigning ARCH_LIVEPATCH_RANGE to an variable.
---
 docs/misc/livepatch.markdown    |  3 +++
 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     | 17 ++++++++++++++++-
 6 files changed, 38 insertions(+), 1 deletion(-)

Comments

Julien Grall Sept. 22, 2016, 12:55 p.m. UTC | #1
Hi Konrad,

On 21/09/16 18:32, Konrad Rzeszutek Wilk wrote:
> 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.*/

NIT: There is no j revision of the ARMv7 manual. The latest on is 0406C.c.

With that:

Reviewed-by: Julien Grall <julien.grall@arm.com>

Regards,
Ross Lagerwall Sept. 23, 2016, 2:36 p.m. UTC | #2
On 09/21/2016 06:32 PM, Konrad Rzeszutek Wilk wrote:
> If the distance is too big 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 fortunately we are not using that.

Wouldn't this be simpler as a BUILD_BUG_ON() in arch_livepatch_init()?

Something like BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) - 
XEN_VIRT_START) > ARCH_LIVEPATCH_RANGE)?

And BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) - XEN_VIRT_START) 
 > ARCH_LIVEPATCH_RANGE)

And something similar for ARM...
Konrad Rzeszutek Wilk Sept. 23, 2016, 3:37 p.m. UTC | #3
On Fri, Sep 23, 2016 at 03:36:27PM +0100, Ross Lagerwall wrote:
> On 09/21/2016 06:32 PM, Konrad Rzeszutek Wilk wrote:
> > If the distance is too big 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 fortunately we are not using that.
> 
> Wouldn't this be simpler as a BUILD_BUG_ON() in arch_livepatch_init()?
> 
> Something like BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) -
> XEN_VIRT_START) > ARCH_LIVEPATCH_RANGE)?
> 
> And BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) - XEN_VIRT_START) >
> ARCH_LIVEPATCH_RANGE)
> 
> And something similar for ARM...

What does that have to with the payload? The displacement calculations(checks)
are done when we load the payload.
> 
> -- 
> Ross Lagerwall
Konrad Rzeszutek Wilk Sept. 23, 2016, 3:59 p.m. UTC | #4
On Fri, Sep 23, 2016 at 11:37:27AM -0400, Konrad Rzeszutek Wilk wrote:
> On Fri, Sep 23, 2016 at 03:36:27PM +0100, Ross Lagerwall wrote:
> > On 09/21/2016 06:32 PM, Konrad Rzeszutek Wilk wrote:
> > > If the distance is too big 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 fortunately we are not using that.
> > 
> > Wouldn't this be simpler as a BUILD_BUG_ON() in arch_livepatch_init()?
> > 
> > Something like BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) -
> > XEN_VIRT_START) > ARCH_LIVEPATCH_RANGE)?
> > 
> > And BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) - XEN_VIRT_START) >
> > ARCH_LIVEPATCH_RANGE)
> > 
> > And something similar for ARM...
> 
> What does that have to with the payload? The displacement calculations(checks)
> are done when we load the payload.

Ooh, you are thinking of just making sure that the displacement in virtual
addresses _should_ be OK.

And that BUILD_BUG_ON would certainly do it.

But my thinking was more of the payload either having some wacky relocations (say
having an initial addendum that along with the XEN_VIRT_START -> XEN_VIRT_END)
causes us to be way past the right place (especially with ARM32 where we only
have 32MB distance). And having this extra check makes me sleep better at night.

Adding the BUILD_BUG_ON as a futher check is fine, but as a different patch.

> > 
> > -- 
> > Ross Lagerwall
Ross Lagerwall Sept. 28, 2016, 10:21 a.m. UTC | #5
On 09/23/2016 04:59 PM, Konrad Rzeszutek Wilk wrote:
> On Fri, Sep 23, 2016 at 11:37:27AM -0400, Konrad Rzeszutek Wilk wrote:
>> On Fri, Sep 23, 2016 at 03:36:27PM +0100, Ross Lagerwall wrote:
>>> On 09/21/2016 06:32 PM, Konrad Rzeszutek Wilk wrote:
>>>> If the distance is too big 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 fortunately we are not using that.
>>>
>>> Wouldn't this be simpler as a BUILD_BUG_ON() in arch_livepatch_init()?
>>>
>>> Something like BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) -
>>> XEN_VIRT_START) > ARCH_LIVEPATCH_RANGE)?
>>>
>>> And BUILD_BUG_ON(((XEN_VIRT_END - NR_CPUS * PAGE_SIZE) - XEN_VIRT_START) >
>>> ARCH_LIVEPATCH_RANGE)
>>>
>>> And something similar for ARM...
>>
>> What does that have to with the payload? The displacement calculations(checks)
>> are done when we load the payload.
>
> Ooh, you are thinking of just making sure that the displacement in virtual
> addresses _should_ be OK.
>
> And that BUILD_BUG_ON would certainly do it.
>
> But my thinking was more of the payload either having some wacky relocations (say
> having an initial addendum that along with the XEN_VIRT_START -> XEN_VIRT_END)
> causes us to be way past the right place (especially with ARM32 where we only
> have 32MB distance). And having this extra check makes me sleep better at night.

Assuming that you had build checked the maximum possible displacement in 
virtual address space, then the only way a payload would fail the check 
is if either old_addr or new_addr doesn't point to hypervisor code or 
payload code (otherwise it would be in the range), but instead points to 
some other bit of memory. In that case, the payload is so completely 
broken you've got bigger problems...

There are many ways that payloads that can be broken. This checks only a 
special case of the payload being broken -- and only if the incorrect 
bit exceeds a certain range...


I don't really object to the check but it seems like an unnecessarily 
special case.

Cheers,
diff mbox

Patch

diff --git a/docs/misc/livepatch.markdown b/docs/misc/livepatch.markdown
index ff2cfb8..16f45f3 100644
--- a/docs/misc/livepatch.markdown
+++ b/docs/misc/livepatch.markdown
@@ -1151,6 +1151,9 @@  with proper offset is used for an unconditional branch to the new code.
 This means that that `old_size` **MUST** be at least four bytes if patching
 in trampoline.
 
+The instruction offset is limited on ARM32 to +/- 32MB to 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.
 
diff --git a/xen/arch/arm/arm64/livepatch.c b/xen/arch/arm/arm64/livepatch.c
index 7cb1812..d3fc9ac 100644
--- a/xen/arch/arm/arm64/livepatch.c
+++ b/xen/arch/arm/arm64/livepatch.c
@@ -40,6 +40,7 @@  void arch_livepatch_apply(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 66f23e0..f2f866c 100644
--- a/xen/common/livepatch.c
+++ b/xen/common/livepatch.c
@@ -545,6 +545,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 b7f66d4..b7b84e7 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
 
 /*
@@ -79,6 +80,21 @@  unsigned int 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 = 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.
@@ -103,7 +119,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;