1  
     2  
     3  
     4  
     5  
     6  
     7  
     8  
     9  
    10  
    11  
    12  package sanitizers_test
    13  
    14  import (
    15  	"bytes"
    16  	"encoding/json"
    17  	"errors"
    18  	"fmt"
    19  	"internal/testenv"
    20  	"os"
    21  	"os/exec"
    22  	"os/user"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  	"sync"
    28  	"syscall"
    29  	"testing"
    30  	"time"
    31  	"unicode"
    32  )
    33  
    34  var overcommit struct {
    35  	sync.Once
    36  	value int
    37  	err   error
    38  }
    39  
    40  
    41  func requireOvercommit(t *testing.T) {
    42  	t.Helper()
    43  
    44  	overcommit.Once.Do(func() {
    45  		var out []byte
    46  		out, overcommit.err = os.ReadFile("/proc/sys/vm/overcommit_memory")
    47  		if overcommit.err != nil {
    48  			return
    49  		}
    50  		overcommit.value, overcommit.err = strconv.Atoi(string(bytes.TrimSpace(out)))
    51  	})
    52  
    53  	if overcommit.err != nil {
    54  		t.Skipf("couldn't determine vm.overcommit_memory (%v); assuming no overcommit", overcommit.err)
    55  	}
    56  	if overcommit.value == 2 {
    57  		t.Skip("vm.overcommit_memory=2")
    58  	}
    59  }
    60  
    61  var env struct {
    62  	sync.Once
    63  	m   map[string]string
    64  	err error
    65  }
    66  
    67  
    68  func goEnv(key string) (string, error) {
    69  	env.Once.Do(func() {
    70  		var out []byte
    71  		out, env.err = exec.Command("go", "env", "-json").Output()
    72  		if env.err != nil {
    73  			return
    74  		}
    75  
    76  		env.m = make(map[string]string)
    77  		env.err = json.Unmarshal(out, &env.m)
    78  	})
    79  	if env.err != nil {
    80  		return "", env.err
    81  	}
    82  
    83  	v, ok := env.m[key]
    84  	if !ok {
    85  		return "", fmt.Errorf("`go env`: no entry for %v", key)
    86  	}
    87  	return v, nil
    88  }
    89  
    90  
    91  func replaceEnv(cmd *exec.Cmd, key, value string) {
    92  	if cmd.Env == nil {
    93  		cmd.Env = cmd.Environ()
    94  	}
    95  	cmd.Env = append(cmd.Env, key+"="+value)
    96  }
    97  
    98  
    99  func appendExperimentEnv(cmd *exec.Cmd, experiments []string) {
   100  	if cmd.Env == nil {
   101  		cmd.Env = cmd.Environ()
   102  	}
   103  	exps := strings.Join(experiments, ",")
   104  	for _, evar := range cmd.Env {
   105  		c := strings.SplitN(evar, "=", 2)
   106  		if c[0] == "GOEXPERIMENT" {
   107  			exps = c[1] + "," + exps
   108  		}
   109  	}
   110  	cmd.Env = append(cmd.Env, "GOEXPERIMENT="+exps)
   111  }
   112  
   113  
   114  func mustRun(t *testing.T, cmd *exec.Cmd) {
   115  	t.Helper()
   116  	out := new(strings.Builder)
   117  	cmd.Stdout = out
   118  	cmd.Stderr = out
   119  
   120  	err := cmd.Start()
   121  	if err != nil {
   122  		t.Fatalf("%v: %v", cmd, err)
   123  	}
   124  
   125  	if deadline, ok := t.Deadline(); ok {
   126  		timeout := time.Until(deadline)
   127  		timeout -= timeout / 10 
   128  		timer := time.AfterFunc(timeout, func() {
   129  			cmd.Process.Signal(syscall.SIGQUIT)
   130  		})
   131  		defer timer.Stop()
   132  	}
   133  
   134  	if err := cmd.Wait(); err != nil {
   135  		t.Fatalf("%v exited with %v\n%s", cmd, err, out)
   136  	}
   137  }
   138  
   139  
   140  func cc(args ...string) (*exec.Cmd, error) {
   141  	CC, err := goEnv("CC")
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	GOGCCFLAGS, err := goEnv("GOGCCFLAGS")
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	
   152  	
   153  	
   154  	
   155  	
   156  	
   157  	var flags []string
   158  	quote := '\000'
   159  	start := 0
   160  	lastSpace := true
   161  	backslash := false
   162  	for i, c := range GOGCCFLAGS {
   163  		if quote == '\000' && unicode.IsSpace(c) {
   164  			if !lastSpace {
   165  				flags = append(flags, GOGCCFLAGS[start:i])
   166  				lastSpace = true
   167  			}
   168  		} else {
   169  			if lastSpace {
   170  				start = i
   171  				lastSpace = false
   172  			}
   173  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
   174  				quote = c
   175  				backslash = false
   176  			} else if !backslash && quote == c {
   177  				quote = '\000'
   178  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
   179  				backslash = true
   180  			} else {
   181  				backslash = false
   182  			}
   183  		}
   184  	}
   185  	if !lastSpace {
   186  		flags = append(flags, GOGCCFLAGS[start:])
   187  	}
   188  
   189  	cmd := exec.Command(CC, flags...)
   190  	cmd.Args = append(cmd.Args, args...)
   191  	return cmd, nil
   192  }
   193  
   194  type version struct {
   195  	name         string
   196  	major, minor int
   197  }
   198  
   199  var compiler struct {
   200  	sync.Once
   201  	version
   202  	err error
   203  }
   204  
   205  
   206  
   207  
   208  
   209  func compilerVersion() (version, error) {
   210  	compiler.Once.Do(func() {
   211  		compiler.err = func() error {
   212  			compiler.name = "unknown"
   213  
   214  			cmd, err := cc("--version")
   215  			if err != nil {
   216  				return err
   217  			}
   218  			out, err := cmd.Output()
   219  			if err != nil {
   220  				
   221  				return nil
   222  			}
   223  
   224  			var match [][]byte
   225  			if bytes.HasPrefix(out, []byte("gcc")) {
   226  				compiler.name = "gcc"
   227  				cmd, err := cc("-dumpfullversion", "-dumpversion")
   228  				if err != nil {
   229  					return err
   230  				}
   231  				out, err := cmd.Output()
   232  				if err != nil {
   233  					
   234  					return err
   235  				}
   236  				gccRE := regexp.MustCompile(`(\d+)\.(\d+)`)
   237  				match = gccRE.FindSubmatch(out)
   238  			} else {
   239  				clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
   240  				if match = clangRE.FindSubmatch(out); len(match) > 0 {
   241  					compiler.name = "clang"
   242  				}
   243  			}
   244  
   245  			if len(match) < 3 {
   246  				return nil 
   247  			}
   248  			if compiler.major, err = strconv.Atoi(string(match[1])); err != nil {
   249  				return err
   250  			}
   251  			if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil {
   252  				return err
   253  			}
   254  			return nil
   255  		}()
   256  	})
   257  	return compiler.version, compiler.err
   258  }
   259  
   260  
   261  
   262  func compilerSupportsLocation() bool {
   263  	compiler, err := compilerVersion()
   264  	if err != nil {
   265  		return false
   266  	}
   267  	switch compiler.name {
   268  	case "gcc":
   269  		
   270  		
   271  		
   272  		
   273  		
   274  		return compiler.major > 10
   275  	case "clang":
   276  		
   277  		
   278  		
   279  		
   280  		if inLUCIBuild() {
   281  			return false
   282  		}
   283  		return true
   284  	default:
   285  		return false
   286  	}
   287  }
   288  
   289  
   290  func inLUCIBuild() bool {
   291  	u, err := user.Current()
   292  	if err != nil {
   293  		return false
   294  	}
   295  	return testenv.Builder() != "" && u.Username == "swarming"
   296  }
   297  
   298  
   299  
   300  func compilerRequiredTsanVersion(goos, goarch string) bool {
   301  	compiler, err := compilerVersion()
   302  	if err != nil {
   303  		return false
   304  	}
   305  	if compiler.name == "gcc" && goarch == "ppc64le" {
   306  		return compiler.major >= 9
   307  	}
   308  	return true
   309  }
   310  
   311  
   312  func compilerRequiredAsanVersion(goos, goarch string) bool {
   313  	compiler, err := compilerVersion()
   314  	if err != nil {
   315  		return false
   316  	}
   317  	switch compiler.name {
   318  	case "gcc":
   319  		if goarch == "loong64" {
   320  			return compiler.major >= 14
   321  		}
   322  		if goarch == "ppc64le" {
   323  			return compiler.major >= 9
   324  		}
   325  		return compiler.major >= 7
   326  	case "clang":
   327  		if goarch == "loong64" {
   328  			return compiler.major >= 16
   329  		}
   330  		return compiler.major >= 9
   331  	default:
   332  		return false
   333  	}
   334  }
   335  
   336  
   337  
   338  func compilerRequiredLsanVersion(goos, goarch string) bool {
   339  	return compilerRequiredAsanVersion(goos, goarch)
   340  }
   341  
   342  type compilerCheck struct {
   343  	once sync.Once
   344  	err  error
   345  	skip bool 
   346  }
   347  
   348  type config struct {
   349  	sanitizer string
   350  
   351  	cFlags, ldFlags, goFlags []string
   352  
   353  	sanitizerCheck, runtimeCheck compilerCheck
   354  }
   355  
   356  var configs struct {
   357  	sync.Mutex
   358  	m map[string]*config
   359  }
   360  
   361  
   362  func configure(sanitizer string) *config {
   363  	configs.Lock()
   364  	defer configs.Unlock()
   365  	if c, ok := configs.m[sanitizer]; ok {
   366  		return c
   367  	}
   368  
   369  	sanitizerOpt := sanitizer
   370  	
   371  	
   372  	
   373  	if sanitizer == "leak" {
   374  		sanitizerOpt = "address"
   375  	}
   376  
   377  	c := &config{
   378  		sanitizer: sanitizer,
   379  		cFlags:    []string{"-fsanitize=" + sanitizerOpt},
   380  		ldFlags:   []string{"-fsanitize=" + sanitizerOpt},
   381  	}
   382  
   383  	if testing.Verbose() {
   384  		c.goFlags = append(c.goFlags, "-x")
   385  	}
   386  
   387  	switch sanitizer {
   388  	case "memory":
   389  		c.goFlags = append(c.goFlags, "-msan")
   390  
   391  	case "thread":
   392  		c.goFlags = append(c.goFlags, "--installsuffix=tsan")
   393  		compiler, _ := compilerVersion()
   394  		if compiler.name == "gcc" {
   395  			c.cFlags = append(c.cFlags, "-fPIC")
   396  			c.ldFlags = append(c.ldFlags, "-fPIC", "-static-libtsan")
   397  		}
   398  
   399  	case "address", "leak":
   400  		c.goFlags = append(c.goFlags, "-asan")
   401  		
   402  		c.cFlags = append(c.cFlags, "-g")
   403  
   404  	case "fuzzer":
   405  		c.goFlags = append(c.goFlags, "-tags=libfuzzer", "-gcflags=-d=libfuzzer")
   406  
   407  	default:
   408  		panic(fmt.Sprintf("unrecognized sanitizer: %q", sanitizer))
   409  	}
   410  
   411  	if configs.m == nil {
   412  		configs.m = make(map[string]*config)
   413  	}
   414  	configs.m[sanitizer] = c
   415  	return c
   416  }
   417  
   418  
   419  
   420  func (c *config) goCmd(subcommand string, args ...string) *exec.Cmd {
   421  	return c.goCmdWithExperiments(subcommand, args, nil)
   422  }
   423  
   424  
   425  
   426  
   427  func (c *config) goCmdWithExperiments(subcommand string, args []string, experiments []string) *exec.Cmd {
   428  	cmd := exec.Command("go", subcommand)
   429  	cmd.Args = append(cmd.Args, c.goFlags...)
   430  	cmd.Args = append(cmd.Args, args...)
   431  	replaceEnv(cmd, "CGO_CFLAGS", strings.Join(c.cFlags, " "))
   432  	replaceEnv(cmd, "CGO_LDFLAGS", strings.Join(c.ldFlags, " "))
   433  	appendExperimentEnv(cmd, experiments)
   434  	return cmd
   435  }
   436  
   437  
   438  
   439  func (c *config) skipIfCSanitizerBroken(t *testing.T) {
   440  	check := &c.sanitizerCheck
   441  	check.once.Do(func() {
   442  		check.skip, check.err = c.checkCSanitizer()
   443  	})
   444  	if check.err != nil {
   445  		t.Helper()
   446  		if check.skip {
   447  			t.Skip(check.err)
   448  		}
   449  		t.Fatal(check.err)
   450  	}
   451  }
   452  
   453  var cMain = []byte(`
   454  int main() {
   455  	return 0;
   456  }
   457  `)
   458  
   459  var cLibFuzzerInput = []byte(`
   460  #include <stddef.h>
   461  int LLVMFuzzerTestOneInput(char *data, size_t size) {
   462  	return 0;
   463  }
   464  `)
   465  
   466  func (c *config) checkCSanitizer() (skip bool, err error) {
   467  	dir, err := os.MkdirTemp("", c.sanitizer)
   468  	if err != nil {
   469  		return false, fmt.Errorf("failed to create temp directory: %v", err)
   470  	}
   471  	defer os.RemoveAll(dir)
   472  
   473  	src := filepath.Join(dir, "return0.c")
   474  	cInput := cMain
   475  	if c.sanitizer == "fuzzer" {
   476  		
   477  		cInput = cLibFuzzerInput
   478  	}
   479  	if err := os.WriteFile(src, cInput, 0600); err != nil {
   480  		return false, fmt.Errorf("failed to write C source file: %v", err)
   481  	}
   482  
   483  	dst := filepath.Join(dir, "return0")
   484  	cmd, err := cc(c.cFlags...)
   485  	if err != nil {
   486  		return false, err
   487  	}
   488  	cmd.Args = append(cmd.Args, c.ldFlags...)
   489  	cmd.Args = append(cmd.Args, "-o", dst, src)
   490  	out, err := cmd.CombinedOutput()
   491  	if err != nil {
   492  		if bytes.Contains(out, []byte("-fsanitize")) &&
   493  			(bytes.Contains(out, []byte("unrecognized")) ||
   494  				bytes.Contains(out, []byte("unsupported"))) {
   495  			return true, errors.New(string(out))
   496  		}
   497  		return true, fmt.Errorf("%#q failed: %v\n%s", cmd, err, out)
   498  	}
   499  
   500  	if c.sanitizer == "fuzzer" {
   501  		
   502  		return false, nil
   503  	}
   504  
   505  	if out, err := exec.Command(dst).CombinedOutput(); err != nil {
   506  		if os.IsNotExist(err) {
   507  			return true, fmt.Errorf("%#q failed to produce executable: %v", cmd, err)
   508  		}
   509  		snippet, _, _ := bytes.Cut(out, []byte("\n"))
   510  		return true, fmt.Errorf("%#q generated broken executable: %v\n%s", cmd, err, snippet)
   511  	}
   512  
   513  	return false, nil
   514  }
   515  
   516  
   517  
   518  func (c *config) skipIfRuntimeIncompatible(t *testing.T) {
   519  	check := &c.runtimeCheck
   520  	check.once.Do(func() {
   521  		check.skip, check.err = c.checkRuntime()
   522  	})
   523  	if check.err != nil {
   524  		t.Helper()
   525  		if check.skip {
   526  			t.Skip(check.err)
   527  		}
   528  		t.Fatal(check.err)
   529  	}
   530  }
   531  
   532  func (c *config) checkRuntime() (skip bool, err error) {
   533  	if c.sanitizer != "thread" {
   534  		return false, nil
   535  	}
   536  
   537  	
   538  	
   539  	
   540  	cmd, err := cc(c.cFlags...)
   541  	if err != nil {
   542  		return false, err
   543  	}
   544  	cmd.Args = append(cmd.Args, "-dM", "-E", "../../../../runtime/cgo/libcgo.h")
   545  	out, err := cmd.CombinedOutput()
   546  	if err != nil {
   547  		return false, fmt.Errorf("%#q exited with %v\n%s", cmd, err, out)
   548  	}
   549  	if !bytes.Contains(out, []byte("#define CGO_TSAN")) {
   550  		return true, fmt.Errorf("%#q did not define CGO_TSAN", cmd)
   551  	}
   552  	return false, nil
   553  }
   554  
   555  
   556  func srcPath(path string) string {
   557  	return "./testdata/" + path
   558  }
   559  
   560  
   561  type tempDir struct {
   562  	base string
   563  }
   564  
   565  func (d *tempDir) RemoveAll(t *testing.T) {
   566  	t.Helper()
   567  	if d.base == "" {
   568  		return
   569  	}
   570  	if err := os.RemoveAll(d.base); err != nil {
   571  		t.Fatalf("Failed to remove temp dir: %v", err)
   572  	}
   573  }
   574  
   575  func (d *tempDir) Base() string {
   576  	return d.base
   577  }
   578  
   579  func (d *tempDir) Join(name string) string {
   580  	return filepath.Join(d.base, name)
   581  }
   582  
   583  func newTempDir(t *testing.T) *tempDir {
   584  	return &tempDir{base: t.TempDir()}
   585  }
   586  
   587  
   588  
   589  
   590  
   591  
   592  
   593  
   594  
   595  func hangProneCmd(name string, arg ...string) *exec.Cmd {
   596  	cmd := exec.Command(name, arg...)
   597  	cmd.SysProcAttr = &syscall.SysProcAttr{
   598  		Pdeathsig: syscall.SIGKILL,
   599  	}
   600  	return cmd
   601  }
   602  
View as plain text