diff mbox series

[v2,3/3] xen/livepatch: Fix .altinstructions safety checks

Message ID 20230417121357.3738919-4-andrew.cooper3@citrix.com (mailing list archive)
State New, archived
Headers show
Series xen/livepatch: Fix .altinstructions safety checks | expand

Commit Message

Andrew Cooper April 17, 2023, 12:13 p.m. UTC
The prior check has && vs || mixups, making it tautologically false and thus
providing no safety at all.  There are boundary errors too.

First start with a comment describing how the .altinstructions and
.altinstr_replacement sections interact, and perform suitable cross-checking.

Second, rewrite the alt_instr loop entirely from scratch.  Origin sites have
non-zero size, and must be fully contained within the livepatches .text
section(s).  Any non-zero sized replacements must be fully contained within
the .altinstr_replacement section.

Fixes: f8a10174e8b1 ("xsplice: Add support for alternatives")
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
CC: Ross Lagerwall <ross.lagerwall@citrix.com>

v2:
 * Rebase over prior patches to keep the ARM build working
 * Tweak commit message and comments for clarity

As a further observation, .altinstr_replacement shouldn't survive beyond its
use in apply_alternatives(), but the disp32 relative references (for x86 at
least) in alt_instr force .altinstr_replacement to be close to the payload
while being applied.
---
 xen/common/livepatch.c       | 68 ++++++++++++++++++++++++++++++++----
 xen/include/xen/elfstructs.h |  2 ++
 2 files changed, 64 insertions(+), 6 deletions(-)

Comments

