1// Copyright 2019 The ChromiumOS Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7import ( 8 "strings" 9) 10 11// Returns whether the flag turns on 'invasive' sanitizers. These are sanitizers incompatible with 12// things like FORTIFY, since they require meaningful runtime support, intercept libc calls, etc. 13func isInvasiveSanitizerFlag(flag string) bool { 14 // There are a few valid spellings here: 15 // -fsanitize=${sanitizer_list}, which enables the given sanitizers 16 // -fsanitize-trap=${sanitizer_list}, which specifies sanitizer behavior _if_ these 17 // sanitizers are already enabled. 18 // -fsanitize-recover=${sanitizer_list}, which also specifies sanitizer behavior _if_ 19 // these sanitizers are already enabled. 20 // -fsanitize-ignorelist=/path/to/file, which designates a config file for sanitizers. 21 // 22 // All we care about is the first one, since that's what actually enables sanitizers. Clang 23 // does not accept a `-fsanitize ${sanitizer_list}` spelling of this flag. 24 fsanitize := "-fsanitize=" 25 if !strings.HasPrefix(flag, fsanitize) { 26 return false 27 } 28 29 sanitizers := flag[len(fsanitize):] 30 if sanitizers == "" { 31 return false 32 } 33 34 for _, sanitizer := range strings.Split(sanitizers, ",") { 35 // Keep an allowlist of sanitizers known to not cause issues. 36 switch sanitizer { 37 case "alignment", "array-bounds", "bool", "bounds", "builtin", "enum", 38 "float-cast-overflow", "integer-divide-by-zero", "local-bounds", 39 "nullability", "nullability-arg", "nullability-assign", 40 "nullability-return", "null", "return", "returns-nonnull-attribute", 41 "shift-base", "shift-exponent", "shift", "unreachable", "vla-bound": 42 // These sanitizers are lightweight. Ignore them. 43 default: 44 return true 45 } 46 } 47 return false 48} 49 50// Returns whether the flag given enables FORTIFY. Notably, this should return false if a flag 51// disables FORTIFY. 52func isFortifyEnableFlag(flag string) bool { 53 prefix := "-D_FORTIFY_SOURCE=" 54 // At the time of writing, -D_FORTIFY_SOURCE has the valid values 0, 1, 2, and 3. Seems 55 // unlikely to go past 9, so don't handle past 9. 56 return strings.HasPrefix(flag, prefix) && len(flag) == len(prefix)+1 && flag[len(prefix)] != '0' 57} 58 59func processSanitizerFlags(builder *commandBuilder) { 60 hasSanitizeFlags := false 61 // TODO: This doesn't take -fno-sanitize flags into account. This doesn't seem to be an 62 // issue in practice. 63 for _, arg := range builder.args { 64 if arg.fromUser && isInvasiveSanitizerFlag(arg.value) { 65 hasSanitizeFlags = true 66 break 67 } 68 } 69 70 if !hasSanitizeFlags { 71 return 72 } 73 74 // Flags not supported by sanitizers (ASan etc.) 75 unsupportedSanitizerFlags := map[string]bool{ 76 "-Wl,--no-undefined": true, 77 "-Wl,-z,defs": true, 78 } 79 80 builder.transformArgs(func(arg builderArg) string { 81 // TODO: This is a bug in the old wrapper to not filter 82 // non user args for gcc. Fix this once we don't compare to the old wrapper anymore. 83 linkerDefinedFlag := ",-z,defs" 84 if builder.target.compilerType != gccType || arg.fromUser { 85 if unsupportedSanitizerFlags[arg.value] || isFortifyEnableFlag(arg.value) { 86 return "" 87 } 88 if strings.Contains(arg.value, linkerDefinedFlag) { 89 return strings.ReplaceAll(arg.value, linkerDefinedFlag, "") 90 } 91 } 92 return arg.value 93 }) 94 95 builder.filterArgPairs(func(arg1, arg2 builderArg) bool { 96 return !(arg1.value == "-Wl,-z" && arg2.value == "-Wl,defs") 97 }) 98} 99