Home | History | Annotate | Download | only in work
      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 
     30 package work
     31 
     32 import (
     33 	"cmd/go/internal/load"
     34 	"fmt"
     35 	"os"
     36 	"regexp"
     37 	"strings"
     38 )
     39 
     40 var re = regexp.MustCompile
     41 
     42 var validCompilerFlags = []*regexp.Regexp{
     43 	re(`-D([A-Za-z_].*)`),
     44 	re(`-I([^@\-].*)`),
     45 	re(`-O`),
     46 	re(`-O([^@\-].*)`),
     47 	re(`-W`),
     48 	re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
     49 	re(`-f(no-)?blocks`),
     50 	re(`-f(no-)?common`),
     51 	re(`-f(no-)?constant-cfstrings`),
     52 	re(`-f(no-)?exceptions`),
     53 	re(`-finput-charset=([^@\-].*)`),
     54 	re(`-f(no-)?lto`),
     55 	re(`-f(no-)?modules`),
     56 	re(`-f(no-)?objc-arc`),
     57 	re(`-f(no-)?omit-frame-pointer`),
     58 	re(`-f(no-)?openmp(-simd)?`),
     59 	re(`-f(no-)?permissive`),
     60 	re(`-f(no-)?(pic|PIC|pie|PIE)`),
     61 	re(`-f(no-)?rtti`),
     62 	re(`-f(no-)?split-stack`),
     63 	re(`-f(no-)?stack-(.+)`),
     64 	re(`-f(no-)?strict-aliasing`),
     65 	re(`-fsanitize=(.+)`),
     66 	re(`-g([^@\-].*)?`),
     67 	re(`-m(arch|cpu|fpu|tune)=([^@\-].*)`),
     68 	re(`-m(no-)?avx[0-9a-z.]*`),
     69 	re(`-m(no-)?ms-bitfields`),
     70 	re(`-m(no-)?stack-(.+)`),
     71 	re(`-mmacosx-(.+)`),
     72 	re(`-mnop-fun-dllimport`),
     73 	re(`-m(no-)?sse[0-9.]*`),
     74 	re(`-pedantic(-errors)?`),
     75 	re(`-pipe`),
     76 	re(`-pthread`),
     77 	re(`-?-std=([^@\-].*)`),
     78 	re(`-x([^@\-].*)`),
     79 }
     80 
     81 var validCompilerFlagsWithNextArg = []string{
     82 	"-D",
     83 	"-I",
     84 	"-isystem",
     85 	"-framework",
     86 	"-x",
     87 }
     88 
     89 var validLinkerFlags = []*regexp.Regexp{
     90 	re(`-F([^@\-].*)`),
     91 	re(`-l([^@\-].*)`),
     92 	re(`-L([^@\-].*)`),
     93 	re(`-f(no-)?(pic|PIC|pie|PIE)`),
     94 	re(`-fsanitize=([^@\-].*)`),
     95 	re(`-g([^@\-].*)?`),
     96 	re(`-m(arch|cpu|fpu|tune)=([^@\-].*)`),
     97 	re(`-(pic|PIC|pie|PIE)`),
     98 	re(`-pthread`),
     99 	re(`-?-static([-a-z0-9+]*)`),
    100 
    101 	// Note that any wildcards in -Wl need to exclude comma,
    102 	// since -Wl splits its argument at commas and passes
    103 	// them all to the linker uninterpreted. Allowing comma
    104 	// in a wildcard would allow tunnelling arbitrary additional
    105 	// linker arguments through one of these.
    106 	re(`-Wl,--(no-)?as-needed`),
    107 	re(`-Wl,-Bdynamic`),
    108 	re(`-Wl,-Bstatic`),
    109 	re(`-Wl,--disable-new-dtags`),
    110 	re(`-Wl,--enable-new-dtags`),
    111 	re(`-Wl,--end-group`),
    112 	re(`-Wl,-framework,[^,@\-][^,]+`),
    113 	re(`-Wl,-headerpad_max_install_names`),
    114 	re(`-Wl,--no-undefined`),
    115 	re(`-Wl,-rpath,([^,@\-][^,]+)`),
    116 	re(`-Wl,-search_paths_first`),
    117 	re(`-Wl,--start-group`),
    118 	re(`-Wl,-?-unresolved-symbols=[^,]+`),
    119 	re(`-Wl,--(no-)?warn-([^,]+)`),
    120 
    121 	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)
    122 }
    123 
    124 var validLinkerFlagsWithNextArg = []string{
    125 	"-F",
    126 	"-l",
    127 	"-L",
    128 	"-framework",
    129 	"-Wl,-framework",
    130 }
    131 
    132 func checkCompilerFlags(name, source string, list []string) error {
    133 	return checkFlags(name, source, list, validCompilerFlags, validCompilerFlagsWithNextArg)
    134 }
    135 
    136 func checkLinkerFlags(name, source string, list []string) error {
    137 	return checkFlags(name, source, list, validLinkerFlags, validLinkerFlagsWithNextArg)
    138 }
    139 
    140 func checkFlags(name, source string, list []string, valid []*regexp.Regexp, validNext []string) error {
    141 	// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
    142 	var (
    143 		allow    *regexp.Regexp
    144 		disallow *regexp.Regexp
    145 	)
    146 	if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
    147 		r, err := regexp.Compile(env)
    148 		if err != nil {
    149 			return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
    150 		}
    151 		allow = r
    152 	}
    153 	if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
    154 		r, err := regexp.Compile(env)
    155 		if err != nil {
    156 			return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
    157 		}
    158 		disallow = r
    159 	}
    160 
    161 Args:
    162 	for i := 0; i < len(list); i++ {
    163 		arg := list[i]
    164 		if disallow != nil && disallow.FindString(arg) == arg {
    165 			goto Bad
    166 		}
    167 		if allow != nil && allow.FindString(arg) == arg {
    168 			continue Args
    169 		}
    170 		for _, re := range valid {
    171 			if re.FindString(arg) == arg { // must be complete match
    172 				continue Args
    173 			}
    174 		}
    175 		for _, x := range validNext {
    176 			if arg == x {
    177 				if i+1 < len(list) && load.SafeArg(list[i+1]) {
    178 					i++
    179 					continue Args
    180 				}
    181 
    182 				// Permit -Wl,-framework -Wl,name.
    183 				if i+1 < len(list) &&
    184 					strings.HasPrefix(arg, "-Wl,") &&
    185 					strings.HasPrefix(list[i+1], "-Wl,") &&
    186 					load.SafeArg(list[i+1][4:]) &&
    187 					!strings.Contains(list[i+1][4:], ",") {
    188 					i++
    189 					continue Args
    190 				}
    191 
    192 				if i+1 < len(list) {
    193 					return fmt.Errorf("invalid flag in %s: %s %s (see https://golang.org/s/invalidflag)", source, arg, list[i+1])
    194 				}
    195 				return fmt.Errorf("invalid flag in %s: %s without argument (see https://golang.org/s/invalidflag)", source, arg)
    196 			}
    197 		}
    198 	Bad:
    199 		return fmt.Errorf("invalid flag in %s: %s", source, arg)
    200 	}
    201 	return nil
    202 }
    203