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_][A-Za-z0-9_]*)(=[^@\-]*)?`),
46	re(`-U([A-Za-z_][A-Za-z0-9_]*)`),
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_][A-Za-z0-9_]*)(=[^@,\-]*)?`),
55	re(`-Wp,-U([A-Za-z_][A-Za-z0-9_]*)`),
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(`-fdebug-prefix-map=([^@]+)=([^@]+)`),
63	re(`-fdiagnostics-show-note-include-stack`),
64	re(`-ffile-prefix-map=([^@]+)=([^@]+)`),
65	re(`-fno-canonical-system-headers`),
66	re(`-f(no-)?eliminate-unused-debug-types`),
67	re(`-f(no-)?exceptions`),
68	re(`-f(no-)?fast-math`),
69	re(`-f(no-)?inline-functions`),
70	re(`-finput-charset=([^@\-].*)`),
71	re(`-f(no-)?fat-lto-objects`),
72	re(`-f(no-)?keep-inline-dllexport`),
73	re(`-f(no-)?lto`),
74	re(`-fmacro-backtrace-limit=(.+)`),
75	re(`-fmessage-length=(.+)`),
76	re(`-f(no-)?modules`),
77	re(`-f(no-)?objc-arc`),
78	re(`-f(no-)?objc-nonfragile-abi`),
79	re(`-f(no-)?objc-legacy-dispatch`),
80	re(`-f(no-)?omit-frame-pointer`),
81	re(`-f(no-)?openmp(-simd)?`),
82	re(`-f(no-)?permissive`),
83	re(`-f(no-)?(pic|PIC|pie|PIE)`),
84	re(`-f(no-)?plt`),
85	re(`-f(no-)?rtti`),
86	re(`-f(no-)?split-stack`),
87	re(`-f(no-)?stack-(.+)`),
88	re(`-f(no-)?strict-aliasing`),
89	re(`-f(un)signed-char`),
90	re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
91	re(`-f(no-)?visibility-inlines-hidden`),
92	re(`-fsanitize=(.+)`),
93	re(`-ftemplate-depth-(.+)`),
94	re(`-fvisibility=(.+)`),
95	re(`-g([^@\-].*)?`),
96	re(`-m32`),
97	re(`-m64`),
98	re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
99	re(`-m(no-)?v?aes`),
100	re(`-marm`),
101	re(`-m(no-)?avx[0-9a-z]*`),
102	re(`-mcmodel=[0-9a-z-]+`),
103	re(`-mfloat-abi=([^@\-].*)`),
104	re(`-mfpmath=[0-9a-z,+]*`),
105	re(`-m(no-)?avx[0-9a-z.]*`),
106	re(`-m(no-)?ms-bitfields`),
107	re(`-m(no-)?stack-(.+)`),
108	re(`-mmacosx-(.+)`),
109	re(`-mios-simulator-version-min=(.+)`),
110	re(`-miphoneos-version-min=(.+)`),
111	re(`-mlarge-data-threshold=[0-9]+`),
112	re(`-mtvos-simulator-version-min=(.+)`),
113	re(`-mtvos-version-min=(.+)`),
114	re(`-mwatchos-simulator-version-min=(.+)`),
115	re(`-mwatchos-version-min=(.+)`),
116	re(`-mnop-fun-dllimport`),
117	re(`-m(no-)?sse[0-9.]*`),
118	re(`-m(no-)?ssse3`),
119	re(`-mthumb(-interwork)?`),
120	re(`-mthreads`),
121	re(`-mwindows`),
122	re(`-no-canonical-prefixes`),
123	re(`--param=ssp-buffer-size=[0-9]*`),
124	re(`-pedantic(-errors)?`),
125	re(`-pipe`),
126	re(`-pthread`),
127	re(`-?-std=([^@\-].*)`),
128	re(`-?-stdlib=([^@\-].*)`),
129	re(`--sysroot=([^@\-].*)`),
130	re(`-w`),
131	re(`-x([^@\-].*)`),
132	re(`-v`),
133}
134
135var validCompilerFlagsWithNextArg = []string{
136	"-arch",
137	"-D",
138	"-U",
139	"-I",
140	"-F",
141	"-framework",
142	"-include",
143	"-isysroot",
144	"-isystem",
145	"--sysroot",
146	"-target",
147	"-x",
148}
149
150var invalidLinkerFlags = []*lazyregexp.Regexp{
151	// On macOS this means the linker loads and executes the next argument.
152	// Have to exclude separately because -lfoo is allowed in general.
153	re(`-lto_library`),
154}
155
156var validLinkerFlags = []*lazyregexp.Regexp{
157	re(`-F([^@\-].*)`),
158	re(`-l([^@\-].*)`),
159	re(`-L([^@\-].*)`),
160	re(`-O`),
161	re(`-O([^@\-].*)`),
162	re(`-f(no-)?(pic|PIC|pie|PIE)`),
163	re(`-f(no-)?openmp(-simd)?`),
164	re(`-fsanitize=([^@\-].*)`),
165	re(`-flat_namespace`),
166	re(`-g([^@\-].*)?`),
167	re(`-headerpad_max_install_names`),
168	re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
169	re(`-mfloat-abi=([^@\-].*)`),
170	re(`-mmacosx-(.+)`),
171	re(`-mios-simulator-version-min=(.+)`),
172	re(`-miphoneos-version-min=(.+)`),
173	re(`-mthreads`),
174	re(`-mwindows`),
175	re(`-(pic|PIC|pie|PIE)`),
176	re(`-pthread`),
177	re(`-rdynamic`),
178	re(`-shared`),
179	re(`-?-static([-a-z0-9+]*)`),
180	re(`-?-stdlib=([^@\-].*)`),
181	re(`-v`),
182
183	// Note that any wildcards in -Wl need to exclude comma,
184	// since -Wl splits its argument at commas and passes
185	// them all to the linker uninterpreted. Allowing comma
186	// in a wildcard would allow tunneling arbitrary additional
187	// linker arguments through one of these.
188	re(`-Wl,--(no-)?allow-multiple-definition`),
189	re(`-Wl,--(no-)?allow-shlib-undefined`),
190	re(`-Wl,--(no-)?as-needed`),
191	re(`-Wl,-Bdynamic`),
192	re(`-Wl,-berok`),
193	re(`-Wl,-Bstatic`),
194	re(`-Wl,-Bsymbolic-functions`),
195	re(`-Wl,-O[0-9]+`),
196	re(`-Wl,-d[ny]`),
197	re(`-Wl,--disable-new-dtags`),
198	re(`-Wl,-e[=,][a-zA-Z0-9]+`),
199	re(`-Wl,--enable-new-dtags`),
200	re(`-Wl,--end-group`),
201	re(`-Wl,--(no-)?export-dynamic`),
202	re(`-Wl,-E`),
203	re(`-Wl,-framework,[^,@\-][^,]+`),
204	re(`-Wl,--hash-style=(sysv|gnu|both)`),
205	re(`-Wl,-headerpad_max_install_names`),
206	re(`-Wl,--no-undefined`),
207	re(`-Wl,-R,?([^@\-,][^,@]*$)`),
208	re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
209	re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
210	re(`-Wl,-s`),
211	re(`-Wl,-search_paths_first`),
212	re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
213	re(`-Wl,--start-group`),
214	re(`-Wl,-?-static`),
215	re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
216	re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
217	re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
218	re(`-Wl,-?-unresolved-symbols=[^,]+`),
219	re(`-Wl,--(no-)?warn-([^,]+)`),
220	re(`-Wl,-?-wrap[=,][^,@\-][^,]*`),
221	re(`-Wl(,-z,(relro|now|(no)?execstack))+`),
222
223	re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so|tbd)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
224	re(`\./.*\.(a|o|obj|dll|dylib|so|tbd)`),
225}
226
227var validLinkerFlagsWithNextArg = []string{
228	"-arch",
229	"-F",
230	"-l",
231	"-L",
232	"-framework",
233	"-isysroot",
234	"--sysroot",
235	"-target",
236	"-Wl,-framework",
237	"-Wl,-rpath",
238	"-Wl,-R",
239	"-Wl,--just-symbols",
240	"-Wl,-undefined",
241}
242
243func checkCompilerFlags(name, source string, list []string) error {
244	checkOverrides := true
245	return checkFlags(name, source, list, nil, validCompilerFlags, validCompilerFlagsWithNextArg, checkOverrides)
246}
247
248func checkLinkerFlags(name, source string, list []string) error {
249	checkOverrides := true
250	return checkFlags(name, source, list, invalidLinkerFlags, validLinkerFlags, validLinkerFlagsWithNextArg, checkOverrides)
251}
252
253// checkCompilerFlagsForInternalLink returns an error if 'list'
254// contains a flag or flags that may not be fully supported by
255// internal linking (meaning that we should punt the link to the
256// external linker).
257func checkCompilerFlagsForInternalLink(name, source string, list []string) error {
258	checkOverrides := false
259	if err := checkFlags(name, source, list, nil, validCompilerFlags, validCompilerFlagsWithNextArg, checkOverrides); err != nil {
260		return err
261	}
262	// Currently the only flag on the allow list that causes problems
263	// for the linker is "-flto"; check for it manually here.
264	for _, fl := range list {
265		if strings.HasPrefix(fl, "-flto") {
266			return fmt.Errorf("flag %q triggers external linking", fl)
267		}
268	}
269	return nil
270}
271
272func checkFlags(name, source string, list []string, invalid, valid []*lazyregexp.Regexp, validNext []string, checkOverrides bool) error {
273	// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
274	var (
275		allow    *regexp.Regexp
276		disallow *regexp.Regexp
277	)
278	if checkOverrides {
279		if env := cfg.Getenv("CGO_" + name + "_ALLOW"); env != "" {
280			r, err := regexp.Compile(env)
281			if err != nil {
282				return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
283			}
284			allow = r
285		}
286		if env := cfg.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
287			r, err := regexp.Compile(env)
288			if err != nil {
289				return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
290			}
291			disallow = r
292		}
293	}
294
295Args:
296	for i := 0; i < len(list); i++ {
297		arg := list[i]
298		if disallow != nil && disallow.FindString(arg) == arg {
299			goto Bad
300		}
301		if allow != nil && allow.FindString(arg) == arg {
302			continue Args
303		}
304		for _, re := range invalid {
305			if re.FindString(arg) == arg { // must be complete match
306				goto Bad
307			}
308		}
309		for _, re := range valid {
310			if re.FindString(arg) == arg { // must be complete match
311				continue Args
312			}
313		}
314		for _, x := range validNext {
315			if arg == x {
316				if i+1 < len(list) && load.SafeArg(list[i+1]) {
317					i++
318					continue Args
319				}
320
321				// Permit -Wl,-framework -Wl,name.
322				if i+1 < len(list) &&
323					strings.HasPrefix(arg, "-Wl,") &&
324					strings.HasPrefix(list[i+1], "-Wl,") &&
325					load.SafeArg(list[i+1][4:]) &&
326					!strings.Contains(list[i+1][4:], ",") {
327					i++
328					continue Args
329				}
330
331				// Permit -I= /path, -I $SYSROOT.
332				if i+1 < len(list) && arg == "-I" {
333					if (strings.HasPrefix(list[i+1], "=") || strings.HasPrefix(list[i+1], "$SYSROOT")) &&
334						load.SafeArg(list[i+1][1:]) {
335						i++
336						continue Args
337					}
338				}
339
340				if i+1 < len(list) {
341					return fmt.Errorf("invalid flag in %s: %s %s (see https://golang.org/s/invalidflag)", source, arg, list[i+1])
342				}
343				return fmt.Errorf("invalid flag in %s: %s without argument (see https://golang.org/s/invalidflag)", source, arg)
344			}
345		}
346	Bad:
347		return fmt.Errorf("invalid flag in %s: %s", source, arg)
348	}
349	return nil
350}
351