diff mbox

[RFC] PV guest kernel address randomization

Message ID 574EB74D02000078000F03E9@prv-mh.provo.novell.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jan Beulich June 1, 2016, 8:22 a.m. UTC
All,

Linux, at least for some arches, has been doing this for a while, yet
the way it's implemented means kernels running in PV Xen guests
can't leverage that functionality. Obviously the whole relocation
process could be carried out in the guest, but it seems like Xen (for
Dom0) or libxc (for DomU) are in much a better position to do at least
the initial placement right at a randomized address. That's what the
attached patch attempts to implement (which appears to be working
fine from the limited testing I can do without a full Linux side
implementation) via two new ELF notes; the other attached patch is
a mechanical prereq, just for reference.

The more interesting question is that of who to have do the
necessary relocations. Native Linux derives a custom format table
from the ELF binary, which it attaches to the end of the ELF image.
Having Xen consume such a custom format (which may change)
doesn't seem like a good idea. It being attached to the end also
means that it won't get loaded by the domain loaders, so for it to
become consumable by the guest kernel it would need to be made
part of the actual ELF image (as e.g. a new section).

The alternative I see would be to require full symbol tables and
relocation sections to be retained in the ELF binary, in which case
Xen / libxc could do the relocations, and no guest OS changes
beyond the respective build logic adjustment and the addition of
the ELF notes would be necessary.

Keeping in mind history, either guest adjustment may be a difficult
sell to Linux maintainers. How would other OSes (e.g. the BSDs)
view this? Do people have preferences either way, or other
suggestions (other than "PV is almost dead, let's not touch it")?

