History

Merge tag 'gcc-plugins-v5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux

Pull gcc-plugins updates from Kees Cook: "This adds additional type coverage to the existing structleak plugin and adds a large set of selftests to help evaluate stack variable zero-initialization coverage. That can be used to test whatever instrumentation might be performing zero-initialization: either with the structleak plugin or with Clang's coming "-ftrivial-auto-var-init=zero" option. Summary: - Add scalar and array initialization coverage - Refactor Kconfig to make options more clear - Add self-test module for testing automatic initialization" * tag 'gcc-plugins-v5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: lib: Introduce test_stackinit module gcc-plugins: structleak: Generalize to all variable types

Linus Torvalds committed 7 years ago

Commit 2bb99542 parents a39f009 50ceaa96 files · +463 −22Browse files

Unified · Split

6 files
  1. lib/Kconfig.debug +10 −0
  2. lib/Makefile +1 −0
  3. lib/test_stackinit.c +378 −0
  4. scripts/Makefile.gcc-plugins +2 −0
  5. scripts/gcc-plugins/Kconfig +47 −11
  6. scripts/gcc-plugins/structleak_plugin.c +25 −11
lib/Kconfig.debug+10−0
@@ -2016,6 +2016,16 @@ config TEST_OBJAGG · 2015 unmodified lines
20162016 (or module load).
20172017
20182018
2019+config TEST_STACKINIT
2020+ tristate "Test level of stack variable initialization"
2021+ help
2022+ Test if the kernel is zero-initializing stack variables and
2023+ padding. Coverage is controlled by compiler flags,
2024+ CONFIG_GCC_PLUGIN_STRUCTLEAK, CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF,
2025+ or CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL.
2026+
2027+ If unsure, say N.
2028+
20192029 endif # RUNTIME_TESTING_MENU
20202030
20212031 config MEMTEST
lib/Makefile+1−0
@@ -77,6 +77,7 @@ obj-$(CONFIG_TEST_KMOD) += test_kmod.o · 76 unmodified lines
7777 obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
7878 obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
7979 obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
80+obj-$(CONFIG_TEST_STACKINIT) += test_stackinit.o
8081
8182 obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
8283
lib/test_stackinit.c+378−0
@@ -0,0 +1,378 @@
1+// SPDX-Licenses: GPLv2
2+/*
3+ * Test cases for compiler-based stack variable zeroing via future
4+ * compiler flags or CONFIG_GCC_PLUGIN_STRUCTLEAK*.
5+ */
6+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
7+
8+#include <linux/init.h>
9+#include <linux/kernel.h>
10+#include <linux/module.h>
11+#include <linux/string.h>
12+
13+/* Exfiltration buffer. */
14+#define MAX_VAR_SIZE 128
15+static char check_buf[MAX_VAR_SIZE];
16+
17+/* Character array to trigger stack protector in all functions. */
18+#define VAR_BUFFER 32
19+
20+/* Volatile mask to convince compiler to copy memory with 0xff. */
21+static volatile u8 forced_mask = 0xff;
22+
23+/* Location and size tracking to validate fill and test are colocated. */
24+static void *fill_start, *target_start;
25+static size_t fill_size, target_size;
26+
27+static bool range_contains(char *haystack_start, size_t haystack_size,
28+ char *needle_start, size_t needle_size)
29+{
30+ if (needle_start >= haystack_start &&
31+ needle_start + needle_size <= haystack_start + haystack_size)
32+ return true;
33+ return false;
34+}
35+
36+#define DO_NOTHING_TYPE_SCALAR(var_type) var_type
37+#define DO_NOTHING_TYPE_STRING(var_type) void
38+#define DO_NOTHING_TYPE_STRUCT(var_type) void
39+
40+#define DO_NOTHING_RETURN_SCALAR(ptr) *(ptr)
41+#define DO_NOTHING_RETURN_STRING(ptr) /**/
42+#define DO_NOTHING_RETURN_STRUCT(ptr) /**/
43+
44+#define DO_NOTHING_CALL_SCALAR(var, name) \
45+ (var) = do_nothing_ ## name(&(var))
46+#define DO_NOTHING_CALL_STRING(var, name) \
47+ do_nothing_ ## name(var)
48+#define DO_NOTHING_CALL_STRUCT(var, name) \
49+ do_nothing_ ## name(&(var))
50+
51+#define FETCH_ARG_SCALAR(var) &var
52+#define FETCH_ARG_STRING(var) var
53+#define FETCH_ARG_STRUCT(var) &var
54+
55+#define FILL_SIZE_STRING 16
56+
57+#define INIT_CLONE_SCALAR /**/
58+#define INIT_CLONE_STRING [FILL_SIZE_STRING]
59+#define INIT_CLONE_STRUCT /**/
60+
61+#define INIT_SCALAR_none /**/
62+#define INIT_SCALAR_zero = 0
63+
64+#define INIT_STRING_none [FILL_SIZE_STRING] /**/
65+#define INIT_STRING_zero [FILL_SIZE_STRING] = { }
66+
67+#define INIT_STRUCT_none /**/
68+#define INIT_STRUCT_zero = { }
69+#define INIT_STRUCT_static_partial = { .two = 0, }
70+#define INIT_STRUCT_static_all = { .one = arg->one, \
71+ .two = arg->two, \
72+ .three = arg->three, \
73+ .four = arg->four, \
74+ }
75+#define INIT_STRUCT_dynamic_partial = { .two = arg->two, }
76+#define INIT_STRUCT_dynamic_all = { .one = arg->one, \
77+ .two = arg->two, \
78+ .three = arg->three, \
79+ .four = arg->four, \
80+ }
81+#define INIT_STRUCT_runtime_partial ; \
82+ var.two = 0
83+#define INIT_STRUCT_runtime_all ; \
84+ var.one = 0; \
85+ var.two = 0; \
86+ var.three = 0; \
87+ memset(&var.four, 0, \
88+ sizeof(var.four))
89+
90+/*
91+ * @name: unique string name for the test
92+ * @var_type: type to be tested for zeroing initialization
93+ * @which: is this a SCALAR, STRING, or STRUCT type?
94+ * @init_level: what kind of initialization is performed
95+ */
96+#define DEFINE_TEST_DRIVER(name, var_type, which) \
97+/* Returns 0 on success, 1 on failure. */ \
98+static noinline __init int test_ ## name (void) \
99+{ \
100+ var_type zero INIT_CLONE_ ## which; \
101+ int ignored; \
102+ u8 sum = 0, i; \
103+ \
104+ /* Notice when a new test is larger than expected. */ \
105+ BUILD_BUG_ON(sizeof(zero) > MAX_VAR_SIZE); \
106+ \
107+ /* Fill clone type with zero for per-field init. */ \
108+ memset(&zero, 0x00, sizeof(zero)); \
109+ /* Fill stack with 0xFF. */ \
110+ ignored = leaf_ ##name((unsigned long)&ignored, 1, \
111+ FETCH_ARG_ ## which(zero)); \
112+ /* Clear entire check buffer for later bit tests. */ \
113+ memset(check_buf, 0x00, sizeof(check_buf)); \
114+ /* Extract stack-defined variable contents. */ \
115+ ignored = leaf_ ##name((unsigned long)&ignored, 0, \
116+ FETCH_ARG_ ## which(zero)); \
117+ \
118+ /* Validate that compiler lined up fill and target. */ \
119+ if (!range_contains(fill_start, fill_size, \
120+ target_start, target_size)) { \
121+ pr_err(#name ": stack fill missed target!?\n"); \
122+ pr_err(#name ": fill %zu wide\n", fill_size); \
123+ pr_err(#name ": target offset by %d\n", \
124+ (int)((ssize_t)(uintptr_t)fill_start - \
125+ (ssize_t)(uintptr_t)target_start)); \
126+ return 1; \
127+ } \
128+ \
129+ /* Look for any set bits in the check region. */ \
130+ for (i = 0; i < sizeof(check_buf); i++) \
131+ sum += (check_buf[i] != 0); \
132+ \
133+ if (sum == 0) \
134+ pr_info(#name " ok\n"); \
135+ else \
136+ pr_warn(#name " FAIL (uninit bytes: %d)\n", \
137+ sum); \
138+ \
139+ return (sum != 0); \
140+}
141+#define DEFINE_TEST(name, var_type, which, init_level) \
142+/* no-op to force compiler into ignoring "uninitialized" vars */\
143+static noinline __init DO_NOTHING_TYPE_ ## which(var_type) \
144+do_nothing_ ## name(var_type *ptr) \
145+{ \
146+ /* Will always be true, but compiler doesn't know. */ \
147+ if ((unsigned long)ptr > 0x2) \
148+ return DO_NOTHING_RETURN_ ## which(ptr); \
149+ else \
150+ return DO_NOTHING_RETURN_ ## which(ptr + 1); \
151+} \
152+static noinline __init int leaf_ ## name(unsigned long sp, \
153+ bool fill, \
154+ var_type *arg) \
155+{ \
156+ char buf[VAR_BUFFER]; \
157+ var_type var INIT_ ## which ## _ ## init_level; \
158+ \
159+ target_start = &var; \
160+ target_size = sizeof(var); \
161+ /* \
162+ * Keep this buffer around to make sure we've got a \
163+ * stack frame of SOME kind... \
164+ */ \
165+ memset(buf, (char)(sp && 0xff), sizeof(buf)); \
166+ /* Fill variable with 0xFF. */ \
167+ if (fill) { \
168+ fill_start = &var; \
169+ fill_size = sizeof(var); \
170+ memset(fill_start, \
171+ (char)((sp && 0xff) | forced_mask), \
172+ fill_size); \
173+ } \
174+ \
175+ /* Silence "never initialized" warnings. */ \
176+ DO_NOTHING_CALL_ ## which(var, name); \
177+ \
178+ /* Exfiltrate "var". */ \
179+ memcpy(check_buf, target_start, target_size); \
180+ \
181+ return (int)buf[0] | (int)buf[sizeof(buf) - 1]; \
182+} \
183+DEFINE_TEST_DRIVER(name, var_type, which)
184+
185+/* Structure with no padding. */
186+struct test_packed {
187+ unsigned long one;
188+ unsigned long two;
189+ unsigned long three;
190+ unsigned long four;
191+};
192+
193+/* Simple structure with padding likely to be covered by compiler. */
194+struct test_small_hole {
195+ size_t one;
196+ char two;
197+ /* 3 byte padding hole here. */
198+ int three;
199+ unsigned long four;
200+};
201+
202+/* Try to trigger unhandled padding in a structure. */
203+struct test_aligned {
204+ u32 internal1;
205+ u64 internal2;
206+} __aligned(64);
207+
208+struct test_big_hole {
209+ u8 one;
210+ u8 two;
211+ u8 three;
212+ /* 61 byte padding hole here. */
213+ struct test_aligned four;
214+} __aligned(64);
215+
216+struct test_trailing_hole {
217+ char *one;
218+ char *two;
219+ char *three;
220+ char four;
221+ /* "sizeof(unsigned long) - 1" byte padding hole here. */
222+};
223+
224+/* Test if STRUCTLEAK is clearing structs with __user fields. */
225+struct test_user {
226+ u8 one;
227+ unsigned long two;
228+ char __user *three;
229+ unsigned long four;
230+};
231+
232+#define DEFINE_SCALAR_TEST(name, init) \
233+ DEFINE_TEST(name ## _ ## init, name, SCALAR, init)
234+
235+#define DEFINE_SCALAR_TESTS(init) \
236+ DEFINE_SCALAR_TEST(u8, init); \
237+ DEFINE_SCALAR_TEST(u16, init); \
238+ DEFINE_SCALAR_TEST(u32, init); \
239+ DEFINE_SCALAR_TEST(u64, init); \
240+ DEFINE_TEST(char_array_ ## init, unsigned char, STRING, init)
241+
242+#define DEFINE_STRUCT_TEST(name, init) \
243+ DEFINE_TEST(name ## _ ## init, \
244+ struct test_ ## name, STRUCT, init)
245+
246+#define DEFINE_STRUCT_TESTS(init) \
247+ DEFINE_STRUCT_TEST(small_hole, init); \
248+ DEFINE_STRUCT_TEST(big_hole, init); \
249+ DEFINE_STRUCT_TEST(trailing_hole, init); \
250+ DEFINE_STRUCT_TEST(packed, init)
251+
252+/* These should be fully initialized all the time! */
253+DEFINE_SCALAR_TESTS(zero);
254+DEFINE_STRUCT_TESTS(zero);
255+/* Static initialization: padding may be left uninitialized. */
256+DEFINE_STRUCT_TESTS(static_partial);
257+DEFINE_STRUCT_TESTS(static_all);
258+/* Dynamic initialization: padding may be left uninitialized. */
259+DEFINE_STRUCT_TESTS(dynamic_partial);
260+DEFINE_STRUCT_TESTS(dynamic_all);
261+/* Runtime initialization: padding may be left uninitialized. */
262+DEFINE_STRUCT_TESTS(runtime_partial);
263+DEFINE_STRUCT_TESTS(runtime_all);
264+/* No initialization without compiler instrumentation. */
265+DEFINE_SCALAR_TESTS(none);
266+DEFINE_STRUCT_TESTS(none);
267+DEFINE_TEST(user, struct test_user, STRUCT, none);
268+
269+/*
270+ * Check two uses through a variable declaration outside either path,
271+ * which was noticed as a special case in porting earlier stack init
272+ * compiler logic.
273+ */
274+static int noinline __leaf_switch_none(int path, bool fill)
275+{
276+ switch (path) {
277+ uint64_t var;
278+
279+ case 1:
280+ target_start = &var;
281+ target_size = sizeof(var);
282+ if (fill) {
283+ fill_start = &var;
284+ fill_size = sizeof(var);
285+
286+ memset(fill_start, forced_mask | 0x55, fill_size);
287+ }
288+ memcpy(check_buf, target_start, target_size);
289+ break;
290+ case 2:
291+ target_start = &var;
292+ target_size = sizeof(var);
293+ if (fill) {
294+ fill_start = &var;
295+ fill_size = sizeof(var);
296+
297+ memset(fill_start, forced_mask | 0xaa, fill_size);
298+ }
299+ memcpy(check_buf, target_start, target_size);
300+ break;
301+ default:
302+ var = 5;
303+ return var & forced_mask;
304+ }
305+ return 0;
306+}
307+
308+static noinline __init int leaf_switch_1_none(unsigned long sp, bool fill,
309+ uint64_t *arg)
310+{
311+ return __leaf_switch_none(1, fill);
312+}
313+
314+static noinline __init int leaf_switch_2_none(unsigned long sp, bool fill,
315+ uint64_t *arg)
316+{
317+ return __leaf_switch_none(2, fill);
318+}
319+
320+DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR);
321+DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR);
322+
323+static int __init test_stackinit_init(void)
324+{
325+ unsigned int failures = 0;
326+
327+#define test_scalars(init) do { \
328+ failures += test_u8_ ## init (); \
329+ failures += test_u16_ ## init (); \
330+ failures += test_u32_ ## init (); \
331+ failures += test_u64_ ## init (); \
332+ failures += test_char_array_ ## init (); \
333+ } while (0)
334+
335+#define test_structs(init) do { \
336+ failures += test_small_hole_ ## init (); \
337+ failures += test_big_hole_ ## init (); \
338+ failures += test_trailing_hole_ ## init (); \
339+ failures += test_packed_ ## init (); \
340+ } while (0)
341+
342+ /* These are explicitly initialized and should always pass. */
343+ test_scalars(zero);
344+ test_structs(zero);
345+ /* Padding here appears to be accidentally always initialized? */
346+ test_structs(dynamic_partial);
347+ /* Padding initialization depends on compiler behaviors. */
348+ test_structs(static_partial);
349+ test_structs(static_all);
350+ test_structs(dynamic_all);
351+ test_structs(runtime_partial);
352+ test_structs(runtime_all);
353+
354+ /* STRUCTLEAK_BYREF_ALL should cover everything from here down. */
355+ test_scalars(none);
356+ failures += test_switch_1_none();
357+ failures += test_switch_2_none();
358+
359+ /* STRUCTLEAK_BYREF should cover from here down. */
360+ test_structs(none);
361+
362+ /* STRUCTLEAK will only cover this. */
363+ failures += test_user();
364+
365+ if (failures == 0)
366+ pr_info("all tests passed!\n");
367+ else
368+ pr_err("failures: %u\n", failures);
369+
370+ return failures ? -EINVAL : 0;
371+}
372+module_init(test_stackinit_init);
373+
374+static void __exit test_stackinit_exit(void)
375+{ }
376+module_exit(test_stackinit_exit);
377+
378+MODULE_LICENSE("GPL");
scripts/Makefile.gcc-plugins+2−0
@@ -15,6 +15,8 @@ gcc-plugin-$(CONFIG_GCC_PLUGIN_SANCOV) += sancov_plugin.so · 14 unmodified lines
1515 gcc-plugin-$(CONFIG_GCC_PLUGIN_STRUCTLEAK) += structleak_plugin.so
1616 gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_VERBOSE) \
1717 += -fplugin-arg-structleak_plugin-verbose
18+gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF) \
19+ += -fplugin-arg-structleak_plugin-byref
1820 gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL) \
1921 += -fplugin-arg-structleak_plugin-byref-all
2022 gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK) \
scripts/gcc-plugins/Kconfig+47−11
@@ -67,23 +67,59 @@ config GCC_PLUGIN_LATENT_ENTROPY · 66 unmodified lines
6767 * https://pax.grsecurity.net/
6868
6969 config GCC_PLUGIN_STRUCTLEAK
70− bool "Force initialization of variables containing userspace addresses"
70+ bool "Zero initialize stack variables"
7171 help
72− This plugin zero-initializes any structures containing a
73− __user attribute. This can prevent some classes of information
74− exposures.
75−
76− This plugin was ported from grsecurity/PaX. More information at:
72+ While the kernel is built with warnings enabled for any missed
73+ stack variable initializations, this warning is silenced for
74+ anything passed by reference to another function, under the
75+ occasionally misguided assumption that the function will do
76+ the initialization. As this regularly leads to exploitable
77+ flaws, this plugin is available to identify and zero-initialize
78+ such variables, depending on the chosen level of coverage.
79+
80+ This plugin was originally ported from grsecurity/PaX. More
81+ information at:
7782 * https://grsecurity.net/
7883 * https://pax.grsecurity.net/
7984
80−config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL
81− bool "Force initialize all struct type variables passed by reference"
85+choice
86+ prompt "Coverage"
8287 depends on GCC_PLUGIN_STRUCTLEAK
83− depends on !COMPILE_TEST
88+ default GCC_PLUGIN_STRUCTLEAK_BYREF_ALL
8489 help
85− Zero initialize any struct type local variable that may be passed by
86− reference without having been initialized.
90+ This chooses the level of coverage over classes of potentially
91+ uninitialized variables. The selected class will be
92+ zero-initialized before use.
93+
94+ config GCC_PLUGIN_STRUCTLEAK_USER
95+ bool "structs marked for userspace"
96+ help
97+ Zero-initialize any structures on the stack containing
98+ a __user attribute. This can prevent some classes of
99+ uninitialized stack variable exploits and information
100+ exposures, like CVE-2013-2141:
101+ https://git.kernel.org/linus/b9e146d8eb3b9eca
102+
103+ config GCC_PLUGIN_STRUCTLEAK_BYREF
104+ bool "structs passed by reference"
105+ help
106+ Zero-initialize any structures on the stack that may
107+ be passed by reference and had not already been
108+ explicitly initialized. This can prevent most classes
109+ of uninitialized stack variable exploits and information
110+ exposures, like CVE-2017-1000410:
111+ https://git.kernel.org/linus/06e7e776ca4d3654
112+
113+ config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL
114+ bool "anything passed by reference"
115+ help
116+ Zero-initialize any stack variables that may be passed
117+ by reference and had not already been explicitly
118+ initialized. This is intended to eliminate all classes
119+ of uninitialized stack variable exploits and information
120+ exposures.
121+
122+endchoice
87123
88124 config GCC_PLUGIN_STRUCTLEAK_VERBOSE
89125 bool "Report forcefully initialized variables"
scripts/gcc-plugins/structleak_plugin.c+25−11
@@ -16,6 +16,7 @@ · 15 unmodified lines
1616 * Options:
1717 * -fplugin-arg-structleak_plugin-disable
1818 * -fplugin-arg-structleak_plugin-verbose
19+ * -fplugin-arg-structleak_plugin-byref
1920 * -fplugin-arg-structleak_plugin-byref-all
2021 *
2122 * Usage:
@@ -26,7 +27,6 @@ · 4 unmodified lines
2627 * $ gcc -fplugin=./structleak_plugin.so test.c -O2
2728 *
2829 * TODO: eliminate redundant initializers
29− * increase type coverage
3030 */
3131
3232 #include "gcc-common.h"
@@ -37,13 +37,18 @@ · 4 unmodified lines
3737 __visible int plugin_is_GPL_compatible;
3838
3939 static struct plugin_info structleak_plugin_info = {
40− .version = "201607271510vanilla",
40+ .version = "20190125vanilla",
4141 .help = "disable\tdo not activate plugin\n"
42− "verbose\tprint all initialized variables\n",
42+ "byref\tinit structs passed by reference\n"
43+ "byref-all\tinit anything passed by reference\n"
44+ "verbose\tprint all initialized variables\n",
4345 };
4446
47+#define BYREF_STRUCT 1
48+#define BYREF_ALL 2
49+
4550 static bool verbose;
46−static bool byref_all;
51+static int byref;
4752
4853 static tree handle_user_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs)
4954 {
@@ -118,6 +123,7 @@ static void initialize(tree var) · 68 unmodified lines
118123 gimple_stmt_iterator gsi;
119124 tree initializer;
120125 gimple init_stmt;
126+ tree type;
121127
122128 /* this is the original entry bb before the forced split */
123129 bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun));
@@ -148,11 +154,15 @@ static void initialize(tree var) · 24 unmodified lines
148154 if (verbose)
149155 inform(DECL_SOURCE_LOCATION(var),
150156 "%s variable will be forcibly initialized",
151− (byref_all && TREE_ADDRESSABLE(var)) ? "byref"
152− : "userspace");
157+ (byref && TREE_ADDRESSABLE(var)) ? "byref"
158+ : "userspace");
153159
154160 /* build the initializer expression */
155− initializer = build_constructor(TREE_TYPE(var), NULL);
161+ type = TREE_TYPE(var);
162+ if (AGGREGATE_TYPE_P(type))
163+ initializer = build_constructor(type, NULL);
164+ else
165+ initializer = fold_convert(type, integer_zero_node);
156166
157167 /* build the initializer stmt */
158168 init_stmt = gimple_build_assign(var, initializer);
@@ -184,13 +194,13 @@ static unsigned int structleak_execute(void) · 25 unmodified lines
184194 if (!auto_var_in_fn_p(var, current_function_decl))
185195 continue;
186196
187− /* only care about structure types */
188− if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE)
197+ /* only care about structure types unless byref-all */
198+ if (byref != BYREF_ALL && TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE)
189199 continue;
190200
191201 /* if the type is of interest, examine the variable */
192202 if (TYPE_USERSPACE(type) ||
193− (byref_all && TREE_ADDRESSABLE(var)))
203+ (byref && TREE_ADDRESSABLE(var)))
194204 initialize(var);
195205 }
196206
@@ -232,8 +242,12 @@ __visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gc · 35 unmodified lines
232242 verbose = true;
233243 continue;
234244 }
245+ if (!strcmp(argv[i].key, "byref")) {
246+ byref = BYREF_STRUCT;
247+ continue;
248+ }
235249 if (!strcmp(argv[i].key, "byref-all")) {
236− byref_all = true;
250+ byref = BYREF_ALL;
237251 continue;
238252 }
239253 error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);