From mboxrd@z Thu Jan 1 00:00:00 1970 From: Kees Cook Date: Mon, 27 Feb 2017 12:43:05 -0800 Message-Id: <1488228186-110679-8-git-send-email-keescook@chromium.org> In-Reply-To: <1488228186-110679-1-git-send-email-keescook@chromium.org> References: <1488228186-110679-1-git-send-email-keescook@chromium.org> Subject: [kernel-hardening] [RFC][PATCH 7/8] gcc-plugins: Add constify plugin To: kernel-hardening@lists.openwall.com Cc: Kees Cook , Mark Rutland , Andy Lutomirski , Hoeun Ryu , PaX Team , Emese Revfy , Russell King , x86@kernel.org List-ID: This is a port of the PaX/grsecurity constify plugin. However, it has the automatic function-pointer struct detection temporarily disabled. As a result, this will only recognize the __do_const annotation, which makes all instances of a marked structure read-only. The rare_write() infrastructure can be used to make changes to such variables. Signed-off-by: Kees Cook --- arch/Kconfig | 13 + include/linux/compiler-gcc.h | 5 + include/linux/compiler.h | 8 + scripts/Makefile.gcc-plugins | 2 + scripts/gcc-plugins/constify_plugin.c | 585 ++++++++++++++++++++++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 scripts/gcc-plugins/constify_plugin.c diff --git a/arch/Kconfig b/arch/Kconfig index 2446de19f66d..a40e38740d8a 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -410,6 +410,19 @@ config GCC_PLUGIN_LATENT_ENTROPY * https://grsecurity.net/ * https://pax.grsecurity.net/ +config GCC_PLUGIN_CONSTIFY + bool "Make function pointer structures and marked variables read-only" + depends on GCC_PLUGINS && HAVE_ARCH_RARE_WRITE && !UML + help + By saying Y here the compiler will automatically constify a class + of types that contain only function pointers, as well as any + manually annotated structures. This reduces the kernel's attack + surface and also produces a better memory layout. + + This plugin was ported from grsecurity/PaX. More information at: + * https://grsecurity.net/ + * https://pax.grsecurity.net/ + config HAVE_CC_STACKPROTECTOR bool help diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h index 0444b1336268..edeb82a2c043 100644 --- a/include/linux/compiler-gcc.h +++ b/include/linux/compiler-gcc.h @@ -189,6 +189,11 @@ #if GCC_VERSION >= 40500 +#ifdef CONSTIFY_PLUGIN +#define __no_const __attribute__((no_const)) +#define __do_const __attribute__((do_const)) +#endif + #ifndef __CHECKER__ #ifdef LATENT_ENTROPY_PLUGIN #define __latent_entropy __attribute__((latent_entropy)) diff --git a/include/linux/compiler.h b/include/linux/compiler.h index f95603a8ee72..1510844ed6a7 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -471,6 +471,14 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s # define __latent_entropy #endif +#ifndef __do_const +# define __do_const +#endif + +#ifndef __no_const +# define __no_const +#endif + /* * Tell gcc if a function is cold. The compiler will assume any path * directly leading to the call is unlikely. diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins index 060d2cb373db..4a1d0a4c6563 100644 --- a/scripts/Makefile.gcc-plugins +++ b/scripts/Makefile.gcc-plugins @@ -5,6 +5,8 @@ ifdef CONFIG_GCC_PLUGINS SANCOV_PLUGIN := -fplugin=$(objtree)/scripts/gcc-plugins/sancov_plugin.so gcc-plugin-$(CONFIG_GCC_PLUGIN_CYC_COMPLEXITY) += cyc_complexity_plugin.so + gcc-plugin-$(CONFIG_GCC_PLUGIN_CONSTIFY) += constify_plugin.so + gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_CONSTIFY) += -DCONSTIFY_PLUGIN gcc-plugin-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += latent_entropy_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += -DLATENT_ENTROPY_PLUGIN diff --git a/scripts/gcc-plugins/constify_plugin.c b/scripts/gcc-plugins/constify_plugin.c new file mode 100644 index 000000000000..e7d6f3140e87 --- /dev/null +++ b/scripts/gcc-plugins/constify_plugin.c @@ -0,0 +1,585 @@ +/* + * Copyright 2011 by Emese Revfy + * Copyright 2011-2016 by PaX Team + * Licensed under the GPL v2, or (at your option) v3 + * + * This gcc plugin constifies all structures which contain only function pointers or are explicitly marked for constification. + * + * Homepage: + * http://www.grsecurity.net/~ephox/const_plugin/ + * + * Usage: + * $ gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared -O2 -o constify_plugin.so constify_plugin.c + * $ gcc -fplugin=constify_plugin.so test.c -O2 + */ + +#include "gcc-common.h" + +// unused C type flag in all versions 4.5-6 +#define TYPE_CONSTIFY_VISITED(TYPE) TYPE_LANG_FLAG_4(TYPE) + +__visible int plugin_is_GPL_compatible; + +static bool enabled = true; + +static struct plugin_info const_plugin_info = { + .version = "201607241840vanilla", + .help = "disable\tturn off constification\n", +}; + +static struct { + const char *name; + const char *asm_op; +} const_sections[] = { + {".init.rodata", "\t.section\t.init.rodata,\"a\""}, + {".ref.rodata", "\t.section\t.ref.rodata,\"a\""}, + {".devinit.rodata", "\t.section\t.devinit.rodata,\"a\""}, + {".devexit.rodata", "\t.section\t.devexit.rodata,\"a\""}, + {".cpuinit.rodata", "\t.section\t.cpuinit.rodata,\"a\""}, + {".cpuexit.rodata", "\t.section\t.cpuexit.rodata,\"a\""}, + {".meminit.rodata", "\t.section\t.meminit.rodata,\"a\""}, + {".memexit.rodata", "\t.section\t.memexit.rodata,\"a\""}, + {".data..read_only", "\t.section\t.data..read_only,\"a\""}, +}; + +typedef struct { + bool has_fptr_field; + bool has_writable_field; + bool has_do_const_field; + bool has_no_const_field; +} constify_info; + +static const_tree get_field_type(const_tree field) +{ + return strip_array_types(TREE_TYPE(field)); +} + +static bool is_fptr(const_tree field) +{ + /* XXX: disable automatic constification. */ + return false; + + const_tree ptr = get_field_type(field); + + if (TREE_CODE(ptr) != POINTER_TYPE) + return false; + + return TREE_CODE(TREE_TYPE(ptr)) == FUNCTION_TYPE; +} + +/* + * determine whether the given structure type meets the requirements for automatic constification, + * including the constification attributes on nested structure types + */ +static void constifiable(const_tree node, constify_info *cinfo) +{ + const_tree field; + + gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE); + + // e.g., pointer to structure fields while still constructing the structure type + if (TYPE_FIELDS(node) == NULL_TREE) + return; + + for (field = TYPE_FIELDS(node); field; field = TREE_CHAIN(field)) { + const_tree type = get_field_type(field); + enum tree_code code = TREE_CODE(type); + + if (node == type) + continue; + + if (is_fptr(field)) + cinfo->has_fptr_field = true; + else if (code == RECORD_TYPE || code == UNION_TYPE) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) + cinfo->has_do_const_field = true; + else if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type))) + cinfo->has_no_const_field = true; + else + constifiable(type, cinfo); + } else if (!TREE_READONLY(field)) + cinfo->has_writable_field = true; + } +} + +static bool constified(const_tree node) +{ + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE); + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(node))) { +// gcc_assert(!TYPE_READONLY(node)); + return false; + } + + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(node))) { + gcc_assert(TYPE_READONLY(node)); + return true; + } + + constifiable(node, &cinfo); + if ((!cinfo.has_fptr_field || cinfo.has_writable_field || cinfo.has_no_const_field) && !cinfo.has_do_const_field) + return false; + + return TYPE_READONLY(node); +} + +static void deconstify_tree(tree node); + +static void deconstify_type(tree type) +{ + tree field; + + gcc_assert(TREE_CODE(type) == RECORD_TYPE || TREE_CODE(type) == UNION_TYPE); + + for (field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) { + const_tree fieldtype = get_field_type(field); + + // special case handling of simple ptr-to-same-array-type members + if (TREE_CODE(TREE_TYPE(field)) == POINTER_TYPE) { + tree ptrtype = TREE_TYPE(TREE_TYPE(field)); + + if (TREE_TYPE(TREE_TYPE(field)) == type) + continue; + if (TREE_CODE(ptrtype) != RECORD_TYPE && TREE_CODE(ptrtype) != UNION_TYPE) + continue; + if (!constified(ptrtype)) + continue; + if (TYPE_MAIN_VARIANT(ptrtype) == TYPE_MAIN_VARIANT(type)) + TREE_TYPE(field) = build_pointer_type(build_qualified_type(type, TYPE_QUALS(ptrtype) & ~TYPE_QUAL_CONST)); + continue; + } + if (TREE_CODE(fieldtype) != RECORD_TYPE && TREE_CODE(fieldtype) != UNION_TYPE) + continue; + if (!constified(fieldtype)) + continue; + + deconstify_tree(field); + TREE_READONLY(field) = 0; + } + TYPE_READONLY(type) = 0; + C_TYPE_FIELDS_READONLY(type) = 0; + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); + TYPE_ATTRIBUTES(type) = remove_attribute("do_const", TYPE_ATTRIBUTES(type)); + } +} + +static void deconstify_tree(tree node) +{ + tree old_type, new_type, field; + + old_type = TREE_TYPE(node); + while (TREE_CODE(old_type) == ARRAY_TYPE && TREE_CODE(TREE_TYPE(old_type)) != ARRAY_TYPE) { + node = TREE_TYPE(node) = copy_node(old_type); + old_type = TREE_TYPE(old_type); + } + + gcc_assert(TREE_CODE(old_type) == RECORD_TYPE || TREE_CODE(old_type) == UNION_TYPE); + gcc_assert(TYPE_READONLY(old_type) && (TYPE_QUALS(old_type) & TYPE_QUAL_CONST)); + + new_type = build_qualified_type(old_type, TYPE_QUALS(old_type) & ~TYPE_QUAL_CONST); + TYPE_FIELDS(new_type) = copy_list(TYPE_FIELDS(new_type)); + for (field = TYPE_FIELDS(new_type); field; field = TREE_CHAIN(field)) + DECL_FIELD_CONTEXT(field) = new_type; + + deconstify_type(new_type); + + TREE_TYPE(node) = new_type; +} + +static tree handle_no_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + tree type; + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + *no_add_attrs = true; + if (TREE_CODE(*node) == FUNCTION_DECL) { + error("%qE attribute does not apply to functions (%qF)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == PARM_DECL) { + error("%qE attribute does not apply to function parameters (%qD)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == VAR_DECL) { + error("%qE attribute does not apply to variables (%qD)", name, *node); + return NULL_TREE; + } + + if (TYPE_P(*node)) { + type = *node; + } else { + if (TREE_CODE(*node) != TYPE_DECL) { + error("%qE attribute does not apply to %qD (%qT)", name, *node, TREE_TYPE(*node)); + return NULL_TREE; + } + type = TREE_TYPE(*node); + } + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) { + error("%qE attribute used on %qT applies to struct and union types only", name, type); + return NULL_TREE; + } + + if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(type))) { + error("%qE attribute is already applied to the type %qT", name, type); + return NULL_TREE; + } + + if (TYPE_P(*node)) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) + error("%qE attribute used on type %qT is incompatible with 'do_const'", name, type); + else + *no_add_attrs = false; + return NULL_TREE; + } + + constifiable(type, &cinfo); + if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + if (enabled) { + if TYPE_P(*node) + deconstify_type(*node); + else + deconstify_tree(*node); + } + if (TYPE_P(*node)) + TYPE_CONSTIFY_VISITED(*node) = 1; + else + TYPE_CONSTIFY_VISITED(TREE_TYPE(*node)) = 1; + return NULL_TREE; + } + + if (enabled && TYPE_FIELDS(type)) + error("%qE attribute used on type %qT that is not constified", name, type); + return NULL_TREE; +} + +static void constify_type(tree type) +{ + gcc_assert(type == TYPE_MAIN_VARIANT(type)); + TYPE_READONLY(type) = 1; + C_TYPE_FIELDS_READONLY(type) = 1; + TYPE_CONSTIFY_VISITED(type) = 1; +// TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); +// TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("do_const"), NULL_TREE, TYPE_ATTRIBUTES(type)); +} + +static tree handle_do_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + *no_add_attrs = true; + if (!TYPE_P(*node)) { + error("%qE attribute applies to types only (%qD)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) != RECORD_TYPE && TREE_CODE(*node) != UNION_TYPE) { + error("%qE attribute used on %qT applies to struct and union types only", name, *node); + return NULL_TREE; + } + + if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(*node))) { + error("%qE attribute used on %qT is already applied to the type", name, *node); + return NULL_TREE; + } + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(*node))) { + error("%qE attribute used on %qT is incompatible with 'no_const'", name, *node); + return NULL_TREE; + } + + *no_add_attrs = false; + return NULL_TREE; +} + +static struct attribute_spec no_const_attr = { + .name = "no_const", + .min_length = 0, + .max_length = 0, + .decl_required = false, + .type_required = false, + .function_type_required = false, + .handler = handle_no_const_attribute, +#if BUILDING_GCC_VERSION >= 4007 + .affects_type_identity = true +#endif +}; + +static struct attribute_spec do_const_attr = { + .name = "do_const", + .min_length = 0, + .max_length = 0, + .decl_required = false, + .type_required = false, + .function_type_required = false, + .handler = handle_do_const_attribute, +#if BUILDING_GCC_VERSION >= 4007 + .affects_type_identity = true +#endif +}; + +static void register_attributes(void *event_data, void *data) +{ + register_attribute(&no_const_attr); + register_attribute(&do_const_attr); +} + +static void finish_type(void *event_data, void *data) +{ + tree type = (tree)event_data; + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + if (type == NULL_TREE || type == error_mark_node) + return; + +#if BUILDING_GCC_VERSION >= 5000 + if (TREE_CODE(type) == ENUMERAL_TYPE) + return; +#endif + + if (TYPE_FIELDS(type) == NULL_TREE || TYPE_CONSTIFY_VISITED(type)) + return; + + constifiable(type, &cinfo); + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type))) { + if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || cinfo.has_do_const_field) { + deconstify_type(type); + TYPE_CONSTIFY_VISITED(type) = 1; + } else + error("'no_const' attribute used on type %qT that is not constified", type); + return; + } + + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + if (!cinfo.has_writable_field && !cinfo.has_no_const_field) { + error("'do_const' attribute used on type %qT that is%sconstified", type, cinfo.has_fptr_field ? " " : " not "); + return; + } + constify_type(type); + return; + } + + if (cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + error("'do_const' attribute used on type %qT that is constified", type); + return; + } + constify_type(type); + return; + } + + deconstify_type(type); + TYPE_CONSTIFY_VISITED(type) = 1; +} + +static bool is_constified_var(varpool_node_ptr node) +{ + tree var = NODE_DECL(node); + tree type = TREE_TYPE(var); + + if (node->alias) + return false; + + if (DECL_EXTERNAL(var)) + return false; + + // XXX handle more complex nesting of arrays/structs + if (TREE_CODE(type) == ARRAY_TYPE) + type = TREE_TYPE(type); + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) + return false; + + if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type)) + return false; + + if (!TYPE_CONSTIFY_VISITED(type)) + return false; + + return true; +} + +static void check_section_mismatch(varpool_node_ptr node) +{ + tree var, section; + size_t i; + const char *name; + + var = NODE_DECL(node); + name = get_decl_section_name(var); + section = lookup_attribute("section", DECL_ATTRIBUTES(var)); + if (!section) { + if (name) { + fprintf(stderr, "DECL_SECTION [%s] ", name); + dump_varpool_node(stderr, node); + gcc_unreachable(); + } + return; + } else + gcc_assert(name); + +//fprintf(stderr, "SECTIONAME: [%s] ", get_decl_section_name(var)); +//debug_tree(var); + + gcc_assert(!TREE_CHAIN(section)); + gcc_assert(TREE_VALUE(section)); + + section = TREE_VALUE(TREE_VALUE(section)); + gcc_assert(!strcmp(TREE_STRING_POINTER(section), name)); +//debug_tree(section); + + for (i = 0; i < ARRAY_SIZE(const_sections); i++) + if (!strcmp(const_sections[i].name, name)) + return; + + error_at(DECL_SOURCE_LOCATION(var), "constified variable %qD placed into writable section %E", var, section); +} + +// this works around a gcc bug/feature where uninitialized globals +// are moved into the .bss section regardless of any constification +// see gcc/varasm.c:bss_initializer_p() +static void fix_initializer(varpool_node_ptr node) +{ + tree var = NODE_DECL(node); + tree type = TREE_TYPE(var); + + if (DECL_INITIAL(var)) + return; + + DECL_INITIAL(var) = build_constructor(type, NULL); +// inform(DECL_SOURCE_LOCATION(var), "constified variable %qE moved into .rodata", var); +} + +static void check_global_variables(void *event_data, void *data) +{ + varpool_node_ptr node; + + FOR_EACH_VARIABLE(node) { + if (!is_constified_var(node)) + continue; + + check_section_mismatch(node); + fix_initializer(node); + } +} + +static unsigned int check_local_variables_execute(void) +{ + unsigned int ret = 0; + tree var; + + unsigned int i; + + FOR_EACH_LOCAL_DECL(cfun, i, var) { + tree type = TREE_TYPE(var); + + gcc_assert(DECL_P(var)); + if (is_global_var(var)) + continue; + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) + continue; + + if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type)) + continue; + + if (!TYPE_CONSTIFY_VISITED(type)) + continue; + + error_at(DECL_SOURCE_LOCATION(var), "constified variable %qE cannot be local", var); + ret = 1; + } + return ret; +} + +#define PASS_NAME check_local_variables +#define NO_GATE +#include "gcc-generate-gimple-pass.h" + +static unsigned int (*old_section_type_flags)(tree decl, const char *name, int reloc); + +static unsigned int constify_section_type_flags(tree decl, const char *name, int reloc) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(const_sections); i++) + if (!strcmp(const_sections[i].name, name)) + return 0; + + return old_section_type_flags(decl, name, reloc); +} + +static void constify_start_unit(void *gcc_data, void *user_data) +{ +// size_t i; + +// for (i = 0; i < ARRAY_SIZE(const_sections); i++) +// const_sections[i].section = get_unnamed_section(0, output_section_asm_op, const_sections[i].asm_op); +// const_sections[i].section = get_section(const_sections[i].name, 0, NULL); + + old_section_type_flags = targetm.section_type_flags; + targetm.section_type_flags = constify_section_type_flags; +} + +__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) +{ + const char * const plugin_name = plugin_info->base_name; + const int argc = plugin_info->argc; + const struct plugin_argument * const argv = plugin_info->argv; + int i; + + struct register_pass_info check_local_variables_pass_info; + + check_local_variables_pass_info.pass = make_check_local_variables_pass(); + check_local_variables_pass_info.reference_pass_name = "ssa"; + check_local_variables_pass_info.ref_pass_instance_number = 1; + check_local_variables_pass_info.pos_op = PASS_POS_INSERT_BEFORE; + + if (!plugin_default_version_check(version, &gcc_version)) { + error(G_("incompatible gcc/plugin versions")); + return 1; + } + + for (i = 0; i < argc; ++i) { + if (!(strcmp(argv[i].key, "disable"))) { + enabled = false; + continue; + } + error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); + } + + if (strncmp(lang_hooks.name, "GNU C", 5) && !strncmp(lang_hooks.name, "GNU C+", 6)) { + inform(UNKNOWN_LOCATION, G_("%s supports C only, not %s"), plugin_name, lang_hooks.name); + enabled = false; + } + + register_callback(plugin_name, PLUGIN_INFO, NULL, &const_plugin_info); + if (enabled) { + register_callback(plugin_name, PLUGIN_ALL_IPA_PASSES_START, check_global_variables, NULL); + register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL); + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &check_local_variables_pass_info); + register_callback(plugin_name, PLUGIN_START_UNIT, constify_start_unit, NULL); + } + register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL); + + return 0; +} -- 2.7.4