162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2013-2017 by PaX Team <pageexec@freemail.hu>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Note: the choice of the license means that the compilation process is
662306a36Sopenharmony_ci *       NOT 'eligible' as defined by gcc's library exception to the GPL v3,
762306a36Sopenharmony_ci *       but for the kernel it doesn't matter since it doesn't link against
862306a36Sopenharmony_ci *       any of the gcc libraries
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * gcc plugin to forcibly initialize certain local variables that could
1162306a36Sopenharmony_ci * otherwise leak kernel stack to userland if they aren't properly initialized
1262306a36Sopenharmony_ci * by later code
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * Homepage: https://pax.grsecurity.net/
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Options:
1762306a36Sopenharmony_ci * -fplugin-arg-structleak_plugin-disable
1862306a36Sopenharmony_ci * -fplugin-arg-structleak_plugin-verbose
1962306a36Sopenharmony_ci * -fplugin-arg-structleak_plugin-byref
2062306a36Sopenharmony_ci * -fplugin-arg-structleak_plugin-byref-all
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Usage:
2362306a36Sopenharmony_ci * $ # for 4.5/4.6/C based 4.7
2462306a36Sopenharmony_ci * $ gcc -I`gcc -print-file-name=plugin`/include -I`gcc -print-file-name=plugin`/include/c-family -fPIC -shared -O2 -o structleak_plugin.so structleak_plugin.c
2562306a36Sopenharmony_ci * $ # for C++ based 4.7/4.8+
2662306a36Sopenharmony_ci * $ g++ -I`g++ -print-file-name=plugin`/include -I`g++ -print-file-name=plugin`/include/c-family -fPIC -shared -O2 -o structleak_plugin.so structleak_plugin.c
2762306a36Sopenharmony_ci * $ gcc -fplugin=./structleak_plugin.so test.c -O2
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * TODO: eliminate redundant initializers
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#include "gcc-common.h"
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* unused C type flag in all versions 4.5-6 */
3562306a36Sopenharmony_ci#define TYPE_USERSPACE(TYPE) TYPE_LANG_FLAG_5(TYPE)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci__visible int plugin_is_GPL_compatible;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic struct plugin_info structleak_plugin_info = {
4062306a36Sopenharmony_ci	.version	= PLUGIN_VERSION,
4162306a36Sopenharmony_ci	.help		= "disable\tdo not activate plugin\n"
4262306a36Sopenharmony_ci			  "byref\tinit structs passed by reference\n"
4362306a36Sopenharmony_ci			  "byref-all\tinit anything passed by reference\n"
4462306a36Sopenharmony_ci			  "verbose\tprint all initialized variables\n",
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define BYREF_STRUCT	1
4862306a36Sopenharmony_ci#define BYREF_ALL	2
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic bool verbose;
5162306a36Sopenharmony_cistatic int byref;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic tree handle_user_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	*no_add_attrs = true;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	/* check for types? for now accept everything linux has to offer */
5862306a36Sopenharmony_ci	if (TREE_CODE(*node) != FIELD_DECL)
5962306a36Sopenharmony_ci		return NULL_TREE;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	*no_add_attrs = false;
6262306a36Sopenharmony_ci	return NULL_TREE;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic struct attribute_spec user_attr = { };
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic void register_attributes(void *event_data, void *data)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	user_attr.name			= "user";
7062306a36Sopenharmony_ci	user_attr.handler		= handle_user_attribute;
7162306a36Sopenharmony_ci	user_attr.affects_type_identity	= true;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	register_attribute(&user_attr);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic tree get_field_type(tree field)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	return strip_array_types(TREE_TYPE(field));
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic bool is_userspace_type(tree type)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	tree field;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	for (field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) {
8662306a36Sopenharmony_ci		tree fieldtype = get_field_type(field);
8762306a36Sopenharmony_ci		enum tree_code code = TREE_CODE(fieldtype);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		if (code == RECORD_TYPE || code == UNION_TYPE)
9062306a36Sopenharmony_ci			if (is_userspace_type(fieldtype))
9162306a36Sopenharmony_ci				return true;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		if (lookup_attribute("user", DECL_ATTRIBUTES(field)))
9462306a36Sopenharmony_ci			return true;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci	return false;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic void finish_type(void *event_data, void *data)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	tree type = (tree)event_data;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (type == NULL_TREE || type == error_mark_node)
10462306a36Sopenharmony_ci		return;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	if (TREE_CODE(type) == ENUMERAL_TYPE)
10762306a36Sopenharmony_ci		return;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (TYPE_USERSPACE(type))
11062306a36Sopenharmony_ci		return;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (is_userspace_type(type))
11362306a36Sopenharmony_ci		TYPE_USERSPACE(type) = 1;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void initialize(tree var)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	basic_block bb;
11962306a36Sopenharmony_ci	gimple_stmt_iterator gsi;
12062306a36Sopenharmony_ci	tree initializer;
12162306a36Sopenharmony_ci	gimple init_stmt;
12262306a36Sopenharmony_ci	tree type;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* this is the original entry bb before the forced split */
12562306a36Sopenharmony_ci	bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun));
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* first check if variable is already initialized, warn otherwise */
12862306a36Sopenharmony_ci	for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
12962306a36Sopenharmony_ci		gimple stmt = gsi_stmt(gsi);
13062306a36Sopenharmony_ci		tree rhs1;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		/* we're looking for an assignment of a single rhs... */
13362306a36Sopenharmony_ci		if (!gimple_assign_single_p(stmt))
13462306a36Sopenharmony_ci			continue;
13562306a36Sopenharmony_ci		rhs1 = gimple_assign_rhs1(stmt);
13662306a36Sopenharmony_ci		/* ... of a non-clobbering expression... */
13762306a36Sopenharmony_ci		if (TREE_CLOBBER_P(rhs1))
13862306a36Sopenharmony_ci			continue;
13962306a36Sopenharmony_ci		/* ... to our variable... */
14062306a36Sopenharmony_ci		if (gimple_get_lhs(stmt) != var)
14162306a36Sopenharmony_ci			continue;
14262306a36Sopenharmony_ci		/* if it's an initializer then we're good */
14362306a36Sopenharmony_ci		if (TREE_CODE(rhs1) == CONSTRUCTOR)
14462306a36Sopenharmony_ci			return;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* these aren't the 0days you're looking for */
14862306a36Sopenharmony_ci	if (verbose)
14962306a36Sopenharmony_ci		inform(DECL_SOURCE_LOCATION(var),
15062306a36Sopenharmony_ci			"%s variable will be forcibly initialized",
15162306a36Sopenharmony_ci			(byref && TREE_ADDRESSABLE(var)) ? "byref"
15262306a36Sopenharmony_ci							 : "userspace");
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	/* build the initializer expression */
15562306a36Sopenharmony_ci	type = TREE_TYPE(var);
15662306a36Sopenharmony_ci	if (AGGREGATE_TYPE_P(type))
15762306a36Sopenharmony_ci		initializer = build_constructor(type, NULL);
15862306a36Sopenharmony_ci	else
15962306a36Sopenharmony_ci		initializer = fold_convert(type, integer_zero_node);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* build the initializer stmt */
16262306a36Sopenharmony_ci	init_stmt = gimple_build_assign(var, initializer);
16362306a36Sopenharmony_ci	gsi = gsi_after_labels(single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
16462306a36Sopenharmony_ci	gsi_insert_before(&gsi, init_stmt, GSI_NEW_STMT);
16562306a36Sopenharmony_ci	update_stmt(init_stmt);
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic unsigned int structleak_execute(void)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	basic_block bb;
17162306a36Sopenharmony_ci	tree var;
17262306a36Sopenharmony_ci	unsigned int i;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* split the first bb where we can put the forced initializers */
17562306a36Sopenharmony_ci	gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
17662306a36Sopenharmony_ci	bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun));
17762306a36Sopenharmony_ci	if (!single_pred_p(bb)) {
17862306a36Sopenharmony_ci		split_edge(single_succ_edge(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
17962306a36Sopenharmony_ci		gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* enumerate all local variables and forcibly initialize our targets */
18362306a36Sopenharmony_ci	FOR_EACH_LOCAL_DECL(cfun, i, var) {
18462306a36Sopenharmony_ci		tree type = TREE_TYPE(var);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci		gcc_assert(DECL_P(var));
18762306a36Sopenharmony_ci		if (!auto_var_in_fn_p(var, current_function_decl))
18862306a36Sopenharmony_ci			continue;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		/* only care about structure types unless byref-all */
19162306a36Sopenharmony_ci		if (byref != BYREF_ALL && TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE)
19262306a36Sopenharmony_ci			continue;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		/* if the type is of interest, examine the variable */
19562306a36Sopenharmony_ci		if (TYPE_USERSPACE(type) ||
19662306a36Sopenharmony_ci		    (byref && TREE_ADDRESSABLE(var)))
19762306a36Sopenharmony_ci			initialize(var);
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci#define PASS_NAME structleak
20462306a36Sopenharmony_ci#define NO_GATE
20562306a36Sopenharmony_ci#define PROPERTIES_REQUIRED PROP_cfg
20662306a36Sopenharmony_ci#define TODO_FLAGS_FINISH TODO_verify_il | TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func | TODO_remove_unused_locals | TODO_update_ssa | TODO_ggc_collect | TODO_verify_flow
20762306a36Sopenharmony_ci#include "gcc-generate-gimple-pass.h"
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	int i;
21262306a36Sopenharmony_ci	const char * const plugin_name = plugin_info->base_name;
21362306a36Sopenharmony_ci	const int argc = plugin_info->argc;
21462306a36Sopenharmony_ci	const struct plugin_argument * const argv = plugin_info->argv;
21562306a36Sopenharmony_ci	bool enable = true;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	PASS_INFO(structleak, "early_optimizations", 1, PASS_POS_INSERT_BEFORE);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (!plugin_default_version_check(version, &gcc_version)) {
22062306a36Sopenharmony_ci		error(G_("incompatible gcc/plugin versions"));
22162306a36Sopenharmony_ci		return 1;
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	if (strncmp(lang_hooks.name, "GNU C", 5) && !strncmp(lang_hooks.name, "GNU C+", 6)) {
22562306a36Sopenharmony_ci		inform(UNKNOWN_LOCATION, G_("%s supports C only, not %s"), plugin_name, lang_hooks.name);
22662306a36Sopenharmony_ci		enable = false;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	for (i = 0; i < argc; ++i) {
23062306a36Sopenharmony_ci		if (!strcmp(argv[i].key, "disable")) {
23162306a36Sopenharmony_ci			enable = false;
23262306a36Sopenharmony_ci			continue;
23362306a36Sopenharmony_ci		}
23462306a36Sopenharmony_ci		if (!strcmp(argv[i].key, "verbose")) {
23562306a36Sopenharmony_ci			verbose = true;
23662306a36Sopenharmony_ci			continue;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci		if (!strcmp(argv[i].key, "byref")) {
23962306a36Sopenharmony_ci			byref = BYREF_STRUCT;
24062306a36Sopenharmony_ci			continue;
24162306a36Sopenharmony_ci		}
24262306a36Sopenharmony_ci		if (!strcmp(argv[i].key, "byref-all")) {
24362306a36Sopenharmony_ci			byref = BYREF_ALL;
24462306a36Sopenharmony_ci			continue;
24562306a36Sopenharmony_ci		}
24662306a36Sopenharmony_ci		error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	register_callback(plugin_name, PLUGIN_INFO, NULL, &structleak_plugin_info);
25062306a36Sopenharmony_ci	if (enable) {
25162306a36Sopenharmony_ci		register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &structleak_pass_info);
25262306a36Sopenharmony_ci		register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL);
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci	register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	return 0;
25762306a36Sopenharmony_ci}
258