github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/cgo/security.go (about)

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