/* * Copyright 2011 by Emese Revfy * Copyright 2011-2017 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 = "201607241840", .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) { 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 __unused, int flags __unused, 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; } static tree handle_do_const_attribute(tree *node, tree name, tree args __unused, int flags __unused, 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 __unused) { 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); // TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); // TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("do_const"), NULL_TREE, TYPE_ATTRIBUTES(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 __unused, void *data __unused) { 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 __unused, void *user_data __unused) { // 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; PASS_INFO(check_local_variables, "ssa", 1, PASS_POS_INSERT_BEFORE); if (!plugin_default_version_check(version, &gcc_version)) { error_gcc_version(version); 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; }