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