Jan
Instead of elf_load_bsdsyms() cooking its own variant, let's just make
the generic thing cope with different bitness (paralleling elf_uval()
and alike).
--- a/tools/xcutils/readnotes.c
+++ b/tools/xcutils/readnotes.c
@@ -162,6 +162,12 @@ static unsigned print_notes(struct elf_b
 		case XEN_ELFNOTE_PHYS32_ENTRY:
 			print_numeric_note("PHYS32_ENTRY", elf , note);
 			break;
+		case XEN_ELFNOTE_RANDOMIZE_LIMIT:
+			print_numeric_note("RANDOMIZE_LIMIT", elf, note);
+			break;
+		case XEN_ELFNOTE_RANDOMIZE_STRIDE:
+			print_numeric_note("RANDOMIZE_STRIDE", elf, note);
+			break;
 		default:
 			printf("unknown note type %#x\n",
 			       (unsigned)elf_uval(elf, note, type));
--- a/xen/common/libelf/libelf-dominfo.c
+++ b/xen/common/libelf/libelf-dominfo.c
@@ -109,6 +109,8 @@ elf_errorstatus elf_xen_parse_note(struc
         [XEN_ELFNOTE_INIT_P2M] = { "INIT_P2M", 0},
         [XEN_ELFNOTE_PADDR_OFFSET] = { "PADDR_OFFSET", 0},
         [XEN_ELFNOTE_HV_START_LOW] = { "HV_START_LOW", 0},
+        [XEN_ELFNOTE_RANDOMIZE_LIMIT] = { "RANDOMIZE_LIMIT", 0},
+        [XEN_ELFNOTE_RANDOMIZE_STRIDE] = { "RANDOMIZE_STRIDE", 0},
         [XEN_ELFNOTE_XEN_VERSION] = { "XEN_VERSION", 1},
         [XEN_ELFNOTE_GUEST_OS] = { "GUEST_OS", 1},
         [XEN_ELFNOTE_GUEST_VERSION] = { "GUEST_VERSION", 1},
@@ -203,6 +205,12 @@ elf_errorstatus elf_xen_parse_note(struc
     case XEN_ELFNOTE_HV_START_LOW:
         parms->virt_hv_start_low = val;
         break;
+    case XEN_ELFNOTE_RANDOMIZE_LIMIT:
+        parms->randomize_limit = val;
+        break;
+    case XEN_ELFNOTE_RANDOMIZE_STRIDE:
+        parms->randomize_stride = val;
+        break;
 
     case XEN_ELFNOTE_FEATURES:
         if ( elf_xen_parse_features(str, parms->f_supported,
@@ -404,6 +412,8 @@ static elf_errorstatus elf_xen_note_chec
 static elf_errorstatus elf_xen_addr_calc_check(struct elf_binary *elf,
                                    struct elf_dom_parms *parms)
 {
+    uint64_t r = 0;
+
     if ( (parms->elf_paddr_offset != UNSET_ADDR) &&
          (parms->virt_base == UNSET_ADDR) )
     {
@@ -418,6 +428,9 @@ static elf_errorstatus elf_xen_addr_calc
         parms->virt_base = 0;
         elf_msg(elf, "%s: VIRT_BASE unset, using 0x%" PRIx64 "\n",
                 __FUNCTION__, parms->virt_base);
+
+        /* Disable randomization if we had to guess virt_base. */
+        parms->randomize_stride = 0;
     }
 
     /*
@@ -439,6 +452,49 @@ static elf_errorstatus elf_xen_addr_calc
                 __FUNCTION__, parms->elf_paddr_offset);
     }
 
+    if ( parms->randomize_limit != UNSET_ADDR && parms->randomize_stride )
+    {
+        unsigned int i, n;
+
+        if ( parms->randomize_limit < elf->pstart )
+            r = (elf->pstart - parms->randomize_limit) /
+                parms->randomize_stride;
+        else if ( parms->randomize_limit > elf->pend )
+            r = (parms->randomize_limit - elf->pend) /
+                parms->randomize_stride;
+
+        r = (get_random() % (r + 1)) * parms->randomize_stride;
+
+        if ( parms->randomize_limit < elf->pstart )
+            r = -r;
+
+        elf_msg(elf, "%s: randomization displacement %#" PRIx64 "\n",
+                __FUNCTION__, r);
+
+        elf->pstart += r;
+        elf->pend += r;
+        parms->virt_hypercall += r;
+
+        /* Adjust program headers for elf_load_binary() (also see there). */
+        n = elf_uval(elf, elf->ehdr, e_phnum);
+        for ( i = 0; i < n; ++i )
+        {
+            ELF_HANDLE_DECL(elf_phdr) phdr = elf_phdr_by_index(elf, i);
+
+            if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
+                break;
+            if ( !elf_phdr_is_loadable(elf, phdr) )
+                continue;
+            elf_store_field(elf, phdr, p_paddr,
+                            elf_uval(elf, phdr, p_paddr) + r);
+            /*
+             * Don't update p_vaddr: We don't use it, and at least various
+             * 64-bit Linux versions have their per-CPU data outside the
+             * "normal" VA range (and that VA must not be adjusted).
+             */
+        }
+    }
+
     parms->virt_offset = parms->virt_base - parms->elf_paddr_offset;
     parms->virt_kstart = elf->pstart + parms->virt_offset;
     parms->virt_kend   = elf->pend   + parms->virt_offset;
@@ -446,6 +502,8 @@ static elf_errorstatus elf_xen_addr_calc
     if ( parms->virt_entry == UNSET_ADDR )
         parms->virt_entry = elf_uval(elf, elf->ehdr, e_entry);
 
+    parms->virt_entry += r;
+
     if ( parms->bsd_symtab )
     {
         elf_parse_bsdsyms(elf, elf->pend);
@@ -504,6 +562,7 @@ elf_errorstatus elf_xen_parse(struct elf
     parms->p2m_base = UNSET_ADDR;
     parms->elf_paddr_offset = UNSET_ADDR;
     parms->phys_entry = UNSET_ADDR32;
+    parms->randomize_limit = UNSET_ADDR;
 
     /* Find and parse elf notes. */
     count = elf_phdr_count(elf);
--- a/xen/common/libelf/libelf-private.h
+++ b/xen/common/libelf/libelf-private.h
@@ -86,6 +86,8 @@ do { strncpy((d),(s),sizeof((d))-1);
      (d)[sizeof((d))-1] = '\0';                 \
 } while (0)
 
+#define get_random() ((unsigned int)rand())
+
 #endif
 
 #undef memcpy
--- a/xen/include/public/elfnote.h
+++ b/xen/include/public/elfnote.h
@@ -210,9 +210,23 @@
 #define XEN_ELFNOTE_PHYS32_ENTRY 18
 
 /*
+ * The physical address boundary of the randomization range.  The
+ * other range boundary is the physical address the image got linked
+ * for (i.e. the lowest ELF program header physical address), and the
+ * value here may be both below or above that value.
+ */
+#define XEN_ELFNOTE_RANDOMIZE_LIMIT 19
+
+/*
+ * The stride to use for base address randomization.  A value of zero
+ * here has the same effect as this note being absent.
+ */
+#define XEN_ELFNOTE_RANDOMIZE_STRIDE 20
+
+/*
  * The number of the highest elfnote defined.
  */
-#define XEN_ELFNOTE_MAX XEN_ELFNOTE_PHYS32_ENTRY
+#define XEN_ELFNOTE_MAX XEN_ELFNOTE_RANDOMIZE_STRIDE
 
 /*
  * System information exported through crash notes.
--- a/xen/include/xen/libelf.h
+++ b/xen/include/xen/libelf.h
@@ -435,6 +435,8 @@ struct elf_dom_parms {
     uint64_t virt_hv_start_low;
     uint64_t p2m_base;
     uint64_t elf_paddr_offset;
+    uint64_t randomize_limit;
+    uint64_t randomize_stride;
     uint32_t f_supported[XENFEAT_NR_SUBMAPS];
     uint32_t f_required[XENFEAT_NR_SUBMAPS];
     uint32_t phys_entry;
diff mbox

Patch

--- a/xen/common/libelf/libelf-loader.c
+++ b/xen/common/libelf/libelf-loader.c
@@ -273,14 +273,6 @@  static void elf_load_bsdsyms(struct elf_
     if ( !elf->bsd_symtab_pstart )
         return;
 
-#define elf_store_field_bitness(_elf, _hdr, _elm, _val)             \
-do {                                                                \
-    if ( elf_64bit(_elf) )                                          \
-        elf_store_field(_elf, _hdr, e64._elm, _val);                \
-    else                                                            \
-        elf_store_field(_elf, _hdr, e32._elm, _val);                \
-} while ( 0 )
-
 #define SYMTAB_INDEX    1
 #define STRTAB_INDEX    2
 
@@ -308,16 +300,16 @@  do {
                     elf_uval(elf, elf->ehdr, e_ehsize));
 
     /* Set the offset to the shdr array. */
-    elf_store_field_bitness(elf, header_handle, e_shoff,
-                            offsetof(typeof(header.elf_header), section));
+    elf_store_field(elf, header_handle, e_shoff,
+                    offsetof(typeof(header.elf_header), section));
 
     /* Set the right number of section headers. */
-    elf_store_field_bitness(elf, header_handle, e_shnum, ELF_BSDSYM_SECTIONS);
+    elf_store_field(elf, header_handle, e_shnum, ELF_BSDSYM_SECTIONS);
 
     /* Clear a couple of fields we don't use. */
-    elf_store_field_bitness(elf, header_handle, e_phoff, 0);
-    elf_store_field_bitness(elf, header_handle, e_phentsize, 0);
-    elf_store_field_bitness(elf, header_handle, e_phnum, 0);
+    elf_store_field(elf, header_handle, e_phoff, 0);
+    elf_store_field(elf, header_handle, e_phentsize, 0);
+    elf_store_field(elf, header_handle, e_phnum, 0);
 
     /* Zero the undefined section. */
     section_handle = ELF_MAKE_HANDLE(elf_shdr,
@@ -354,10 +346,9 @@  do {
     }
 
     /* Adjust the sh_offset and sh_link of the copied section header. */
-    elf_store_field_bitness(elf, section_handle, sh_offset,
-                            symtab_base - elf_header_base);
-    elf_store_field_bitness(elf, section_handle, sh_link,
-                            STRTAB_INDEX);
+    elf_store_field(elf, section_handle, sh_offset,
+                    symtab_base - elf_header_base);
+    elf_store_field(elf, section_handle, sh_link, STRTAB_INDEX);
 
     /* Calculate the guest address where strtab is loaded. */
     strtab_base = elf_round_up(elf, symtab_base +
@@ -387,8 +378,8 @@  do {
         return;
     }
 
-    elf_store_field_bitness(elf, section_handle, sh_offset,
-                            strtab_base - elf_header_base);
+    elf_store_field(elf, section_handle, sh_offset,
+                    strtab_base - elf_header_base);
 
     /* Store the whole size (including headers and loaded sections). */
     header.size = strtab_base + elf_uval(elf, section_handle, sh_size) -
@@ -409,7 +400,6 @@  do {
 
 #undef SYMTAB_INDEX
 #undef STRTAB_INDEX
-#undef elf_store_field_bitness
 }
 
 void elf_parse_binary(struct elf_binary *elf)
--- a/xen/include/xen/libelf.h
+++ b/xen/include/xen/libelf.h
@@ -303,9 +303,13 @@  bool elf_access_ok(struct elf_binary * e
   /* Stores a value at a particular PTRVAL. */
 
 #define elf_store_field(elf, hdr, elm, val)                             \
-    (elf_store_val((elf), ELF__HANDLE_FIELD_TYPE(hdr, elm),                   \
-                   ELF_HANDLE_PTRVAL(hdr) + ELF__HANDLE_FIELD_OFFSET(hdr, elm), \
-                   (val)))
+    ((ELFCLASS64 == (elf)->class)                                       \
+     ? elf_store_val(elf, ELF__HANDLE_FIELD_TYPE(hdr, e64.elm),         \
+                     ELF_HANDLE_PTRVAL(hdr) +                           \
+                     ELF__HANDLE_FIELD_OFFSET(hdr, e64.elm), val)       \
+     : elf_store_val(elf, ELF__HANDLE_FIELD_TYPE(hdr, e32.elm),         \
+                     ELF_HANDLE_PTRVAL(hdr) +                           \
+                     ELF__HANDLE_FIELD_OFFSET(hdr, e32.elm), val))
   /* Stores a 32/64-bit field.  hdr is a HANDLE and elm is the field name. */