/* * parse xen-specific informations out of elf kernel binaries. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libelf-private.h" /* ------------------------------------------------------------------------ */ /* xen features */ static const char *const elf_xen_feature_names[] = { [XENFEAT_writable_page_tables] = "writable_page_tables", [XENFEAT_writable_descriptor_tables] = "writable_descriptor_tables", [XENFEAT_auto_translated_physmap] = "auto_translated_physmap", [XENFEAT_supervisor_mode_kernel] = "supervisor_mode_kernel", [XENFEAT_pae_pgdir_above_4gb] = "pae_pgdir_above_4gb", [XENFEAT_hvm_callback_vector] = "hvm_callback_vector", [XENFEAT_dom0] = "dom0" }; static const unsigned elf_xen_features = sizeof(elf_xen_feature_names) / sizeof(elf_xen_feature_names[0]); elf_errorstatus elf_xen_parse_features(const char *features, uint32_t *supported, uint32_t *required) { unsigned char feature[64]; unsigned pos, len, i; if ( features == NULL ) return 0; for ( pos = 0; features[pos] != '\0'; pos += len ) { elf_memset_unchecked(feature, 0, sizeof(feature)); for ( len = 0;; len++ ) { if ( len >= sizeof(feature)-1 ) break; if ( features[pos + len] == '\0' ) break; if ( features[pos + len] == '|' ) { len++; break; } feature[len] = features[pos + len]; } for ( i = 0; i < elf_xen_features; i++ ) { if ( !elf_xen_feature_names[i] ) continue; if ( feature[0] == '!' ) { /* required */ if ( !strcmp(feature + 1, elf_xen_feature_names[i]) ) { elf_xen_feature_set(i, supported); if ( required ) elf_xen_feature_set(i, required); break; } } else { /* supported */ if ( !strcmp(feature, elf_xen_feature_names[i]) ) { elf_xen_feature_set(i, supported); break; } } } if ( i == elf_xen_features && required && feature[0] == '!' ) return -1; } return 0; } /* ------------------------------------------------------------------------ */ /* xen elf notes */ elf_errorstatus elf_xen_parse_note(struct elf_binary *elf, struct elf_dom_parms *parms, ELF_HANDLE_DECL(elf_note) note) { /* *INDENT-OFF* */ static const struct { char *name; bool str; } note_desc[] = { [XEN_ELFNOTE_ENTRY] = { "ENTRY", 0}, [XEN_ELFNOTE_HYPERCALL_PAGE] = { "HYPERCALL_PAGE", 0}, [XEN_ELFNOTE_VIRT_BASE] = { "VIRT_BASE", 0}, [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_XEN_VERSION] = { "XEN_VERSION", 1}, [XEN_ELFNOTE_GUEST_OS] = { "GUEST_OS", 1}, [XEN_ELFNOTE_GUEST_VERSION] = { "GUEST_VERSION", 1}, [XEN_ELFNOTE_LOADER] = { "LOADER", 1}, [XEN_ELFNOTE_PAE_MODE] = { "PAE_MODE", 1}, [XEN_ELFNOTE_FEATURES] = { "FEATURES", 1}, [XEN_ELFNOTE_SUPPORTED_FEATURES] = { "SUPPORTED_FEATURES", 0}, [XEN_ELFNOTE_BSD_SYMTAB] = { "BSD_SYMTAB", 1}, [XEN_ELFNOTE_SUSPEND_CANCEL] = { "SUSPEND_CANCEL", 0 }, [XEN_ELFNOTE_MOD_START_PFN] = { "MOD_START_PFN", 0 }, }; /* *INDENT-ON* */ const char *str = NULL; uint64_t val = 0; unsigned int i; unsigned type = elf_uval(elf, note, type); if ( (type >= sizeof(note_desc) / sizeof(note_desc[0])) || (note_desc[type].name == NULL) ) { elf_msg(elf, "%s: unknown xen elf note (0x%x)\n", __FUNCTION__, type); return 0; } if ( note_desc[type].str ) { str = elf_strval(elf, elf_note_desc(elf, note)); if (str == NULL) /* elf_strval will mark elf broken if it fails so no need to log */ return 0; elf_msg(elf, "%s: %s = \"%s\"\n", __FUNCTION__, note_desc[type].name, str); parms->elf_notes[type].type = XEN_ENT_STR; parms->elf_notes[type].data.str = str; } else { val = elf_note_numeric(elf, note); elf_msg(elf, "%s: %s = 0x%" PRIx64 "\n", __FUNCTION__, note_desc[type].name, val); parms->elf_notes[type].type = XEN_ENT_LONG; parms->elf_notes[type].data.num = val; } parms->elf_notes[type].name = note_desc[type].name; switch ( type ) { case XEN_ELFNOTE_LOADER: safe_strcpy(parms->loader, str); break; case XEN_ELFNOTE_GUEST_OS: safe_strcpy(parms->guest_os, str); break; case XEN_ELFNOTE_GUEST_VERSION: safe_strcpy(parms->guest_ver, str); break; case XEN_ELFNOTE_XEN_VERSION: safe_strcpy(parms->xen_ver, str); break; case XEN_ELFNOTE_PAE_MODE: if ( !strcmp(str, "yes") ) parms->pae = 2 /* extended_cr3 */; if ( strstr(str, "bimodal") ) parms->pae = 3 /* bimodal */; break; case XEN_ELFNOTE_BSD_SYMTAB: if ( !strcmp(str, "yes") ) parms->bsd_symtab = 1; break; case XEN_ELFNOTE_VIRT_BASE: parms->virt_base = val; break; case XEN_ELFNOTE_ENTRY: parms->virt_entry = val; break; case XEN_ELFNOTE_INIT_P2M: parms->p2m_base = val; break; case XEN_ELFNOTE_PADDR_OFFSET: parms->elf_paddr_offset = val; break; case XEN_ELFNOTE_HYPERCALL_PAGE: parms->virt_hypercall = val; break; case XEN_ELFNOTE_HV_START_LOW: parms->virt_hv_start_low = val; break; case XEN_ELFNOTE_FEATURES: if ( elf_xen_parse_features(str, parms->f_supported, parms->f_required) ) return -1; break; case XEN_ELFNOTE_SUPPORTED_FEATURES: for ( i = 0; i < XENFEAT_NR_SUBMAPS; ++i ) parms->f_supported[i] |= elf_note_numeric_array( elf, note, sizeof(*parms->f_supported), i); break; } return 0; } #define ELF_NOTE_INVALID (~0U) static unsigned elf_xen_parse_notes(struct elf_binary *elf, struct elf_dom_parms *parms, elf_ptrval start, elf_ptrval end, unsigned *total_note_count) { unsigned xen_elfnotes = 0; ELF_HANDLE_DECL(elf_note) note; const char *note_name; parms->elf_note_start = start; parms->elf_note_end = end; for ( note = ELF_MAKE_HANDLE(elf_note, parms->elf_note_start); ELF_HANDLE_PTRVAL(note) < parms->elf_note_end; note = elf_note_next(elf, note) ) { if ( *total_note_count >= ELF_MAX_TOTAL_NOTE_COUNT ) { elf_mark_broken(elf, "too many ELF notes"); break; } (*total_note_count)++; note_name = elf_note_name(elf, note); if ( note_name == NULL ) continue; if ( strcmp(note_name, "Xen") ) continue; if ( elf_xen_parse_note(elf, parms, note) ) return ELF_NOTE_INVALID; xen_elfnotes++; } return xen_elfnotes; } /* ------------------------------------------------------------------------ */ /* __xen_guest section */ elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf, struct elf_dom_parms *parms) { elf_ptrval h; unsigned char name[32], value[128]; unsigned len; h = parms->guest_info; #define STAR(h) (elf_access_unsigned(elf, (h), 0, 1)) while ( STAR(h) ) { elf_memset_unchecked(name, 0, sizeof(name)); elf_memset_unchecked(value, 0, sizeof(value)); for ( len = 0;; len++, h++ ) { if ( len >= sizeof(name)-1 ) break; if ( STAR(h) == '\0' ) break; if ( STAR(h) == ',' ) { h++; break; } if ( STAR(h) == '=' ) { h++; for ( len = 0;; len++, h++ ) { if ( len >= sizeof(value)-1 ) break; if ( STAR(h) == '\0' ) break; if ( STAR(h) == ',' ) { h++; break; } value[len] = STAR(h); } break; } name[len] = STAR(h); } elf_msg(elf, "%s: %s=\"%s\"\n", __FUNCTION__, name, value); /* strings */ if ( !strcmp(name, "LOADER") ) safe_strcpy(parms->loader, value); if ( !strcmp(name, "GUEST_OS") ) safe_strcpy(parms->guest_os, value); if ( !strcmp(name, "GUEST_VER") ) safe_strcpy(parms->guest_ver, value); if ( !strcmp(name, "XEN_VER") ) safe_strcpy(parms->xen_ver, value); if ( !strcmp(name, "PAE") ) { if ( !strcmp(value, "yes[extended-cr3]") ) parms->pae = 2 /* extended_cr3 */; else if ( !strncmp(value, "yes", 3) ) parms->pae = 1 /* yes */; } if ( !strcmp(name, "BSD_SYMTAB") ) parms->bsd_symtab = 1; /* longs */ if ( !strcmp(name, "VIRT_BASE") ) parms->virt_base = strtoull(value, NULL, 0); if ( !strcmp(name, "VIRT_ENTRY") ) parms->virt_entry = strtoull(value, NULL, 0); if ( !strcmp(name, "ELF_PADDR_OFFSET") ) parms->elf_paddr_offset = strtoull(value, NULL, 0); if ( !strcmp(name, "HYPERCALL_PAGE") ) parms->virt_hypercall = (strtoull(value, NULL, 0) << 12) + parms->virt_base; /* other */ if ( !strcmp(name, "FEATURES") ) if ( elf_xen_parse_features(value, parms->f_supported, parms->f_required) ) return -1; } return 0; } /* ------------------------------------------------------------------------ */ /* sanity checks */ static elf_errorstatus elf_xen_note_check(struct elf_binary *elf, struct elf_dom_parms *parms) { if ( (ELF_PTRVAL_INVALID(parms->elf_note_start)) && (ELF_PTRVAL_INVALID(parms->guest_info)) ) { unsigned machine = elf_uval(elf, elf->ehdr, e_machine); if ( (machine == EM_386) || (machine == EM_X86_64) ) { elf_err(elf, "%s: ERROR: Not a Xen-ELF image: " "No ELF notes or '__xen_guest' section found.\n", __FUNCTION__); return -1; } return 0; } if ( elf_uval(elf, elf->ehdr, e_machine) == EM_ARM ) { elf_msg(elf, "%s: Not bothering with notes on ARM\n", __FUNCTION__); return 0; } /* Check the contents of the Xen notes or guest string. */ if ( ((strlen(parms->loader) == 0) || strncmp(parms->loader, "generic", 7)) && ((strlen(parms->guest_os) == 0) || strncmp(parms->guest_os, "linux", 5)) ) { elf_err(elf, "%s: ERROR: Will only load images built for the generic " "loader or Linux images", __FUNCTION__); return -1; } if ( (strlen(parms->xen_ver) == 0) || strncmp(parms->xen_ver, "xen-3.0", 7) ) { elf_err(elf, "%s: ERROR: Xen will only load images built " "for Xen v3.0\n", __FUNCTION__); return -1; } return 0; } static elf_errorstatus elf_xen_addr_calc_check(struct elf_binary *elf, struct elf_dom_parms *parms) { if ( (parms->elf_paddr_offset != UNSET_ADDR) && (parms->virt_base == UNSET_ADDR) ) { elf_err(elf, "%s: ERROR: ELF_PADDR_OFFSET set, VIRT_BASE unset\n", __FUNCTION__); return -1; } /* Initial guess for virt_base is 0 if it is not explicitly defined. */ if ( parms->virt_base == UNSET_ADDR ) { parms->virt_base = 0; elf_msg(elf, "%s: VIRT_BASE unset, using 0x%" PRIx64 "\n", __FUNCTION__, parms->virt_base); } /* * If we are using the legacy __xen_guest section then elf_pa_off * defaults to v_start in order to maintain compatibility with * older hypervisors which set padd in the ELF header to * virt_base. * * If we are using the modern ELF notes interface then the default * is 0. */ if ( parms->elf_paddr_offset == UNSET_ADDR ) { if ( parms->elf_note_start ) parms->elf_paddr_offset = 0; else parms->elf_paddr_offset = parms->virt_base; elf_msg(elf, "%s: ELF_PADDR_OFFSET unset, using 0x%" PRIx64 "\n", __FUNCTION__, parms->elf_paddr_offset); } 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; if ( parms->virt_entry == UNSET_ADDR ) parms->virt_entry = elf_uval(elf, elf->ehdr, e_entry); if ( parms->bsd_symtab ) { elf_parse_bsdsyms(elf, parms->virt_kend); if ( elf->bsd_symtab_pend ) parms->virt_kend = elf->bsd_symtab_pend + parms->virt_offset; } elf_msg(elf, "%s: addresses:\n", __FUNCTION__); elf_msg(elf, " virt_base = 0x%" PRIx64 "\n", parms->virt_base); elf_msg(elf, " elf_paddr_offset = 0x%" PRIx64 "\n", parms->elf_paddr_offset); elf_msg(elf, " virt_offset = 0x%" PRIx64 "\n", parms->virt_offset); elf_msg(elf, " virt_kstart = 0x%" PRIx64 "\n", parms->virt_kstart); elf_msg(elf, " virt_kend = 0x%" PRIx64 "\n", parms->virt_kend); elf_msg(elf, " virt_entry = 0x%" PRIx64 "\n", parms->virt_entry); elf_msg(elf, " p2m_base = 0x%" PRIx64 "\n", parms->p2m_base); if ( (parms->virt_kstart > parms->virt_kend) || (parms->virt_entry < parms->virt_kstart) || (parms->virt_entry > parms->virt_kend) || (parms->virt_base > parms->virt_kstart) ) { elf_err(elf, "%s: ERROR: ELF start or entries are out of bounds.\n", __FUNCTION__); return -1; } if ( (parms->p2m_base != UNSET_ADDR) && (parms->p2m_base >= parms->virt_kstart) && (parms->p2m_base < parms->virt_kend) ) { elf_err(elf, "%s: ERROR: P->M table base is out of bounds.\n", __FUNCTION__); return -1; } return 0; } /* ------------------------------------------------------------------------ */ /* glue it all together ... */ elf_errorstatus elf_xen_parse(struct elf_binary *elf, struct elf_dom_parms *parms) { ELF_HANDLE_DECL(elf_shdr) shdr; ELF_HANDLE_DECL(elf_phdr) phdr; unsigned xen_elfnotes = 0; unsigned i, count, more_notes; unsigned total_note_count = 0; elf_memset_unchecked(parms, 0, sizeof(*parms)); parms->virt_base = UNSET_ADDR; parms->virt_entry = UNSET_ADDR; parms->virt_hypercall = UNSET_ADDR; parms->virt_hv_start_low = UNSET_ADDR; parms->p2m_base = UNSET_ADDR; parms->elf_paddr_offset = UNSET_ADDR; /* Find and parse elf notes. */ count = elf_phdr_count(elf); for ( i = 0; i < count; i++ ) { phdr = elf_phdr_by_index(elf, i); if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) ) /* input has an insane program header count field */ break; if ( elf_uval(elf, phdr, p_type) != PT_NOTE ) continue; /* * Some versions of binutils do not correctly set p_offset for * note segments. */ if (elf_uval(elf, phdr, p_offset) == 0) continue; more_notes = elf_xen_parse_notes(elf, parms, elf_segment_start(elf, phdr), elf_segment_end(elf, phdr), &total_note_count); if ( more_notes == ELF_NOTE_INVALID ) return -1; xen_elfnotes += more_notes; } /* * Fall back to any SHT_NOTE sections if no valid note segments * were found. */ if ( xen_elfnotes == 0 ) { count = elf_shdr_count(elf); for ( i = 0; i < count; i++ ) { shdr = elf_shdr_by_index(elf, i); if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) ) /* input has an insane section header count field */ break; if ( elf_uval(elf, shdr, sh_type) != SHT_NOTE ) continue; more_notes = elf_xen_parse_notes(elf, parms, elf_section_start(elf, shdr), elf_section_end(elf, shdr), &total_note_count); if ( more_notes == ELF_NOTE_INVALID ) return -1; if ( xen_elfnotes == 0 && more_notes > 0 ) elf_msg(elf, "%s: using notes from SHT_NOTE section\n", __FUNCTION__); xen_elfnotes += more_notes; } } /* * Finally fall back to the __xen_guest section. */ if ( xen_elfnotes == 0 ) { shdr = elf_shdr_by_name(elf, "__xen_guest"); if ( ELF_HANDLE_VALID(shdr) ) { parms->guest_info = elf_section_start(elf, shdr); parms->elf_note_start = ELF_INVALID_PTRVAL; parms->elf_note_end = ELF_INVALID_PTRVAL; elf_msg(elf, "%s: __xen_guest: \"%s\"\n", __FUNCTION__, elf_strfmt(elf, parms->guest_info)); elf_xen_parse_guest_info(elf, parms); } } if ( elf_xen_note_check(elf, parms) != 0 ) return -1; if ( elf_xen_addr_calc_check(elf, parms) != 0 ) return -1; return 0; } /* * Local variables: * mode: C * c-file-style: "BSD" * c-basic-offset: 4 * tab-width: 4 * indent-tabs-mode: nil * End: */