Jan Beulich April 17, 2023, 12:35 p.m. UTC | #1
On 17.04.2023 14:13, Andrew Cooper wrote:
> --- a/xen/common/livepatch.c
> +++ b/xen/common/livepatch.c
> @@ -803,28 +803,84 @@ static int prepare_payload(struct payload *payload,
>      if ( sec )
>      {
>  #ifdef CONFIG_HAS_ALTERNATIVE
> +        /*
> +         * (As of April 2023), Alternatives are formed of:
> +         * - An .altinstructions section with an array of struct alt_instr's.
> +         * - An .altinstr_replacement section containing instructions.
> +         *
> +         * An individual alt_instr contains:
> +         * - An orig reference, pointing into .text with a nonzero length
> +         * - A repl reference, pointing into .altinstr_replacement
> +         *
> +         * It is legal to have zero-length replacements, meaning it is legal
> +         * for the .altinstr_replacement section to be empty too.  An
> +         * implementation detail means that a zero-length replacement's repl
> +         * reference will still be in the .altinstr_replacement section.

Didn't you agree that "will" is not really true, and it's at best "may", but
then also doesn't really matter here in the first place (suggesting that the
sentence might best be dropped, to avoid drawing attention to something that
might at best confuse the reader as to its relevance)?

Jan
Andrew Cooper April 17, 2023, 1:37 p.m. UTC | #2
On 17/04/2023 1:35 pm, Jan Beulich wrote:
> On 17.04.2023 14:13, Andrew Cooper wrote:
>> --- a/xen/common/livepatch.c
>> +++ b/xen/common/livepatch.c
>> @@ -803,28 +803,84 @@ static int prepare_payload(struct payload *payload,
>>      if ( sec )
>>      {
>>  #ifdef CONFIG_HAS_ALTERNATIVE
>> +        /*
>> +         * (As of April 2023), Alternatives are formed of:
>> +         * - An .altinstructions section with an array of struct alt_instr's.
>> +         * - An .altinstr_replacement section containing instructions.
>> +         *
>> +         * An individual alt_instr contains:
>> +         * - An orig reference, pointing into .text with a nonzero length
>> +         * - A repl reference, pointing into .altinstr_replacement
>> +         *
>> +         * It is legal to have zero-length replacements, meaning it is legal
>> +         * for the .altinstr_replacement section to be empty too.  An
>> +         * implementation detail means that a zero-length replacement's repl
>> +         * reference will still be in the .altinstr_replacement section.
> Didn't you agree that "will" is not really true, and it's at best "may", but
> then also doesn't really matter here in the first place (suggesting that the
> sentence might best be dropped, to avoid drawing attention to something that
> might at best confuse the reader as to its relevance)?

Only that "will be at 0" wasn't actually true.

Right now, the repl reference *will* be somewhere in
altinstr_replacement.  It is discussed here because it is what the check
enforces.

As an implementation detail, it is of course free to change in the
future if needs be.

~Andrew
Jan Beulich April 17, 2023, 1:48 p.m. UTC | #3
On 17.04.2023 15:37, Andrew Cooper wrote:
> On 17/04/2023 1:35 pm, Jan Beulich wrote:
>> On 17.04.2023 14:13, Andrew Cooper wrote:
>>> --- a/xen/common/livepatch.c
>>> +++ b/xen/common/livepatch.c
>>> @@ -803,28 +803,84 @@ static int prepare_payload(struct payload *payload,
>>>      if ( sec )
>>>      {
>>>  #ifdef CONFIG_HAS_ALTERNATIVE
>>> +        /*
>>> +         * (As of April 2023), Alternatives are formed of:
>>> +         * - An .altinstructions section with an array of struct alt_instr's.
>>> +         * - An .altinstr_replacement section containing instructions.
>>> +         *
>>> +         * An individual alt_instr contains:
>>> +         * - An orig reference, pointing into .text with a nonzero length
>>> +         * - A repl reference, pointing into .altinstr_replacement
>>> +         *
>>> +         * It is legal to have zero-length replacements, meaning it is legal
>>> +         * for the .altinstr_replacement section to be empty too.  An
>>> +         * implementation detail means that a zero-length replacement's repl
>>> +         * reference will still be in the .altinstr_replacement section.
>> Didn't you agree that "will" is not really true, and it's at best "may", but
>> then also doesn't really matter here in the first place (suggesting that the
>> sentence might best be dropped, to avoid drawing attention to something that
>> might at best confuse the reader as to its relevance)?
> 
> Only that "will be at 0" wasn't actually true.

Oh, right - I'm sorry for the noise.

Jan

> Right now, the repl reference *will* be somewhere in
> altinstr_replacement.  It is discussed here because it is what the check
> enforces.
> 
> As an implementation detail, it is of course free to change in the
> future if needs be.
> 
> ~Andrew
Ross Lagerwall April 18, 2023, 4:25 p.m. UTC | #4
> From: Andrew Cooper <andrew.cooper3@citrix.com>
> Sent: Monday, April 17, 2023 1:13 PM
> To: Xen-devel <xen-devel@lists.xenproject.org>
> Cc: Andrew Cooper <Andrew.Cooper3@citrix.com>; Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>; Ross Lagerwall <ross.lagerwall@citrix.com>
> Subject: [PATCH v2 3/3] xen/livepatch: Fix .altinstructions safety checks 
>  
> The prior check has && vs || mixups, making it tautologically false and thus
> providing no safety at all.  There are boundary errors too.
> 
> First start with a comment describing how the .altinstructions and
> .altinstr_replacement sections interact, and perform suitable cross-checking.
> 
> Second, rewrite the alt_instr loop entirely from scratch.  Origin sites have
> non-zero size, and must be fully contained within the livepatches .text
> section(s).  Any non-zero sized replacements must be fully contained within
> the .altinstr_replacement section.
> 
> Fixes: f8a10174e8b1 ("xsplice: Add support for alternatives")
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> ---

Reviewed-by: Ross Lagerwall <ross.lagerwall@citrix.com>
diff mbox series

Patch

diff --git a/xen/common/livepatch.c b/xen/common/livepatch.c
index c10ab1f374e0..004b5a436569 100644
--- a/xen/common/livepatch.c
+++ b/xen/common/livepatch.c
@@ -803,28 +803,84 @@  static int prepare_payload(struct payload *payload,
     if ( sec )
     {
 #ifdef CONFIG_HAS_ALTERNATIVE
+        /*
+         * (As of April 2023), Alternatives are formed of:
+         * - An .altinstructions section with an array of struct alt_instr's.
+         * - An .altinstr_replacement section containing instructions.
+         *
+         * An individual alt_instr contains:
+         * - An orig reference, pointing into .text with a nonzero length
+         * - A repl reference, pointing into .altinstr_replacement
+         *
+         * It is legal to have zero-length replacements, meaning it is legal
+         * for the .altinstr_replacement section to be empty too.  An
+         * implementation detail means that a zero-length replacement's repl
+         * reference will still be in the .altinstr_replacement section.
+         */
+        const struct livepatch_elf_sec *repl_sec;
         struct alt_instr *a, *start, *end;
 
         if ( !section_ok(elf, sec, sizeof(*a)) )
             return -EINVAL;
 
+        /* Tolerate an empty .altinstructions section... */
+        if ( sec->sec->sh_size == 0 )
+            goto alt_done;
+
+        /* ... but otherwise, there needs to be something to alter... */
+        if ( payload->text_size == 0 )
+        {
+            printk(XENLOG_ERR LIVEPATCH "%s Alternatives provided, but no .text\n",
+                   elf->name);
+            return -EINVAL;
+        }
+
+        /* ... and something to be altered to. */
+        repl_sec = livepatch_elf_sec_by_name(elf, ".altinstr_replacement");
+        if ( !repl_sec )
+        {
+            printk(XENLOG_ERR LIVEPATCH "%s .altinstructions provided, but no .altinstr_replacement\n",
+                   elf->name);
+            return -EINVAL;
+        }
+
         start = sec->load_addr;
         end = sec->load_addr + sec->sec->sh_size;
 
         for ( a = start; a < end; a++ )
         {
-            const void *instr = ALT_ORIG_PTR(a);
-            const void *replacement = ALT_REPL_PTR(a);
+            const void *orig = ALT_ORIG_PTR(a);
+            const void *repl = ALT_REPL_PTR(a);
+
+            /* orig must be fully within .text. */
+            if ( orig               < payload->text_addr ||
+                 a->orig_len        > payload->text_size ||
+                 orig + a->orig_len > payload->text_addr + payload->text_size )
+            {
+                printk(XENLOG_ERR LIVEPATCH
+                       "%s Alternative orig %p+%#x outside payload text %p+%#zx\n",
+                       elf->name, orig, a->orig_len,
+                       payload->text_addr, payload->text_size);
+                return -EINVAL;
+            }
 
-            if ( (instr < region->start && instr >= region->end) ||
-                 (replacement < region->start && replacement >= region->end) )
+            /*
+             * repl must be fully within .altinstr_replacement, even if the
+             * replacement and the section happen to both have zero length.
+             */
+            if ( repl               < repl_sec->load_addr ||
+                 a->repl_len        > repl_sec->sec->sh_size ||
+                 repl + a->repl_len > repl_sec->load_addr + repl_sec->sec->sh_size )
             {
-                printk(XENLOG_ERR LIVEPATCH "%s Alt patching outside payload: %p\n",
-                       elf->name, instr);
+                printk(XENLOG_ERR LIVEPATCH
+                       "%s Alternative repl %p+%#x outside .altinstr_replacement %p+%#"PRIxElfWord"\n",
+                       elf->name, repl, a->repl_len,
+                       repl_sec->load_addr, repl_sec->sec->sh_size);
                 return -EINVAL;
             }
         }
         apply_alternatives(start, end);
+    alt_done:;
 #else
         printk(XENLOG_ERR LIVEPATCH "%s: We don't support alternative patching\n",
                elf->name);
diff --git a/xen/include/xen/elfstructs.h b/xen/include/xen/elfstructs.h
index 3124469faeb4..eb6b87a823a8 100644
--- a/xen/include/xen/elfstructs.h
+++ b/xen/include/xen/elfstructs.h
@@ -563,6 +563,7 @@  typedef struct {
 #if defined(ELFSIZE) && (ELFSIZE == 32)
 #define PRIxElfAddr 	PRIx32
 #define PRIuElfWord 	PRIu32
+#define PRIxElfWord 	PRIx32
 
 #define Elf_Ehdr	Elf32_Ehdr
 #define Elf_Phdr	Elf32_Phdr
@@ -591,6 +592,7 @@  typedef struct {
 #elif defined(ELFSIZE) && (ELFSIZE == 64)
 #define PRIxElfAddr	PRIx64
 #define PRIuElfWord	PRIu64
+#define PRIxElfWord	PRIx64
 
 #define Elf_Ehdr	Elf64_Ehdr
 #define Elf_Phdr	Elf64_Phdr