1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Checking of compiler and linker flags.
6// We must avoid flags like -fplugin=, which can allow
7// arbitrary code execution during the build.
8// Do not make changes here without carefully
9// considering the implications.
10// (That's why the code is isolated in a file named security.go.)
11//
12// Note that -Wl,foo means split foo on commas and pass to
13// the linker, so that -Wl,-foo,bar means pass -foo bar to
14// the linker. Similarly -Wa,foo for the assembler and so on.
15// If any of these are permitted, the wildcard portion must
16// disallow commas.
17//
18// Note also that GNU binutils accept any argument @foo
19// as meaning "read more flags from the file foo", so we must
20// guard against any command-line argument beginning with @,
21// even things like "-I @foo".
22// We use load.SafeArg (which is even more conservative)
23// to reject these.
24//
25// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
26// so although gcc doesn't expand the @foo, cc1 will.
27// So out of paranoia, we reject @ at the beginning of every
28// flag argument that might be split into its own argument.
29
30package work
31
32import (
33	"fmt"
34	"internal/lazyregexp"
35	"regexp"
36	"strings"
37
38	"cmd/go/internal/cfg"
39	"cmd/go/internal/load"
40)
41
42var re = lazyregexp.New
43
44var validCompilerFlags = []*lazyregexp.Regexp{
45	re(`-D([A-Za-z_].*)`),
46	re(`-U([A-Za-z_]*)`),
47	re(`-F([^@\-].*)`),
48	re(`-I([^@\-].*)`),
49	re(`-O`),
50	re(`-O([^@\-].*)`),
51	re(`-W`),
52	re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
53	re(`-Wa,-mbig-obj`),
54	re(`-Wp,-D([A-Za-z_].*)`),
55	re(`-Wp,-U([A-Za-z_]*)`),
56	re(`-ansi`),
57	re(`-f(no-)?asynchronous-unwind-tables`),
58	re(`-f(no-)?blocks`),
59	re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
60	re(`-f(no-)?common`),
61	re(`-f(no-)?constant-cfstrings`),
62	re(`-fdiagnostics-show-note-include-stack`),
63	re(`-f(no-)?eliminate-unused-debug-types`),
64	re(`-f(no-)?exceptions`),
65	re(`-f(no-)?fast-math`),
66	re(`-f(no-)?inline-functions`),
67	re(`-finput-charset=([^@\-].*)`),
68	re(`-f(no-)?fat-lto-objects`),
69	re(`-f(no-)?keep-inline-dllexport`),
70	re(`-f(no-)?lto`),
71	re(`-fmacro-backtrace-limit=(.+)`),
72	re(`-fmessage-length=(.+)`),
73	re(`-f(no-)?modules`),
74	re(`-f(no-)?objc-arc`),
75	re(`-f(no-)?objc-nonfragile-abi`),
76	re(`-f(no-)?objc-legacy-dispatch`),
77	re(`-f(no-)?omit-frame-pointer`),
78	re(`-f(no-)?openmp(-simd)?`),
79	re(`-f(no-)?permissive`),
80	re(`-f(no-)?(pic|PIC|pie|PIE)`),
81	re(`-f(no-)?plt`),
82	re(`-f(no-)?rtti`),
83	re(`-f(no-)?split-stack`),
84	re(`-f(no-)?stack-(.+)`),
85	re(`-f(no-)?strict-aliasing`),
86	re(`-f(un)signed-char`),
87	re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
88	re(`-f(no-)?visibility-inlines-hidden`),
89	re(`-fsanitize=(.+)`),
90	re(`-ftemplate-depth-(.+)`),
91	re(`-fvisibility=(.+)`),
92	re(`-g([^@\-].*)?`),
93	re(`-m32`),
94	re(`-m64`),
95	re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
96	re(`-m(no-)?v?aes`),
97	re(`-marm`),
98	re(`-m(no-)?avx[0-9a-z]*`),
99	re(`-mfloat-abi=([^@\-].*)`),
100	re(`-mfpmath=[0-9a-z,+]*`),
101	re(`-m(no-)?avx[0-9a-z.]*`),
102	re(`-m(no-)?ms-bitfields`),
103	re(`-m(no-)?stack-(.+)`),
104	re(`-mmacosx-(.+)`),
105	re(`-mios-simulator-version-min=(.+)`),
106	re(`-miphoneos-version-min=(.+)`),
107	re(`-mtvos-simulator-version-min=(.+)`),
108	re(`-mtvos-version-min=(.+)`),
109	re(`-mwatchos-simulator-version-min=(.+)`),
110	re(`-mwatchos-version-min=(.+)`),
111	re(`-mnop-fun-dllimport`),
112	re(`-m(no-)?sse[0-9.]*`),
113	re(`-m(no-)?ssse3`),
114	re(`-mthumb(-interwork)?`),
115	re(`-mthreads`),
116	re(`-mwindows`),
117	re(`--param=ssp-buffer-size=[0-9]*`),
118	re(`-pedantic(-errors)?`),
119	re(`-pipe`),
120	re(`-pthread`),
121	re(`-?-std=([^@\-].*)`),
122	re(`-?-stdlib=([^@\-].*)`),
123	re(`--sysroot=([^@\-].*)`),
124	re(`-w`),
125	re(`-x([^@\-].*)`),
126	re(`-v`),
127}
128
129var validCompilerFlagsWithNextArg = []string{
130	"-arch",
131	"-D",
132	"-U",
133	"-I",
134	"-framework",
135	"-isysroot",
136	"-isystem",
137	"--sysroot",
138	"-target",
139	"-x",
140}
141
142var validLinkerFlags = []*lazyregexp.Regexp{
143	re(`-F([^@\-].*)`),
144	re(`-l([^@\-].*)`),
145	re(`-L([^@\-].*)`),
146	re(`-O`),
147	re(`-O([^@\-].*)`),
148	re(`-f(no-)?(pic|PIC|pie|PIE)`),
149	re(`-f(no-)?openmp(-simd)?`),
150	re(`-fsanitize=([^@\-].*)`),
151	re(`-flat_namespace`),
152	re(`-g([^@\-].*)?`),
153	re(`-headerpad_max_install_names`),
154	re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
155	re(`-mfloat-abi=([^@\-].*)`),
156	re(`-mmacosx-(.+)`),
157	re(`-mios-simulator-version-min=(.+)`),
158	re(`-miphoneos-version-min=(.+)`),
159	re(`-mthreads`),
160	re(`-mwindows`),
161	re(`-(pic|PIC|pie|PIE)`),
162	re(`-pthread`),
163	re(`-rdynamic`),
164	re(`-shared`),
165	re(`-?-static([-a-z0-9+]*)`),
166	re(`-?-stdlib=([^@\-].*)`),
167	re(`-v`),
168
169	// Note that any wildcards in -Wl need to exclude comma,
170	// since -Wl splits its argument at commas and passes
171	// them all to the linker uninterpreted. Allowing comma
172	// in a wildcard would allow tunnelling arbitrary additional
173	// linker arguments through one of these.
174	re(`-Wl,--(no-)?allow-multiple-definition`),
175	re(`-Wl,--(no-)?allow-shlib-undefined`),
176	re(`-Wl,--(no-)?as-needed`),
177	re(`-Wl,-Bdynamic`),
178	re(`-Wl,-berok`),
179	re(`-Wl,-Bstatic`),
180	re(`-WL,-O([^@,\-][^,]*)?`),
181	re(`-Wl,-d[ny]`),
182	re(`-Wl,--disable-new-dtags`),
183	re(`-Wl,-e[=,][a-zA-Z0-9]*`),
184	re(`-Wl,--enable-new-dtags`),
185	re(`-Wl,--end-group`),
186	re(`-Wl,--(no-)?export-dynamic`),
187	re(`-Wl,-framework,[^,@\-][^,]+`),
188	re(`-Wl,-headerpad_max_install_names`),
189	re(`-Wl,--no-undefined`),
190	re(`-Wl,-R([^@\-][^,@]*$)`),
191	re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
192	re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
193	re(`-Wl,-s`),
194	re(`-Wl,-search_paths_first`),
195	re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
196	re(`-Wl,--start-group`),
197	re(`-Wl,-?-static`),
198	re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
199	re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
200	re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
201	re(`-Wl,-?-unresolved-symbols=[^,]+`),
202	re(`-Wl,--(no-)?warn-([^,]+)`),
203	re(`-Wl,-z,(no)?execstack`),
204	re(`-Wl,-z,relro`),
205
206	re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
207	re(`\./.*\.(a|o|obj|dll|dylib|so)`),
208}
209
210var validLinkerFlagsWithNextArg = []string{
211	"-arch",
212	"-F",
213	"-l",
214	"-L",
215	"-framework",
216	"-isysroot",
217	"--sysroot",
218	"-target",
219	"-Wl,-framework",
220	"-Wl,-rpath",
221	"-Wl,-R",
222	"-Wl,--just-symbols",
223	"-Wl,-undefined",
224}
225
226func checkCompilerFlags(name, source string, list []string) error {
227	return checkFlags(name, source, list, validCompilerFlags, validCompilerFlagsWithNextArg)
228}
229
230func checkLinkerFlags(name, source string, list []string) error {
231	return checkFlags(name, source, list, validLinkerFlags, validLinkerFlagsWithNextArg)
232}
233
234func checkFlags(name, source string, list []string, valid []*lazyregexp.Regexp, validNext []string) error {
235	// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
236	var (
237		allow    *regexp.Regexp
238		disallow *regexp.Regexp
239	)
240	if env := cfg.Getenv("CGO_" + name + "_ALLOW"); env != "" {
241		r, err := regexp.Compile(env)
242		if err != nil {
243			return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
244		}
245		allow = r
246	}
247	if env := cfg.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
248		r, err := regexp.Compile(env)
249		if err != nil {
250			return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
251		}
252		disallow = r
253	}
254
255Args:
256	for i := 0; i < len(list); i++ {
257		arg := list[i]
258		if disallow != nil && disallow.FindString(arg) == arg {
259			goto Bad
260		}
261		if allow != nil && allow.FindString(arg) == arg {
262			continue Args
263		}
264		for _, re := range valid {
265			if re.FindString(arg) == arg { // must be complete match
266				continue Args
267			}
268		}
269		for _, x := range validNext {
270			if arg == x {
271				if i+1 < len(list) && load.SafeArg(list[i+1]) {
272					i++
273					continue Args
274				}
275
276				// Permit -Wl,-framework -Wl,name.
277				if i+1 < len(list) &&
278					strings.HasPrefix(arg, "-Wl,") &&
279					strings.HasPrefix(list[i+1], "-Wl,") &&
280					load.SafeArg(list[i+1][4:]) &&
281					!strings.Contains(list[i+1][4:], ",") {
282					i++
283					continue Args
284				}
285
286				// Permit -I= /path, -I $SYSROOT.
287				if i+1 < len(list) && arg == "-I" {
288					if (strings.HasPrefix(list[i+1], "=") || strings.HasPrefix(list[i+1], "$SYSROOT")) &&
289						load.SafeArg(list[i+1][1:]) {
290						i++
291						continue Args
292					}
293				}
294
295				if i+1 < len(list) {
296					return fmt.Errorf("invalid flag in %s: %s %s (see https://golang.org/s/invalidflag)", source, arg, list[i+1])
297				}
298				return fmt.Errorf("invalid flag in %s: %s without argument (see https://golang.org/s/invalidflag)", source, arg)
299			}
300		}
301	Bad:
302		return fmt.Errorf("invalid flag in %s: %s", source, arg)
303	}
304	return nil
305}
306