summaryrefslogtreecommitdiff
path: root/scripts/mod/modpost.c
diff options
context:
space:
mode:
authorQuentin Casasnovas <quentin.casasnovas@oracle.com>2015-04-13 20:52:53 +0930
committerRusty Russell <rusty@rustcorp.com.au>2015-04-13 21:03:03 +0930
commit52dc0595d540155436d91811f929bdc8afd6a2a1 (patch)
tree9e90d8e5753fb53747bc6767156750dc659cb0c4 /scripts/mod/modpost.c
parentc31e4b832f124dccdb5d80ba3c1cd4f9081f7fb2 (diff)
downloadlwn-52dc0595d540155436d91811f929bdc8afd6a2a1.tar.gz
lwn-52dc0595d540155436d91811f929bdc8afd6a2a1.zip
modpost: handle relocations mismatch in __ex_table.
__ex_table is a simple table section where each entry is a pair of addresses - the first address is an address which can fault in kernel space, and the second address points to where the kernel should jump to when handling that fault. This is how copy_from_user() does not crash the kernel if userspace gives a borked pointer for example. If one of these addresses point to a non-executable section, something is seriously wrong since it either means the kernel will never fault from there or it will not be able to jump to there. As both cases are serious enough, we simply error out in these cases so the build fails and the developper has to fix the issue. In case the section is executable, but it isn't referenced in our list of authorized sections to point to from __ex_table, we just dump a warning giving more information about it. We do this in case the new section is executable but isn't supposed to be executed by the kernel. This happened with .altinstr_replacement, which is executable but is only used to copy instructions from - we should never have our instruction pointer pointing in .altinstr_replacement. Admitedly, a proper fix in that case would be to just set .altinstr_replacement NX, but we need to warn about future cases like this. Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com> Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> (added long casts)
Diffstat (limited to 'scripts/mod/modpost.c')
-rw-r--r--scripts/mod/modpost.c141
1 files changed, 141 insertions, 0 deletions
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index bf0cf8173beb..e95aa28ce0f7 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -875,6 +875,8 @@ static void check_section(const char *modname, struct elf_info *elf,
#define DATA_SECTIONS ".data", ".data.rel"
#define TEXT_SECTIONS ".text", ".text.unlikely", ".sched.text", \
".kprobes.text"
+#define OTHER_TEXT_SECTIONS ".ref.text", ".head.text", ".spinlock.text", \
+ ".fixup", ".entry.text"
#define INIT_SECTIONS ".init.*"
#define MEM_INIT_SECTIONS ".meminit.*"
@@ -882,6 +884,9 @@ static void check_section(const char *modname, struct elf_info *elf,
#define EXIT_SECTIONS ".exit.*"
#define MEM_EXIT_SECTIONS ".memexit.*"
+#define ALL_TEXT_SECTIONS ALL_INIT_TEXT_SECTIONS, ALL_EXIT_TEXT_SECTIONS, \
+ TEXT_SECTIONS, OTHER_TEXT_SECTIONS
+
/* init data sections */
static const char *const init_data_sections[] =
{ ALL_INIT_DATA_SECTIONS, NULL };
@@ -922,6 +927,7 @@ enum mismatch {
ANY_INIT_TO_ANY_EXIT,
ANY_EXIT_TO_ANY_INIT,
EXPORT_TO_INIT_EXIT,
+ EXTABLE_TO_NON_TEXT,
};
struct sectioncheck {
@@ -936,6 +942,11 @@ struct sectioncheck {
};
+static void extable_mismatch_handler(const char *modname, struct elf_info *elf,
+ const struct sectioncheck* const mismatch,
+ Elf_Rela *r, Elf_Sym *sym,
+ const char *fromsec);
+
static const struct sectioncheck sectioncheck[] = {
/* Do not reference init/exit code/data from
* normal code and data
@@ -1013,6 +1024,16 @@ static const struct sectioncheck sectioncheck[] = {
.bad_tosec = { INIT_SECTIONS, EXIT_SECTIONS, NULL },
.mismatch = EXPORT_TO_INIT_EXIT,
.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },
+},
+{
+ .fromsec = { "__ex_table", NULL },
+ /* If you're adding any new black-listed sections in here, consider
+ * adding a special 'printer' for them in scripts/check_extable.
+ */
+ .bad_tosec = { ".altinstr_replacement", NULL },
+ .good_tosec = {ALL_TEXT_SECTIONS , NULL},
+ .mismatch = EXTABLE_TO_NON_TEXT,
+ .handler = extable_mismatch_handler,
}
};
@@ -1418,6 +1439,10 @@ static void report_sec_mismatch(const char *modname,
tosym, prl_to, prl_to, tosym);
free(prl_to);
break;
+ case EXTABLE_TO_NON_TEXT:
+ fatal("There's a special handler for this mismatch type, "
+ "we should never get here.");
+ break;
}
fprintf(stderr, "\n");
}
@@ -1453,6 +1478,120 @@ static void default_mismatch_handler(const char *modname, struct elf_info *elf,
}
}
+static int is_executable_section(struct elf_info* elf, unsigned int section_index)
+{
+ if (section_index > elf->num_sections)
+ fatal("section_index is outside elf->num_sections!\n");
+
+ return ((elf->sechdrs[section_index].sh_flags & SHF_EXECINSTR) == SHF_EXECINSTR);
+}
+
+/*
+ * We rely on a gross hack in section_rel[a]() calling find_extable_entry_size()
+ * to know the sizeof(struct exception_table_entry) for the target architecture.
+ */
+static unsigned int extable_entry_size = 0;
+static void find_extable_entry_size(const char* const sec, const Elf_Rela* r,
+ const void* start, const void* cur)
+{
+ /*
+ * If we're currently checking the second relocation within __ex_table,
+ * that relocation offset tells us the offsetof(struct
+ * exception_table_entry, fixup) which is equal to sizeof(struct
+ * exception_table_entry) divided by two. We use that to our advantage
+ * since there's no portable way to get that size as every architecture
+ * seems to go with different sized types. Not pretty but better than
+ * hard-coding the size for every architecture..
+ */
+ if (!extable_entry_size && cur == start + 1 &&
+ strcmp("__ex_table", sec) == 0)
+ extable_entry_size = r->r_offset * 2;
+}
+static inline bool is_extable_fault_address(Elf_Rela *r)
+{
+ if (!extable_entry_size == 0)
+ fatal("extable_entry size hasn't been discovered!\n");
+
+ return ((r->r_offset == 0) ||
+ (r->r_offset % extable_entry_size == 0));
+}
+
+static void report_extable_warnings(const char* modname, struct elf_info* elf,
+ const struct sectioncheck* const mismatch,
+ Elf_Rela* r, Elf_Sym* sym,
+ const char* fromsec, const char* tosec)
+{
+ Elf_Sym* fromsym = find_elf_symbol2(elf, r->r_offset, fromsec);
+ const char* fromsym_name = sym_name(elf, fromsym);
+ Elf_Sym* tosym = find_elf_symbol(elf, r->r_addend, sym);
+ const char* tosym_name = sym_name(elf, tosym);
+ const char* from_pretty_name;
+ const char* from_pretty_name_p;
+ const char* to_pretty_name;
+ const char* to_pretty_name_p;
+
+ get_pretty_name(is_function(fromsym),
+ &from_pretty_name, &from_pretty_name_p);
+ get_pretty_name(is_function(tosym),
+ &to_pretty_name, &to_pretty_name_p);
+
+ warn("%s(%s+0x%lx): Section mismatch in reference"
+ " from the %s %s%s to the %s %s:%s%s\n",
+ modname, fromsec, (long)r->r_offset, from_pretty_name,
+ fromsym_name, from_pretty_name_p,
+ to_pretty_name, tosec, tosym_name, to_pretty_name_p);
+
+ if (!match(tosec, mismatch->bad_tosec) &&
+ is_executable_section(elf, get_secindex(elf, sym)))
+ fprintf(stderr,
+ "The relocation at %s+0x%lx references\n"
+ "section \"%s\" which is not in the list of\n"
+ "authorized sections. If you're adding a new section\n"
+ "and/or if this reference is valid, add \"%s\" to the\n"
+ "list of authorized sections to jump to on fault.\n"
+ "This can be achieved by adding \"%s\" to \n"
+ "OTHER_TEXT_SECTIONS in scripts/mod/modpost.c.\n",
+ fromsec, (long)r->r_offset, tosec, tosec, tosec);
+}
+
+static void extable_mismatch_handler(const char* modname, struct elf_info *elf,
+ const struct sectioncheck* const mismatch,
+ Elf_Rela* r, Elf_Sym* sym,
+ const char *fromsec)
+{
+ const char* tosec = sec_name(elf, get_secindex(elf, sym));
+
+ sec_mismatch_count++;
+
+ if (sec_mismatch_verbose)
+ report_extable_warnings(modname, elf, mismatch, r, sym,
+ fromsec, tosec);
+
+ if (match(tosec, mismatch->bad_tosec))
+ fatal("The relocation at %s+0x%lx references\n"
+ "section \"%s\" which is black-listed.\n"
+ "Something is seriously wrong and should be fixed.\n"
+ "You might get more information about where this is\n"
+ "coming from by using scripts/check_extable.sh %s\n",
+ fromsec, (long)r->r_offset, tosec, modname);
+ else if (!is_executable_section(elf, get_secindex(elf, sym))) {
+ if (is_extable_fault_address(r))
+ fatal("The relocation at %s+0x%lx references\n"
+ "section \"%s\" which is not executable, IOW\n"
+ "it is not possible for the kernel to fault\n"
+ "at that address. Something is seriously wrong\n"
+ "and should be fixed.\n",
+ fromsec, (long)r->r_offset, tosec);
+ else
+ fatal("The relocation at %s+0x%lx references\n"
+ "section \"%s\" which is not executable, IOW\n"
+ "the kernel will fault if it ever tries to\n"
+ "jump to it. Something is seriously wrong\n"
+ "and should be fixed.\n",
+ fromsec, (long)r->r_offset, tosec);
+ }
+}
+
static void check_section_mismatch(const char *modname, struct elf_info *elf,
Elf_Rela *r, Elf_Sym *sym, const char *fromsec)
{
@@ -1605,6 +1744,7 @@ static void section_rela(const char *modname, struct elf_info *elf,
/* Skip special sections */
if (is_shndx_special(sym->st_shndx))
continue;
+ find_extable_entry_size(fromsec, &r, start, rela);
check_section_mismatch(modname, elf, &r, sym, fromsec);
}
}
@@ -1663,6 +1803,7 @@ static void section_rel(const char *modname, struct elf_info *elf,
/* Skip special sections */
if (is_shndx_special(sym->st_shndx))
continue;
+ find_extable_entry_size(fromsec, &r, start, rel);
check_section_mismatch(modname, elf, &r, sym, fromsec);
}
}